From 2d123aab4747d0a2a36163c50b833a547cfcb3a3 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 28 Jun 2021 22:16:03 -0400 Subject: [PATCH 01/98] Lotus version 1.11.0 --- CHANGELOG.md | 4 ++++ build/version.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f7d300b..718e86f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Lotus changelog +# 1.11.0-rc1 / 2021-06-28 + +This is the first release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes and feature development. A more detailed changelog will follow. + # 1.10.0 / 2021-06-23 This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The diff --git a/build/version.go b/build/version.go index 5a4a494fc..49ec3f446 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-dev" +const BuildVersion = "1.11.0-rc1" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { From af1dd7d2edf96743650114ba62652f75876a3b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 Jul 2021 12:15:58 +0200 Subject: [PATCH 02/98] commit batch: Initialize the FailedSectors map --- extern/storage-sealing/commit_batch.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 097d55a1a..f16f1d208 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -229,7 +229,9 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa total := len(b.todo) - var res sealiface.CommitBatchRes + res := sealiface.CommitBatchRes{ + FailedSectors: map[abi.SectorNumber]string{}, + } params := miner5.ProveCommitAggregateParams{ SectorNumbers: bitfield.New(), @@ -341,7 +343,8 @@ func (b *CommitBatcher) processIndividually() ([]sealiface.CommitBatchRes, error for sn, info := range b.todo { r := sealiface.CommitBatchRes{ - Sectors: []abi.SectorNumber{sn}, + Sectors: []abi.SectorNumber{sn}, + FailedSectors: map[abi.SectorNumber]string{}, } mcid, err := b.processSingle(mi, sn, info, tok) From a4f65bed41b8b51bff5667c9e08c36b807b51116 Mon Sep 17 00:00:00 2001 From: Jerry <1032246642@qq.com> Date: Tue, 29 Jun 2021 14:06:41 +0800 Subject: [PATCH 03/98] fix: miner balance is not enough, so that ProveCommitAggregate msg exec failed --- extern/storage-sealing/commit_batch.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index f16f1d208..e4cfc8915 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -309,14 +309,16 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa aggFee := policy.AggregateNetworkFee(nv, len(infos), bf) - goodFunds := big.Add(maxFee, big.Add(collateral, aggFee)) + needFunds := big.Add(collateral, aggFee) - from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, collateral) + goodFunds := big.Add(maxFee, needFunds) + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, needFunds) if err != nil { return []sealiface.CommitBatchRes{res}, xerrors.Errorf("no good address found: %w", err) } - mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, collateral, maxFee, enc.Bytes()) + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, needFunds, maxFee, enc.Bytes()) if err != nil { return []sealiface.CommitBatchRes{res}, xerrors.Errorf("sending message failed: %w", err) } From c164a9fbdc48f34ec7ac746d3c486221a2e82bcf Mon Sep 17 00:00:00 2001 From: Jerry <1032246642@qq.com> Date: Tue, 29 Jun 2021 14:36:26 +0800 Subject: [PATCH 04/98] ensure agg fee is adequate --- extern/storage-sealing/commit_batch.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index e4cfc8915..740e98fca 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -32,6 +32,9 @@ import ( const arp = abi.RegisteredAggregationProof_SnarkPackV1 +var aggFeeNum = big.NewInt(110) +var aggFeeDen = big.NewInt(100) + //go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_commit_batcher.go -package=mocks . CommitBatcherApi type CommitBatcherApi interface { @@ -307,7 +310,7 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa return []sealiface.CommitBatchRes{res}, xerrors.Errorf("getting network version: %s", err) } - aggFee := policy.AggregateNetworkFee(nv, len(infos), bf) + aggFee := big.Div(big.Mul(policy.AggregateNetworkFee(nv, len(infos), bf), aggFeeNum), aggFeeDen) needFunds := big.Add(collateral, aggFee) From 10e00b1ba981f17e3ad747f542bc56f3c0cca217 Mon Sep 17 00:00:00 2001 From: wangchao Date: Mon, 28 Jun 2021 17:50:31 +0800 Subject: [PATCH 05/98] remove precommit check in handleCommitFailed --- extern/storage-sealing/states_failed.go | 28 +------------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index 7bef19b92..3a0177978 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -182,7 +182,7 @@ func (m *Sealing) handleComputeProofFailed(ctx statemachine.Context, sector Sect } func (m *Sealing) handleCommitFailed(ctx statemachine.Context, sector SectorInfo) error { - tok, height, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) return nil @@ -216,32 +216,6 @@ func (m *Sealing) handleCommitFailed(ctx statemachine.Context, sector SectorInfo } } - if err := checkPrecommit(ctx.Context(), m.maddr, sector, tok, height, m.api); err != nil { - switch err.(type) { - case *ErrApi: - log.Errorf("handleCommitFailed: api error, not proceeding: %+v", err) - return nil - case *ErrBadCommD: - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad CommD error: %w", err)}) - case *ErrExpiredTicket: - return ctx.Send(SectorTicketExpired{xerrors.Errorf("ticket expired error, removing sector: %w", err)}) - case *ErrBadTicket: - return ctx.Send(SectorTicketExpired{xerrors.Errorf("expired ticket, removing sector: %w", err)}) - case *ErrInvalidDeals: - log.Warnf("invalid deals in sector %d: %v", sector.SectorNumber, err) - return ctx.Send(SectorInvalidDealIDs{Return: RetCommitFailed}) - case *ErrExpiredDeals: - return ctx.Send(SectorDealsExpired{xerrors.Errorf("sector deals expired: %w", err)}) - case nil: - return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("no precommit: %w", err)}) - case *ErrPrecommitOnChain: - // noop, this is expected - case *ErrSectorNumberAllocated: - // noop, already committed? - default: - return xerrors.Errorf("checkPrecommit sanity check error (%T): %w", err, err) - } - } if err := m.checkCommit(ctx.Context(), sector, sector.Proof, tok); err != nil { switch err.(type) { From 5f3b21269d72f08956be7a491606729691e03b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 30 Jun 2021 15:19:29 +0200 Subject: [PATCH 06/98] gofmt --- extern/storage-sealing/states_failed.go | 1 - 1 file changed, 1 deletion(-) diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index 3a0177978..201c4456f 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -216,7 +216,6 @@ func (m *Sealing) handleCommitFailed(ctx statemachine.Context, sector SectorInfo } } - if err := m.checkCommit(ctx.Context(), sector, sector.Proof, tok); err != nil { switch err.(type) { case *ErrApi: From e9f3a2f4861c5f43c313dc719a26b1e55949c1ec Mon Sep 17 00:00:00 2001 From: johnli-helloworld Date: Fri, 25 Jun 2021 17:25:17 +0800 Subject: [PATCH 07/98] handleSubmitCommitAggregate() exception handling --- extern/storage-sealing/fsm.go | 1 + extern/storage-sealing/states_sealing.go | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index d3e1e9d52..d04aef790 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -115,6 +115,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto SubmitCommitAggregate: planOne( on(SectorCommitAggregateSent{}, CommitWait), on(SectorCommitFailed{}, CommitFailed), + on(SectorRetrySubmitCommit{}, SubmitCommit), ), CommitWait: planOne( on(SectorProving{}, FinalizeSector), diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 360eeafa6..bd566cdcd 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -624,11 +624,21 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S Spt: sector.SectorType, }) if err != nil { - return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) + return ctx.Send(SectorRetrySubmitCommit{}) } if res.Error != "" { - return ctx.Send(SectorCommitFailed{xerrors.Errorf("aggregate error: %s", res.Error)}) + tok, _, err := m.api.ChainHead(ctx.Context()) + if err != nil { + log.Errorf("handleSubmitCommit: api error, not proceeding: %+v", err) + return nil + } + + if err := m.checkCommit(ctx.Context(), sector, sector.Proof, tok); err != nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("commit check error: %w", err)}) + } + + return ctx.Send(SectorRetrySubmitCommit{}) } if e, found := res.FailedSectors[sector.SectorNumber]; found { From 22f183e8effd96e995ee06801c9e22a8062b23b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 Jul 2021 13:33:54 +0200 Subject: [PATCH 08/98] commit batch: AggregateAboveBaseFee config --- chain/types/fil.go | 5 ++ extern/storage-sealing/commit_batch.go | 20 +++++- extern/storage-sealing/commit_batch_test.go | 72 +++++++++++++++++---- extern/storage-sealing/sealiface/config.go | 8 ++- node/config/def.go | 6 ++ node/modules/storageminer.go | 22 ++++--- 6 files changed, 108 insertions(+), 25 deletions(-) diff --git a/chain/types/fil.go b/chain/types/fil.go index 7438410c8..21125e6d6 100644 --- a/chain/types/fil.go +++ b/chain/types/fil.go @@ -23,6 +23,11 @@ func (f FIL) Unitless() string { return strings.TrimRight(strings.TrimRight(r.FloatString(18), "0"), ".") } +var AttoFil = NewInt(1) +var FemtoFil = BigMul(AttoFil, NewInt(1000)) +var PicoFil = BigMul(FemtoFil, NewInt(1000)) +var NanoFil = BigMul(PicoFil, NewInt(1000)) + var unitPrefixes = []string{"a", "f", "p", "n", "μ", "m"} func (f FIL) Short() string { diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 740e98fca..6eaa3e908 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -196,7 +196,25 @@ func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, var res []sealiface.CommitBatchRes - if total < cfg.MinCommitBatch || total < miner5.MinAggregatedSectors { + individual := (total < cfg.MinCommitBatch) || (total < miner5.MinAggregatedSectors) + + if !individual && !cfg.AggregateAboveBaseFee.Equals(big.Zero()) { + tok, _, err := b.api.ChainHead(b.mctx) + if err != nil { + return nil, err + } + + bf, err := b.api.ChainBaseFee(b.mctx, tok) + if err != nil { + return nil, xerrors.Errorf("couldn't get base fee: %w", err) + } + + if bf.LessThan(cfg.AggregateAboveBaseFee) { + individual = true + } + } + + if individual { res, err = b.processIndividually() } else { res, err = b.processBatch(cfg) diff --git a/extern/storage-sealing/commit_batch_test.go b/extern/storage-sealing/commit_batch_test.go index ad2bc8f6f..d7cf4a9a4 100644 --- a/extern/storage-sealing/commit_batch_test.go +++ b/extern/storage-sealing/commit_batch_test.go @@ -20,6 +20,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/extern/storage-sealing/mocks" @@ -58,6 +59,8 @@ func TestCommitBatcher(t *testing.T) { CommitBatchWait: 24 * time.Hour, CommitBatchSlack: 1 * time.Hour, + AggregateAboveBaseFee: types.BigMul(types.PicoFil, types.NewInt(150)), // 0.15 nFIL + TerminateBatchMin: 1, TerminateBatchMax: 100, TerminateBatchWait: 5 * time.Minute, @@ -143,7 +146,7 @@ func TestCommitBatcher(t *testing.T) { } } - expectSend := func(expect []abi.SectorNumber) action { + expectSend := func(expect []abi.SectorNumber, aboveBalancer bool) action { return func(t *testing.T, s *mocks.MockCommitBatcherApi, pcb *sealing.CommitBatcher) promise { s.EXPECT().StateMinerInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(miner.MinerInfo{Owner: t0123, Worker: t0123}, nil) @@ -153,6 +156,22 @@ func TestCommitBatcher(t *testing.T) { batch = true ti = 1 } + + basefee := types.PicoFil + if aboveBalancer { + basefee = types.NanoFil + } + + if batch { + s.EXPECT().ChainHead(gomock.Any()).Return(nil, abi.ChainEpoch(1), nil) + s.EXPECT().ChainBaseFee(gomock.Any(), gomock.Any()).Return(basefee, nil) + } + + if !aboveBalancer { + batch = false + ti = len(expect) + } + s.EXPECT().ChainHead(gomock.Any()).Return(nil, abi.ChainEpoch(1), nil) s.EXPECT().StateSectorPreCommitInfo(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&miner.SectorPreCommitOnChainInfo{ PreCommitDeposit: big.Zero(), @@ -160,7 +179,7 @@ func TestCommitBatcher(t *testing.T) { s.EXPECT().StateMinerInitialPledgeCollateral(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(big.Zero(), nil).Times(len(expect)) if batch { s.EXPECT().StateNetworkVersion(gomock.Any(), gomock.Any()).Return(network.Version13, nil) - s.EXPECT().ChainBaseFee(gomock.Any(), gomock.Any()).Return(big.NewInt(2000), nil) + s.EXPECT().ChainBaseFee(gomock.Any(), gomock.Any()).Return(basefee, nil) } s.EXPECT().SendMsg(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), funMatcher(func(i interface{}) bool { @@ -183,11 +202,11 @@ func TestCommitBatcher(t *testing.T) { } } - flush := func(expect []abi.SectorNumber) action { + flush := func(expect []abi.SectorNumber, aboveBalancer bool) action { return func(t *testing.T, s *mocks.MockCommitBatcherApi, pcb *sealing.CommitBatcher) promise { - _ = expectSend(expect)(t, s, pcb) + _ = expectSend(expect, aboveBalancer)(t, s, pcb) - batch := len(expect) >= minBatch + batch := len(expect) >= minBatch && aboveBalancer r, err := pcb.Flush(ctx) require.NoError(t, err) @@ -227,30 +246,57 @@ func TestCommitBatcher(t *testing.T) { tcs := map[string]struct { actions []action }{ - "addSingle": { + "addSingle-aboveBalancer": { actions: []action{ addSector(0), waitPending(1), - flush([]abi.SectorNumber{0}), + flush([]abi.SectorNumber{0}, true), }, }, - "addTwo": { + "addTwo-aboveBalancer": { actions: []action{ addSectors(getSectors(2)), waitPending(2), - flush(getSectors(2)), + flush(getSectors(2), true), }, }, - "addAte": { + "addAte-aboveBalancer": { actions: []action{ addSectors(getSectors(8)), waitPending(8), - flush(getSectors(8)), + flush(getSectors(8), true), }, }, - "addMax": { + "addMax-aboveBalancer": { actions: []action{ - expectSend(getSectors(maxBatch)), + expectSend(getSectors(maxBatch), true), + addSectors(getSectors(maxBatch)), + }, + }, + "addSingle-belowBalancer": { + actions: []action{ + addSector(0), + waitPending(1), + flush([]abi.SectorNumber{0}, false), + }, + }, + "addTwo-belowBalancer": { + actions: []action{ + addSectors(getSectors(2)), + waitPending(2), + flush(getSectors(2), false), + }, + }, + "addAte-belowBalancer": { + actions: []action{ + addSectors(getSectors(8)), + waitPending(8), + flush(getSectors(8), false), + }, + }, + "addMax-belowBalancer": { + actions: []action{ + expectSend(getSectors(maxBatch), false), addSectors(getSectors(maxBatch)), }, }, diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index b237072d3..0410b92c0 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -1,6 +1,10 @@ package sealiface -import "time" +import ( + "time" + + "github.com/filecoin-project/go-state-types/abi" +) // this has to be in a separate package to not make lotus API depend on filecoin-ffi @@ -31,6 +35,8 @@ type Config struct { CommitBatchWait time.Duration CommitBatchSlack time.Duration + AggregateAboveBaseFee abi.TokenAmount + TerminateBatchMax uint64 TerminateBatchMin uint64 TerminateBatchWait time.Duration diff --git a/node/config/def.go b/node/config/def.go index b331b1f49..240fadbd9 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -144,6 +144,10 @@ type SealingConfig struct { // time buffer for forceful batch submission before sectors/deals in batch would start expiring CommitBatchSlack Duration + // network BaseFee below which to stop doing commit aggregation, instead + // submitting proofs to the chain individually + AggregateAboveBaseFee types.FIL + TerminateBatchMax uint64 TerminateBatchMin uint64 TerminateBatchWait Duration @@ -330,6 +334,8 @@ func DefaultStorageMiner() *StorageMiner { CommitBatchWait: Duration(24 * time.Hour), // this can be up to 30 days CommitBatchSlack: Duration(1 * time.Hour), // time buffer for forceful batch submission before sectors/deals in batch would start expiring, higher value will lower the chances for message fail due to expiration + AggregateAboveBaseFee: types.FIL(types.BigMul(types.PicoFil, types.NewInt(150))), // 0.15 nFIL + TerminateBatchMin: 1, TerminateBatchMax: 100, TerminateBatchWait: Duration(5 * time.Minute), diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 09b1e2dfd..3d1d08071 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -866,11 +866,12 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error 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), + AggregateCommits: cfg.AggregateCommits, + MinCommitBatch: cfg.MinCommitBatch, + MaxCommitBatch: cfg.MaxCommitBatch, + CommitBatchWait: config.Duration(cfg.CommitBatchWait), + CommitBatchSlack: config.Duration(cfg.CommitBatchSlack), + AggregateAboveBaseFee: types.FIL(cfg.AggregateAboveBaseFee), TerminateBatchMax: cfg.TerminateBatchMax, TerminateBatchMin: cfg.TerminateBatchMin, @@ -897,11 +898,12 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error 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), + AggregateCommits: cfg.Sealing.AggregateCommits, + MinCommitBatch: cfg.Sealing.MinCommitBatch, + MaxCommitBatch: cfg.Sealing.MaxCommitBatch, + CommitBatchWait: time.Duration(cfg.Sealing.CommitBatchWait), + CommitBatchSlack: time.Duration(cfg.Sealing.CommitBatchSlack), + AggregateAboveBaseFee: types.BigInt(cfg.Sealing.AggregateAboveBaseFee), TerminateBatchMax: cfg.Sealing.TerminateBatchMax, TerminateBatchMin: cfg.Sealing.TerminateBatchMin, From 3875746f808fe17e558116c756e1c0a97f074820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 Jul 2021 13:51:11 +0200 Subject: [PATCH 09/98] commit batch: Regression test nil FailedSectors map --- extern/storage-sealing/commit_batch_test.go | 65 ++++++++++++++++----- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/extern/storage-sealing/commit_batch_test.go b/extern/storage-sealing/commit_batch_test.go index d7cf4a9a4..aea6d455e 100644 --- a/extern/storage-sealing/commit_batch_test.go +++ b/extern/storage-sealing/commit_batch_test.go @@ -146,7 +146,7 @@ func TestCommitBatcher(t *testing.T) { } } - expectSend := func(expect []abi.SectorNumber, aboveBalancer bool) action { + expectSend := func(expect []abi.SectorNumber, aboveBalancer, failOnePCI bool) action { return func(t *testing.T, s *mocks.MockCommitBatcherApi, pcb *sealing.CommitBatcher) promise { s.EXPECT().StateMinerInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(miner.MinerInfo{Owner: t0123, Worker: t0123}, nil) @@ -173,10 +173,20 @@ func TestCommitBatcher(t *testing.T) { } s.EXPECT().ChainHead(gomock.Any()).Return(nil, abi.ChainEpoch(1), nil) + + pciC := len(expect) + if failOnePCI { + s.EXPECT().StateSectorPreCommitInfo(gomock.Any(), gomock.Any(), abi.SectorNumber(1), gomock.Any()).Return(nil, nil).Times(1) // not found + pciC = len(expect) - 1 + if !batch { + ti-- + } + } s.EXPECT().StateSectorPreCommitInfo(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&miner.SectorPreCommitOnChainInfo{ PreCommitDeposit: big.Zero(), - }, nil).Times(len(expect)) - s.EXPECT().StateMinerInitialPledgeCollateral(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(big.Zero(), nil).Times(len(expect)) + }, nil).Times(pciC) + s.EXPECT().StateMinerInitialPledgeCollateral(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(big.Zero(), nil).Times(pciC) + if batch { s.EXPECT().StateNetworkVersion(gomock.Any(), gomock.Any()).Return(network.Version13, nil) s.EXPECT().ChainBaseFee(gomock.Any(), gomock.Any()).Return(basefee, nil) @@ -202,9 +212,9 @@ func TestCommitBatcher(t *testing.T) { } } - flush := func(expect []abi.SectorNumber, aboveBalancer bool) action { + flush := func(expect []abi.SectorNumber, aboveBalancer, failOnePCI bool) action { return func(t *testing.T, s *mocks.MockCommitBatcherApi, pcb *sealing.CommitBatcher) promise { - _ = expectSend(expect, aboveBalancer)(t, s, pcb) + _ = expectSend(expect, aboveBalancer, failOnePCI)(t, s, pcb) batch := len(expect) >= minBatch && aboveBalancer @@ -217,6 +227,13 @@ func TestCommitBatcher(t *testing.T) { return r[0].Sectors[i] < r[0].Sectors[j] }) require.Equal(t, expect, r[0].Sectors) + if !failOnePCI { + require.Len(t, r[0].FailedSectors, 0) + } else { + require.Len(t, r[0].FailedSectors, 1) + _, found := r[0].FailedSectors[1] + require.True(t, found) + } } else { require.Len(t, r, len(expect)) for _, res := range r { @@ -228,6 +245,13 @@ func TestCommitBatcher(t *testing.T) { }) for i, res := range r { require.Equal(t, abi.SectorNumber(i), res.Sectors[0]) + if failOnePCI && res.Sectors[0] == 1 { + require.Len(t, res.FailedSectors, 1) + _, found := res.FailedSectors[1] + require.True(t, found) + } else { + require.Empty(t, res.FailedSectors) + } } } @@ -250,26 +274,26 @@ func TestCommitBatcher(t *testing.T) { actions: []action{ addSector(0), waitPending(1), - flush([]abi.SectorNumber{0}, true), + flush([]abi.SectorNumber{0}, true, false), }, }, "addTwo-aboveBalancer": { actions: []action{ addSectors(getSectors(2)), waitPending(2), - flush(getSectors(2), true), + flush(getSectors(2), true, false), }, }, "addAte-aboveBalancer": { actions: []action{ addSectors(getSectors(8)), waitPending(8), - flush(getSectors(8), true), + flush(getSectors(8), true, false), }, }, "addMax-aboveBalancer": { actions: []action{ - expectSend(getSectors(maxBatch), true), + expectSend(getSectors(maxBatch), true, false), addSectors(getSectors(maxBatch)), }, }, @@ -277,29 +301,44 @@ func TestCommitBatcher(t *testing.T) { actions: []action{ addSector(0), waitPending(1), - flush([]abi.SectorNumber{0}, false), + flush([]abi.SectorNumber{0}, false, false), }, }, "addTwo-belowBalancer": { actions: []action{ addSectors(getSectors(2)), waitPending(2), - flush(getSectors(2), false), + flush(getSectors(2), false, false), }, }, "addAte-belowBalancer": { actions: []action{ addSectors(getSectors(8)), waitPending(8), - flush(getSectors(8), false), + flush(getSectors(8), false, false), }, }, "addMax-belowBalancer": { actions: []action{ - expectSend(getSectors(maxBatch), false), + expectSend(getSectors(maxBatch), false, false), addSectors(getSectors(maxBatch)), }, }, + + "addAte-aboveBalancer-failOne": { + actions: []action{ + addSectors(getSectors(8)), + waitPending(8), + flush(getSectors(8), true, true), + }, + }, + "addAte-belowBalancer-failOne": { + actions: []action{ + addSectors(getSectors(8)), + waitPending(8), + flush(getSectors(8), false, true), + }, + }, } for name, tc := range tcs { From 31c929e7b728e0e094acee9fc150007740746d78 Mon Sep 17 00:00:00 2001 From: llifezou Date: Wed, 30 Jun 2021 16:32:44 +0800 Subject: [PATCH 10/98] fix ticket expiration check, otherwise it may cause a large number of loops to retry GetTicket when retrying PreCommit1 --- extern/storage-sealing/states_sealing.go | 38 ++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index bd566cdcd..92613cbdc 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -105,6 +105,10 @@ func checkTicketExpired(ticket, head abi.ChainEpoch) bool { return head-ticket > MaxTicketAge // TODO: allow configuring expected seal durations } +func checkProveCommitExpired(preCommitEpoch, msd abi.ChainEpoch, currEpoch abi.ChainEpoch) bool { + return currEpoch > preCommitEpoch+msd +} + func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.SealRandomness, abi.ChainEpoch, error) { tok, epoch, err := m.api.ChainHead(ctx.Context()) if err != nil { @@ -126,7 +130,14 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se if pci != nil { ticketEpoch = pci.Info.SealRandEpoch - if checkTicketExpired(ticketEpoch, epoch) { + nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + if err != nil { + return nil, 0, xerrors.Errorf("getTicket: StateNetworkVersion: api error, not proceeding: %+v", err) + } + + msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) + + if checkProveCommitExpired(pci.PreCommitEpoch, msd, epoch) { return nil, 0, xerrors.Errorf("ticket expired for precommitted sector") } } @@ -182,14 +193,35 @@ func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) } } - _, height, err := m.api.ChainHead(ctx.Context()) + tok, height, err := m.api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err) return nil } if checkTicketExpired(sector.TicketEpoch, height) { - return ctx.Send(SectorOldTicket{}) // go get new ticket + pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + if err != nil { + log.Errorf("handlePreCommit1: StateSectorPreCommitInfo: api error, not proceeding: %+v", err) + return nil + } + + if pci == nil { + return ctx.Send(SectorOldTicket{}) // go get new ticket + } + + nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + if err != nil { + log.Errorf("handlePreCommit1: StateNetworkVersion: api error, not proceeding: %+v", err) + return nil + } + + msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) + + // if height > PreCommitEpoch + msd, there is no need to recalculate + if checkProveCommitExpired(pci.PreCommitEpoch, msd, height) { + return ctx.Send(SectorOldTicket{}) // will be removed + } } pc1o, err := m.sealer.SealPreCommit1(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), sector.TicketValue, sector.pieceInfos()) From 21528c0a95b6e4f72c9af4411aa422fabcf52951 Mon Sep 17 00:00:00 2001 From: llifezou Date: Thu, 1 Jul 2021 10:53:42 +0800 Subject: [PATCH 11/98] fix getTicket: sector precommitted but expired case --- extern/storage-sealing/states_sealing.go | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 92613cbdc..1d0e092e9 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -109,22 +109,30 @@ func checkProveCommitExpired(preCommitEpoch, msd abi.ChainEpoch, currEpoch abi.C return currEpoch > preCommitEpoch+msd } -func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.SealRandomness, abi.ChainEpoch, error) { +func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.SealRandomness, abi.ChainEpoch, bool, error) { tok, epoch, err := m.api.ChainHead(ctx.Context()) if err != nil { - log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err) - return nil, 0, nil + log.Errorf("getTicket: api error, not proceeding: %+v", err) + return nil, 0, false, nil + } + + // the reason why the StateMinerSectorAllocated function is placed here, if it is outside, + // if the MarshalCBOR function and StateSectorPreCommitInfo function return err, it will be executed + allocated, aerr := m.api.StateMinerSectorAllocated(ctx.Context(), m.maddr, sector.SectorNumber, nil) + if aerr != nil { + log.Errorf("getTicket: api error, checking if sector is allocated: %+v", aerr) + return nil, 0, false, nil } ticketEpoch := epoch - policy.SealRandomnessLookback buf := new(bytes.Buffer) if err := m.maddr.MarshalCBOR(buf); err != nil { - return nil, 0, err + return nil, 0, allocated, err } pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { - return nil, 0, xerrors.Errorf("getting precommit info: %w", err) + return nil, 0, allocated, xerrors.Errorf("getting precommit info: %w", err) } if pci != nil { @@ -132,32 +140,31 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) if err != nil { - return nil, 0, xerrors.Errorf("getTicket: StateNetworkVersion: api error, not proceeding: %+v", err) + return nil, 0, allocated, xerrors.Errorf("getTicket: StateNetworkVersion: api error, not proceeding: %+v", err) } msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) if checkProveCommitExpired(pci.PreCommitEpoch, msd, epoch) { - return nil, 0, xerrors.Errorf("ticket expired for precommitted sector") + return nil, 0, allocated, xerrors.Errorf("ticket expired for precommitted sector") } } + if allocated { // allocated is true, sector precommitted but expired, will SectorCommitFailed or SectorRemove + return nil, 0, allocated, xerrors.Errorf("Sector %s precommitted but expired", sector.SectorNumber) + } + rand, err := m.api.ChainGetRandomnessFromTickets(ctx.Context(), tok, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes()) if err != nil { - return nil, 0, err + return nil, 0, allocated, err } - return abi.SealRandomness(rand), ticketEpoch, nil + return abi.SealRandomness(rand), ticketEpoch, allocated, nil } func (m *Sealing) handleGetTicket(ctx statemachine.Context, sector SectorInfo) error { - ticketValue, ticketEpoch, err := m.getTicket(ctx, sector) + ticketValue, ticketEpoch, allocated, err := m.getTicket(ctx, sector) if err != nil { - allocated, aerr := m.api.StateMinerSectorAllocated(ctx.Context(), m.maddr, sector.SectorNumber, nil) - if aerr != nil { - log.Errorf("error checking if sector is allocated: %+v", aerr) - } - if allocated { if sector.CommitMessage != nil { // Some recovery paths with unfortunate timing lead here From 101d152540b3bd5f76f4606f9755bbc655824dcc Mon Sep 17 00:00:00 2001 From: llifezou <46102475+llifezou@users.noreply.github.com> Date: Fri, 2 Jul 2021 11:38:04 +0800 Subject: [PATCH 12/98] Update extern/storage-sealing/states_sealing.go fix log Co-authored-by: Aayush Rajasekaran --- 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 1d0e092e9..dcea02a99 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -151,7 +151,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se } if allocated { // allocated is true, sector precommitted but expired, will SectorCommitFailed or SectorRemove - return nil, 0, allocated, xerrors.Errorf("Sector %s precommitted but expired", sector.SectorNumber) + return nil, 0, allocated, xerrors.Errorf("sector %s precommitted but expired", sector.SectorNumber) } rand, err := m.api.ChainGetRandomnessFromTickets(ctx.Context(), tok, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes()) From d5b7e81b095932b789f0f4195445a55fc19a69e3 Mon Sep 17 00:00:00 2001 From: llifezou <46102475+llifezou@users.noreply.github.com> Date: Fri, 2 Jul 2021 11:44:46 +0800 Subject: [PATCH 13/98] Update extern/storage-sealing/states_sealing.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix sector precommitted but expired judgment Co-authored-by: Łukasz Magiera --- 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 dcea02a99..1442d82cc 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -150,7 +150,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se } } - if allocated { // allocated is true, sector precommitted but expired, will SectorCommitFailed or SectorRemove + if pci == nil && allocated { // allocated is true, sector precommitted but expired, will SectorCommitFailed or SectorRemove return nil, 0, allocated, xerrors.Errorf("sector %s precommitted but expired", sector.SectorNumber) } From dbdbb6f0dd96e2f44d4c586eedad15925bc67af2 Mon Sep 17 00:00:00 2001 From: johnli-helloworld Date: Wed, 30 Jun 2021 16:56:40 +0800 Subject: [PATCH 14/98] to optimize the batchwait --- extern/storage-sealing/commit_batch.go | 22 ++++++++++++++++------ extern/storage-sealing/precommit_batch.go | 22 ++++++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 6eaa3e908..63bd3c7db 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -106,6 +106,7 @@ func (b *CommitBatcher) run() { panic(err) } + timer := time.NewTimer(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) for { if forceRes != nil { forceRes <- lastMsg @@ -121,7 +122,7 @@ func (b *CommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack): + case <-timer.C: // do nothing case fr := <-b.force: // user triggered forceRes = fr @@ -132,17 +133,26 @@ func (b *CommitBatcher) run() { if err != nil { log.Warnw("CommitBatcher processBatch error", "error", err) } + + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + + timer.Reset(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) } } -func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time { +func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { now := time.Now() b.lk.Lock() defer b.lk.Unlock() if len(b.todo) == 0 { - return nil + return maxWait } var cutoff time.Time @@ -160,12 +170,12 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time } if cutoff.IsZero() { - return time.After(maxWait) + return maxWait } cutoff = cutoff.Add(-slack) if cutoff.Before(now) { - return time.After(time.Nanosecond) // can't return 0 + return time.Nanosecond // can't return 0 } wait := cutoff.Sub(now) @@ -173,7 +183,7 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time wait = maxWait } - return time.After(wait) + return wait } func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, error) { diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 3dc3510c2..1b43bc6b9 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -88,6 +88,7 @@ func (b *PreCommitBatcher) run() { panic(err) } + timer := time.NewTimer(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) for { if forceRes != nil { forceRes <- lastRes @@ -102,7 +103,7 @@ func (b *PreCommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack): + case <-timer.C: // do nothing case fr := <-b.force: // user triggered forceRes = fr @@ -113,17 +114,26 @@ func (b *PreCommitBatcher) run() { if err != nil { log.Warnw("PreCommitBatcher processBatch error", "error", err) } + + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + + timer.Reset(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) } } -func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time { +func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { now := time.Now() b.lk.Lock() defer b.lk.Unlock() if len(b.todo) == 0 { - return nil + return maxWait } var cutoff time.Time @@ -141,12 +151,12 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.T } if cutoff.IsZero() { - return time.After(maxWait) + return maxWait } cutoff = cutoff.Add(-slack) if cutoff.Before(now) { - return time.After(time.Nanosecond) // can't return 0 + return time.Nanosecond // can't return 0 } wait := cutoff.Sub(now) @@ -154,7 +164,7 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.T wait = maxWait } - return time.After(wait) + return wait } func (b *PreCommitBatcher) maybeStartBatch(notif bool) ([]sealiface.PreCommitBatchRes, error) { From 57d70e58f3a53f9348a28ef63bae59918f78ff66 Mon Sep 17 00:00:00 2001 From: zhoutian527 Date: Fri, 2 Jul 2021 15:53:21 +0800 Subject: [PATCH 15/98] Fix: precommit_batch method used the wrong cfg.PreCommitBatchWait --- extern/storage-sealing/precommit_batch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 1b43bc6b9..8b132a2eb 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -88,7 +88,7 @@ func (b *PreCommitBatcher) run() { panic(err) } - timer := time.NewTimer(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) + timer := time.NewTimer(b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack)) for { if forceRes != nil { forceRes <- lastRes @@ -122,7 +122,7 @@ func (b *PreCommitBatcher) run() { } } - timer.Reset(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)) + timer.Reset(b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack)) } } From da0920584cf791ab1916dea10464569e4d059ad4 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 2 Jul 2021 18:02:56 +0200 Subject: [PATCH 16/98] Fix tiny error in check-client-datacap --- cli/filplus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/filplus.go b/cli/filplus.go index 53dc5092b..007071ea2 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -210,7 +210,7 @@ var filplusCheckClientCmd = &cli.Command{ return err } if dcap == nil { - return xerrors.Errorf("client %s is not a verified client", err) + return xerrors.Errorf("client %s is not a verified client", caddr) } fmt.Println(*dcap) From 6610965f8749e5de36dc1d0877f0faa096636fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 Jul 2021 21:07:53 +0200 Subject: [PATCH 17/98] storage: Fix FinalizeSector with sectors in stoage paths --- .circleci/config.yml | 5 +++ cmd/lotus-seal-worker/storage.go | 2 +- cmd/lotus-storage-miner/storage.go | 2 +- extern/sector-storage/manager.go | 17 ++++++- itests/deals_test.go | 2 +- itests/kit/node_miner.go | 47 +++++++++++++++++++- itests/sector_finalize_early_test.go | 66 ++++++++++++++++++++++++++++ node/modules/storageminer.go | 52 ++++++++++++---------- 8 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 itests/sector_finalize_early_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f8f28b86..d151026f0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -826,6 +826,11 @@ workflows: suite: itest-sdr_upgrade target: "./itests/sdr_upgrade_test.go" + - test: + name: test-itest-sector_finalize_early + suite: itest-sector_finalize_early + target: "./itests/sector_finalize_early_test.go" + - test: name: test-itest-sector_pledge suite: itest-sector_pledge diff --git a/cmd/lotus-seal-worker/storage.go b/cmd/lotus-seal-worker/storage.go index afb566166..be662a6c3 100644 --- a/cmd/lotus-seal-worker/storage.go +++ b/cmd/lotus-seal-worker/storage.go @@ -101,7 +101,7 @@ var storageAttachCmd = &cli.Command{ } if !(cfg.CanStore || cfg.CanSeal) { - return xerrors.Errorf("must specify at least one of --store of --seal") + return xerrors.Errorf("must specify at least one of --store or --seal") } b, err := json.MarshalIndent(cfg, "", " ") diff --git a/cmd/lotus-storage-miner/storage.go b/cmd/lotus-storage-miner/storage.go index b4ab26ad3..f2068ea86 100644 --- a/cmd/lotus-storage-miner/storage.go +++ b/cmd/lotus-storage-miner/storage.go @@ -145,7 +145,7 @@ over time } if !(cfg.CanStore || cfg.CanSeal) { - return xerrors.Errorf("must specify at least one of --store of --seal") + return xerrors.Errorf("must specify at least one of --store or --seal") } b, err := json.MarshalIndent(cfg, "", " ") diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index 136c00252..79eca74d6 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -528,10 +528,25 @@ func (m *Manager) FinalizeSector(ctx context.Context, sector storage.SectorRef, } } + pathType := storiface.PathStorage + { + sealedStores, err := m.index.StorageFindSector(ctx, sector.ID, storiface.FTSealed, 0, false) + if err != nil { + return xerrors.Errorf("finding sealed sector: %w", err) + } + + for _, store := range sealedStores { + if store.CanSeal { + pathType = storiface.PathSealing + break + } + } + } + selector := newExistingSelector(m.index, sector.ID, storiface.FTCache|storiface.FTSealed, false) err := m.sched.Schedule(ctx, sector, sealtasks.TTFinalize, selector, - m.schedFetch(sector, storiface.FTCache|storiface.FTSealed|unsealed, storiface.PathSealing, storiface.AcquireMove), + m.schedFetch(sector, storiface.FTCache|storiface.FTSealed|unsealed, pathType, storiface.AcquireMove), func(ctx context.Context, w Worker) error { _, err := m.waitSimpleCall(ctx)(w.FinalizeSector(ctx, sector, keepUnsealed)) return err diff --git a/itests/deals_test.go b/itests/deals_test.go index f8389bbd6..8aff414e0 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -14,7 +14,7 @@ func TestDealsWithSealingAndRPC(t *testing.T) { kit.QuietMiningLogs() - var blockTime = 1 * time.Second + var blockTime = 50 * time.Millisecond client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) // no mock proofs. ens.InterconnectAll().BeginMining(blockTime) diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go index d3f0d2e3c..eea2bc0c1 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit/node_miner.go @@ -2,22 +2,29 @@ package kit import ( "context" + "encoding/json" "fmt" + "io/ioutil" + "os" + "path/filepath" "strings" "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/extern/sector-storage/stores" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/miner" libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" "github.com/multiformats/go-multiaddr" - "github.com/stretchr/testify/require" ) // TestMiner represents a miner enrolled in an Ensemble. @@ -119,3 +126,41 @@ func (tm *TestMiner) FlushSealingBatches(ctx context.Context) { fmt.Printf("COMMIT BATCH: %+v\n", cb) } } + +const metaFile = "sectorstore.json" + +func (tm *TestMiner) AddStorage(ctx context.Context, t *testing.T, weight uint64, seal, store bool) { + p, err := ioutil.TempDir("", "lotus-testsectors-") + require.NoError(t, err) + + if err := os.MkdirAll(p, 0755); err != nil { + if !os.IsExist(err) { + require.NoError(t, err) + } + } + + _, err = os.Stat(filepath.Join(p, metaFile)) + if !os.IsNotExist(err) { + require.NoError(t, err) + } + + cfg := &stores.LocalStorageMeta{ + ID: stores.ID(uuid.New().String()), + Weight: weight, + CanSeal: seal, + CanStore: store, + } + + if !(cfg.CanStore || cfg.CanSeal) { + t.Fatal("must specify at least one of CanStore or cfg.CanSeal") + } + + b, err := json.MarshalIndent(cfg, "", " ") + require.NoError(t, err) + + err = ioutil.WriteFile(filepath.Join(p, metaFile), b, 0644) + require.NoError(t, err) + + err = tm.StorageAddLocal(ctx, p) + require.NoError(t, err) +} diff --git a/itests/sector_finalize_early_test.go b/itests/sector_finalize_early_test.go new file mode 100644 index 000000000..3eb980f9e --- /dev/null +++ b/itests/sector_finalize_early_test.go @@ -0,0 +1,66 @@ +package itests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo" +) + +func TestDealsWithFinalizeEarly(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + var blockTime = 50 * time.Millisecond + + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(), kit.ConstructorOpts( + node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { + return func() (sealiface.Config, error) { + cf := config.DefaultStorageMiner() + cf.Sealing.FinalizeEarly = true + return modules.ToSealingConfig(cf), nil + }, nil + })))) // no mock proofs. + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + ctx := context.Background() + + miner.AddStorage(ctx, t, 1000000000, true, false) + miner.AddStorage(ctx, t, 1000000000, false, true) + + sl, err := miner.StorageList(ctx) + require.NoError(t, err) + for si, d := range sl { + i, err := miner.StorageInfo(ctx, si) + require.NoError(t, err) + + fmt.Printf("stor d:%d %+v\n", len(d), i) + } + + t.Run("single", func(t *testing.T) { + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) + }) + + sl, err = miner.StorageList(ctx) + require.NoError(t, err) + for si, d := range sl { + i, err := miner.StorageInfo(ctx, si) + require.NoError(t, err) + + fmt.Printf("stor d:%d %+v\n", len(d), i) + } +} diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 3d1d08071..8508850d3 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -882,33 +882,37 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error }, nil } +func ToSealingConfig(cfg *config.StorageMiner) sealiface.Config { + return sealiface.Config{ + MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, + MaxSealingSectors: cfg.Sealing.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, + WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), + AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.Sealing.FinalizeEarly, + + BatchPreCommits: cfg.Sealing.BatchPreCommits, + 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), + AggregateAboveBaseFee: types.BigInt(cfg.Sealing.AggregateAboveBaseFee), + + TerminateBatchMax: cfg.Sealing.TerminateBatchMax, + TerminateBatchMin: cfg.Sealing.TerminateBatchMin, + TerminateBatchWait: time.Duration(cfg.Sealing.TerminateBatchWait), + } +} + func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error) { return func() (out sealiface.Config, err error) { err = readCfg(r, func(cfg *config.StorageMiner) { - out = sealiface.Config{ - MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, - MaxSealingSectors: cfg.Sealing.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, - WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), - AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.Sealing.FinalizeEarly, - - BatchPreCommits: cfg.Sealing.BatchPreCommits, - 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), - AggregateAboveBaseFee: types.BigInt(cfg.Sealing.AggregateAboveBaseFee), - - TerminateBatchMax: cfg.Sealing.TerminateBatchMax, - TerminateBatchMin: cfg.Sealing.TerminateBatchMin, - TerminateBatchWait: time.Duration(cfg.Sealing.TerminateBatchWait), - } + out = ToSealingConfig(cfg) }) return }, nil From 947c86bd77c01c80297386d68f965925f52c8c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 Jul 2021 20:57:59 +0200 Subject: [PATCH 18/98] set version to v1.11.0-rc2 --- build/openrpc/full.json.gz | Bin 23439 -> 23439 bytes build/openrpc/miner.json.gz | Bin 8102 -> 8102 bytes build/openrpc/worker.json.gz | Bin 2513 -> 2513 bytes build/version.go | 2 +- documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus-worker.md | 2 +- documentation/en/cli-lotus.md | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 0cbb105e18b2e38b1576c3caadcd6f782ec7e8d4..b46c9ba5b3689df5e203ff5d4b9217f036db7442 100644 GIT binary patch delta 23 fcmeC*&e*@5aY7g4?2X-jqBv?-PW$b@l!XBRf{_Z2 delta 23 fcmeC*&e*@5aY7fP^v3Q#Q5^0n+ok=NvM>Mua-;~9 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index fe77f76c469f2823f39e81872279e8225a81d3f7..fd98bad0d831c578aaee8647650fc35b33430b48 100644 GIT binary patch delta 21 dcmZ2xzs!C@2jlXMoucv_m2X~f{O)07003pw2;Kky delta 21 dcmZ2xzs!C@2jl9Eoucv_nXWV5{_bI9003i@2*3aU diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 38c9a073b725d0b2b4a0958e5a8b2503cfac09dc..c6d1eb204fede74e7b3e5a0c3907fac345db636f 100644 GIT binary patch delta 21 dcmca8d{KBp6XU^+&2F3=mCs+D>?&el003#B2{!-$ delta 21 dcmca8d{KBp6XUUs&2F3=n~UyD?kZwn003tq2)zIR diff --git a/build/version.go b/build/version.go index 49ec3f446..92f011424 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-rc1" +const BuildVersion = "1.11.0-rc2" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index c8abb574b..42f964715 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -7,7 +7,7 @@ USAGE: lotus-miner [global options] command [command options] [arguments...] VERSION: - 1.11.0-dev + 1.11.0-rc2 COMMANDS: init Initialize a lotus miner repo diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 0b29da503..b7e04e5f1 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -7,7 +7,7 @@ USAGE: lotus-worker [global options] command [command options] [arguments...] VERSION: - 1.11.0-dev + 1.11.0-rc2 COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index a1583d522..1326c93f6 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -7,7 +7,7 @@ USAGE: lotus [global options] command [command options] [arguments...] VERSION: - 1.11.0-dev + 1.11.0-rc2 COMMANDS: daemon Start a lotus daemon process From 68bde3397773777b433e91ee0962b6d13245afb7 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Fri, 2 Jul 2021 15:58:56 -0400 Subject: [PATCH 19/98] update changelog and bump version to v1.11.0-rc2 --- CHANGELOG.md | 254 ++++++++++++++++++++++++++++++++++++++++++++++- build/version.go | 2 +- 2 files changed, 253 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718e86f9d..6a9530820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,258 @@ # Lotus changelog -# 1.11.0-rc1 / 2021-06-28 +# 1.11.0-rc2 / 2021-07-02 -This is the first release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes and feature development. A more detailed changelog will follow. +This is the second release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes +and feature development. A more detailed changelog will follow upon final release. + +- github.com/filecoin-project/lotus: + - update changelog and bump version to v1.11.0-rc2 + - Lotus version 1.11.0 + - gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) + - Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) + - revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) + - downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) + - Fix wallet error messages ([filecoin-project/lotus#6594](https://github.com/filecoin-project/lotus/pull/6594)) + - Fix CircleCI gen ([filecoin-project/lotus#6589](https://github.com/filecoin-project/lotus/pull/6589)) + - Make query-ask CLI more graceful ([filecoin-project/lotus#6590](https://github.com/filecoin-project/lotus/pull/6590)) + - ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) + - move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) + - scale up sector expiration to avoid sector expire in batch-pre-commit waitting ([filecoin-project/lotus#6566](https://github.com/filecoin-project/lotus/pull/6566)) + - Merge release branch into master ([filecoin-project/lotus#6583](https://github.com/filecoin-project/lotus/pull/6583)) + - ([filecoin-project/lotus#6582](https://github.com/filecoin-project/lotus/pull/6582)) + - fix circleci being out of sync. ([filecoin-project/lotus#6573](https://github.com/filecoin-project/lotus/pull/6573)) + - dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6571](https://github.com/filecoin-project/lotus/pull/6571)) + - Fix helptext ([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) + - extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6540](https://github.com/filecoin-project/lotus/pull/6540)) + - Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) + - fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) + - Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) + - Fix soup ([filecoin-project/lotus#6501](https://github.com/filecoin-project/lotus/pull/6501)) + - fix: pick the correct partitions-per-post limit ([filecoin-project/lotus#6502](https://github.com/filecoin-project/lotus/pull/6502)) + - Fix the build + - Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) + - Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) + - Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) + - sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) + - Merge 1.10 into master ([filecoin-project/lotus#6487](https://github.com/filecoin-project/lotus/pull/6487)) + - Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) + - Merge 1.10 changes into master ([filecoin-project/lotus#6466](https://github.com/filecoin-project/lotus/pull/6466)) + - Update chain list with correct help instructions ([filecoin-project/lotus#6465](https://github.com/filecoin-project/lotus/pull/6465)) + - clean failed sectors in batch commit ([filecoin-project/lotus#6451](https://github.com/filecoin-project/lotus/pull/6451)) + - itests/kit: add guard to ensure imports from tests only. ([filecoin-project/lotus#6445](https://github.com/filecoin-project/lotus/pull/6445)) + - consolidate integration tests into `itests` package; create test kit; cleanup ([filecoin-project/lotus#6311](https://github.com/filecoin-project/lotus/pull/6311)) + - Remove rc changelog, compile the new changelog for final release only ([filecoin-project/lotus#6444](https://github.com/filecoin-project/lotus/pull/6444)) + - updated configuration comments for docs ([filecoin-project/lotus#6440](https://github.com/filecoin-project/lotus/pull/6440)) + - Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6441](https://github.com/filecoin-project/lotus/pull/6441)) + - Merge release/v1.10.10 into master ([filecoin-project/lotus#6439](https://github.com/filecoin-project/lotus/pull/6439)) + - implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) + - Merge v1.10 release branch into master ([filecoin-project/lotus#6435](https://github.com/filecoin-project/lotus/pull/6435)) + - Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) + - Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) + - UX: lotus state power CLI should fail if called with a not-miner ([filecoin-project/lotus#6425](https://github.com/filecoin-project/lotus/pull/6425)) + - network reset friday + - Increase message size limit ([filecoin-project/lotus#6419](https://github.com/filecoin-project/lotus/pull/6419)) + - polish(stmgr): define ExecMonitor for message application callback ([filecoin-project/lotus#6389](https://github.com/filecoin-project/lotus/pull/6389)) + - upgrade testground action version ([filecoin-project/lotus#6403](https://github.com/filecoin-project/lotus/pull/6403)) + - Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) + - Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) + - Bypass task scheduler for reading unsealed pieces ([filecoin-project/lotus#6280](https://github.com/filecoin-project/lotus/pull/6280)) + - testplans: lotus-soup: use default WPoStChallengeWindow ([filecoin-project/lotus#6400](https://github.com/filecoin-project/lotus/pull/6400)) + - build snapcraft ([filecoin-project/lotus#6388](https://github.com/filecoin-project/lotus/pull/6388)) + - Fix the doc errors of the sealing config funcs ([filecoin-project/lotus#6399](https://github.com/filecoin-project/lotus/pull/6399)) + - Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) + - Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) + - Fix some flaky tests ([filecoin-project/lotus#6397](https://github.com/filecoin-project/lotus/pull/6397)) + - build appimage in CI ([filecoin-project/lotus#6384](https://github.com/filecoin-project/lotus/pull/6384)) + - Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) + - Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) + - Add interop network ([filecoin-project/lotus#6387](https://github.com/filecoin-project/lotus/pull/6387)) + - Network version 13 (v1.11) ([filecoin-project/lotus#6342](https://github.com/filecoin-project/lotus/pull/6342)) + - Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) + - lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) + - Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) + - update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) + - Add test for AddVerifiedClient ([filecoin-project/lotus#6317](https://github.com/filecoin-project/lotus/pull/6317)) + - Typo fix in error message: "pubusb" -> "pubsub" ([filecoin-project/lotus#6365](https://github.com/filecoin-project/lotus/pull/6365)) + - Improve the cli state call command ([filecoin-project/lotus#6226](https://github.com/filecoin-project/lotus/pull/6226)) + - Upscale mineOne message to a WARN on unexpected ineligibility ([filecoin-project/lotus#6358](https://github.com/filecoin-project/lotus/pull/6358)) + - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) + - Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) + - lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) + - Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) + - events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) + - Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) + - Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6333](https://github.com/filecoin-project/lotus/pull/6333)) + - Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) + - Revert "Allow starting networks from arbitrary actor versions" ([filecoin-project/lotus#6330](https://github.com/filecoin-project/lotus/pull/6330)) + - separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) + - feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) + - Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) + - Fix logging around mineOne ([filecoin-project/lotus#6310](https://github.com/filecoin-project/lotus/pull/6310)) + - Fix shell completions ([filecoin-project/lotus#6316](https://github.com/filecoin-project/lotus/pull/6316)) + - Allow 8MB sectors in devnet ([filecoin-project/lotus#6312](https://github.com/filecoin-project/lotus/pull/6312)) + - fix ticket expired ([filecoin-project/lotus#6304](https://github.com/filecoin-project/lotus/pull/6304)) + - oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) + - Move verifreg shed utils to CLI ([filecoin-project/lotus#6135](https://github.com/filecoin-project/lotus/pull/6135)) + - consider storiface.PathStorage when calculating storage requirements ([filecoin-project/lotus#6233](https://github.com/filecoin-project/lotus/pull/6233)) + - `storage` module: add go docs and minor code quality refactors ([filecoin-project/lotus#6259](https://github.com/filecoin-project/lotus/pull/6259)) + - Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) + - Increase data transfer timeouts ([filecoin-project/lotus#6300](https://github.com/filecoin-project/lotus/pull/6300)) + - gateway: spin off from cmd to package ([filecoin-project/lotus#6294](https://github.com/filecoin-project/lotus/pull/6294)) + - Update to markets 1.3 ([filecoin-project/lotus#6149](https://github.com/filecoin-project/lotus/pull/6149)) + - Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) + - Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) + - Merge v1.9.0 to master ([filecoin-project/lotus#6275](https://github.com/filecoin-project/lotus/pull/6275)) + - Backport 6200 to master ([filecoin-project/lotus#6272](https://github.com/filecoin-project/lotus/pull/6272)) + - Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) + - chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) + - fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) + - Update RELEASE_ISSUE_TEMPLATE.md + - fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) + - add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) + - Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) + - chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) + - Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) + - wip actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) + - Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) + - Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) + - Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) + - cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) + - This is a 1:1 forward-port of PR#6183 from 1.9.x to master ([filecoin-project/lotus#6196](https://github.com/filecoin-project/lotus/pull/6196)) + - Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) + - drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) + - Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) + - mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) + - Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) + - Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) + - feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) + - attempt to do better padding on pieces being written into sectors ([filecoin-project/lotus#5988](https://github.com/filecoin-project/lotus/pull/5988)) + - remove duplicate ask and calculate ping before lock ([filecoin-project/lotus#5968](https://github.com/filecoin-project/lotus/pull/5968)) + - Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) + - flaky tests improvement: separate TestBatchDealInput from TestAPIDealFlow ([filecoin-project/lotus#6141](https://github.com/filecoin-project/lotus/pull/6141)) + - Testground checks on push ([filecoin-project/lotus#5887](https://github.com/filecoin-project/lotus/pull/5887)) + - Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) + - Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) + - fix: use a consistent tipset in commands ([filecoin-project/lotus#6142](https://github.com/filecoin-project/lotus/pull/6142)) + - go mod tidy for lotus-soup testplans ([filecoin-project/lotus#6124](https://github.com/filecoin-project/lotus/pull/6124)) + - fix testground payment channel tests: use 1 miner ([filecoin-project/lotus#6126](https://github.com/filecoin-project/lotus/pull/6126)) + - fix: use the parent state when listing actors ([filecoin-project/lotus#6143](https://github.com/filecoin-project/lotus/pull/6143)) + - Speed up StateListMessages in some cases ([filecoin-project/lotus#6007](https://github.com/filecoin-project/lotus/pull/6007)) + - Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) + - fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) + - shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) + - upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122)) + - Merge releases (1.8.0) into master ([filecoin-project/lotus#6118](https://github.com/filecoin-project/lotus/pull/6118)) +- github.com/filecoin-project/go-commp-utils (v0.1.0 -> v0.1.1-0.20210427191551-70bf140d31c7): + - add a padding helper function ([filecoin-project/go-commp-utils#3](https://github.com/filecoin-project/go-commp-utils/pull/3)) +- github.com/filecoin-project/go-data-transfer (v1.4.3 -> v1.6.0): + - release: v1.6.0 + - fix: option to disable accept and complete timeouts + - fix: disable restart ack timeout + - release: v1.5.0 + - Add isRestart param to validators (#197) ([filecoin-project/go-data-transfer#197](https://github.com/filecoin-project/go-data-transfer/pull/197)) + - fix: flaky TestChannelMonitorAutoRestart (#198) ([filecoin-project/go-data-transfer#198](https://github.com/filecoin-project/go-data-transfer/pull/198)) + - Channel monitor watches for errors instead of measuring data rate (#190) ([filecoin-project/go-data-transfer#190](https://github.com/filecoin-project/go-data-transfer/pull/190)) + - fix: prevent concurrent restarts for same channel (#195) ([filecoin-project/go-data-transfer#195](https://github.com/filecoin-project/go-data-transfer/pull/195)) + - fix: channel state machine event handling (#194) ([filecoin-project/go-data-transfer#194](https://github.com/filecoin-project/go-data-transfer/pull/194)) + - Dont double count data sent (#185) ([filecoin-project/go-data-transfer#185](https://github.com/filecoin-project/go-data-transfer/pull/185)) + - release: v1.4.3 (#189) ([filecoin-project/go-data-transfer#189](https://github.com/filecoin-project/go-data-transfer/pull/189)) +- github.com/filecoin-project/go-fil-markets (v1.2.5 -> v1.5.0): + - release: v1.5.0 + - Dynamic Retrieval Pricing (#542) ([filecoin-project/go-fil-markets#542](https://github.com/filecoin-project/go-fil-markets/pull/542)) + - release: v1.4.0 (#551) ([filecoin-project/go-fil-markets#551](https://github.com/filecoin-project/go-fil-markets/pull/551)) + - Update to go data transfer v1.6.0 (#550) ([filecoin-project/go-fil-markets#550](https://github.com/filecoin-project/go-fil-markets/pull/550)) + - fix first make error (#548) ([filecoin-project/go-fil-markets#548](https://github.com/filecoin-project/go-fil-markets/pull/548)) + - release: v1.3.0 (#544) ([filecoin-project/go-fil-markets#544](https://github.com/filecoin-project/go-fil-markets/pull/544)) + - fix restarts during data transfer for a retrieval deal (#540) ([filecoin-project/go-fil-markets#540](https://github.com/filecoin-project/go-fil-markets/pull/540)) + - Test Retrieval for offline deals (#541) ([filecoin-project/go-fil-markets#541](https://github.com/filecoin-project/go-fil-markets/pull/541)) + - Allow anonymous submodule checkout (#535) ([filecoin-project/go-fil-markets#535](https://github.com/filecoin-project/go-fil-markets/pull/535)) +- github.com/filecoin-project/specs-actors (v0.9.13 -> v0.9.14): + - Set ConsensusMinerMinPower to 10 TiB (#1427) ([filecoin-project/specs-actors#1427](https://github.com/filecoin-project/specs-actors/pull/1427)) +- github.com/filecoin-project/specs-actors/v2 (v2.3.5-0.20210114162132-5b58b773f4fb -> v2.3.5): + - Set ConsensusMinerMinPower to 10 TiB (#1428) ([filecoin-project/specs-actors#1428](https://github.com/filecoin-project/specs-actors/pull/1428)) + - v2 VM satisfies SimVM interface (#1355) ([filecoin-project/specs-actors#1355](https://github.com/filecoin-project/specs-actors/pull/1355)) +- github.com/filecoin-project/specs-actors/v3 (v3.1.0 -> v3.1.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1429) ([filecoin-project/specs-actors#1429](https://github.com/filecoin-project/specs-actors/pull/1429)) +- github.com/filecoin-project/specs-actors/v4 (v4.0.0 -> v4.0.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1430) ([filecoin-project/specs-actors#1430](https://github.com/filecoin-project/specs-actors/pull/1430)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Raúl Kripalani | 118 | +11972/-10860 | 472 | +| Łukasz Magiera | 65 | +10824/-4158 | 353 | +| aarshkshah1992 | 59 | +8057/-3355 | 224 | +| Aayush Rajasekaran | 41 | +8786/-1691 | 331 | +| Steven Allen | 106 | +7653/-2718 | 273 | +| dirkmc | 11 | +2580/-1371 | 77 | +| Dirk McCormick | 39 | +1865/-1194 | 79 | +| Jakub Sztandera | 19 | +1973/-485 | 81 | +| vyzo | 4 | +1748/-330 | 50 | +| Aarsh Shah | 5 | +1462/-213 | 27 | +| Cory Schwartz | 35 | +568/-206 | 59 | +| chadwick2143 | 3 | +739/-1 | 4 | +| Peter Rabbitson | 21 | +487/-164 | 36 | +| hannahhoward | 5 | +544/-5 | 19 | +| Jennifer Wang | 9 | +241/-174 | 19 | +| frrist | 1 | +137/-88 | 7 | +| Travis Person | 3 | +175/-6 | 7 | +| Alex Wade | 1 | +48/-129 | 1 | +| whyrusleeping | 8 | +161/-13 | 11 | +| lotus | 1 | +114/-46 | 1 | +| Anton Evangelatov | 8 | +107/-53 | 20 | +| Rjan | 4 | +115/-33 | 4 | +| ZenGround0 | 3 | +114/-1 | 4 | +| Aloxaf | 1 | +43/-61 | 7 | +| yaohcn | 4 | +89/-9 | 5 | +| mitchellsoo | 1 | +51/-0 | 1 | +| Mike Greenberg | 3 | +28/-18 | 4 | +| Jennifer | 6 | +9/-14 | 6 | +| Frank | 2 | +11/-10 | 2 | +| wangchao | 3 | +5/-4 | 4 | +| Steve Loeppky | 1 | +7/-1 | 1 | +| Lion | 1 | +4/-2 | 1 | +| Mimir | 1 | +2/-2 | 1 | +| raulk | 1 | +1/-1 | 1 | +| Jack Yao | 1 | +1/-1 | 1 | +| IPFSUnion | 1 | +1/-1 | 1 | + +# 1.10.1-rc1 / 2021-07-02 + +This is an optional, but **highly recommended** release of Lotus that have many bug fixes and improvements based on the feedbacks we got from the community since HyperDrive. + +## New Features +- commit batch: AggregateAboveBaseFee config #6650 + - `AggregateAboveBaseFee` is added to miner sealing configuration for setting the network base fee to start aggregating proofs. When the network base fee is lower than this value, the prove commits will be submitted individually via `ProveCommitSector`. According to the [Batch Incentive Alignment](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013. md#batch-incentive-alignment) introduced in FIP-0013, we recommend miners to set this value to 0.15 nanoFIL(which is also the default) to avoid unexpected aggregation fee in burn and enjoy the most benefits of aggregation! + +## Bug Fixes +- storage: Fix FinalizeSector with sectors in storage paths #6652 +- Fix tiny error in check-client-datacap #6664 +- Fix: precommit_batch method used the wrong cfg.PreCommitBatchWait #6658 +- to optimize the batchwait #6636 +- fix getTicket: sector precommitted but expired case #6635 +- handleSubmitCommitAggregate() exception handling #6595 +- remove precommit check in handleCommitFailed #6634 +- ensure agg fee is adequate +- fix: miner balance is not enough, so that ProveCommitAggregate msg exec failed #6623 +- commit batch: Initialize the FailedSectors map #6647 + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Łukasz Magiera | 7 | +151/-56 | 21 | +| llifezou | 4 | +59/-20 | 4 | +| johnli-helloworld | 2 | +45/-14 | 4 | +| wangchao | 1 | +1/-27 | 1 | +| Jerry | 2 | +9/-4 | 2 | +| zhoutian527 | 1 | +2/-2 | 1 | +| Peter Rabbitson | 1 | +1/-1 | 1 | # 1.10.0 / 2021-06-23 diff --git a/build/version.go b/build/version.go index 49ec3f446..92f011424 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-rc1" +const BuildVersion = "1.11.0-rc2" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { From e52c639f02767b280f2964af04eb6b2b38ee63da Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 22 Jul 2021 04:50:31 -0400 Subject: [PATCH 20/98] Release v1.11.0 --- CHANGELOG.md | 414 ++++++++++++--------------- build/openrpc/full.json.gz | Bin 23439 -> 23435 bytes build/openrpc/miner.json.gz | Bin 8102 -> 8098 bytes build/openrpc/worker.json.gz | Bin 2513 -> 2510 bytes build/version.go | 2 +- documentation/en/cli-lotus-miner.md | 4 +- documentation/en/cli-lotus-worker.md | 4 +- documentation/en/cli-lotus.md | 2 +- 8 files changed, 196 insertions(+), 230 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6891aaa9e..bbd03b031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,239 +1,205 @@ # Lotus changelog -# 1.11.0-rc2 / 2021-07-02 +# 1.11.0 / 2021-07-22 -This is the second release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes -and feature development. A more detailed changelog will follow upon final release. +This is a **highly recommended** release of Lotus that have many bug fixes, improvements and new features. -- github.com/filecoin-project/lotus: - - update changelog and bump version to v1.11.0-rc2 +## Highlights +- Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) + - Set `SimultaneousTransfers` in lotus miner config to configure the maximum number of parallel online data transfers, including both storage and retrieval deals. +- Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) + - Customize your retrieval ask price, see a quick tutorial [here](https://github.com/filecoin-project/lotus/discussions/6780). +- Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) + - run `lotus mpool manage and follow the instructions! + - Demo available at https://www.youtube.com/watch?v=QDocpLQjZgQ. +- Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) + +## More New Features +- feat: implement lotus-sim ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) +- implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) +- Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) + - run `lotus-shed market get-deal-fees` +- Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) + - run `lotus client list-retrievals` +- lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) +- lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) +- Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) +- oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) +- Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) +- Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) +- Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) + - run `lotus-shed actor` +- actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) +- Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) +- shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) +- feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) +- Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) + - run `lotus state miner-proving-deadline` - - Lotus version 1.11.0 - - gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) - - Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) - - revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) - - downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) - - Fix wallet error messages ([filecoin-project/lotus#6594](https://github.com/filecoin-project/lotus/pull/6594)) - - Fix CircleCI gen ([filecoin-project/lotus#6589](https://github.com/filecoin-project/lotus/pull/6589)) - - Make query-ask CLI more graceful ([filecoin-project/lotus#6590](https://github.com/filecoin-project/lotus/pull/6590)) - - ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) - - move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) - - scale up sector expiration to avoid sector expire in batch-pre-commit waitting ([filecoin-project/lotus#6566](https://github.com/filecoin-project/lotus/pull/6566)) - - Merge release branch into master ([filecoin-project/lotus#6583](https://github.com/filecoin-project/lotus/pull/6583)) - - ([filecoin-project/lotus#6582](https://github.com/filecoin-project/lotus/pull/6582)) - - fix circleci being out of sync. ([filecoin-project/lotus#6573](https://github.com/filecoin-project/lotus/pull/6573)) - - dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) - - Merge 1.10 branch into master ([filecoin-project/lotus#6571](https://github.com/filecoin-project/lotus/pull/6571)) - - Fix helptext ([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) - - extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) - - Merge 1.10 branch into master ([filecoin-project/lotus#6540](https://github.com/filecoin-project/lotus/pull/6540)) - - Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) - - fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) - - Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) - - Fix soup ([filecoin-project/lotus#6501](https://github.com/filecoin-project/lotus/pull/6501)) - - fix: pick the correct partitions-per-post limit ([filecoin-project/lotus#6502](https://github.com/filecoin-project/lotus/pull/6502)) - - Fix the build - - Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) - - Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) - - Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) - - sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) - - Merge 1.10 into master ([filecoin-project/lotus#6487](https://github.com/filecoin-project/lotus/pull/6487)) - - Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) - - Merge 1.10 changes into master ([filecoin-project/lotus#6466](https://github.com/filecoin-project/lotus/pull/6466)) - - Update chain list with correct help instructions ([filecoin-project/lotus#6465](https://github.com/filecoin-project/lotus/pull/6465)) - - clean failed sectors in batch commit ([filecoin-project/lotus#6451](https://github.com/filecoin-project/lotus/pull/6451)) - - itests/kit: add guard to ensure imports from tests only. ([filecoin-project/lotus#6445](https://github.com/filecoin-project/lotus/pull/6445)) - - consolidate integration tests into `itests` package; create test kit; cleanup ([filecoin-project/lotus#6311](https://github.com/filecoin-project/lotus/pull/6311)) - - Remove rc changelog, compile the new changelog for final release only ([filecoin-project/lotus#6444](https://github.com/filecoin-project/lotus/pull/6444)) - - updated configuration comments for docs ([filecoin-project/lotus#6440](https://github.com/filecoin-project/lotus/pull/6440)) - - Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6441](https://github.com/filecoin-project/lotus/pull/6441)) - - Merge release/v1.10.10 into master ([filecoin-project/lotus#6439](https://github.com/filecoin-project/lotus/pull/6439)) - - implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) - - Merge v1.10 release branch into master ([filecoin-project/lotus#6435](https://github.com/filecoin-project/lotus/pull/6435)) - - Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) - - Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) - - UX: lotus state power CLI should fail if called with a not-miner ([filecoin-project/lotus#6425](https://github.com/filecoin-project/lotus/pull/6425)) - - network reset friday - - Increase message size limit ([filecoin-project/lotus#6419](https://github.com/filecoin-project/lotus/pull/6419)) - - polish(stmgr): define ExecMonitor for message application callback ([filecoin-project/lotus#6389](https://github.com/filecoin-project/lotus/pull/6389)) - - upgrade testground action version ([filecoin-project/lotus#6403](https://github.com/filecoin-project/lotus/pull/6403)) - - Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) - - Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) - - Bypass task scheduler for reading unsealed pieces ([filecoin-project/lotus#6280](https://github.com/filecoin-project/lotus/pull/6280)) - - testplans: lotus-soup: use default WPoStChallengeWindow ([filecoin-project/lotus#6400](https://github.com/filecoin-project/lotus/pull/6400)) - - build snapcraft ([filecoin-project/lotus#6388](https://github.com/filecoin-project/lotus/pull/6388)) - - Fix the doc errors of the sealing config funcs ([filecoin-project/lotus#6399](https://github.com/filecoin-project/lotus/pull/6399)) - - Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) - - Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) - - Fix some flaky tests ([filecoin-project/lotus#6397](https://github.com/filecoin-project/lotus/pull/6397)) - - build appimage in CI ([filecoin-project/lotus#6384](https://github.com/filecoin-project/lotus/pull/6384)) - - Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) - - Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) - - Add interop network ([filecoin-project/lotus#6387](https://github.com/filecoin-project/lotus/pull/6387)) - - Network version 13 (v1.11) ([filecoin-project/lotus#6342](https://github.com/filecoin-project/lotus/pull/6342)) - - Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) - - lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) - - Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) - - update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) - - Add test for AddVerifiedClient ([filecoin-project/lotus#6317](https://github.com/filecoin-project/lotus/pull/6317)) - - Typo fix in error message: "pubusb" -> "pubsub" ([filecoin-project/lotus#6365](https://github.com/filecoin-project/lotus/pull/6365)) - - Improve the cli state call command ([filecoin-project/lotus#6226](https://github.com/filecoin-project/lotus/pull/6226)) - - Upscale mineOne message to a WARN on unexpected ineligibility ([filecoin-project/lotus#6358](https://github.com/filecoin-project/lotus/pull/6358)) - - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) - - Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) - - lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) - - Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) - - events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) - - Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) - - Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) - - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6333](https://github.com/filecoin-project/lotus/pull/6333)) - - Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) - - Revert "Allow starting networks from arbitrary actor versions" ([filecoin-project/lotus#6330](https://github.com/filecoin-project/lotus/pull/6330)) - - separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) - - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) - - feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) - - Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) - - Fix logging around mineOne ([filecoin-project/lotus#6310](https://github.com/filecoin-project/lotus/pull/6310)) - - Fix shell completions ([filecoin-project/lotus#6316](https://github.com/filecoin-project/lotus/pull/6316)) - - Allow 8MB sectors in devnet ([filecoin-project/lotus#6312](https://github.com/filecoin-project/lotus/pull/6312)) - - fix ticket expired ([filecoin-project/lotus#6304](https://github.com/filecoin-project/lotus/pull/6304)) - - oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) - - Move verifreg shed utils to CLI ([filecoin-project/lotus#6135](https://github.com/filecoin-project/lotus/pull/6135)) - - consider storiface.PathStorage when calculating storage requirements ([filecoin-project/lotus#6233](https://github.com/filecoin-project/lotus/pull/6233)) - - `storage` module: add go docs and minor code quality refactors ([filecoin-project/lotus#6259](https://github.com/filecoin-project/lotus/pull/6259)) - - Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) - - Increase data transfer timeouts ([filecoin-project/lotus#6300](https://github.com/filecoin-project/lotus/pull/6300)) - - gateway: spin off from cmd to package ([filecoin-project/lotus#6294](https://github.com/filecoin-project/lotus/pull/6294)) - - Update to markets 1.3 ([filecoin-project/lotus#6149](https://github.com/filecoin-project/lotus/pull/6149)) - - Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) - - Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) - - Merge v1.9.0 to master ([filecoin-project/lotus#6275](https://github.com/filecoin-project/lotus/pull/6275)) - - Backport 6200 to master ([filecoin-project/lotus#6272](https://github.com/filecoin-project/lotus/pull/6272)) - - Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) - - chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) - - fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) - - Update RELEASE_ISSUE_TEMPLATE.md - - fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) - - add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) - - Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) - - chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) - - Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) - - wip actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) - - Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) - - Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) - - Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) - - cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) - - This is a 1:1 forward-port of PR#6183 from 1.9.x to master ([filecoin-project/lotus#6196](https://github.com/filecoin-project/lotus/pull/6196)) - - Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) - - drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) - - Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) - - mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) - - Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) - - Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) - - feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) - - attempt to do better padding on pieces being written into sectors ([filecoin-project/lotus#5988](https://github.com/filecoin-project/lotus/pull/5988)) - - remove duplicate ask and calculate ping before lock ([filecoin-project/lotus#5968](https://github.com/filecoin-project/lotus/pull/5968)) - - Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) - - flaky tests improvement: separate TestBatchDealInput from TestAPIDealFlow ([filecoin-project/lotus#6141](https://github.com/filecoin-project/lotus/pull/6141)) - - Testground checks on push ([filecoin-project/lotus#5887](https://github.com/filecoin-project/lotus/pull/5887)) - - Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) - - Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) - - fix: use a consistent tipset in commands ([filecoin-project/lotus#6142](https://github.com/filecoin-project/lotus/pull/6142)) - - go mod tidy for lotus-soup testplans ([filecoin-project/lotus#6124](https://github.com/filecoin-project/lotus/pull/6124)) - - fix testground payment channel tests: use 1 miner ([filecoin-project/lotus#6126](https://github.com/filecoin-project/lotus/pull/6126)) - - fix: use the parent state when listing actors ([filecoin-project/lotus#6143](https://github.com/filecoin-project/lotus/pull/6143)) - - Speed up StateListMessages in some cases ([filecoin-project/lotus#6007](https://github.com/filecoin-project/lotus/pull/6007)) - - Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) - - fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) - - shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) - - upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122)) - - Merge releases (1.8.0) into master ([filecoin-project/lotus#6118](https://github.com/filecoin-project/lotus/pull/6118)) -- github.com/filecoin-project/go-commp-utils (v0.1.0 -> v0.1.1-0.20210427191551-70bf140d31c7): - - add a padding helper function ([filecoin-project/go-commp-utils#3](https://github.com/filecoin-project/go-commp-utils/pull/3)) -- github.com/filecoin-project/go-data-transfer (v1.4.3 -> v1.6.0): - - release: v1.6.0 - - fix: option to disable accept and complete timeouts - - fix: disable restart ack timeout - - release: v1.5.0 - - Add isRestart param to validators (#197) ([filecoin-project/go-data-transfer#197](https://github.com/filecoin-project/go-data-transfer/pull/197)) - - fix: flaky TestChannelMonitorAutoRestart (#198) ([filecoin-project/go-data-transfer#198](https://github.com/filecoin-project/go-data-transfer/pull/198)) - - Channel monitor watches for errors instead of measuring data rate (#190) ([filecoin-project/go-data-transfer#190](https://github.com/filecoin-project/go-data-transfer/pull/190)) - - fix: prevent concurrent restarts for same channel (#195) ([filecoin-project/go-data-transfer#195](https://github.com/filecoin-project/go-data-transfer/pull/195)) - - fix: channel state machine event handling (#194) ([filecoin-project/go-data-transfer#194](https://github.com/filecoin-project/go-data-transfer/pull/194)) - - Dont double count data sent (#185) ([filecoin-project/go-data-transfer#185](https://github.com/filecoin-project/go-data-transfer/pull/185)) - - release: v1.4.3 (#189) ([filecoin-project/go-data-transfer#189](https://github.com/filecoin-project/go-data-transfer/pull/189)) -- github.com/filecoin-project/go-fil-markets (v1.2.5 -> v1.5.0): - - release: v1.5.0 - - Dynamic Retrieval Pricing (#542) ([filecoin-project/go-fil-markets#542](https://github.com/filecoin-project/go-fil-markets/pull/542)) - - release: v1.4.0 (#551) ([filecoin-project/go-fil-markets#551](https://github.com/filecoin-project/go-fil-markets/pull/551)) - - Update to go data transfer v1.6.0 (#550) ([filecoin-project/go-fil-markets#550](https://github.com/filecoin-project/go-fil-markets/pull/550)) - - fix first make error (#548) ([filecoin-project/go-fil-markets#548](https://github.com/filecoin-project/go-fil-markets/pull/548)) - - release: v1.3.0 (#544) ([filecoin-project/go-fil-markets#544](https://github.com/filecoin-project/go-fil-markets/pull/544)) - - fix restarts during data transfer for a retrieval deal (#540) ([filecoin-project/go-fil-markets#540](https://github.com/filecoin-project/go-fil-markets/pull/540)) - - Test Retrieval for offline deals (#541) ([filecoin-project/go-fil-markets#541](https://github.com/filecoin-project/go-fil-markets/pull/541)) - - Allow anonymous submodule checkout (#535) ([filecoin-project/go-fil-markets#535](https://github.com/filecoin-project/go-fil-markets/pull/535)) -- github.com/filecoin-project/specs-actors (v0.9.13 -> v0.9.14): - - Set ConsensusMinerMinPower to 10 TiB (#1427) ([filecoin-project/specs-actors#1427](https://github.com/filecoin-project/specs-actors/pull/1427)) -- github.com/filecoin-project/specs-actors/v2 (v2.3.5-0.20210114162132-5b58b773f4fb -> v2.3.5): - - Set ConsensusMinerMinPower to 10 TiB (#1428) ([filecoin-project/specs-actors#1428](https://github.com/filecoin-project/specs-actors/pull/1428)) - - v2 VM satisfies SimVM interface (#1355) ([filecoin-project/specs-actors#1355](https://github.com/filecoin-project/specs-actors/pull/1355)) -- github.com/filecoin-project/specs-actors/v3 (v3.1.0 -> v3.1.1): - - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1429) ([filecoin-project/specs-actors#1429](https://github.com/filecoin-project/specs-actors/pull/1429)) -- github.com/filecoin-project/specs-actors/v4 (v4.0.0 -> v4.0.1): - - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1430) ([filecoin-project/specs-actors#1430](https://github.com/filecoin-project/specs-actors/pull/1430)) -Contributors +## Bug Fixes +- Fix wallet error messages ([filecoin-project/lotus#6594](https://github.com/filecoin-project/lotus/pull/6594)) +- Fix CircleCI gen ([filecoin-project/lotus#6589](https://github.com/filecoin-project/lotus/pull/6589)) +- Make query-ask CLI more graceful ([filecoin-project/lotus#6590](https://github.com/filecoin-project/lotus/pull/6590)) +- scale up sector expiration to avoid sector expire in batch-pre-commit waitting ([filecoin-project/lotus#6566](https://github.com/filecoin-project/lotus/pull/6566)) +- Fix an error in msigLockCancel ([filecoin-project/lotus#6582](https://github.com/filecoin-project/lotus/pull/6582) +- fix circleci being out of sync. ([filecoin-project/lotus#6573](https://github.com/filecoin-project/lotus/pull/6573)) +- Fix helptext for ask price([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) +- fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) +- Fix soup ([filecoin-project/lotus#6501](https://github.com/filecoin-project/lotus/pull/6501)) +- fix: pick the correct partitions-per-post limit ([filecoin-project/lotus#6502](https://github.com/filecoin-project/lotus/pull/6502)) +- sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) +- Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) +- Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) +- Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) +- storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) +- events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) +- Fix logging around mineOne ([filecoin-project/lotus#6310](https://github.com/filecoin-project/lotus/pull/6310)) +- Fix shell completions ([filecoin-project/lotus#6316](https://github.com/filecoin-project/lotus/pull/6316)) +- Allow 8MB sectors in devnet ([filecoin-project/lotus#6312](https://github.com/filecoin-project/lotus/pull/6312)) +- fix ticket expired ([filecoin-project/lotus#6304](https://github.com/filecoin-project/lotus/pull/6304)) +- Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) +- fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) +- fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) +- fix: use a consistent tipset in commands ([filecoin-project/lotus#6142](https://github.com/filecoin-project/lotus/pull/6142)) +- go mod tidy for lotus-soup testplans ([filecoin-project/lotus#6124](https://github.com/filecoin-project/lotus/pull/6124)) +- fix testground payment channel tests: use 1 miner ([filecoin-project/lotus#6126](https://github.com/filecoin-project/lotus/pull/6126)) +- fix: use the parent state when listing actors ([filecoin-project/lotus#6143](https://github.com/filecoin-project/lotus/pull/6143)) +- Speed up StateListMessages in some cases ([filecoin-project/lotus#6007](https://github.com/filecoin-project/lotus/pull/6007)) +- fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) +- drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) + +## Improvements +- gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) +- revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) +- move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) +- dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) +- extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) +- Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) +- Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) +- Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) +- Update chain list with correct help instructions ([filecoin-project/lotus#6465](https://github.com/filecoin-project/lotus/pull/6465)) +- clean failed sectors in batch commit ([filecoin-project/lotus#6451](https://github.com/filecoin-project/lotus/pull/6451)) +- itests/kit: add guard to ensure imports from tests only. ([filecoin-project/lotus#6445](https://github.com/filecoin-project/lotus/pull/6445)) +- consolidate integration tests into `itests` package; create test kit; cleanup ([filecoin-project/lotus#6311](https://github.com/filecoin-project/lotus/pull/6311)) +- Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) +- UX: lotus state power CLI should fail if called with a not-miner ([filecoin-project/lotus#6425](https://github.com/filecoin-project/lotus/pull/6425)) +- Increase message size limit ([filecoin-project/lotus#6419](https://github.com/filecoin-project/lotus/pull/6419)) +- polish(stmgr): define ExecMonitor for message application callback ([filecoin-project/lotus#6389](https://github.com/filecoin-project/lotus/pull/6389)) +- upgrade testground action version ([filecoin-project/lotus#6403](https://github.com/filecoin-project/lotus/pull/6403)) +- Bypass task scheduler for reading unsealed pieces ([filecoin-project/lotus#6280](https://github.com/filecoin-project/lotus/pull/6280)) +- testplans: lotus-soup: use default WPoStChallengeWindow ([filecoin-project/lotus#6400](https://github.com/filecoin-project/lotus/pull/6400)) +- Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) +- Fix some flaky tests ([filecoin-project/lotus#6397](https://github.com/filecoin-project/lotus/pull/6397)) +- build appimage in CI ([filecoin-project/lotus#6384](https://github.com/filecoin-project/lotus/pull/6384)) +- Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) +- Add test for AddVerifiedClient ([filecoin-project/lotus#6317](https://github.com/filecoin-project/lotus/pull/6317)) +- Typo fix in error message: "pubusb" -> "pubsub" ([filecoin-project/lotus#6365](https://github.com/filecoin-project/lotus/pull/6365)) +- Improve the cli state call command ([filecoin-project/lotus#6226](https://github.com/filecoin-project/lotus/pull/6226)) +- Upscale mineOne message to a WARN on unexpected ineligibility ([filecoin-project/lotus#6358](https://github.com/filecoin-project/lotus/pull/6358)) +- Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) +- Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) +- Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) +- Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) +- separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) +- feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) +- Move verifreg shed utils to CLI ([filecoin-project/lotus#6135](https://github.com/filecoin-project/lotus/pull/6135)) +- consider storiface.PathStorage when calculating storage requirements ([filecoin-project/lotus#6233](https://github.com/filecoin-project/lotus/pull/6233)) +- `storage` module: add go docs and minor code quality refactors ([filecoin-project/lotus#6259](https://github.com/filecoin-project/lotus/pull/6259)) +- Increase data transfer timeouts ([filecoin-project/lotus#6300](https://github.com/filecoin-project/lotus/pull/6300)) +- gateway: spin off from cmd to package ([filecoin-project/lotus#6294](https://github.com/filecoin-project/lotus/pull/6294)) +- Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) +- add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) +- chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) +- cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) +- Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) +- mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) +- attempt to do better padding on pieces being written into sectors ([filecoin-project/lotus#5988](https://github.com/filecoin-project/lotus/pull/5988)) +- remove duplicate ask and calculate ping before lock ([filecoin-project/lotus#5968](https://github.com/filecoin-project/lotus/pull/5968)) +- flaky tests improvement: separate TestBatchDealInput from TestAPIDealFlow ([filecoin-project/lotus#6141](https://github.com/filecoin-project/lotus/pull/6141)) +- Testground checks on push ([filecoin-project/lotus#5887](https://github.com/filecoin-project/lotus/pull/5887)) +- Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) +- upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122) + +## Dependency Updates +- downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) +- Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) +- update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) +- Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) +- chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) +- Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) + +## Others +- Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) +- Remove rc changelog, compile the new changelog for final release only ([filecoin-project/lotus#6444](https://github.com/filecoin-project/lotus/pull/6444)) +- updated configuration comments for docs ([filecoin-project/lotus#6440](https://github.com/filecoin-project/lotus/pull/6440)) +- Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6441](https://github.com/filecoin-project/lotus/pull/6441)) +- build snapcraft ([filecoin-project/lotus#6388](https://github.com/filecoin-project/lotus/pull/6388)) +- Fix the doc errors of the sealing config funcs ([filecoin-project/lotus#6399](https://github.com/filecoin-project/lotus/pull/6399)) +- Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) +- Add interop network ([filecoin-project/lotus#6387](https://github.com/filecoin-project/lotus/pull/6387)) +- Network version 13 (v1.11) ([filecoin-project/lotus#6342](https://github.com/filecoin-project/lotus/pull/6342)) +- Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) +- Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) +- Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) +- Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) +- Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) +- This is a 1:1 forward-port of PR#6183 from 1.9.x to master ([filecoin-project/lotus#6196](https://github.com/filecoin-project/lotus/pull/6196)) +- Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) +- Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) + +## Contributors | Contributor | Commits | Lines ± | Files Changed | |-------------|---------|---------|---------------| -| Raúl Kripalani | 118 | +11972/-10860 | 472 | -| Łukasz Magiera | 65 | +10824/-4158 | 353 | -| aarshkshah1992 | 59 | +8057/-3355 | 224 | -| Aayush Rajasekaran | 41 | +8786/-1691 | 331 | -| Steven Allen | 106 | +7653/-2718 | 273 | +| @raulk | 118 | +11972/-10860 | 472 | +| @magik6k | 65 | +10824/-4158 | 353 | +| @aarshkshah1992 | 59 | +8057/-3355 | 224 | +| @arajasek | 41 | +8786/-1691 | 331 | +| @Stebalien | 106 | +7653/-2718 | 273 | | dirkmc | 11 | +2580/-1371 | 77 | -| Dirk McCormick | 39 | +1865/-1194 | 79 | -| Jakub Sztandera | 19 | +1973/-485 | 81 | -| vyzo | 4 | +1748/-330 | 50 | -| Aarsh Shah | 5 | +1462/-213 | 27 | -| Cory Schwartz | 35 | +568/-206 | 59 | -| chadwick2143 | 3 | +739/-1 | 4 | -| Peter Rabbitson | 21 | +487/-164 | 36 | -| hannahhoward | 5 | +544/-5 | 19 | -| Jennifer Wang | 9 | +241/-174 | 19 | -| frrist | 1 | +137/-88 | 7 | -| Travis Person | 3 | +175/-6 | 7 | -| Alex Wade | 1 | +48/-129 | 1 | -| whyrusleeping | 8 | +161/-13 | 11 | +| @dirkmc | 39 | +1865/-1194 | 79 | +| @Kubuxu | 19 | +1973/-485 | 81 | +| @vyzo | 4 | +1748/-330 | 50 | +| @aarshkshah1992 | 5 | +1462/-213 | 27 | +| @coryschwartz | 35 | +568/-206 | 59 | +| @chadwick2143 | 3 | +739/-1 | 4 | +| @ribasushi | 21 | +487/-164 | 36 | +| @hannahhoward | 5 | +544/-5 | 19 | +| @jennijuju | 9 | +241/-174 | 19 | +| @frrist | 1 | +137/-88 | 7 | +| @travisperson | 3 | +175/-6 | 7 | +| @wadeAlexC | 1 | +48/-129 | 1 | +| @whyrusleeping | 8 | +161/-13 | 11 | | lotus | 1 | +114/-46 | 1 | -| Anton Evangelatov | 8 | +107/-53 | 20 | -| Rjan | 4 | +115/-33 | 4 | -| ZenGround0 | 3 | +114/-1 | 4 | -| Aloxaf | 1 | +43/-61 | 7 | -| yaohcn | 4 | +89/-9 | 5 | -| mitchellsoo | 1 | +51/-0 | 1 | -| Mike Greenberg | 3 | +28/-18 | 4 | -| Jennifer | 6 | +9/-14 | 6 | -| Frank | 2 | +11/-10 | 2 | -| wangchao | 3 | +5/-4 | 4 | -| Steve Loeppky | 1 | +7/-1 | 1 | -| Lion | 1 | +4/-2 | 1 | -| Mimir | 1 | +2/-2 | 1 | -| raulk | 1 | +1/-1 | 1 | -| Jack Yao | 1 | +1/-1 | 1 | -| IPFSUnion | 1 | +1/-1 | 1 | +| @nonsense | 8 | +107/-53 | 20 | +| @rjan90 | 4 | +115/-33 | 4 | +| @ZenGround0 | 3 | +114/-1 | 4 | +| @Aloxaf | 1 | +43/-61 | 7 | +| @yaohcn | 4 | +89/-9 | 5 | +| @mitchellsoo | 1 | +51/-0 | 1 | +| @placer14 | 3 | +28/-18 | 4 | +| @jennijuju | 6 | +9/-14 | 6 | +| @Frank | 2 | +11/-10 | 2 | +| @wangchao | 3 | +5/-4 | 4 | +| @Steve Loeppky | 1 | +7/-1 | 1 | +| @Lion | 1 | +4/-2 | 1 | +| @Mimir | 1 | +2/-2 | 1 | +| @raulk | 1 | +1/-1 | 1 | +| @Jack Yao | 1 | +1/-1 | 1 | +| @IPFSUnion | 1 | +1/-1 | 1 | -# 1.10.1-rc1 / 2021-07-02 +# 1.10.1 / 2021-07-05 -This is an optional, but **highly recommended** release of Lotus that have many bug fixes and improvements based on the feedbacks we got from the community since HyperDrive. +This is an optional but **highly recommended** release of Lotus for lotus miners that has many bug fixes and improvements based on the feedback we got from the community since HyperDrive. ## New Features - commit batch: AggregateAboveBaseFee config #6650 - - `AggregateAboveBaseFee` is added to miner sealing configuration for setting the network base fee to start aggregating proofs. When the network base fee is lower than this value, the prove commits will be submitted individually via `ProveCommitSector`. According to the [Batch Incentive Alignment](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013. md#batch-incentive-alignment) introduced in FIP-0013, we recommend miners to set this value to 0.15 nanoFIL(which is also the default) to avoid unexpected aggregation fee in burn and enjoy the most benefits of aggregation! - + - `AggregateAboveBaseFee` is added to miner sealing configuration for setting the network base fee to start aggregating proofs. When the network base fee is lower than this value, the prove commits will be submitted individually via `ProveCommitSector`. According to the [Batch Incentive Alignment](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md#batch-incentive-alignment) introduced in FIP-0013, we recommend miners to set this value to 0.15 nanoFIL(which is the default value) to avoid unexpected aggregation fee in burn and enjoy the most benefits of aggregation! + ## Bug Fixes - storage: Fix FinalizeSector with sectors in storage paths #6652 -- Fix tiny error in check-client-datacap #6664 +- Fix tiny error in check-client-datacap #6664 - Fix: precommit_batch method used the wrong cfg.PreCommitBatchWait #6658 - to optimize the batchwait #6636 - fix getTicket: sector precommitted but expired case #6635 @@ -247,13 +213,13 @@ Contributors | Contributor | Commits | Lines ± | Files Changed | |-------------|---------|---------|---------------| -| Łukasz Magiera | 7 | +151/-56 | 21 | -| llifezou | 4 | +59/-20 | 4 | -| johnli-helloworld | 2 | +45/-14 | 4 | -| wangchao | 1 | +1/-27 | 1 | +| @magik6k| 7 | +151/-56 | 21 | +| @llifezou | 4 | +59/-20 | 4 | +| @johnli-helloworld | 2 | +45/-14 | 4 | +| @wangchao | 1 | +1/-27 | 1 | | Jerry | 2 | +9/-4 | 2 | -| zhoutian527 | 1 | +2/-2 | 1 | -| Peter Rabbitson | 1 | +1/-1 | 1 | +| @zhoutian527 | 1 | +2/-2 | 1 | +| @ribasushi| 1 | +1/-1 | 1 | # 1.10.0 / 2021-06-23 diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index b46c9ba5b3689df5e203ff5d4b9217f036db7442..fe0c17662ad3ad0af07e497d156c2d06e58adb4f 100644 GIT binary patch delta 23373 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 delta 23377 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�BXMbXtZq+(c?L=v7?DiFI$j*^p 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index fd98bad0d831c578aaee8647650fc35b33430b48..b653c2e7f7795f541bfd2b3aeccecc93b9f77a89 100644 GIT binary patch delta 8034 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index c6d1eb204fede74e7b3e5a0c3907fac345db636f..4b2eb6a5a4f3c66cb107472c37695e8eda692360 100644 GIT binary patch delta 2408 zcmV-u377WK6V4Nme}C}Gzz(Q6w!=@fH(Hn_=`^LpRh-)Q0Q=OrR?2X$(CT9wsRAU(YYaq9cq~e0ipm7bR;^GRX z268*-`z@K!##iPN%YtBlYx)xmW1Cj-C+Yu&vA%Y-~rxD5Ug2GBX1Zs zuuBASf-Ufb-p0w|+~tz`TNYkUY_r==?Q3dh5_9Vx<{TWN&lsMFa&g7h!A8^ zewE$S%q7=YWgW47X}3DP+l-Z!Tt+WfwX~rqrd9cxq8TW}(XH+zJzMFqL*BMcR3@ie z9g5h1z-Bya2edNd*4yCEdB$JSA6&^wgu=WmB)U7TgF~K1!GP_)fgQ?XzrxOejIWH? z+Ka&emGffACy>tP1mn{H)fHLPL zkKd;$hZ8sEF=*HGYZ%}I#m&VrQS-357i)fR`rH1ATTlTmM(h^DiZv*Fg`hA`JP(JM z_5hdCs4uNZg`^HkAU0()9Aio<;kWDu!===?7+(&5fXYnOFs_Dirv~HN{Twe&8L{b4 z2!GF_q&rU#m(Bk~?N@}&DtYJa*&{byyXT`&p<{5*TT)gnS5snZzf7kRRI$DF6Dt)r zrefx&_H4ChJ2lVN>1BJi5u5Uo{!Ei(rzW=V1L&T+{8KpELGlz?{5+$$iB3h7_n%y< z^;8-8h@7f6FB4fLQ<31c+l$@y5ahfxa%KRAVEB6 zInoWo@mCrA?}Pt8ZGcxLHGaeJG7|U@^RF^^UiXPoG^?!@l$bf-zKGU-2z##WQi{=S zJCfMQB2y&xwje=pGPNn`UUWOzrsNZUfa?H9l(Z-@%KfChD&XWK4>d?WDsOxYAbbGvaYxCv5TgOq`A zmC}m@akheZ9<>@@zwX9Usll?+xz3OT-m6g};BWv8JF7dc7$E3kn`_{t=Z z@--M+)PHx{K0SRMk{lJlI);#>&fC{#8mVFQayU% z^F96(1i{TR_hM^`A+=-${C}8S%64g{o~Oo zq^aCYUhX{O+fasbpD3t~_v(1>C4<^`Irq6XcrNX}i-Out`Qi%Q8p6mgp86#m+NpDx z$EG~TfIZVv_VVyp?TBhebaIX;=MJ+r=wkuZ6D%9zz2sQ7!kvmPD#27dTT*+fqtLEn z@Kh(CC8;w9wU4iT{C_L*@xSL(&Lliym3VVQTrK3>xZ~0~r1+{@DoXVc2e>Aj+dHbd5`#Qb*|qM593t)|Qh#%Z2*A4WU zE2QQ`|3xK|pcG}Z8|IJ&Qsf>GK~s1{5=_o4i4;fLh>{smpnoVL?~xW%%d_`bNA~x; zuC6ZB`q=Gv^6qH`2<*LO`A`0o75OA}exM;Jo9&YP)L8lAzqeq)Pi7Q*e^~tG0{eVl z*Xt9-oUHbrM1S621_rC77{(}bMG%Ost(f$u37$`XRveRK9$pkv{1tz#UGt&UH2v_Y z*{MbIS46X$lZ?@468PH+k{J;0RWR2>)^hnOMC!Som$cepz>cuLb_J~ndeSJ;zErI`EC>Svdo_2iUBlBkX1v07C7*eZ3 z#yq)(`gwsmbzbOII4QY3;5RUOt?s*hdihl%qdLHzd=xEyv#~_v^KuWL>U60m4Bc(TB%7p_Knk*1O{|>$*Wgvov+yWE5 zd7M*ojwrLJUZS4E@oYs+ef8WW<~>;JS(FdH39}GEpLDM|k@H4UaQ5>sK>fb&`bJZI zqbb0G{hZ8XsLLgBU`Ul6*R0LM^EhlX#z9^um0`EAP5Xg-Ubo%tysPZ>j#YeevWeda zRsZrRsFDJ-rppweXEQT#(rn3D_dpYyoA(lVOi>%DIS4>R0Aj(t7Kwz%n16@n7lrNB arAprR@oIIu`fmUL0RR8Kc1!k#asU8O9K=2V delta 2411 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 diff --git a/build/version.go b/build/version.go index 92f011424..9262af171 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-rc2" +const BuildVersion = "1.11.0" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 42f964715..b3dcebaae 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -7,7 +7,7 @@ USAGE: lotus-miner [global options] command [command options] [arguments...] VERSION: - 1.11.0-rc2 + 1.11.0 COMMANDS: init Initialize a lotus miner repo @@ -42,7 +42,7 @@ COMMANDS: 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] + --miner-repo value, --storagerepo value Specify miner repo path. flag(storagerepo) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON (default: "/Users/jennijuju/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] --help, -h show help (default: false) --version, -v print the version (default: false) ``` diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index b7e04e5f1..a983a596c 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -7,7 +7,7 @@ USAGE: lotus-worker [global options] command [command options] [arguments...] VERSION: - 1.11.0-rc2 + 1.11.0 COMMANDS: run Start lotus worker @@ -20,7 +20,7 @@ COMMANDS: GLOBAL OPTIONS: --worker-repo value, --workerrepo value Specify worker repo path. flag workerrepo and env WORKER_PATH are DEPRECATION, will REMOVE SOON (default: "~/.lotusworker") [$LOTUS_WORKER_PATH, $WORKER_PATH] - --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] + --miner-repo value, --storagerepo value Specify miner repo path. flag storagerepo and env LOTUS_STORAGE_PATH are DEPRECATION, will REMOVE SOON (default: "/Users/jennijuju/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] --enable-gpu-proving enable use of GPU for mining operations (default: true) --help, -h show help (default: false) --version, -v print the version (default: false) diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 1326c93f6..aeefd8dfa 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -7,7 +7,7 @@ USAGE: lotus [global options] command [command options] [arguments...] VERSION: - 1.11.0-rc2 + 1.11.0 COMMANDS: daemon Start a lotus daemon process From adba595350f1847c5831b5e98b5dccd3e4a8d67a Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Thu, 22 Jul 2021 18:41:57 +0200 Subject: [PATCH 21/98] commit from @dirkmc - initial export cmd for martkers related metadata --- cmd/lotus-shed/market.go | 136 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/cmd/lotus-shed/market.go b/cmd/lotus-shed/market.go index e2e322784..cde66191d 100644 --- a/cmd/lotus-shed/market.go +++ b/cmd/lotus-shed/market.go @@ -2,6 +2,16 @@ package main import ( "fmt" + "io/ioutil" + "os" + "path" + + "github.com/filecoin-project/lotus/lib/backupds" + + "github.com/filecoin-project/lotus/node/repo" + "github.com/ipfs/go-datastore" + dsq "github.com/ipfs/go-datastore/query" + logging "github.com/ipfs/go-log/v2" lcli "github.com/filecoin-project/lotus/cli" @@ -18,6 +28,7 @@ var marketCmd = &cli.Command{ Flags: []cli.Flag{}, Subcommands: []*cli.Command{ marketDealFeesCmd, + marketExportDatastoreCmd, }, } @@ -100,3 +111,128 @@ var marketDealFeesCmd = &cli.Command{ return xerrors.New("must provide either --provider or --dealId flag") }, } + +var marketExportDatastoreCmd = &cli.Command{ + Name: "export-datastore", + Description: "export datastore to a file", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Usage: "path to the repo", + }, + &cli.StringFlag{ + Name: "backup-dir", + Usage: "path to the backup directory", + }, + }, + Action: func(cctx *cli.Context) error { + logging.SetLogLevel("badger", "ERROR") // nolint:errcheck + + backupDir := cctx.String("backup-dir") + if backupDir == "" { + backupDir = os.TempDir() + } + + r, err := repo.NewFS(cctx.String("repo")) + if err != nil { + return xerrors.Errorf("opening fs repo: %w", err) + } + + exists, err := r.Exists() + if err != nil { + return err + } + if !exists { + return xerrors.Errorf("lotus repo doesn't exist") + } + + lr, err := r.Lock(repo.StorageMiner) + if err != nil { + return err + } + defer lr.Close() //nolint:errcheck + + namespace := "metadata" + ds, err := lr.Datastore(cctx.Context, datastore.NewKey(namespace).String()) + if err != nil { + return err + } + + backupRepoDir, err := ioutil.TempDir("", "backup-repo-dir") + if err != nil { + return err + } + + backupRepo, err := repo.NewFS(cctx.String(backupRepoDir)) + if err != nil { + return xerrors.Errorf("opening backup repo: %w", err) + } + + lockedBackupRepo, err := backupRepo.Lock(repo.StorageMiner) + if err != nil { + return err + } + defer lockedBackupRepo.Close() //nolint:errcheck + + backupDs, err := lockedBackupRepo.Datastore(cctx.Context, datastore.NewKey(namespace).String()) + if err != nil { + return err + } + + prefixes := []string{ + "/deals/provider", + "/retrievals/provider", + "/storagemarket", + } + for _, prefix := range prefixes { + err := exportPrefix(prefix, ds, backupDs) + if err != nil { + return err + } + } + + bds, err := backupds.Wrap(backupDs, "") + if err != nil { + return xerrors.Errorf("opening backupds: %w", err) + } + + fpath := path.Join(backupDir, "datastore.backup") + out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return xerrors.Errorf("open %s: %w", fpath, err) + } + if err := bds.Backup(out); err != nil { + if cerr := out.Close(); cerr != nil { + log.Errorw("error closing backup file while handling backup error", "closeErr", cerr, "backupErr", err) + } + return xerrors.Errorf("backup error: %w", err) + } + if err := out.Close(); err != nil { + return xerrors.Errorf("closing backup file: %w", err) + } + + fmt.Println("Wrote backup file to " + fpath) + + return nil + }, +} + +func exportPrefix(prefix string, ds datastore.Batching, backupDs datastore.Batching) error { + q, err := ds.Query(dsq.Query{ + Prefix: prefix, + }) + if err != nil { + return xerrors.Errorf("datastore query: %w", err) + } + defer q.Close() //nolint:errcheck + + for res := range q.Next() { + fmt.Println("Exporting key " + res.Key) + err := backupDs.Put(datastore.NewKey(res.Key), res.Value) + if err != nil { + return xerrors.Errorf("putting %s to backup datastore: %w", res.Key, err) + } + } + + return nil +} From 9e34cce1f2f3984573b33f2fef839e81c34ca1ee Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Tue, 20 Jul 2021 17:19:28 -0400 Subject: [PATCH 22/98] feat(config): support configurable CC sector expiration --- extern/storage-sealing/sealiface/config.go | 2 ++ node/config/def.go | 6 ++++++ storage/miner.go | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index e33b36263..59c3d8c4f 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -20,6 +20,8 @@ type Config struct { WaitDealsDelay time.Duration + CommittedCapacityDefaultLifetime time.Duration + AlwaysKeepUnsealedCopy bool FinalizeEarly bool diff --git a/node/config/def.go b/node/config/def.go index 7042cf644..bf6526e4d 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -131,6 +131,12 @@ type SealingConfig struct { WaitDealsDelay Duration + // CommittedCapacityDefaultLifetime is the default duration a Committed Capacity (CC) + // sector will live before it must be extended or converted into sector containing deals + // before it is terminated. + // Value must be between 180-540 days inclusive. + CommittedCapacityDefaultLifetime Duration + AlwaysKeepUnsealedCopy bool // Run sector finalization before submitting sector proof to the chain diff --git a/storage/miner.go b/storage/miner.go index cdacc2734..06ee874a1 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -178,8 +178,11 @@ func (m *Miner) Run(ctx context.Context) error { // provides extra methods. adaptedAPI = NewSealingAPIAdapter(m.api) + // sealing configuration. + cfg = sealing.GetSealingConfigFunc(m.getSealConfig) + // Instantiate a precommit policy. - defaultDuration = policy.GetMaxSectorExpirationExtension() - (md.WPoStProvingPeriod * 2) + defaultDuration = getDefaultSectorExpirationExtension(cfg) - (md.WPoStProvingPeriod * 2) provingBoundary = md.PeriodStart % md.WPoStProvingPeriod // TODO: Maybe we update this policy after actor upgrades? @@ -189,9 +192,6 @@ func (m *Miner) Run(ctx context.Context) error { as = func(ctx context.Context, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { return m.addrSel.AddressFor(ctx, m.api, mi, use, goodFunds, minFunds) } - - // sealing configuration. - cfg = sealing.GetSealingConfigFunc(m.getSealConfig) ) // Instantiate the sealing FSM. @@ -203,6 +203,16 @@ func (m *Miner) Run(ctx context.Context) error { return nil } +func getDefaultSectorExpirationExtension(cfg sealing.GetSealingConfigFunc) abi.ChainEpoch { + c, err := cfg() + if err != nil { + log.Warnf("failed to load sealing config, using default sector extension expiration") + log.Errorf("sealing config load error: %s", err.Error()) + return policy.GetMaxSectorExpirationExtension() + } + return abi.ChainEpoch(c.CommittedCapacityDefaultLifetime.Truncate(builtin.EpochDurationSeconds)) +} + func (m *Miner) handleSealingNotifications(before, after sealing.SectorInfo) { m.journal.RecordEvent(m.sealingEvtType, func() interface{} { return SealingStateEvt{ From adb62a3fff61d74aa07bd450ea4daa6044366546 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 22 Jul 2021 08:37:35 -0400 Subject: [PATCH 23/98] chore: Move cfg getter into PCPolicy; Clamp values on get --- chain/actors/policy/policy.go | 4 ++ extern/storage-sealing/precommit_policy.go | 48 +++++++++++++++++----- extern/storage-sealing/sealiface/config.go | 2 +- node/config/def.go | 13 +++--- storage/miner.go | 18 +------- 5 files changed, 53 insertions(+), 32 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index c06c85d38..06db09ea8 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -301,6 +301,10 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { return ChainFinality } +func GetMinSectorExpiration() abi.ChainEpoch { + return miner5.MinSectorExpiration +} + func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner5.MaxSectorExpirationExtension } diff --git a/extern/storage-sealing/precommit_policy.go b/extern/storage-sealing/precommit_policy.go index a6add5693..bc5a0a530 100644 --- a/extern/storage-sealing/precommit_policy.go +++ b/extern/storage-sealing/precommit_policy.go @@ -3,11 +3,13 @@ package sealing import ( "context" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - - "github.com/filecoin-project/go-state-types/network" + "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" ) type PreCommitPolicy interface { @@ -34,21 +36,21 @@ type Chain interface { // If we're in Mode 2: The pre-commit expiration epoch will be set to the // current epoch + the provided default duration. type BasicPreCommitPolicy struct { - api Chain + api Chain + getSealingConfig GetSealingConfigFunc provingBoundary abi.ChainEpoch - duration abi.ChainEpoch } // NewBasicPreCommitPolicy produces a BasicPreCommitPolicy. // // The provided duration is used as the default sector expiry when the sector // contains no deals. The proving boundary is used to adjust/align the sector's expiration. -func NewBasicPreCommitPolicy(api Chain, duration abi.ChainEpoch, provingBoundary abi.ChainEpoch) BasicPreCommitPolicy { +func NewBasicPreCommitPolicy(api Chain, cfgGetter GetSealingConfigFunc, provingBoundary abi.ChainEpoch) BasicPreCommitPolicy { return BasicPreCommitPolicy{ - api: api, - provingBoundary: provingBoundary, - duration: duration, + api: api, + getSealingConfig: cfgGetter, + provingBoundary: provingBoundary, } } @@ -79,7 +81,13 @@ func (p *BasicPreCommitPolicy) Expiration(ctx context.Context, ps ...Piece) (abi } if end == nil { - tmp := epoch + p.duration + // no deal pieces, get expiration for committed capacity sector + expirationDuration, err := p.getCCSectorLifetime() + if err != nil { + return 0, err + } + + tmp := epoch + expirationDuration end = &tmp } @@ -87,3 +95,23 @@ func (p *BasicPreCommitPolicy) Expiration(ctx context.Context, ps ...Piece) (abi return *end, nil } + +func (p *BasicPreCommitPolicy) getCCSectorLifetime() (abi.ChainEpoch, error) { + c, err := p.getSealingConfig() + if err != nil { + return 0, xerrors.Errorf("sealing config load error: %w", err) + } + + sectorExpiration := abi.ChainEpoch(c.CommittedCapacitySectorLifetime.Truncate(builtin.EpochDurationSeconds) / builtin.EpochDurationSeconds) + + if minExpiration := policy.GetMinSectorExpiration(); sectorExpiration < minExpiration { + log.Warnf("value for CommittedCapacitySectorLiftime is too short, using default minimum (%d epochs)", minExpiration) + return minExpiration, nil + } + if maxExpiration := policy.GetMaxSectorExpirationExtension(); sectorExpiration > maxExpiration { + log.Warnf("value for CommittedCapacitySectorLiftime is too long, using default maximum (%d epochs)", maxExpiration) + return maxExpiration, nil + } + + return sectorExpiration, nil +} diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 59c3d8c4f..95b851609 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -20,7 +20,7 @@ type Config struct { WaitDealsDelay time.Duration - CommittedCapacityDefaultLifetime time.Duration + CommittedCapacitySectorLifetime time.Duration AlwaysKeepUnsealedCopy bool diff --git a/node/config/def.go b/node/config/def.go index bf6526e4d..17f43ffc3 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -10,6 +10,8 @@ import ( "github.com/filecoin-project/go-state-types/big" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" ) @@ -131,11 +133,10 @@ type SealingConfig struct { WaitDealsDelay Duration - // CommittedCapacityDefaultLifetime is the default duration a Committed Capacity (CC) - // sector will live before it must be extended or converted into sector containing deals - // before it is terminated. - // Value must be between 180-540 days inclusive. - CommittedCapacityDefaultLifetime Duration + // CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will + // live before it must be extended or converted into sector containing deals before it is + // terminated. Value must be between 180-540 days inclusive. + CommittedCapacitySectorLifetime Duration AlwaysKeepUnsealedCopy bool @@ -358,6 +359,8 @@ func DefaultStorageMiner() *StorageMiner { PreCommitBatchWait: Duration(24 * time.Hour), // this should be less than 31.5 hours, which is the expiration of a precommit ticket PreCommitBatchSlack: Duration(3 * time.Hour), // time buffer for forceful batch submission before sectors/deals in batch would start expiring, higher value will lower the chances for message fail due to expiration + CommittedCapacitySectorLifetime: Duration(builtin.EpochDurationSeconds * policy.GetMaxSectorExpirationExtension()), + AggregateCommits: true, MinCommitBatch: miner5.MinAggregatedSectors, // per FIP13, we must have at least four proofs to aggregate, where 4 is the cross over point where aggregation wins out on single provecommit gas costs MaxCommitBatch: miner5.MaxAggregatedSectors, // maximum 819 sectors, this is the maximum aggregation per FIP13 diff --git a/storage/miner.go b/storage/miner.go index 06ee874a1..c38671c8c 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -27,7 +27,6 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/types" @@ -178,15 +177,12 @@ func (m *Miner) Run(ctx context.Context) error { // provides extra methods. adaptedAPI = NewSealingAPIAdapter(m.api) - // sealing configuration. - cfg = sealing.GetSealingConfigFunc(m.getSealConfig) - // Instantiate a precommit policy. - defaultDuration = getDefaultSectorExpirationExtension(cfg) - (md.WPoStProvingPeriod * 2) + cfg = sealing.GetSealingConfigFunc(m.getSealConfig) provingBoundary = md.PeriodStart % md.WPoStProvingPeriod // TODO: Maybe we update this policy after actor upgrades? - pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, defaultDuration, provingBoundary) + pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBoundary) // address selector. as = func(ctx context.Context, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { @@ -203,16 +199,6 @@ func (m *Miner) Run(ctx context.Context) error { return nil } -func getDefaultSectorExpirationExtension(cfg sealing.GetSealingConfigFunc) abi.ChainEpoch { - c, err := cfg() - if err != nil { - log.Warnf("failed to load sealing config, using default sector extension expiration") - log.Errorf("sealing config load error: %s", err.Error()) - return policy.GetMaxSectorExpirationExtension() - } - return abi.ChainEpoch(c.CommittedCapacityDefaultLifetime.Truncate(builtin.EpochDurationSeconds)) -} - func (m *Miner) handleSealingNotifications(before, after sealing.SectorInfo) { m.journal.RecordEvent(m.sealingEvtType, func() interface{} { return SealingStateEvt{ From 7ee46ad4e0638822c7dfcdf6fb4aa306ec3f9f40 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 22 Jul 2021 14:44:39 -0400 Subject: [PATCH 24/98] fix: PreCommitPolicy unit tests --- extern/storage-sealing/precommit_policy.go | 16 +++-- .../storage-sealing/precommit_policy_test.go | 63 ++++++++++++++----- storage/miner.go | 3 +- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/extern/storage-sealing/precommit_policy.go b/extern/storage-sealing/precommit_policy.go index bc5a0a530..6d1fddc47 100644 --- a/extern/storage-sealing/precommit_policy.go +++ b/extern/storage-sealing/precommit_policy.go @@ -40,17 +40,19 @@ type BasicPreCommitPolicy struct { getSealingConfig GetSealingConfigFunc provingBoundary abi.ChainEpoch + provingBuffer abi.ChainEpoch } // NewBasicPreCommitPolicy produces a BasicPreCommitPolicy. // // The provided duration is used as the default sector expiry when the sector // contains no deals. The proving boundary is used to adjust/align the sector's expiration. -func NewBasicPreCommitPolicy(api Chain, cfgGetter GetSealingConfigFunc, provingBoundary abi.ChainEpoch) BasicPreCommitPolicy { +func NewBasicPreCommitPolicy(api Chain, cfgGetter GetSealingConfigFunc, provingBoundary abi.ChainEpoch, provingBuffer abi.ChainEpoch) BasicPreCommitPolicy { return BasicPreCommitPolicy{ api: api, getSealingConfig: cfgGetter, provingBoundary: provingBoundary, + provingBuffer: provingBuffer, } } @@ -102,16 +104,20 @@ func (p *BasicPreCommitPolicy) getCCSectorLifetime() (abi.ChainEpoch, error) { return 0, xerrors.Errorf("sealing config load error: %w", err) } - sectorExpiration := abi.ChainEpoch(c.CommittedCapacitySectorLifetime.Truncate(builtin.EpochDurationSeconds) / builtin.EpochDurationSeconds) + var ccLifetimeEpochs = abi.ChainEpoch(uint64(c.CommittedCapacitySectorLifetime.Truncate(builtin.EpochDurationSeconds).Seconds()) / builtin.EpochDurationSeconds) + // if zero value in config, assume maximum sector extension + if ccLifetimeEpochs == 0 { + ccLifetimeEpochs = policy.GetMaxSectorExpirationExtension() + } - if minExpiration := policy.GetMinSectorExpiration(); sectorExpiration < minExpiration { + if minExpiration := policy.GetMinSectorExpiration(); ccLifetimeEpochs < minExpiration { log.Warnf("value for CommittedCapacitySectorLiftime is too short, using default minimum (%d epochs)", minExpiration) return minExpiration, nil } - if maxExpiration := policy.GetMaxSectorExpirationExtension(); sectorExpiration > maxExpiration { + if maxExpiration := policy.GetMaxSectorExpirationExtension(); ccLifetimeEpochs > maxExpiration { log.Warnf("value for CommittedCapacitySectorLiftime is too long, using default maximum (%d epochs)", maxExpiration) return maxExpiration, nil } - return sectorExpiration, nil + return (ccLifetimeEpochs - p.provingBuffer), nil } diff --git a/extern/storage-sealing/precommit_policy_test.go b/extern/storage-sealing/precommit_policy_test.go index a6c17d3fd..aa56f1e6f 100644 --- a/extern/storage-sealing/precommit_policy_test.go +++ b/extern/storage-sealing/precommit_policy_test.go @@ -3,10 +3,15 @@ package sealing_test import ( "context" "testing" + "time" "github.com/filecoin-project/go-state-types/network" api "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" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" "github.com/ipfs/go-cid" "github.com/stretchr/testify/assert" @@ -14,14 +19,31 @@ import ( commcid "github.com/filecoin-project/go-fil-commcid" "github.com/filecoin-project/go-state-types/abi" - - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" ) type fakeChain struct { h abi.ChainEpoch } +func fakeConfigGetter(stub interface{}, t *testing.T) sealing.GetSealingConfigFunc { + return func() (sealiface.Config, error) { + if stub == nil { + return sealiface.Config{}, nil + } + + castStub, ok := stub.(struct { + CCSectorLifetime time.Duration + }) + if !ok { + t.Fatal("failed to cast fakeConfig") + } + + return sealiface.Config{ + CommittedCapacitySectorLifetime: castStub.CCSectorLifetime, + }, nil + } +} + func (f *fakeChain) StateNetworkVersion(ctx context.Context, tok sealing.TipSetToken) (network.Version, error) { return build.NewestNetworkVersion, nil } @@ -38,21 +60,29 @@ func fakePieceCid(t *testing.T) cid.Cid { } func TestBasicPolicyEmptySector(t *testing.T) { - policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ - h: abi.ChainEpoch(55), - }, 10, 0) + cfg := fakeConfigGetter(nil, t) + h := abi.ChainEpoch(55) + pBoundary := abi.ChainEpoch(0) + pBuffer := abi.ChainEpoch(2) + pcp := sealing.NewBasicPreCommitPolicy(&fakeChain{h: h}, cfg, pBoundary, pBuffer) + exp, err := pcp.Expiration(context.Background()) - exp, err := policy.Expiration(context.Background()) require.NoError(t, err) - assert.Equal(t, 2879, int(exp)) + // as set when there are no deal pieces + expected := h + policy.GetMaxSectorExpirationExtension() - (pBuffer * 2) + // as set just before returning within Expiration() + expected += miner.WPoStProvingPeriod - (expected % miner.WPoStProvingPeriod) + pBoundary - 1 + assert.Equal(t, int(expected), int(exp)) } func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { + cfg := fakeConfigGetter(nil, t) + pPeriod := abi.ChainEpoch(11) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 11) - + }, cfg, pPeriod, 2) + longestDealEpochEnd := abi.ChainEpoch(100) pieces := []sealing.Piece{ { Piece: abi.PieceInfo{ @@ -76,7 +106,7 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { DealID: abi.DealID(43), DealSchedule: api.DealSchedule{ StartEpoch: abi.ChainEpoch(80), - EndEpoch: abi.ChainEpoch(100), + EndEpoch: longestDealEpochEnd, }, }, }, @@ -85,13 +115,15 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2890, int(exp)) + expected := longestDealEpochEnd + miner.WPoStProvingPeriod - (longestDealEpochEnd % miner.WPoStProvingPeriod) + pPeriod - 1 + assert.Equal(t, int(expected), int(exp)) } func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { + cfg := fakeConfigGetter(nil, t) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 0) + }, cfg, 0, 0) pieces := []sealing.Piece{ { @@ -112,13 +144,14 @@ func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2879, int(exp)) + assert.Equal(t, 1558079, int(exp)) } func TestMissingDealIsIgnored(t *testing.T) { + cfg := fakeConfigGetter(nil, t) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 11) + }, cfg, 11, 0) pieces := []sealing.Piece{ { @@ -146,5 +179,5 @@ func TestMissingDealIsIgnored(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2890, int(exp)) + assert.Equal(t, 1558090, int(exp)) } diff --git a/storage/miner.go b/storage/miner.go index c38671c8c..59c64eb41 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -180,9 +180,10 @@ func (m *Miner) Run(ctx context.Context) error { // Instantiate a precommit policy. cfg = sealing.GetSealingConfigFunc(m.getSealConfig) provingBoundary = md.PeriodStart % md.WPoStProvingPeriod + provingBuffer = md.WPoStProvingPeriod * 2 // TODO: Maybe we update this policy after actor upgrades? - pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBoundary) + pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBoundary, provingBuffer) // address selector. as = func(ctx context.Context, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { From 123a976f6247ec84e3bb3ab0ddaa9fe7d1a6532a Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 22 Jul 2021 14:54:52 -0400 Subject: [PATCH 25/98] chore: Add unit test for custom CC lifetime value expiration --- .../storage-sealing/precommit_policy_test.go | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/extern/storage-sealing/precommit_policy_test.go b/extern/storage-sealing/precommit_policy_test.go index aa56f1e6f..7c92cd061 100644 --- a/extern/storage-sealing/precommit_policy_test.go +++ b/extern/storage-sealing/precommit_policy_test.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-state-types/network" api "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" @@ -25,21 +26,18 @@ type fakeChain struct { h abi.ChainEpoch } -func fakeConfigGetter(stub interface{}, t *testing.T) sealing.GetSealingConfigFunc { +type fakeConfigStub struct { + CCSectorLifetime time.Duration +} + +func fakeConfigGetter(stub *fakeConfigStub) sealing.GetSealingConfigFunc { return func() (sealiface.Config, error) { if stub == nil { return sealiface.Config{}, nil } - castStub, ok := stub.(struct { - CCSectorLifetime time.Duration - }) - if !ok { - t.Fatal("failed to cast fakeConfig") - } - return sealiface.Config{ - CommittedCapacitySectorLifetime: castStub.CCSectorLifetime, + CommittedCapacitySectorLifetime: stub.CCSectorLifetime, }, nil } } @@ -60,7 +58,7 @@ func fakePieceCid(t *testing.T) cid.Cid { } func TestBasicPolicyEmptySector(t *testing.T) { - cfg := fakeConfigGetter(nil, t) + cfg := fakeConfigGetter(nil) h := abi.ChainEpoch(55) pBoundary := abi.ChainEpoch(0) pBuffer := abi.ChainEpoch(2) @@ -76,8 +74,28 @@ func TestBasicPolicyEmptySector(t *testing.T) { assert.Equal(t, int(expected), int(exp)) } +func TestCustomCCSectorConfig(t *testing.T) { + customLifetime := 200 * 24 * time.Hour + customLifetimeEpochs := abi.ChainEpoch(int64(customLifetime.Truncate(builtin.EpochDurationSeconds).Seconds()) / builtin.EpochDurationSeconds) + cfgStub := fakeConfigStub{CCSectorLifetime: customLifetime} + cfg := fakeConfigGetter(&cfgStub) + h := abi.ChainEpoch(55) + pBoundary := abi.ChainEpoch(0) + pBuffer := abi.ChainEpoch(2) + pcp := sealing.NewBasicPreCommitPolicy(&fakeChain{h: h}, cfg, pBoundary, pBuffer) + exp, err := pcp.Expiration(context.Background()) + + require.NoError(t, err) + + // as set when there are no deal pieces + expected := h + customLifetimeEpochs - (pBuffer * 2) + // as set just before returning within Expiration() + expected += miner.WPoStProvingPeriod - (expected % miner.WPoStProvingPeriod) + pBoundary - 1 + assert.Equal(t, int(expected), int(exp)) +} + func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { - cfg := fakeConfigGetter(nil, t) + cfg := fakeConfigGetter(nil) pPeriod := abi.ChainEpoch(11) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), @@ -120,7 +138,7 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { } func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { - cfg := fakeConfigGetter(nil, t) + cfg := fakeConfigGetter(nil) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), }, cfg, 0, 0) @@ -148,7 +166,7 @@ func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { } func TestMissingDealIsIgnored(t *testing.T) { - cfg := fakeConfigGetter(nil, t) + cfg := fakeConfigGetter(nil) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), }, cfg, 11, 0) From 3829d6bd9adb22e8ac42f15b7c54e4440308c793 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 22 Jul 2021 15:05:03 -0400 Subject: [PATCH 26/98] fix: Remove actor method from autogen source; Move inline to pkg --- chain/actors/policy/policy.go | 4 ---- extern/storage-sealing/precommit_policy.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 06db09ea8..c06c85d38 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -301,10 +301,6 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { return ChainFinality } -func GetMinSectorExpiration() abi.ChainEpoch { - return miner5.MinSectorExpiration -} - func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner5.MaxSectorExpirationExtension } diff --git a/extern/storage-sealing/precommit_policy.go b/extern/storage-sealing/precommit_policy.go index 6d1fddc47..3856fd548 100644 --- a/extern/storage-sealing/precommit_policy.go +++ b/extern/storage-sealing/precommit_policy.go @@ -110,7 +110,7 @@ func (p *BasicPreCommitPolicy) getCCSectorLifetime() (abi.ChainEpoch, error) { ccLifetimeEpochs = policy.GetMaxSectorExpirationExtension() } - if minExpiration := policy.GetMinSectorExpiration(); ccLifetimeEpochs < minExpiration { + if minExpiration := abi.ChainEpoch(miner.MinSectorExpiration); ccLifetimeEpochs < minExpiration { log.Warnf("value for CommittedCapacitySectorLiftime is too short, using default minimum (%d epochs)", minExpiration) return minExpiration, nil } From d6783736dcb119711c120033e37d4c107dcc2931 Mon Sep 17 00:00:00 2001 From: Liviu Damian Date: Thu, 22 Jul 2021 16:31:28 +0300 Subject: [PATCH 27/98] Nerpa v13 upgrade --- build/params_nerpanet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/params_nerpanet.go b/build/params_nerpanet.go index 6663a9162..3cba64ae2 100644 --- a/build/params_nerpanet.go +++ b/build/params_nerpanet.go @@ -42,7 +42,7 @@ const UpgradeOrangeHeight = 300 const UpgradeTrustHeight = 600 const UpgradeNorwegianHeight = 201000 const UpgradeTurboHeight = 203000 -const UpgradeHyperdriveHeight = 999999999 +const UpgradeHyperdriveHeight = 379178 func init() { // Minimum block production power is set to 4 TiB From 7ef167b04f719941ac1bbf91faa9d9b932dfd5f0 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Fri, 23 Jul 2021 09:33:26 +0200 Subject: [PATCH 28/98] refactor: simplify market datastore backup --- cmd/lotus-shed/market.go | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/cmd/lotus-shed/market.go b/cmd/lotus-shed/market.go index cde66191d..10d71bbf2 100644 --- a/cmd/lotus-shed/market.go +++ b/cmd/lotus-shed/market.go @@ -2,10 +2,12 @@ package main import ( "fmt" - "io/ioutil" "os" "path" + levelds "github.com/ipfs/go-ds-leveldb" + ldbopts "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/filecoin-project/lotus/lib/backupds" "github.com/filecoin-project/lotus/node/repo" @@ -128,16 +130,19 @@ var marketExportDatastoreCmd = &cli.Command{ Action: func(cctx *cli.Context) error { logging.SetLogLevel("badger", "ERROR") // nolint:errcheck + // If the backup dir is not specified, just use the OS temp dir backupDir := cctx.String("backup-dir") if backupDir == "" { backupDir = os.TempDir() } + // Create a new repo at the repo path r, err := repo.NewFS(cctx.String("repo")) if err != nil { return xerrors.Errorf("opening fs repo: %w", err) } + // Make sure the repo exists exists, err := r.Exists() if err != nil { return err @@ -146,39 +151,30 @@ var marketExportDatastoreCmd = &cli.Command{ return xerrors.Errorf("lotus repo doesn't exist") } + // Lock the repo lr, err := r.Lock(repo.StorageMiner) if err != nil { return err } defer lr.Close() //nolint:errcheck + // Open the metadata datastore on the repo namespace := "metadata" ds, err := lr.Datastore(cctx.Context, datastore.NewKey(namespace).String()) if err != nil { return err } - backupRepoDir, err := ioutil.TempDir("", "backup-repo-dir") - if err != nil { - return err - } - - backupRepo, err := repo.NewFS(cctx.String(backupRepoDir)) - if err != nil { - return xerrors.Errorf("opening backup repo: %w", err) - } - - lockedBackupRepo, err := backupRepo.Lock(repo.StorageMiner) - if err != nil { - return err - } - defer lockedBackupRepo.Close() //nolint:errcheck - - backupDs, err := lockedBackupRepo.Datastore(cctx.Context, datastore.NewKey(namespace).String()) - if err != nil { - return err - } + // Create a tmp datastore that we'll add the exported key / values to + // and then backup + backupDs, err := levelds.NewDatastore(backupDir, &levelds.Options{ + Compression: ldbopts.NoCompression, + NoSync: false, + Strict: ldbopts.StrictAll, + ReadOnly: false, + }) + // Export the key / values prefixes := []string{ "/deals/provider", "/retrievals/provider", @@ -191,16 +187,20 @@ var marketExportDatastoreCmd = &cli.Command{ } } + // Wrap the datastore in a backup datastore bds, err := backupds.Wrap(backupDs, "") if err != nil { return xerrors.Errorf("opening backupds: %w", err) } - fpath := path.Join(backupDir, "datastore.backup") + // Create a file for the backup + fpath := path.Join(backupDir, "markets.datastore.backup") out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return xerrors.Errorf("open %s: %w", fpath, err) } + + // Write the backup to the file if err := bds.Backup(out); err != nil { if cerr := out.Close(); cerr != nil { log.Errorw("error closing backup file while handling backup error", "closeErr", cerr, "backupErr", err) From 52498f78a1da96e8f9a62c2e6ce9a354c556b7ef Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Fri, 23 Jul 2021 10:30:58 +0200 Subject: [PATCH 29/98] feat: add import market datastore cmd --- cmd/lotus-shed/market.go | 117 +++++++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 23 deletions(-) diff --git a/cmd/lotus-shed/market.go b/cmd/lotus-shed/market.go index 10d71bbf2..eb1b56a94 100644 --- a/cmd/lotus-shed/market.go +++ b/cmd/lotus-shed/market.go @@ -31,6 +31,7 @@ var marketCmd = &cli.Command{ Subcommands: []*cli.Command{ marketDealFeesCmd, marketExportDatastoreCmd, + marketImportDatastoreCmd, }, } @@ -114,9 +115,11 @@ var marketDealFeesCmd = &cli.Command{ }, } +const mktsMetadataNamespace = "metadata" + var marketExportDatastoreCmd = &cli.Command{ Name: "export-datastore", - Description: "export datastore to a file", + Description: "export markets datastore key/values to a file", Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", @@ -136,43 +139,37 @@ var marketExportDatastoreCmd = &cli.Command{ backupDir = os.TempDir() } - // Create a new repo at the repo path - r, err := repo.NewFS(cctx.String("repo")) - if err != nil { - return xerrors.Errorf("opening fs repo: %w", err) - } - - // Make sure the repo exists - exists, err := r.Exists() - if err != nil { - return err - } - if !exists { - return xerrors.Errorf("lotus repo doesn't exist") - } - - // Lock the repo - lr, err := r.Lock(repo.StorageMiner) + // Open the repo at the repo path + repoPath := cctx.String("repo") + lr, err := openLockedRepo(repoPath) if err != nil { return err } defer lr.Close() //nolint:errcheck // Open the metadata datastore on the repo - namespace := "metadata" - ds, err := lr.Datastore(cctx.Context, datastore.NewKey(namespace).String()) + ds, err := lr.Datastore(cctx.Context, datastore.NewKey(mktsMetadataNamespace).String()) if err != nil { - return err + return xerrors.Errorf("opening datastore %s on repo %s: %w", mktsMetadataNamespace, repoPath, err) } // Create a tmp datastore that we'll add the exported key / values to // and then backup - backupDs, err := levelds.NewDatastore(backupDir, &levelds.Options{ + backupDsDir := path.Join(backupDir, "markets-backup-datastore") + if err := os.MkdirAll(backupDsDir, 0775); err != nil { //nolint:gosec + return xerrors.Errorf("creating tmp datastore directory: %w", err) + } + defer os.RemoveAll(backupDsDir) //nolint:errcheck + + backupDs, err := levelds.NewDatastore(backupDsDir, &levelds.Options{ Compression: ldbopts.NoCompression, NoSync: false, Strict: ldbopts.StrictAll, ReadOnly: false, }) + if err != nil { + return xerrors.Errorf("opening backup datastore at %s: %w", backupDir, err) + } // Export the key / values prefixes := []string{ @@ -197,7 +194,7 @@ var marketExportDatastoreCmd = &cli.Command{ fpath := path.Join(backupDir, "markets.datastore.backup") out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return xerrors.Errorf("open %s: %w", fpath, err) + return xerrors.Errorf("opening backup file %s: %w", fpath, err) } // Write the backup to the file @@ -236,3 +233,77 @@ func exportPrefix(prefix string, ds datastore.Batching, backupDs datastore.Batch return nil } + +var marketImportDatastoreCmd = &cli.Command{ + Name: "import-datastore", + Description: "import markets datastore key/values from a backup file", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Usage: "path to the repo", + }, + &cli.StringFlag{ + Name: "backup-path", + Usage: "path to the backup directory", + Required: true, + }, + }, + Action: func(cctx *cli.Context) error { + logging.SetLogLevel("badger", "ERROR") // nolint:errcheck + + backupPath := cctx.String("backup-path") + + // Open the repo at the repo path + lr, err := openLockedRepo(cctx.String("repo")) + if err != nil { + return err + } + defer lr.Close() //nolint:errcheck + + // Open the metadata datastore on the repo + repoDs, err := lr.Datastore(cctx.Context, datastore.NewKey(mktsMetadataNamespace).String()) + if err != nil { + return err + } + + r, err := os.Open(backupPath) + if err != nil { + return xerrors.Errorf("opening backup path %s: %w", backupPath, err) + } + + fmt.Println("Importing from backup file " + backupPath) + err = backupds.RestoreInto(r, repoDs) + if err != nil { + return xerrors.Errorf("restoring backup from path %s: %w", backupPath, err) + } + + fmt.Println("Completed importing from backup file " + backupPath) + + return nil + }, +} + +func openLockedRepo(path string) (repo.LockedRepo, error) { + // Open the repo at the repo path + rpo, err := repo.NewFS(path) + if err != nil { + return nil, xerrors.Errorf("could not open repo %s: %w", path, err) + } + + // Make sure the repo exists + exists, err := rpo.Exists() + if err != nil { + return nil, xerrors.Errorf("checking repo %s exists: %w", path, err) + } + if !exists { + return nil, xerrors.Errorf("repo does not exist: %s", path) + } + + // Lock the repo + lr, err := rpo.Lock(repo.StorageMiner) + if err != nil { + return nil, xerrors.Errorf("locking repo %s: %w", path, err) + } + + return lr, nil +} From 41bce7925a1852d2eba72b72357ec5d342402cb0 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Fri, 23 Jul 2021 20:12:30 -0400 Subject: [PATCH 30/98] fix: Remove unnecessary Truncate --- extern/storage-sealing/precommit_policy.go | 2 +- extern/storage-sealing/precommit_policy_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/precommit_policy.go b/extern/storage-sealing/precommit_policy.go index 3856fd548..c911ccc8c 100644 --- a/extern/storage-sealing/precommit_policy.go +++ b/extern/storage-sealing/precommit_policy.go @@ -104,7 +104,7 @@ func (p *BasicPreCommitPolicy) getCCSectorLifetime() (abi.ChainEpoch, error) { return 0, xerrors.Errorf("sealing config load error: %w", err) } - var ccLifetimeEpochs = abi.ChainEpoch(uint64(c.CommittedCapacitySectorLifetime.Truncate(builtin.EpochDurationSeconds).Seconds()) / builtin.EpochDurationSeconds) + var ccLifetimeEpochs = abi.ChainEpoch(uint64(c.CommittedCapacitySectorLifetime.Seconds()) / builtin.EpochDurationSeconds) // if zero value in config, assume maximum sector extension if ccLifetimeEpochs == 0 { ccLifetimeEpochs = policy.GetMaxSectorExpirationExtension() diff --git a/extern/storage-sealing/precommit_policy_test.go b/extern/storage-sealing/precommit_policy_test.go index 7c92cd061..7f5aff0df 100644 --- a/extern/storage-sealing/precommit_policy_test.go +++ b/extern/storage-sealing/precommit_policy_test.go @@ -76,7 +76,7 @@ func TestBasicPolicyEmptySector(t *testing.T) { func TestCustomCCSectorConfig(t *testing.T) { customLifetime := 200 * 24 * time.Hour - customLifetimeEpochs := abi.ChainEpoch(int64(customLifetime.Truncate(builtin.EpochDurationSeconds).Seconds()) / builtin.EpochDurationSeconds) + customLifetimeEpochs := abi.ChainEpoch(int64(customLifetime.Seconds()) / builtin.EpochDurationSeconds) cfgStub := fakeConfigStub{CCSectorLifetime: customLifetime} cfg := fakeConfigGetter(&cfgStub) h := abi.ChainEpoch(55) From e68c59f984b67bacd234a3d27393c96894f7e8d8 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Fri, 23 Jul 2021 21:05:50 -0400 Subject: [PATCH 31/98] fix: Update node config docs --- node/config/doc_gen.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index ea68dc344..5163ac409 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -562,6 +562,14 @@ Note that setting this number too high in relation to deal ingestion rate may re Comment: `Upper bound on how many sectors can be sealing at the same time when creating new sectors with deals (0 = unlimited)`, }, + { + Name: "CommittedCapacitySectorLifetime", + Type: "Duration", + + Comment: `CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will +live before it must be extended or converted into sector containing deals before it is +terminated. Value must be between 180-540 days inclusive`, + }, { Name: "WaitDealsDelay", Type: "Duration", From b741d61b201d9d9b9596c9eec10503ad93fe19c5 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 14:33:15 +0300 Subject: [PATCH 32/98] implement BlockstoreMover in badger --- blockstore/badger/blockstore.go | 373 +++++++++++++++++++++++++++++--- 1 file changed, 343 insertions(+), 30 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f8d077760..7e1b1769f 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "sync" + "time" "github.com/dgraph-io/badger/v2" "github.com/dgraph-io/badger/v2/options" @@ -35,6 +36,8 @@ var ( log = logger.Logger("badgerbs") ) +const moveBatchSize = 16384 + // aliases to mask badger dependencies. const ( // FileIO is equivalent to badger/options.FileIO. @@ -80,13 +83,26 @@ const ( stateClosed ) +const ( + moveStateNone = iota + moveStateMoving + moveStateCleanup + moveStateLock +) + // Blockstore is a badger-backed IPLD blockstore. type Blockstore struct { stateLk sync.RWMutex state int viewers sync.WaitGroup - DB *badger.DB + moveMx sync.RWMutex + moveCond *sync.Cond + moveState int + rlock int + + db *badger.DB + db2 *badger.DB // when moving opts Options prefixing bool @@ -113,13 +129,15 @@ func Open(opts Options) (*Blockstore, error) { return nil, fmt.Errorf("failed to open badger blockstore: %w", err) } - bs := &Blockstore{DB: db, opts: opts} + bs := &Blockstore{db: db, opts: opts} if p := opts.Prefix; p != "" { bs.prefixing = true bs.prefix = []byte(p) bs.prefixLen = len(bs.prefix) } + bs.moveCond = sync.NewCond(&bs.moveMx) + return bs, nil } @@ -143,7 +161,7 @@ func (b *Blockstore) Close() error { // wait for all accesses to complete b.viewers.Wait() - return b.DB.Close() + return b.db.Close() } func (b *Blockstore) access() error { @@ -165,26 +183,252 @@ func (b *Blockstore) isOpen() bool { return b.state == stateOpen } -// CollectGarbage runs garbage collection on the value log +// lockDB/unlockDB implement a recursive lock contigent on move state +func (b *Blockstore) lockDB() { + b.moveMx.Lock() + defer b.moveMx.Unlock() + + if b.rlock == 0 { + for b.moveState == moveStateLock { + b.moveCond.Wait() + } + } + + b.rlock++ +} + +func (b *Blockstore) unlockDB() { + b.moveMx.Lock() + defer b.moveMx.Unlock() + + b.rlock-- + if b.rlock == 0 && b.moveState == moveStateLock { + b.moveCond.Broadcast() + } +} + +// lockMove/unlockMove implement an exclusive lock of move state +func (b *Blockstore) lockMove() { + b.moveMx.Lock() + b.moveState = moveStateLock + for b.rlock > 0 { + b.moveCond.Wait() + } +} + +func (b *Blockstore) unlockMove(state int) { + b.moveState = state + b.moveCond.Broadcast() + b.moveMx.Unlock() +} + +// MoveTo implements the BlockstoreMover trait +func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + // this inlines moveLock/moveUnlock for the initial state check to prevent a second move + // while one is in progress without clobbering state + b.moveMx.Lock() + if b.moveState != moveStateNone { + b.moveMx.Unlock() + return fmt.Errorf("move in progress") + } + + b.moveState = moveStateLock + for b.rlock > 0 { + b.moveCond.Wait() + } + + b.moveState = moveStateMoving + b.moveCond.Broadcast() + b.moveMx.Unlock() + + if path == "" { + path = fmt.Sprintf("%s.%d", b.opts.Dir, time.Now().Unix()) + } + + defer func() { + b.lockMove() + + db2 := b.db2 + b.db2 = nil + + var state int + if db2 != nil { + state = moveStateCleanup + } else { + state = moveStateNone + } + + b.unlockMove(state) + + if db2 != nil { + err := db2.Close() + if err != nil { + log.Warnf("error closing badger db: %s", err) + } + b.deleteDB(path) + + b.lockMove() + b.unlockMove(moveStateNone) + } + }() + + opts := b.opts + opts.Dir = path + opts.ValueDir = path + + db2, err := badger.Open(opts.Options) + if err != nil { + return fmt.Errorf("failed to open badger blockstore in %s: %w", path, err) + } + + b.lockMove() + b.db2 = db2 + b.unlockMove(moveStateMoving) + + err = b.doCopy(b.db, b.db2, filter) + if err != nil { + return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) + } + + b.lockMove() + db1 := b.db + b.db = b.db2 + b.db2 = nil + b.unlockMove(moveStateCleanup) + + err = db1.Close() + if err != nil { + log.Warnf("error closing badger db: %s", err) + } + + dbpath := b.opts.Dir + oldpath := fmt.Sprintf("%s.old.%d", dbpath, time.Now().Unix()) + + ok := true + err = os.Rename(dbpath, oldpath) + if err != nil { + // this is bad, but not catastrophic; new data will be written in db2 and user can fix + log.Errorf("error renaming badger db dir from %s to %s; USER ACTION REQUIRED", dbpath, oldpath) + ok = false + } + + if ok { + err = os.Symlink(path, dbpath) + if err != nil { + // ditto, this is bad, but not catastrophic; user can fix + log.Errorf("error symlinking badger db dir from %s to %s; USER ACTION REQUIRED", path, dbpath) + } + + b.deleteDB(oldpath) + } + + return nil +} + +func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) error { + count := 0 + batch := to.NewWriteBatch() + defer batch.Cancel() + + txn := from.NewTransaction(false) + defer txn.Discard() + + opts := badger.IteratorOptions{PrefetchSize: moveBatchSize} + iter := txn.NewIterator(opts) + defer iter.Close() + + var buf []byte + for iter.Rewind(); iter.Valid(); iter.Next() { + if !b.isOpen() { + return ErrBlockstoreClosed + } + + item := iter.Item() + + if filter != nil { + k := item.Key() + if b.prefixing { + k = k[b.prefixLen:] + } + + klen := base32.RawStdEncoding.DecodedLen(len(k)) + if klen > len(buf) { + buf = make([]byte, klen) + } + + n, err := base32.RawStdEncoding.Decode(buf, k) + if err != nil { + return err + } + + c := cid.NewCidV1(cid.Raw, buf[:n]) + if !filter(c) { + continue + } + } + + k := item.KeyCopy(nil) + v, err := item.ValueCopy(nil) + if err != nil { + return err + } + + if err := batch.Set(k, v); err != nil { + return err + } + + count++ + if count == moveBatchSize { + if err := batch.Flush(); err != nil { + return err + } + count = 0 + } + } + + if count > 0 { + return batch.Flush() + } + + return nil +} + +func (b *Blockstore) deleteDB(path string) { + err := os.RemoveAll(path) + if err != nil { + log.Warnf("error deleting db at %s: %s", path, err) + } +} + +// CollectGarbage compacts and runs garbage collection on the value log; +// implements the BlockstoreGC trait func (b *Blockstore) CollectGarbage() error { if err := b.access(); err != nil { return err } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + // compact first to gather the necessary statistics for GC nworkers := runtime.NumCPU() / 2 if nworkers < 2 { nworkers = 2 } - err := b.DB.Flatten(nworkers) + err := b.db.Flatten(nworkers) if err != nil { return err } for err == nil { - err = b.DB.RunValueLogGC(0.125) + err = b.db.RunValueLogGC(0.125) } if err == badger.ErrNoRewrite { @@ -202,7 +446,10 @@ func (b *Blockstore) Size() (int64, error) { } defer b.viewers.Done() - lsm, vlog := b.DB.Size() + b.lockDB() + defer b.unlockDB() + + lsm, vlog := b.db.Size() size := lsm + vlog if size == 0 { @@ -234,12 +481,15 @@ func (b *Blockstore) View(cid cid.Cid, fn func([]byte) error) error { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(cid) if pooled { defer KeyPool.Put(k) } - return b.DB.View(func(txn *badger.Txn) error { + return b.db.View(func(txn *badger.Txn) error { switch item, err := txn.Get(k); err { case nil: return item.Value(fn) @@ -258,12 +508,15 @@ func (b *Blockstore) Has(cid cid.Cid) (bool, error) { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(cid) if pooled { defer KeyPool.Put(k) } - err := b.DB.View(func(txn *badger.Txn) error { + err := b.db.View(func(txn *badger.Txn) error { _, err := txn.Get(k) return err }) @@ -289,13 +542,16 @@ func (b *Blockstore) Get(cid cid.Cid) (blocks.Block, error) { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(cid) if pooled { defer KeyPool.Put(k) } var val []byte - err := b.DB.View(func(txn *badger.Txn) error { + err := b.db.View(func(txn *badger.Txn) error { switch item, err := txn.Get(k); err { case nil: val, err = item.ValueCopy(nil) @@ -319,13 +575,16 @@ func (b *Blockstore) GetSize(cid cid.Cid) (int, error) { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(cid) if pooled { defer KeyPool.Put(k) } var size int - err := b.DB.View(func(txn *badger.Txn) error { + err := b.db.View(func(txn *badger.Txn) error { switch item, err := txn.Get(k); err { case nil: size = int(item.ValueSize()) @@ -349,18 +608,36 @@ func (b *Blockstore) Put(block blocks.Block) error { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(block.Cid()) if pooled { defer KeyPool.Put(k) } - err := b.DB.Update(func(txn *badger.Txn) error { - return txn.Set(k, block.RawData()) - }) - if err != nil { - err = fmt.Errorf("failed to put block in badger blockstore: %w", err) + put := func(db *badger.DB) error { + err := db.Update(func(txn *badger.Txn) error { + return txn.Set(k, block.RawData()) + }) + if err != nil { + return fmt.Errorf("failed to put block in badger blockstore: %w", err) + } + + return nil } - return err + + if err := put(b.db); err != nil { + return err + } + + if b.db2 != nil { + if err := put(b.db2); err != nil { + return err + } + } + + return nil } // PutMany implements Blockstore.PutMany. @@ -370,6 +647,9 @@ func (b *Blockstore) PutMany(blocks []blocks.Block) error { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + // toReturn tracks the byte slices to return to the pool, if we're using key // prefixing. we can't return each slice to the pool after each Set, because // badger holds on to the slice. @@ -383,24 +663,45 @@ func (b *Blockstore) PutMany(blocks []blocks.Block) error { }() } - batch := b.DB.NewWriteBatch() - defer batch.Cancel() - + keys := make([][]byte, 0, len(blocks)) for _, block := range blocks { k, pooled := b.PooledStorageKey(block.Cid()) if pooled { toReturn = append(toReturn, k) } - if err := batch.Set(k, block.RawData()); err != nil { + keys = append(keys, k) + } + + put := func(db *badger.DB) error { + batch := db.NewWriteBatch() + defer batch.Cancel() + + for i, block := range blocks { + k := keys[i] + if err := batch.Set(k, block.RawData()); err != nil { + return err + } + } + + err := batch.Flush() + if err != nil { + return fmt.Errorf("failed to put blocks in badger blockstore: %w", err) + } + + return nil + } + + if err := put(b.db); err != nil { + return err + } + + if b.db2 != nil { + if err := put(b.db2); err != nil { return err } } - err := batch.Flush() - if err != nil { - err = fmt.Errorf("failed to put blocks in badger blockstore: %w", err) - } - return err + return nil } // DeleteBlock implements Blockstore.DeleteBlock. @@ -410,12 +711,15 @@ func (b *Blockstore) DeleteBlock(cid cid.Cid) error { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + k, pooled := b.PooledStorageKey(cid) if pooled { defer KeyPool.Put(k) } - return b.DB.Update(func(txn *badger.Txn) error { + return b.db.Update(func(txn *badger.Txn) error { return txn.Delete(k) }) } @@ -426,6 +730,9 @@ func (b *Blockstore) DeleteMany(cids []cid.Cid) error { } defer b.viewers.Done() + b.lockDB() + defer b.unlockDB() + // toReturn tracks the byte slices to return to the pool, if we're using key // prefixing. we can't return each slice to the pool after each Set, because // badger holds on to the slice. @@ -439,7 +746,7 @@ func (b *Blockstore) DeleteMany(cids []cid.Cid) error { }() } - batch := b.DB.NewWriteBatch() + batch := b.db.NewWriteBatch() defer batch.Cancel() for _, cid := range cids { @@ -465,7 +772,10 @@ func (b *Blockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return nil, err } - txn := b.DB.NewTransaction(false) + b.lockDB() + defer b.unlockDB() + + txn := b.db.NewTransaction(false) opts := badger.IteratorOptions{PrefetchSize: 100} if b.prefixing { opts.Prefix = b.prefix @@ -519,7 +829,10 @@ func (b *Blockstore) ForEachKey(f func(cid.Cid) error) error { } defer b.viewers.Done() - txn := b.DB.NewTransaction(false) + b.lockDB() + defer b.unlockDB() + + txn := b.db.NewTransaction(false) defer txn.Discard() opts := badger.IteratorOptions{PrefetchSize: 100} From 001c04f2dd2f10228b304cedfd4db73424bf44a3 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 14:43:52 +0300 Subject: [PATCH 33/98] use pooled slices for the copy --- blockstore/badger/blockstore.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 7e1b1769f..d5f30d9d7 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -342,6 +342,20 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro iter := txn.NewIterator(opts) defer iter.Close() + pooled := make([][]byte, 0, 2*moveBatchSize) + getPooled := func(size int) []byte { + buf := pool.Get(size) + pooled = append(pooled, buf) + return buf + } + putPooled := func() { + for _, buf := range pooled { + pool.Put(buf) + } + pooled = pooled[:0] + } + defer putPooled() + var buf []byte for iter.Rewind(); iter.Valid(); iter.Next() { if !b.isOpen() { @@ -350,8 +364,9 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro item := iter.Item() + kk := item.Key() if filter != nil { - k := item.Key() + k := kk if b.prefixing { k = k[b.prefixLen:] } @@ -372,8 +387,15 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro } } - k := item.KeyCopy(nil) - v, err := item.ValueCopy(nil) + k := getPooled(len(kk)) + copy(k, kk) + + var v []byte + err := item.Value(func(vv []byte) error { + v = getPooled(len(vv)) + copy(v, vv) + return nil + }) if err != nil { return err } @@ -388,6 +410,7 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro return err } count = 0 + putPooled() } } From aec126879e3a27a0c2fb8ad7caaed6e6b709789d Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 17:51:20 +0300 Subject: [PATCH 34/98] add MoveTo test --- blockstore/badger/blockstore_test.go | 155 ++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index 3221458d2..dab1fdc3a 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -1,12 +1,19 @@ package badgerbs import ( + "bytes" + "fmt" "io/ioutil" "os" + "path/filepath" + "strings" "testing" - blocks "github.com/ipfs/go-block-format" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" "github.com/filecoin-project/lotus/blockstore" ) @@ -89,3 +96,149 @@ func openBlockstore(optsSupplier func(path string) Options) func(tb testing.TB, return Open(optsSupplier(path)) } } + +func testMove(t *testing.T, optsF func(string) Options) { + basePath, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + + dbPath := filepath.Join(basePath, "db") + + t.Cleanup(func() { + _ = os.RemoveAll(basePath) + }) + + db, err := Open(optsF(dbPath)) + if err != nil { + t.Fatal(err) + } + + defer db.Close() //nolint + + var have []blocks.Block + var deleted []cid.Cid + + // add some blocks + for i := 0; i < 10; i++ { + blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i))) + err := db.Put(blk) + if err != nil { + t.Fatal(err) + } + have = append(have, blk) + } + + // delete some of them + for i := 5; i < 10; i++ { + c := have[i].Cid() + err := db.DeleteBlock(c) + if err != nil { + t.Fatal(err) + } + deleted = append(deleted, c) + } + have = have[:5] + + // start a move concurrent with some more puts + g := new(errgroup.Group) + g.Go(func() error { + for i := 10; i < 1000; i++ { + blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i))) + err := db.Put(blk) + if err != nil { + return err + } + have = append(have, blk) + } + return nil + }) + g.Go(func() error { + return db.MoveTo("", nil) + }) + + err = g.Wait() + if err != nil { + t.Fatal(err) + } + + // now check that we have all the blocks in have and none in the deleted lists + for _, blk := range have { + has, err := db.Has(blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("missing block") + } + + blk2, err := db.Get(blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(blk.RawData(), blk2.RawData()) { + t.Fatal("data mismatch") + } + } + + for _, c := range deleted { + has, err := db.Has(c) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("resurrected block") + } + } + + // check the basePath -- it should contain a directory with name db.{timestamp}, soft-linked + // to db and nothing else + entries, err := os.ReadDir(basePath) + if err != nil { + t.Fatal(err) + } + + if len(entries) != 2 { + t.Fatalf("too many entries; expected %d but got %d", 2, len(entries)) + } + + var haveDB, haveDBLink bool + for _, e := range entries { + if e.Name() == "db" { + if (e.Type() & os.ModeSymlink) == 0 { + t.Fatal("found db, but it's not a symlink") + } + haveDBLink = true + continue + } + if strings.HasPrefix(e.Name(), "db.") { + if !e.Type().IsDir() { + t.Fatal("found db prefix, but it's not a directory") + } + haveDB = true + continue + } + } + + if !haveDB { + t.Fatal("db directory is missing") + } + if !haveDBLink { + t.Fatal("db link is missing") + } +} + +func TestMoveNoPrefix(t *testing.T) { + testMove(t, DefaultOptions) +} + +func TestMoveWithPrefix(t *testing.T) { + testMove(t, func(path string) Options { + opts := DefaultOptions(path) + opts.Prefix = "/prefixed/" + return opts + }) +} From 4715b1f4369114320f7dd1310965149ea36f36a1 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 18:00:54 +0300 Subject: [PATCH 35/98] fix lotus-shed --- blockstore/badger/blockstore.go | 6 ++++++ cmd/lotus-shed/pruning.go | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index d5f30d9d7..f0f602b20 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -950,3 +950,9 @@ func (b *Blockstore) StorageKey(dst []byte, cid cid.Cid) []byte { } return dst[:reqsize] } + +// this method is added for lotus-shed needs +// WARNING: THIS IS COMPLETELY UNSAFE; DONT USE THIS IN PRODUCTION CODE +func (b *Blockstore) DB() *badger.DB { + return b.db +} diff --git a/cmd/lotus-shed/pruning.go b/cmd/lotus-shed/pruning.go index 1afe76c4d..188f5b28f 100644 --- a/cmd/lotus-shed/pruning.go +++ b/cmd/lotus-shed/pruning.go @@ -161,7 +161,7 @@ var stateTreePruneCmd = &cli.Command{ if cctx.Bool("only-ds-gc") { fmt.Println("running datastore gc....") for i := 0; i < cctx.Int("gc-count"); i++ { - if err := badgbs.DB.RunValueLogGC(DiscardRatio); err != nil { + if err := badgbs.DB().RunValueLogGC(DiscardRatio); err != nil { return xerrors.Errorf("datastore GC failed: %w", err) } } @@ -208,7 +208,7 @@ var stateTreePruneCmd = &cli.Command{ return nil } - b := badgbs.DB.NewWriteBatch() + b := badgbs.DB().NewWriteBatch() defer b.Cancel() markForRemoval := func(c cid.Cid) error { @@ -249,7 +249,7 @@ var stateTreePruneCmd = &cli.Command{ fmt.Println("running datastore gc....") for i := 0; i < cctx.Int("gc-count"); i++ { - if err := badgbs.DB.RunValueLogGC(DiscardRatio); err != nil { + if err := badgbs.DB().RunValueLogGC(DiscardRatio); err != nil { return xerrors.Errorf("datastore GC failed: %w", err) } } From 4b0b37a4efbe03938cf90194d890877a262a87fe Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 18:03:32 +0300 Subject: [PATCH 36/98] fix lint the great spellchecker strikes again --- blockstore/badger/blockstore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f0f602b20..b78da9965 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -183,7 +183,7 @@ func (b *Blockstore) isOpen() bool { return b.state == stateOpen } -// lockDB/unlockDB implement a recursive lock contigent on move state +// lockDB/unlockDB implement a recursive lock contingent on move state func (b *Blockstore) lockDB() { b.moveMx.Lock() defer b.moveMx.Unlock() From 608a9f84d25a66536c384c91b06c392380851136 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 11 Jul 2021 19:25:48 +0300 Subject: [PATCH 37/98] fix copy: flush discards the transaction --- blockstore/badger/blockstore.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index b78da9965..f1d87f597 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -409,6 +409,8 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro if err := batch.Flush(); err != nil { return err } + // Flush discards the transaction, so we need a new batch + batch = to.NewWriteBatch() count = 0 putPooled() } From f2c7b08be5b2bc7985910d6357ff7cc90bce4630 Mon Sep 17 00:00:00 2001 From: vyzo Date: Mon, 12 Jul 2021 08:25:09 +0300 Subject: [PATCH 38/98] follow symbolic links when deleting old dbs --- blockstore/badger/blockstore.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f1d87f597..4df25a696 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -424,9 +424,34 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro } func (b *Blockstore) deleteDB(path string) { - err := os.RemoveAll(path) - if err != nil { - log.Warnf("error deleting db at %s: %s", path, err) + // follow symbolic links, otherwise the data wil be left behind + lpath := path + for { + fi, err := os.Lstat(lpath) + if err != nil { + log.Warnf("error stating %s: %s", path, err) + return + } + + if fi.Mode()&os.ModeSymlink == 0 { + break + } + + lpath, err := os.Readlink(lpath) + if err != nil { + log.Warnf("error resolving symbolic link %s: %s", lpath, err) + } + } + + if err := os.RemoveAll(lpath); err != nil { + log.Warnf("error deleting db at %s: %s", lpath, err) + return + } + + if path != lpath { + if err := os.Remove(path); err != nil { + log.Warnf("error removing symbolic link %s", err) + } } } From 524564e2cfbad5d5d728721a04ada04ca4c212f6 Mon Sep 17 00:00:00 2001 From: vyzo Date: Mon, 12 Jul 2021 12:06:44 +0300 Subject: [PATCH 39/98] add some more logging around move --- blockstore/badger/blockstore.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 4df25a696..db558d972 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -277,6 +277,8 @@ func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { } }() + log.Infof("moving blockstore from %s to %s", b.opts.Dir, path) + opts := b.opts opts.Dir = path opts.ValueDir = path @@ -290,6 +292,7 @@ func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { b.db2 = db2 b.unlockMove(moveStateMoving) + log.Info("copying blockstore") err = b.doCopy(b.db, b.db2, filter) if err != nil { return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) @@ -327,6 +330,7 @@ func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { b.deleteDB(oldpath) } + log.Info("moving blockstore done") return nil } @@ -429,7 +433,7 @@ func (b *Blockstore) deleteDB(path string) { for { fi, err := os.Lstat(lpath) if err != nil { - log.Warnf("error stating %s: %s", path, err) + log.Warnf("error stating %s: %s", lpath, err) return } @@ -437,18 +441,24 @@ func (b *Blockstore) deleteDB(path string) { break } - lpath, err := os.Readlink(lpath) + log.Infof("resolving symbolic link %s", lpath) + newpath, err := os.Readlink(lpath) if err != nil { log.Warnf("error resolving symbolic link %s: %s", lpath, err) + return } + log.Infof("resolved symbolic link %s -> %s", lpath, newpath) + lpath = newpath } + log.Infof("removing data directory %s", lpath) if err := os.RemoveAll(lpath); err != nil { log.Warnf("error deleting db at %s: %s", lpath, err) return } if path != lpath { + log.Infof("removing link %s", path) if err := os.Remove(path); err != nil { log.Warnf("error removing symbolic link %s", err) } From 5cf6fdf81d2f7592757c9007de10bd497fd52373 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 13 Jul 2021 12:10:32 +0300 Subject: [PATCH 40/98] don't heap allocate the cond, just set L --- blockstore/badger/blockstore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index db558d972..ed0368efc 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -97,7 +97,7 @@ type Blockstore struct { viewers sync.WaitGroup moveMx sync.RWMutex - moveCond *sync.Cond + moveCond sync.Cond moveState int rlock int @@ -136,7 +136,7 @@ func Open(opts Options) (*Blockstore, error) { bs.prefixLen = len(bs.prefix) } - bs.moveCond = sync.NewCond(&bs.moveMx) + bs.moveCond.L = &bs.moveMx return bs, nil } From 94509968a0109606404470ff8c067db90248a6d3 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 22:25:32 +0300 Subject: [PATCH 41/98] make moveTo a private method --- blockstore/badger/blockstore.go | 20 ++++++++++++-------- blockstore/badger/blockstore_test.go | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index ed0368efc..b0da429f0 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -222,13 +222,15 @@ func (b *Blockstore) unlockMove(state int) { b.moveMx.Unlock() } -// MoveTo implements the BlockstoreMover trait -func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { - if err := b.access(); err != nil { - return err - } - defer b.viewers.Done() - +// moveTo moves the blockstore to path, and creates a symlink from the current path +// to the new path; the old blockstore is deleted. +// If path is empty, then a new path adjacent to the current path is created +// automatically. +// The blockstore must accept new writes during the move and ensure that these +// are persisted to the new blockstore; if a failure occurs aboring the move, +// then they must be peristed to the old blockstore. +// In short, the blockstore must not lose data from new writes during the move. +func (b *Blockstore) moveTo(path string) error { // this inlines moveLock/moveUnlock for the initial state check to prevent a second move // while one is in progress without clobbering state b.moveMx.Lock() @@ -293,7 +295,7 @@ func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { b.unlockMove(moveStateMoving) log.Info("copying blockstore") - err = b.doCopy(b.db, b.db2, filter) + err = b.doCopy(b.db, b.db2, nil) if err != nil { return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) } @@ -334,6 +336,8 @@ func (b *Blockstore) MoveTo(path string, filter func(cid.Cid) bool) error { return nil } +// doCopy copies a badger blockstore to another, with an optional filter; if the filter +// is not nil, then only cids that satisfy the filter will be copied. func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) error { count := 0 batch := to.NewWriteBatch() diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index dab1fdc3a..a4cfae652 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -154,7 +154,7 @@ func testMove(t *testing.T, optsF func(string) Options) { return nil }) g.Go(func() error { - return db.MoveTo("", nil) + return db.moveTo("") }) err = g.Wait() From a84366513228ca33507a45c952521ff336a4ddfb Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 22:30:40 +0300 Subject: [PATCH 42/98] add options to BlockstoreGC trait --- blockstore/badger/blockstore.go | 2 +- blockstore/blockstore.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index b0da429f0..dd3a710f4 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -471,7 +471,7 @@ func (b *Blockstore) deleteDB(path string) { // CollectGarbage compacts and runs garbage collection on the value log; // implements the BlockstoreGC trait -func (b *Blockstore) CollectGarbage() error { +func (b *Blockstore) CollectGarbage(options map[interface{}]interface{}) error { if err := b.access(); err != nil { return err } diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go index 43e6cd1a4..a9482b323 100644 --- a/blockstore/blockstore.go +++ b/blockstore/blockstore.go @@ -37,9 +37,21 @@ type BlockstoreIterator interface { // BlockstoreGC is a trait for blockstores that support online garbage collection type BlockstoreGC interface { - CollectGarbage() error + CollectGarbage(options map[interface{}]interface{}) error } +// garbage collection options +type blockstoreMovingGCKey struct{} +type blockstoreMovingGCPath struct{} + +// BlockstoreMovingGC is a garbage collection option that instructs the blockstore +// to use moving GC if supported. +var BlockstoreMovingGC = blockstoreMovingGCKey{} + +// BlockstoreMovingGCPath is a garbage collection option that specifies an optional +// target path for moving GC. +var BlockstoreMovingGCPath = blockstoreMovingGCPath{} + // BlockstoreSize is a trait for on-disk blockstores that can report their size type BlockstoreSize interface { Size() (int64, error) From c747f2f1e229a8b1bcd6cd7c9c5a01cd7b00882d Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 22:44:54 +0300 Subject: [PATCH 43/98] do moving GC if the user asks for it --- blockstore/badger/blockstore.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index dd3a710f4..1979ddebe 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -477,6 +477,32 @@ func (b *Blockstore) CollectGarbage(options map[interface{}]interface{}) error { } defer b.viewers.Done() + var movingGC bool + movingGCOpt, ok := options[blockstore.BlockstoreMovingGC] + if ok { + movingGC, ok = movingGCOpt.(bool) + if !ok { + return fmt.Errorf("incorrect type for moving gc option; expected bool but got %T", movingGCOpt) + } + } + + if !movingGC { + return b.onlineGC() + } + + var movingGCPath string + movingGCPathOpt, ok := options[blockstore.BlockstoreMovingGCPath] + if ok { + movingGCPath, ok = movingGCPathOpt.(string) + if !ok { + return fmt.Errorf("incorrect type for moving gc path option; expected string but got %T", movingGCPathOpt) + } + } + + return b.moveTo(movingGCPath) +} + +func (b *Blockstore) onlineGC() error { b.lockDB() defer b.unlockDB() From fb3986226f9f57b09565acc1e51d2f89e423aae7 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 22:55:03 +0300 Subject: [PATCH 44/98] do hotstore moving GC in splitstore with a user-specified frequency --- blockstore/splitstore/splitstore.go | 7 +++++++ blockstore/splitstore/splitstore_gc.go | 15 +++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/blockstore/splitstore/splitstore.go b/blockstore/splitstore/splitstore.go index 7a2abf9a8..07f211be2 100644 --- a/blockstore/splitstore/splitstore.go +++ b/blockstore/splitstore/splitstore.go @@ -81,6 +81,13 @@ type Config struct { // - a positive integer indicates the number of finalities, outside the compaction boundary, // for which messages will be retained in the hotstore. HotStoreMessageRetention uint64 + + // HotstoreMovingGCFrequency indicates how frequently to garbage collect the hotstore using + // moving GC (if supported by the hotstore). + // A value of 0 disables moving GC entirely. + // A positive value is the number of compactions before a moving GC is performed; + // a value of 1 will perform moving GC in every compaction. + HotStoreMovingGCFrequency uint } // ChainAccessor allows the Splitstore to access the chain. It will most likely diff --git a/blockstore/splitstore/splitstore_gc.go b/blockstore/splitstore/splitstore_gc.go index 46668167c..cd7703963 100644 --- a/blockstore/splitstore/splitstore_gc.go +++ b/blockstore/splitstore/splitstore_gc.go @@ -8,17 +8,24 @@ import ( ) func (s *SplitStore) gcHotstore() { - if err := s.gcBlockstoreOnline(s.hot); err != nil { + var opts map[interface{}]interface{} + if s.cfg.HotStoreMovingGCFrequency > 0 && s.compactionIndex%int64(s.cfg.HotStoreMovingGCFrequency) == 0 { + opts = map[interface{}]interface{}{ + bstore.BlockstoreMovingGC: true, + } + } + + if err := s.gcBlockstore(s.hot, opts); err != nil { log.Warnf("error garbage collecting hostore: %s", err) } } -func (s *SplitStore) gcBlockstoreOnline(b bstore.Blockstore) error { +func (s *SplitStore) gcBlockstore(b bstore.Blockstore, opts map[interface{}]interface{}) error { if gc, ok := b.(bstore.BlockstoreGC); ok { log.Info("garbage collecting blockstore") startGC := time.Now() - if err := gc.CollectGarbage(); err != nil { + if err := gc.CollectGarbage(opts); err != nil { return err } @@ -26,5 +33,5 @@ func (s *SplitStore) gcBlockstoreOnline(b bstore.Blockstore) error { return nil } - return fmt.Errorf("blockstore doesn't support online gc: %T", b) + return fmt.Errorf("blockstore doesn't support garbage collection: %T", b) } From 5acae50e07939e063dfe0d16a388e05818474525 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 22:58:34 +0300 Subject: [PATCH 45/98] add config option for splitstore moving gc frequency --- blockstore/splitstore/splitstore.go | 2 +- node/config/def.go | 2 ++ node/config/types.go | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/blockstore/splitstore/splitstore.go b/blockstore/splitstore/splitstore.go index 07f211be2..d463859b9 100644 --- a/blockstore/splitstore/splitstore.go +++ b/blockstore/splitstore/splitstore.go @@ -87,7 +87,7 @@ type Config struct { // A value of 0 disables moving GC entirely. // A positive value is the number of compactions before a moving GC is performed; // a value of 1 will perform moving GC in every compaction. - HotStoreMovingGCFrequency uint + HotStoreMovingGCFrequency uint64 } // ChainAccessor allows the Splitstore to access the chain. It will most likely diff --git a/node/config/def.go b/node/config/def.go index ef5dcd3ba..554ae66ad 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -72,6 +72,8 @@ func DefaultFullNode() *FullNode { ColdStoreType: "universal", HotStoreType: "badger", MarkSetType: "map", + + HotStoreMovingGCFrequency: 20, }, }, } diff --git a/node/config/types.go b/node/config/types.go index 63a493f51..35c4962c7 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -282,7 +282,8 @@ type Splitstore struct { HotStoreType string MarkSetType string - HotStoreMessageRetention uint64 + HotStoreMessageRetention uint64 + HotStoreMovingGCFrequency uint64 } // // Full Node From b1f60e85e96e5157db7149b6346d23ce76193480 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 23 Jul 2021 23:05:59 +0300 Subject: [PATCH 46/98] document moving GC frequency option in README --- blockstore/splitstore/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/blockstore/splitstore/README.md b/blockstore/splitstore/README.md index b6f30ef43..079b50cc7 100644 --- a/blockstore/splitstore/README.md +++ b/blockstore/splitstore/README.md @@ -59,6 +59,17 @@ These are options in the `[Chainstore.Splitstore]` section of the configuration: nodes beyond 4 finalities, while running with the discard coldstore option. It is also useful for miners who accept deals and need to lookback messages beyond the 4 finalities, which would otherwise hit the coldstore. +- `HotStoreMovingGCFrequency` -- specifies how frequenty to garbage collect the hotstore + using moving GC. + The default value is 20, which uses moving GC every 20 compactions; set to 0 to disable moving + GC altogether. + Rationale: badger supports online GC, and this is used by default. However it has proven to + be ineffective in practice with the hotstore size slowly creeping up. In order to address this, + we have added moving GC support in our badger wrapper, which can effectively reclaim all space. + The downside is that it takes a bit of time to perform a moving GC (about 40min) and you also + need enough space to house the new hotstore while the old one is still live. + This option controls how frequently to perform moving GC, with the default of 20 corresponding + to about once a week. ## Operation From 21e7c188da7ff27e9a0b27855ae09a7ad15e3764 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 24 Jul 2021 08:43:15 +0300 Subject: [PATCH 47/98] use CollectGarbage in blockstore move test, as it is the real interface --- blockstore/badger/blockstore_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index a4cfae652..f40ccbaf8 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -154,7 +154,9 @@ func testMove(t *testing.T, optsF func(string) Options) { return nil }) g.Go(func() error { - return db.moveTo("") + return db.CollectGarbage(map[interface{}]interface{}{ + blockstore.BlockstoreMovingGC: true, + }) }) err = g.Wait() From 4cdb34e44888c2c568250a59decf9c28cbc3bd46 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 24 Jul 2021 08:59:15 +0300 Subject: [PATCH 48/98] add docstrings for splitstore config --- node/config/types.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/node/config/types.go b/node/config/types.go index 35c4962c7..66a179bd5 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -278,11 +278,22 @@ type Chainstore struct { } type Splitstore struct { + // ColdStoreType specifies the type of the coldstore. + // It can be "universal" (default) or "discard" for discarding cold blocks. ColdStoreType string - HotStoreType string - MarkSetType string + // HotStoreType specifies the type of the hotstore. + // Only currently supported value is "badger". + HotStoreType string + // MarkSetType specifies the type of the markset. + // It can be "map" (default) for in memory marking or "badger" for on-disk marking. + MarkSetType string - HotStoreMessageRetention uint64 + // HotStoreMessageRetention specifies the retention policy for messages, in finalities beyond + // the compaction boundary; default is 0. + HotStoreMessageRetention uint64 + // HotStoreMovingGCFrequency specifies how often to perform moving GC on the hotstore. + // A value of 0 disables, while a value 1 will do moving GC in every compaction. + // Default is 20 (about once a week). HotStoreMovingGCFrequency uint64 } From 20f93a520fb848407b0a93b2d26b228ac5aadc58 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 24 Jul 2021 09:00:00 +0300 Subject: [PATCH 49/98] make cfgdoc-gen --- node/config/doc_gen.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index ea68dc344..7ebecc5bc 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -685,25 +685,37 @@ submitting proofs to the chain individually`, Name: "ColdStoreType", Type: "string", - Comment: ``, + Comment: `ColdStoreType specifies the type of the coldstore. +It can be "universal" (default) or "discard" for discarding cold blocks.`, }, { Name: "HotStoreType", Type: "string", - Comment: ``, + Comment: `HotStoreType specifies the type of the hotstore. +Only currently supported value is "badger".`, }, { Name: "MarkSetType", Type: "string", - Comment: ``, + Comment: `MarkSetType specifies the type of the markset. +It can be "map" (default) for in memory marking or "badger" for on-disk marking.`, }, { Name: "HotStoreMessageRetention", Type: "uint64", - Comment: ``, + Comment: `HotStoreMessageRetention specifies the retention policy for messages, in finalities beyond +the compaction boundary; default is 0.`, + }, + { + Name: "HotStoreMovingGCFrequency", + Type: "uint64", + + Comment: `HotStoreMovingGCFrequency specifies how often to perform moving GC on the hotstore. +A value of 0 disables, while a value 1 will do moving GC in every compaction. +Default is 20 (about once a week).`, }, }, "StorageMiner": []DocField{ From aa0bd51b2c39eec1d9cea05eabbe584115b03ee4 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 24 Jul 2021 11:17:47 +0300 Subject: [PATCH 50/98] thread GCFrequency option into the splitstore config --- node/modules/blockstore.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/node/modules/blockstore.go b/node/modules/blockstore.go index 2588e3f98..32e67cf62 100644 --- a/node/modules/blockstore.go +++ b/node/modules/blockstore.go @@ -78,9 +78,10 @@ func SplitBlockstore(cfg *config.Chainstore) func(lc fx.Lifecycle, r repo.Locked } cfg := &splitstore.Config{ - MarkSetType: cfg.Splitstore.MarkSetType, - DiscardColdBlocks: cfg.Splitstore.ColdStoreType == "discard", - HotStoreMessageRetention: cfg.Splitstore.HotStoreMessageRetention, + MarkSetType: cfg.Splitstore.MarkSetType, + DiscardColdBlocks: cfg.Splitstore.ColdStoreType == "discard", + HotStoreMessageRetention: cfg.Splitstore.HotStoreMessageRetention, + HotStoreMovingGCFrequency: cfg.Splitstore.HotStoreMovingGCFrequency, } ss, err := splitstore.Open(path, ds, hot, cold, cfg) if err != nil { From 938330e6c3cc490ee2c4f06ab8293e3b91d78ef3 Mon Sep 17 00:00:00 2001 From: vyzo Date: Mon, 26 Jul 2021 15:09:31 +0300 Subject: [PATCH 51/98] moveMx is not an RWMutex, just a regular mutex --- blockstore/badger/blockstore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 1979ddebe..50a092d12 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -96,7 +96,7 @@ type Blockstore struct { state int viewers sync.WaitGroup - moveMx sync.RWMutex + moveMx sync.Mutex moveCond sync.Cond moveState int rlock int From 9d61f91cdf8a431f167ddbfffeee22c6ebf9cb4e Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Mon, 26 Jul 2021 16:41:22 +0200 Subject: [PATCH 52/98] Update cmd/lotus-shed/market.go Co-authored-by: Aayush Rajasekaran --- cmd/lotus-shed/market.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-shed/market.go b/cmd/lotus-shed/market.go index eb1b56a94..8221e53eb 100644 --- a/cmd/lotus-shed/market.go +++ b/cmd/lotus-shed/market.go @@ -244,7 +244,7 @@ var marketImportDatastoreCmd = &cli.Command{ }, &cli.StringFlag{ Name: "backup-path", - Usage: "path to the backup directory", + Usage: "path to the backup file", Required: true, }, }, From 0dd325866d435d208f3373810f60a801ce65ede2 Mon Sep 17 00:00:00 2001 From: Jiaying Wang <42981373+jennijuju@users.noreply.github.com> Date: Mon, 26 Jul 2021 23:15:09 -0400 Subject: [PATCH 53/98] Update RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 8adab9671..8eec88f88 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -64,23 +64,19 @@ Testing an RC: - [ ] **Stage 2 - Community Testing** - [ ] Inform beta miners (@lotus-early-testers-miner in Filecoin Slack #fil-lotus) - - [ ] Ask close ecosystem partners to test their projects (@lotus-early-testers-eco-dev in Filecoin slack #fil-lotus) + - [ ] Inform close ecosystem partners to test their projects (@lotus-early-testers-eco-dev in Filecoin slack #fil-lotus-dev) - [ ] Powergate - [ ] Glif - - [ ] Zondax - [ ] Stats dashboard - - [ ] Community dashboards - - [ ] Infura - [ ] Sentinel - [ ] Protofire - - [ ] Fleek - [ ] **Stage 3 - Community Prod Testing** - [ ] Documentation - [ ] Ensure that [CHANGELOG.md](https://github.com/filecoin-project/lotus/blob/master/CHANGELOG.md) is up to date - [ ] Check if any [config](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#configuration) updates are needed - [ ] Invite the wider community through (link to the release issue): - - [ ] Check `Create a discussion for this release` when tagging for the major rcs(new features, hot-fixes) release + - [ ] Check `Create a discussion for this release` when tagging for the major/close-to-final rcs(new features, hot-fixes) release - [ ] Link the disucssion in #fil-lotus on Filecoin slack - [ ] **Stage 4 - Release** @@ -91,11 +87,10 @@ Testing an RC: - [ ] Merge `release-vX.Y.Z` into the `releases` branch. - [ ] Tag this merge commit (on the `releases` branch) with `vX.Y.Z` - [ ] Cut the release [here](https://github.com/filecoin-project/lotus/releases/new?prerelease=true&target=releases). - - [ ] Check `Create a discussion for this release` when tagging the release - [ ] Final announcements - [ ] Update network.filecoin.io for mainnet, calib and nerpa. - - [ ] repost in #fil-lotus in filecoin slack - - [ ] Inform node provides (Protofire, Digital Ocean..) + - [ ] repost in #fil-lotus-announcement in filecoin slack + - [ ] Inform node providers (Protofire, Digital Ocean..) - [ ] **Post-Release** - [ ] Merge the `releases` branch back into `master`, ignoring the changes to `version.go` (keep the `-dev` version from master). Do NOT delete the `releases` branch when doing so! @@ -104,11 +99,7 @@ Testing an RC: ## ❤️ Contributors -< list generated by scripts/mkreleaselog > - -Would you like to contribute to Lotus and don't know how? Well, there are a few places you can get started: - -- TODO +See the final release notes! ## ⁉️ Do you have questions? From 96c1123c33756618e419d1255cc1bcddcfc794be Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 09:50:44 +0300 Subject: [PATCH 54/98] use functional options in the BlockstoreGC interface --- blockstore/badger/blockstore.go | 71 ++++++++++++---------------- blockstore/badger/blockstore_test.go | 4 +- blockstore/blockstore.go | 23 +++++---- 3 files changed, 43 insertions(+), 55 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 50a092d12..1ef622f39 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -222,15 +222,14 @@ func (b *Blockstore) unlockMove(state int) { b.moveMx.Unlock() } -// moveTo moves the blockstore to path, and creates a symlink from the current path -// to the new path; the old blockstore is deleted. -// If path is empty, then a new path adjacent to the current path is created -// automatically. -// The blockstore must accept new writes during the move and ensure that these +// movingGC moves the blockstore to a new path, adjacent to the current path, and creates +// a symlink from the current path to the new path; the old blockstore is deleted. +// +// The blockstore MUST accept new writes during the move and ensure that these // are persisted to the new blockstore; if a failure occurs aboring the move, // then they must be peristed to the old blockstore. // In short, the blockstore must not lose data from new writes during the move. -func (b *Blockstore) moveTo(path string) error { +func (b *Blockstore) movingGC() error { // this inlines moveLock/moveUnlock for the initial state check to prevent a second move // while one is in progress without clobbering state b.moveMx.Lock() @@ -248,9 +247,7 @@ func (b *Blockstore) moveTo(path string) error { b.moveCond.Broadcast() b.moveMx.Unlock() - if path == "" { - path = fmt.Sprintf("%s.%d", b.opts.Dir, time.Now().Unix()) - } + path := fmt.Sprintf("%s.%d", b.opts.Dir, time.Now().Unix()) defer func() { b.lockMove() @@ -469,39 +466,6 @@ func (b *Blockstore) deleteDB(path string) { } } -// CollectGarbage compacts and runs garbage collection on the value log; -// implements the BlockstoreGC trait -func (b *Blockstore) CollectGarbage(options map[interface{}]interface{}) error { - if err := b.access(); err != nil { - return err - } - defer b.viewers.Done() - - var movingGC bool - movingGCOpt, ok := options[blockstore.BlockstoreMovingGC] - if ok { - movingGC, ok = movingGCOpt.(bool) - if !ok { - return fmt.Errorf("incorrect type for moving gc option; expected bool but got %T", movingGCOpt) - } - } - - if !movingGC { - return b.onlineGC() - } - - var movingGCPath string - movingGCPathOpt, ok := options[blockstore.BlockstoreMovingGCPath] - if ok { - movingGCPath, ok = movingGCPathOpt.(string) - if !ok { - return fmt.Errorf("incorrect type for moving gc path option; expected string but got %T", movingGCPathOpt) - } - } - - return b.moveTo(movingGCPath) -} - func (b *Blockstore) onlineGC() error { b.lockDB() defer b.unlockDB() @@ -529,6 +493,29 @@ func (b *Blockstore) onlineGC() error { return err } +// CollectGarbage compacts and runs garbage collection on the value log; +// implements the BlockstoreGC trait +func (b *Blockstore) CollectGarbage(opts ...blockstore.BlockstoreGCOption) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + var options blockstore.BlockstoreGCOptions + for _, opt := range opts { + err := opt(&options) + if err != nil { + return err + } + } + + if options.FullGC { + return b.movingGC() + } + + return b.onlineGC() +} + // Size returns the aggregate size of the blockstore func (b *Blockstore) Size() (int64, error) { if err := b.access(); err != nil { diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index f40ccbaf8..d1312eccc 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -154,9 +154,7 @@ func testMove(t *testing.T, optsF func(string) Options) { return nil }) g.Go(func() error { - return db.CollectGarbage(map[interface{}]interface{}{ - blockstore.BlockstoreMovingGC: true, - }) + return db.CollectGarbage(blockstore.WithFullGC(true)) }) err = g.Wait() diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go index a9482b323..8ede31eb9 100644 --- a/blockstore/blockstore.go +++ b/blockstore/blockstore.go @@ -37,20 +37,23 @@ type BlockstoreIterator interface { // BlockstoreGC is a trait for blockstores that support online garbage collection type BlockstoreGC interface { - CollectGarbage(options map[interface{}]interface{}) error + CollectGarbage(options ...BlockstoreGCOption) error } -// garbage collection options -type blockstoreMovingGCKey struct{} -type blockstoreMovingGCPath struct{} +// BlockstoreGCOption is a functional interface for controlling blockstore GC options +type BlockstoreGCOption = func(*BlockstoreGCOptions) error -// BlockstoreMovingGC is a garbage collection option that instructs the blockstore -// to use moving GC if supported. -var BlockstoreMovingGC = blockstoreMovingGCKey{} +// BlockstoreGCOptions is a struct with GC options +type BlockstoreGCOptions struct { + FullGC bool +} -// BlockstoreMovingGCPath is a garbage collection option that specifies an optional -// target path for moving GC. -var BlockstoreMovingGCPath = blockstoreMovingGCPath{} +func WithFullGC(fullgc bool) BlockstoreGCOption { + return func(opts *BlockstoreGCOptions) error { + opts.FullGC = fullgc + return nil + } +} // BlockstoreSize is a trait for on-disk blockstores that can report their size type BlockstoreSize interface { From 9d254647039cf562db5d5c4a6041bb967e6cf735 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 09:53:22 +0300 Subject: [PATCH 55/98] use functional options for hotstore gc, rename MovingGC to FullGC --- blockstore/splitstore/splitstore.go | 12 ++++++------ blockstore/splitstore/splitstore_gc.go | 12 +++++------- node/config/def.go | 2 +- node/config/types.go | 6 +++--- node/modules/blockstore.go | 8 ++++---- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/blockstore/splitstore/splitstore.go b/blockstore/splitstore/splitstore.go index d463859b9..171b5a6e4 100644 --- a/blockstore/splitstore/splitstore.go +++ b/blockstore/splitstore/splitstore.go @@ -82,12 +82,12 @@ type Config struct { // for which messages will be retained in the hotstore. HotStoreMessageRetention uint64 - // HotstoreMovingGCFrequency indicates how frequently to garbage collect the hotstore using - // moving GC (if supported by the hotstore). - // A value of 0 disables moving GC entirely. - // A positive value is the number of compactions before a moving GC is performed; - // a value of 1 will perform moving GC in every compaction. - HotStoreMovingGCFrequency uint64 + // HotstoreFullGCFrequency indicates how frequently (in terms of compactions) to garbage collect + // the hotstore using full (moving) GC if supported by the hotstore. + // A value of 0 disables full GC entirely. + // A positive value is the number of compactions before a full GC is performed; + // a value of 1 will perform full GC in every compaction. + HotStoreFullGCFrequency uint64 } // ChainAccessor allows the Splitstore to access the chain. It will most likely diff --git a/blockstore/splitstore/splitstore_gc.go b/blockstore/splitstore/splitstore_gc.go index cd7703963..2e1ffd4ad 100644 --- a/blockstore/splitstore/splitstore_gc.go +++ b/blockstore/splitstore/splitstore_gc.go @@ -8,11 +8,9 @@ import ( ) func (s *SplitStore) gcHotstore() { - var opts map[interface{}]interface{} - if s.cfg.HotStoreMovingGCFrequency > 0 && s.compactionIndex%int64(s.cfg.HotStoreMovingGCFrequency) == 0 { - opts = map[interface{}]interface{}{ - bstore.BlockstoreMovingGC: true, - } + var opts []bstore.BlockstoreGCOption + if s.cfg.HotStoreFullGCFrequency > 0 && s.compactionIndex%int64(s.cfg.HotStoreFullGCFrequency) == 0 { + opts = append(opts, bstore.WithFullGC(true)) } if err := s.gcBlockstore(s.hot, opts); err != nil { @@ -20,12 +18,12 @@ func (s *SplitStore) gcHotstore() { } } -func (s *SplitStore) gcBlockstore(b bstore.Blockstore, opts map[interface{}]interface{}) error { +func (s *SplitStore) gcBlockstore(b bstore.Blockstore, opts []bstore.BlockstoreGCOption) error { if gc, ok := b.(bstore.BlockstoreGC); ok { log.Info("garbage collecting blockstore") startGC := time.Now() - if err := gc.CollectGarbage(opts); err != nil { + if err := gc.CollectGarbage(opts...); err != nil { return err } diff --git a/node/config/def.go b/node/config/def.go index 554ae66ad..c5c455c68 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -73,7 +73,7 @@ func DefaultFullNode() *FullNode { HotStoreType: "badger", MarkSetType: "map", - HotStoreMovingGCFrequency: 20, + HotStoreFullGCFrequency: 20, }, }, } diff --git a/node/config/types.go b/node/config/types.go index 66a179bd5..fe42aa27e 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -291,10 +291,10 @@ type Splitstore struct { // HotStoreMessageRetention specifies the retention policy for messages, in finalities beyond // the compaction boundary; default is 0. HotStoreMessageRetention uint64 - // HotStoreMovingGCFrequency specifies how often to perform moving GC on the hotstore. - // A value of 0 disables, while a value 1 will do moving GC in every compaction. + // HotStoreFullGCFrequency specifies how often to perform a full (moving) GC on the hotstore. + // A value of 0 disables, while a value 1 will do full GC in every compaction. // Default is 20 (about once a week). - HotStoreMovingGCFrequency uint64 + HotStoreFullGCFrequency uint64 } // // Full Node diff --git a/node/modules/blockstore.go b/node/modules/blockstore.go index 32e67cf62..2486b9744 100644 --- a/node/modules/blockstore.go +++ b/node/modules/blockstore.go @@ -78,10 +78,10 @@ func SplitBlockstore(cfg *config.Chainstore) func(lc fx.Lifecycle, r repo.Locked } cfg := &splitstore.Config{ - MarkSetType: cfg.Splitstore.MarkSetType, - DiscardColdBlocks: cfg.Splitstore.ColdStoreType == "discard", - HotStoreMessageRetention: cfg.Splitstore.HotStoreMessageRetention, - HotStoreMovingGCFrequency: cfg.Splitstore.HotStoreMovingGCFrequency, + MarkSetType: cfg.Splitstore.MarkSetType, + DiscardColdBlocks: cfg.Splitstore.ColdStoreType == "discard", + HotStoreMessageRetention: cfg.Splitstore.HotStoreMessageRetention, + HotStoreFullGCFrequency: cfg.Splitstore.HotStoreFullGCFrequency, } ss, err := splitstore.Open(path, ds, hot, cold, cfg) if err != nil { From c03859c1b53645257028fa71340656c9bf3dbb8b Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 10:05:35 +0300 Subject: [PATCH 56/98] resolve symlinks when constructing the new db path so that the new path is adjacent to the old path, allowing the user to symlink the db in a different file system. --- blockstore/badger/blockstore.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 1ef622f39..83c5012c1 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -247,7 +247,7 @@ func (b *Blockstore) movingGC() error { b.moveCond.Broadcast() b.moveMx.Unlock() - path := fmt.Sprintf("%s.%d", b.opts.Dir, time.Now().Unix()) + var path string defer func() { b.lockMove() @@ -276,6 +276,23 @@ func (b *Blockstore) movingGC() error { } }() + // we resolve symlinks to create the new path in the adjacent to the old path. + // this allows the user to symlink the db directory into a separate filesystem. + basePath := b.opts.Dir + linkPath, err := filepath.EvalSymlinks(basePath) + if err != nil { + return fmt.Errorf("error resolving symlink %s: %w", basePath, err) + } + + if basePath == linkPath { + path = basePath + } else { + name := filepath.Base(basePath) + dir := filepath.Dir(linkPath) + path = filepath.Join(dir, name) + } + path = fmt.Sprintf("%s.%d", path, time.Now().Unix()) + log.Infof("moving blockstore from %s to %s", b.opts.Dir, path) opts := b.opts From cbaffab9dd3029bfd18bc22a8632075949240dc2 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 10:08:07 +0300 Subject: [PATCH 57/98] use EvalSymlinks in deleteDB --- blockstore/badger/blockstore.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 83c5012c1..6bab69915 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -447,26 +447,10 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro func (b *Blockstore) deleteDB(path string) { // follow symbolic links, otherwise the data wil be left behind - lpath := path - for { - fi, err := os.Lstat(lpath) - if err != nil { - log.Warnf("error stating %s: %s", lpath, err) - return - } - - if fi.Mode()&os.ModeSymlink == 0 { - break - } - - log.Infof("resolving symbolic link %s", lpath) - newpath, err := os.Readlink(lpath) - if err != nil { - log.Warnf("error resolving symbolic link %s: %s", lpath, err) - return - } - log.Infof("resolved symbolic link %s -> %s", lpath, newpath) - lpath = newpath + lpath, err := filepath.EvalSymlinks(path) + if err != nil { + log.Warnf("error resolving symlinks in %s", path) + return } log.Infof("removing data directory %s", lpath) From d6ace68540826625888cb36a201d8bb440826509 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 10:16:50 +0300 Subject: [PATCH 58/98] extend test to do a double move and check symlink following --- blockstore/badger/blockstore.go | 2 +- blockstore/badger/blockstore_test.go | 124 +++++++++++++++------------ 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 6bab69915..8730c8134 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -291,7 +291,7 @@ func (b *Blockstore) movingGC() error { dir := filepath.Dir(linkPath) path = filepath.Join(dir, name) } - path = fmt.Sprintf("%s.%d", path, time.Now().Unix()) + path = fmt.Sprintf("%s.%d", path, time.Now().UnixNano()) log.Infof("moving blockstore from %s to %s", b.opts.Dir, path) diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index d1312eccc..ddfa6f28d 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -163,72 +163,88 @@ func testMove(t *testing.T, optsF func(string) Options) { } // now check that we have all the blocks in have and none in the deleted lists - for _, blk := range have { - has, err := db.Has(blk.Cid()) - if err != nil { - t.Fatal(err) + checkBlocks := func() { + for _, blk := range have { + has, err := db.Has(blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("missing block") + } + + blk2, err := db.Get(blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(blk.RawData(), blk2.RawData()) { + t.Fatal("data mismatch") + } } - if !has { - t.Fatal("missing block") - } + for _, c := range deleted { + has, err := db.Has(c) + if err != nil { + t.Fatal(err) + } - blk2, err := db.Get(blk.Cid()) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(blk.RawData(), blk2.RawData()) { - t.Fatal("data mismatch") + if has { + t.Fatal("resurrected block") + } } } - for _, c := range deleted { - has, err := db.Has(c) - if err != nil { - t.Fatal(err) - } - - if has { - t.Fatal("resurrected block") - } - } + checkBlocks() // check the basePath -- it should contain a directory with name db.{timestamp}, soft-linked // to db and nothing else - entries, err := os.ReadDir(basePath) - if err != nil { + checkPath := func() { + entries, err := os.ReadDir(basePath) + if err != nil { + t.Fatal(err) + } + + if len(entries) != 2 { + t.Fatalf("too many entries; expected %d but got %d", 2, len(entries)) + } + + var haveDB, haveDBLink bool + for _, e := range entries { + if e.Name() == "db" { + if (e.Type() & os.ModeSymlink) == 0 { + t.Fatal("found db, but it's not a symlink") + } + haveDBLink = true + continue + } + if strings.HasPrefix(e.Name(), "db.") { + if !e.Type().IsDir() { + t.Fatal("found db prefix, but it's not a directory") + } + haveDB = true + continue + } + } + + if !haveDB { + t.Fatal("db directory is missing") + } + if !haveDBLink { + t.Fatal("db link is missing") + } + } + + checkPath() + + // now do another FullGC to test the double move and following of symlinks + if err := db.CollectGarbage(blockstore.WithFullGC(true)); err != nil { t.Fatal(err) } - if len(entries) != 2 { - t.Fatalf("too many entries; expected %d but got %d", 2, len(entries)) - } - - var haveDB, haveDBLink bool - for _, e := range entries { - if e.Name() == "db" { - if (e.Type() & os.ModeSymlink) == 0 { - t.Fatal("found db, but it's not a symlink") - } - haveDBLink = true - continue - } - if strings.HasPrefix(e.Name(), "db.") { - if !e.Type().IsDir() { - t.Fatal("found db prefix, but it's not a directory") - } - haveDB = true - continue - } - } - - if !haveDB { - t.Fatal("db directory is missing") - } - if !haveDBLink { - t.Fatal("db link is missing") - } + checkBlocks() + checkPath() } func TestMoveNoPrefix(t *testing.T) { From 649fc6286387bf11c016297101f93f4b595bdd0d Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 10:27:16 +0300 Subject: [PATCH 59/98] panic if we fail to correctly setup the db paths. we can't really continue and leave a ticking bomb for the next restart; the user might not see it. --- blockstore/badger/blockstore.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 8730c8134..f13bac746 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -322,30 +322,27 @@ func (b *Blockstore) movingGC() error { err = db1.Close() if err != nil { - log.Warnf("error closing badger db: %s", err) + log.Warnf("error closing old badger db: %s", err) } dbpath := b.opts.Dir oldpath := fmt.Sprintf("%s.old.%d", dbpath, time.Now().Unix()) - ok := true - err = os.Rename(dbpath, oldpath) - if err != nil { - // this is bad, but not catastrophic; new data will be written in db2 and user can fix - log.Errorf("error renaming badger db dir from %s to %s; USER ACTION REQUIRED", dbpath, oldpath) - ok = false + if err = os.Rename(dbpath, oldpath); err != nil { + // this is not catastrophic in the sense that we have not lost any data. + // but it is pretty bad, as the db path points to the old db, while we are now using to the new + // db; we can't continue and leave a ticking bomb for the next restart. + // so a panic is appropriate and user can fix. + panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbpath, oldpath, err)) //nolint } - if ok { - err = os.Symlink(path, dbpath) - if err != nil { - // ditto, this is bad, but not catastrophic; user can fix - log.Errorf("error symlinking badger db dir from %s to %s; USER ACTION REQUIRED", path, dbpath) - } - - b.deleteDB(oldpath) + if err = os.Symlink(path, dbpath); err != nil { + // same here; the db path is pointing to the void. panic and let the user fix. + panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", path, dbpath, err)) //nolint } + b.deleteDB(oldpath) + log.Info("moving blockstore done") return nil } From 0baeec068618312b7de3990f47070018908c606f Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 11:18:24 +0300 Subject: [PATCH 60/98] remove filter from doCopy; it's not used it was there to support a potential CopyTo interface; but we'll cross that bridge when we get there. --- blockstore/badger/blockstore.go | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f13bac746..f06f17c58 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -309,7 +309,7 @@ func (b *Blockstore) movingGC() error { b.unlockMove(moveStateMoving) log.Info("copying blockstore") - err = b.doCopy(b.db, b.db2, nil) + err = b.doCopy(b.db, b.db2) if err != nil { return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) } @@ -349,7 +349,7 @@ func (b *Blockstore) movingGC() error { // doCopy copies a badger blockstore to another, with an optional filter; if the filter // is not nil, then only cids that satisfy the filter will be copied. -func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) error { +func (b *Blockstore) doCopy(from, to *badger.DB) error { count := 0 batch := to.NewWriteBatch() defer batch.Cancel() @@ -375,7 +375,6 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro } defer putPooled() - var buf []byte for iter.Rewind(); iter.Valid(); iter.Next() { if !b.isOpen() { return ErrBlockstoreClosed @@ -384,28 +383,6 @@ func (b *Blockstore) doCopy(from, to *badger.DB, filter func(cid.Cid) bool) erro item := iter.Item() kk := item.Key() - if filter != nil { - k := kk - if b.prefixing { - k = k[b.prefixLen:] - } - - klen := base32.RawStdEncoding.DecodedLen(len(k)) - if klen > len(buf) { - buf = make([]byte, klen) - } - - n, err := base32.RawStdEncoding.Decode(buf, k) - if err != nil { - return err - } - - c := cid.NewCidV1(cid.Raw, buf[:n]) - if !filter(c) { - continue - } - } - k := getPooled(len(kk)) copy(k, kk) From ac8937245c41259af1caddc21aebcd08bbcca557 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 27 Jul 2021 14:51:45 +0800 Subject: [PATCH 61/98] fix ticket check --- extern/storage-sealing/checks.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extern/storage-sealing/checks.go b/extern/storage-sealing/checks.go index 5ba23026d..115eedea5 100644 --- a/extern/storage-sealing/checks.go +++ b/extern/storage-sealing/checks.go @@ -93,27 +93,29 @@ func checkPrecommit(ctx context.Context, maddr address.Address, si SectorInfo, t return &ErrBadCommD{xerrors.Errorf("on chain CommD differs from sector: %s != %s", commD, si.CommD)} } - ticketEarliest := height - policy.MaxPreCommitRandomnessLookback - - if si.TicketEpoch < ticketEarliest { - return &ErrExpiredTicket{xerrors.Errorf("ticket expired: seal height: %d, head: %d", si.TicketEpoch+policy.SealRandomnessLookback, height)} - } - pci, err := api.StateSectorPreCommitInfo(ctx, maddr, si.SectorNumber, tok) if err != nil { if err == ErrSectorAllocated { + //committed P2 message but commit C2 message too late, pci should be null in this case return &ErrSectorNumberAllocated{err} } return &ErrApi{xerrors.Errorf("getting precommit info: %w", err)} } if pci != nil { + // committed P2 message if pci.Info.SealRandEpoch != si.TicketEpoch { return &ErrBadTicket{xerrors.Errorf("bad ticket epoch: %d != %d", pci.Info.SealRandEpoch, si.TicketEpoch)} } return &ErrPrecommitOnChain{xerrors.Errorf("precommit already on chain")} } + //never commit P2 message before, check ticket expiration + ticketEarliest := height - policy.MaxPreCommitRandomnessLookback + + if si.TicketEpoch < ticketEarliest { + return &ErrExpiredTicket{xerrors.Errorf("ticket expired: seal height: %d, head: %d", si.TicketEpoch+policy.SealRandomnessLookback, height)} + } return nil } From 59aebba0d9234661d6a1bd03267e494f54278f1a Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 11:35:21 +0300 Subject: [PATCH 62/98] use a slab allocator for the copy --- blockstore/badger/blockstore.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f06f17c58..5a451169e 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -361,19 +361,31 @@ func (b *Blockstore) doCopy(from, to *badger.DB) error { iter := txn.NewIterator(opts) defer iter.Close() - pooled := make([][]byte, 0, 2*moveBatchSize) - getPooled := func(size int) []byte { - buf := pool.Get(size) - pooled = append(pooled, buf) + // allocate a slab to improve performance; buffers larger than 1K will be allocated from the pool + var pooled [][]byte + const maxSlabSize = 1024 + slab := make([]byte, moveBatchSize*maxSlabSize) + slabStart := 0 + getSlab := func(size int) []byte { + if size > maxSlabSize { + buf := pool.Get(size) + pooled = append(pooled, buf) + return buf + } + + slabEnd := slabStart + size + buf := slab[slabStart:slabEnd] + slabStart = slabEnd return buf } - putPooled := func() { + resetSlab := func() { + slabStart = 0 for _, buf := range pooled { pool.Put(buf) } pooled = pooled[:0] } - defer putPooled() + defer resetSlab() for iter.Rewind(); iter.Valid(); iter.Next() { if !b.isOpen() { @@ -383,12 +395,12 @@ func (b *Blockstore) doCopy(from, to *badger.DB) error { item := iter.Item() kk := item.Key() - k := getPooled(len(kk)) + k := getSlab(len(kk)) copy(k, kk) var v []byte err := item.Value(func(vv []byte) error { - v = getPooled(len(vv)) + v = getSlab(len(vv)) copy(v, vv) return nil }) @@ -408,7 +420,7 @@ func (b *Blockstore) doCopy(from, to *badger.DB) error { // Flush discards the transaction, so we need a new batch batch = to.NewWriteBatch() count = 0 - putPooled() + resetSlab() } } From b82f953fd58e6c2d3931ce8a81606380edccfee2 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 11:46:35 +0300 Subject: [PATCH 63/98] use the badger streaming interface in doCopy --- blockstore/badger/blockstore.go | 87 ++++++--------------------------- 1 file changed, 16 insertions(+), 71 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 5a451169e..e8b5247ce 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -12,6 +12,7 @@ import ( "github.com/dgraph-io/badger/v2" "github.com/dgraph-io/badger/v2/options" + "github.com/dgraph-io/badger/v2/pb" "github.com/multiformats/go-base32" "go.uber.org/zap" @@ -36,8 +37,6 @@ var ( log = logger.Logger("badgerbs") ) -const moveBatchSize = 16384 - // aliases to mask badger dependencies. const ( // FileIO is equivalent to badger/options.FileIO. @@ -350,85 +349,31 @@ func (b *Blockstore) movingGC() error { // doCopy copies a badger blockstore to another, with an optional filter; if the filter // is not nil, then only cids that satisfy the filter will be copied. func (b *Blockstore) doCopy(from, to *badger.DB) error { - count := 0 - batch := to.NewWriteBatch() - defer batch.Cancel() - - txn := from.NewTransaction(false) - defer txn.Discard() - - opts := badger.IteratorOptions{PrefetchSize: moveBatchSize} - iter := txn.NewIterator(opts) - defer iter.Close() - - // allocate a slab to improve performance; buffers larger than 1K will be allocated from the pool - var pooled [][]byte - const maxSlabSize = 1024 - slab := make([]byte, moveBatchSize*maxSlabSize) - slabStart := 0 - getSlab := func(size int) []byte { - if size > maxSlabSize { - buf := pool.Get(size) - pooled = append(pooled, buf) - return buf - } - - slabEnd := slabStart + size - buf := slab[slabStart:slabEnd] - slabStart = slabEnd - return buf + workers := runtime.NumCPU() / 2 + if workers < 2 { + workers = 2 } - resetSlab := func() { - slabStart = 0 - for _, buf := range pooled { - pool.Put(buf) - } - pooled = pooled[:0] - } - defer resetSlab() - for iter.Rewind(); iter.Valid(); iter.Next() { - if !b.isOpen() { - return ErrBlockstoreClosed - } + stream := from.NewStream() + stream.NumGo = workers + stream.LogPrefix = "doCopy" + stream.Send = func(list *pb.KVList) error { + batch := to.NewWriteBatch() + defer batch.Cancel() - item := iter.Item() - - kk := item.Key() - k := getSlab(len(kk)) - copy(k, kk) - - var v []byte - err := item.Value(func(vv []byte) error { - v = getSlab(len(vv)) - copy(v, vv) - return nil - }) - if err != nil { - return err - } - - if err := batch.Set(k, v); err != nil { - return err - } - - count++ - if count == moveBatchSize { - if err := batch.Flush(); err != nil { + for _, kv := range list.Kv { + if kv.Key == nil || kv.Value == nil { + continue + } + if err := batch.Set(kv.Key, kv.Value); err != nil { return err } - // Flush discards the transaction, so we need a new batch - batch = to.NewWriteBatch() - count = 0 - resetSlab() } - } - if count > 0 { return batch.Flush() } - return nil + return stream.Orchestrate(context.Background()) } func (b *Blockstore) deleteDB(path string) { From bb2d99908c0d2e3298d7a99eb7c4ad03065471c0 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 12:03:26 +0300 Subject: [PATCH 64/98] make state constants typed --- blockstore/badger/blockstore.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index e8b5247ce..f577d81f8 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -76,28 +76,40 @@ func (b *badgerLogger) Warningf(format string, args ...interface{}) { b.skip2.Warnf(format, args...) } +// bsState is the current blockstore state +type bsState int + const ( - stateOpen = iota + // stateOpen signifies an open blockstore + stateOpen bsState = iota + // stateClosing signifies a blockstore that is currently closing stateClosing + // stateClosed signifies a blockstore that has been colosed stateClosed ) +type bsMoveState int + const ( - moveStateNone = iota + // moveStateNone signifies that there is no move in progress + moveStateNone bsMoveState = iota + // moveStateMoving signifies that there is a move in a progress moveStateMoving + // moveStateCleanup signifies that a move has completed or aborted and we are cleaning up moveStateCleanup + // moveStateLock signifies that an exclusive lock has been acquired moveStateLock ) // Blockstore is a badger-backed IPLD blockstore. type Blockstore struct { stateLk sync.RWMutex - state int + state bsState viewers sync.WaitGroup moveMx sync.Mutex moveCond sync.Cond - moveState int + moveState bsMoveState rlock int db *badger.DB @@ -215,7 +227,7 @@ func (b *Blockstore) lockMove() { } } -func (b *Blockstore) unlockMove(state int) { +func (b *Blockstore) unlockMove(state bsMoveState) { b.moveState = state b.moveCond.Broadcast() b.moveMx.Unlock() @@ -254,7 +266,7 @@ func (b *Blockstore) movingGC() error { db2 := b.db2 b.db2 = nil - var state int + var state bsMoveState if db2 != nil { state = moveStateCleanup } else { From c21c4136954733af5ddb6487551652b65751f2ae Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 12:06:40 +0300 Subject: [PATCH 65/98] remove db2 to dbNext --- blockstore/badger/blockstore.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index f577d81f8..8e1a3a1ff 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -112,9 +112,9 @@ type Blockstore struct { moveState bsMoveState rlock int - db *badger.DB - db2 *badger.DB // when moving - opts Options + db *badger.DB + dbNext *badger.DB // when moving + opts Options prefixing bool prefix []byte @@ -263,8 +263,8 @@ func (b *Blockstore) movingGC() error { defer func() { b.lockMove() - db2 := b.db2 - b.db2 = nil + db2 := b.dbNext + b.dbNext = nil var state bsMoveState if db2 != nil { @@ -316,19 +316,19 @@ func (b *Blockstore) movingGC() error { } b.lockMove() - b.db2 = db2 + b.dbNext = db2 b.unlockMove(moveStateMoving) log.Info("copying blockstore") - err = b.doCopy(b.db, b.db2) + err = b.doCopy(b.db, b.dbNext) if err != nil { return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) } b.lockMove() db1 := b.db - b.db = b.db2 - b.db2 = nil + b.db = b.dbNext + b.dbNext = nil b.unlockMove(moveStateCleanup) err = db1.Close() @@ -652,8 +652,8 @@ func (b *Blockstore) Put(block blocks.Block) error { return err } - if b.db2 != nil { - if err := put(b.db2); err != nil { + if b.dbNext != nil { + if err := put(b.dbNext); err != nil { return err } } @@ -716,8 +716,8 @@ func (b *Blockstore) PutMany(blocks []blocks.Block) error { return err } - if b.db2 != nil { - if err := put(b.db2); err != nil { + if b.dbNext != nil { + if err := put(b.dbNext); err != nil { return err } } From 88097071582528d4fed28b7ddbc6a77fb430d246 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 12:08:39 +0300 Subject: [PATCH 66/98] update README --- blockstore/splitstore/README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/blockstore/splitstore/README.md b/blockstore/splitstore/README.md index 079b50cc7..4efd6f61d 100644 --- a/blockstore/splitstore/README.md +++ b/blockstore/splitstore/README.md @@ -59,17 +59,15 @@ These are options in the `[Chainstore.Splitstore]` section of the configuration: nodes beyond 4 finalities, while running with the discard coldstore option. It is also useful for miners who accept deals and need to lookback messages beyond the 4 finalities, which would otherwise hit the coldstore. -- `HotStoreMovingGCFrequency` -- specifies how frequenty to garbage collect the hotstore - using moving GC. - The default value is 20, which uses moving GC every 20 compactions; set to 0 to disable moving - GC altogether. +- `HotStoreFullGCFrequency` -- specifies how frequenty to garbage collect the hotstore + using full (moving) GC. + The default value is 20, which uses full GC every 20 compactions (about once a week); + set to 0 to disable full GC altogether. Rationale: badger supports online GC, and this is used by default. However it has proven to be ineffective in practice with the hotstore size slowly creeping up. In order to address this, we have added moving GC support in our badger wrapper, which can effectively reclaim all space. - The downside is that it takes a bit of time to perform a moving GC (about 40min) and you also - need enough space to house the new hotstore while the old one is still live. - This option controls how frequently to perform moving GC, with the default of 20 corresponding - to about once a week. + The downside is that it takes a bit longer to perform a moving GC and you also need enough + space to house the new hotstore while the old one is still live. ## Operation From 7c195245a10100951c711581c5cda2cd9ed158e7 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 27 Jul 2021 12:13:26 +0300 Subject: [PATCH 67/98] make cfgdoc-gen --- node/config/doc_gen.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 7ebecc5bc..5d4a91d5f 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -710,11 +710,11 @@ It can be "map" (default) for in memory marking or "badger" for on-disk marking. the compaction boundary; default is 0.`, }, { - Name: "HotStoreMovingGCFrequency", + Name: "HotStoreFullGCFrequency", Type: "uint64", - Comment: `HotStoreMovingGCFrequency specifies how often to perform moving GC on the hotstore. -A value of 0 disables, while a value 1 will do moving GC in every compaction. + Comment: `HotStoreFullGCFrequency specifies how often to perform a full (moving) GC on the hotstore. +A value of 0 disables, while a value 1 will do full GC in every compaction. Default is 20 (about once a week).`, }, }, From 06d01d7dc02b5b3d3a78537d97a57af35e9436d3 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Mon, 26 Jul 2021 22:57:44 -0400 Subject: [PATCH 68/98] Add github actions for staled pr --- .github/workflows/stale.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 20b2feb8a..16a9feebe 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: Close and mark stale issue on: schedule: - - cron: '0 0 * * *' + - cron: '0 12 * * *' jobs: stale: @@ -18,10 +18,16 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 24 hours.' close-issue-message: 'This issue was closed because it is missing author input.' + stale-pr-message: 'Thank you for submitting the PR and contributing to lotus! Lotus maintainers need more of your input before merging it, please address the suggested changes or reply to the comments or this PR will be closed in 48 hours. You are always more than welcome to reopen the PR later as well!' + close-pr-message: 'This PR was closed because it is missing author input. Please feel free to reopen the PR when you get to it! Thank you for your interest in contributing to lotus!' stale-issue-label: 'kind/stale' - any-of-labels: 'hint/needs-author-input' - days-before-issue-stale: 5 + stale-pr-label: 'kind/stale' + any-of-labels: 'need/author-input ' + days-before-issue-stale: 3 days-before-issue-close: 1 + days-before-pr-stale: 5 + days-before-pr-close: 2 + remove-stale-when-updated: true enable-statistics: true From 6ba42e3e6bbcb119ac3691ddb9cdda79c5a724fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 14:41:36 +0200 Subject: [PATCH 69/98] chainstore: refactor store.go into more subfiles --- chain/store/messages.go | 303 +++++++++++++++++++ chain/store/rand.go | 182 +++++++++++ chain/store/snapshot.go | 205 +++++++++++++ chain/store/store.go | 648 ---------------------------------------- 4 files changed, 690 insertions(+), 648 deletions(-) create mode 100644 chain/store/messages.go create mode 100644 chain/store/rand.go create mode 100644 chain/store/snapshot.go diff --git a/chain/store/messages.go b/chain/store/messages.go new file mode 100644 index 000000000..50cf0e6a2 --- /dev/null +++ b/chain/store/messages.go @@ -0,0 +1,303 @@ +package store + +import ( + "context" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + block "github.com/ipfs/go-block-format" + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +type storable interface { + ToStorageBlock() (block.Block, error) +} + +func PutMessage(bs bstore.Blockstore, m storable) (cid.Cid, error) { + b, err := m.ToStorageBlock() + if err != nil { + return cid.Undef, err + } + + if err := bs.Put(b); err != nil { + return cid.Undef, err + } + + return b.Cid(), nil +} + +func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) { + return PutMessage(cs.chainBlockstore, m) +} + +func (cs *ChainStore) GetCMessage(c cid.Cid) (types.ChainMsg, error) { + m, err := cs.GetMessage(c) + if err == nil { + return m, nil + } + if err != bstore.ErrNotFound { + log.Warnf("GetCMessage: unexpected error getting unsigned message: %s", err) + } + + return cs.GetSignedMessage(c) +} + +func (cs *ChainStore) GetMessage(c cid.Cid) (*types.Message, error) { + var msg *types.Message + err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { + msg, err = types.DecodeMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error) { + var msg *types.SignedMessage + err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { + msg, err = types.DecodeSignedMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { + ctx := context.TODO() + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), root) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var ( + cids []cid.Cid + cborCid cbg.CborCid + ) + if err := a.ForEach(&cborCid, func(i int64) error { + c := cid.Cid(cborCid) + cids = append(cids, c) + return nil + }); err != nil { + return nil, xerrors.Errorf("failed to traverse amt: %w", err) + } + + if uint64(len(cids)) != a.Length() { + return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length()) + } + + return cids, nil +} + +type BlockMessages struct { + Miner address.Address + BlsMessages []types.ChainMsg + SecpkMessages []types.ChainMsg + WinCount int64 +} + +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) { + 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 + } + + // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise + if _, ok := applied[sender]; !ok { + applied[sender] = m.Nonce + } + + if applied[sender] != m.Nonce { + return false, nil + } + + applied[sender]++ + + return true, nil + } + + var out []BlockMessages + for _, b := range ts.Blocks() { + + bms, sms, err := cs.MessagesForBlock(b) + if err != nil { + return nil, xerrors.Errorf("failed to get messages for block: %w", err) + } + + bm := BlockMessages{ + Miner: b.Miner, + BlsMessages: make([]types.ChainMsg, 0, len(bms)), + SecpkMessages: make([]types.ChainMsg, 0, len(sms)), + WinCount: b.ElectionProof.WinCount, + } + + for _, bmsg := range bms { + b, err := selectMsg(bmsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.BlsMessages = append(bm.BlsMessages, bmsg) + } + } + + for _, smsg := range sms { + b, err := selectMsg(smsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.SecpkMessages = append(bm.SecpkMessages, smsg) + } + } + + out = append(out, bm) + } + + return out, nil +} + +func (cs *ChainStore) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { + bmsgs, err := cs.BlockMsgsForTipset(ts) + if err != nil { + return nil, err + } + + var out []types.ChainMsg + for _, bm := range bmsgs { + for _, blsm := range bm.BlsMessages { + out = append(out, blsm) + } + + for _, secm := range bm.SecpkMessages { + out = append(out, secm) + } + } + + return out, nil +} + +type mmCids struct { + bls []cid.Cid + secpk []cid.Cid +} + +func (cs *ChainStore) ReadMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { + o, ok := cs.mmCache.Get(mmc) + if ok { + mmcids := o.(*mmCids) + return mmcids.bls, mmcids.secpk, nil + } + + cst := cbor.NewCborStore(cs.chainLocalBlockstore) + var msgmeta types.MsgMeta + if err := cst.Get(context.TODO(), mmc, &msgmeta); err != nil { + return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) + } + + blscids, err := cs.readAMTCids(msgmeta.BlsMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) + } + + secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) + } + + cs.mmCache.Add(mmc, &mmCids{ + bls: blscids, + secpk: secpkcids, + }) + + return blscids, secpkcids, nil +} + +func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { + blscids, secpkcids, err := cs.ReadMsgMetaCids(b.Messages) + if err != nil { + return nil, nil, err + } + + blsmsgs, err := cs.LoadMessagesFromCids(blscids) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) + } + + return blsmsgs, secpkmsgs, nil +} + +func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) { + ctx := context.TODO() + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), b.ParentMessageReceipts) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var r types.MessageReceipt + if found, err := a.Get(uint64(i), &r); err != nil { + return nil, err + } else if !found { + return nil, xerrors.Errorf("failed to find receipt %d", i) + } + + return &r, nil +} + +func (cs *ChainStore) LoadMessagesFromCids(cids []cid.Cid) ([]*types.Message, error) { + msgs := make([]*types.Message, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetMessage(c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} + +func (cs *ChainStore) LoadSignedMessagesFromCids(cids []cid.Cid) ([]*types.SignedMessage, error) { + msgs := make([]*types.SignedMessage, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetSignedMessage(c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} diff --git a/chain/store/rand.go b/chain/store/rand.go new file mode 100644 index 000000000..1fa9e678f --- /dev/null +++ b/chain/store/rand.go @@ -0,0 +1,182 @@ +package store + +import ( + "context" + "encoding/binary" + "os" + + "github.com/ipfs/go-cid" + "github.com/minio/blake2b-simd" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + h := blake2b.New256() + if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + VRFDigest := blake2b.Sum256(rbase) + _, err := h.Write(VRFDigest[:]) + if err != nil { + return nil, xerrors.Errorf("hashing VRFDigest: %w", err) + } + if err := binary.Write(h, binary.BigEndian, round); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + _, err = h.Write(entropy) + if err != nil { + return nil, xerrors.Errorf("hashing entropy: %w", err) + } + + return h.Sum(nil), nil +} + +func (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))) + + ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + be, err := cs.GetLatestBeaconEntry(randTs) + if err != nil { + return nil, err + } + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(be.Data, pers, round, entropy) +} + +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))) + + ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + mtb := randTs.MinTicketBlock() + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(mtb.Ticket.VRFProof, pers, round, entropy) +} + +func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry, error) { + cur := ts + for i := 0; i < 20; i++ { + cbe := cur.Blocks()[0].BeaconEntries + if len(cbe) > 0 { + return &cbe[len(cbe)-1], nil + } + + if cur.Height() == 0 { + return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") + } + + next, err := cs.LoadTipSet(cur.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) + } + cur = next + } + + if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { + return &types.BeaconEntry{ + Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, + }, nil + } + + return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") +} + +type chainRand struct { + cs *ChainStore + blks []cid.Cid +} + +func NewChainRand(cs *ChainStore, blks []cid.Cid) vm.Rand { + return &chainRand{ + cs: cs, + blks: blks, + } +} + +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) 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) { + if tsk.IsEmpty() { + return cs.GetHeaviestTipSet(), nil + } + return cs.LoadTipSet(tsk) +} diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go new file mode 100644 index 000000000..1d4ce3758 --- /dev/null +++ b/chain/store/snapshot.go @@ -0,0 +1,205 @@ +package store + +import ( + "bytes" + "context" + "io" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car" + carutil "github.com/ipld/go-car/util" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs bool, w io.Writer) error { + h := &car.CarHeader{ + Roots: ts.Cids(), + Version: 1, + } + + if err := car.WriteHeader(h, w); err != nil { + return xerrors.Errorf("failed to write car header: %s", err) + } + + unionBs := bstore.Union(cs.stateBlockstore, cs.chainBlockstore) + return cs.WalkSnapshot(ctx, ts, inclRecentRoots, skipOldMsgs, true, func(c cid.Cid) error { + blk, err := unionBs.Get(c) + if err != nil { + return xerrors.Errorf("writing object to car, bs.Get: %w", err) + } + + if err := carutil.LdWrite(w, c.Bytes(), blk.RawData()); err != nil { + return xerrors.Errorf("failed to write block to car output: %w", err) + } + + return nil + }) +} + +func (cs *ChainStore) Import(r io.Reader) (*types.TipSet, error) { + // TODO: writing only to the state blockstore is incorrect. + // At this time, both the state and chain blockstores are backed by the + // universal store. When we physically segregate the stores, we will need + // to route state objects to the state blockstore, and chain objects to + // the chain blockstore. + header, err := car.LoadCar(cs.StateBlockstore(), r) + if err != nil { + return nil, xerrors.Errorf("loadcar failed: %w", err) + } + + root, err := cs.LoadTipSet(types.NewTipSetKey(header.Roots...)) + if err != nil { + return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + } + + return root, nil +} + +func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs, skipMsgReceipts bool, cb func(cid.Cid) error) error { + if ts == nil { + ts = cs.GetHeaviestTipSet() + } + + seen := cid.NewSet() + walked := cid.NewSet() + + blocksToWalk := ts.Cids() + currentMinHeight := ts.Height() + + walkChain := func(blk cid.Cid) error { + if !seen.Visit(blk) { + return nil + } + + if err := cb(blk); err != nil { + return err + } + + data, err := cs.chainBlockstore.Get(blk) + if err != nil { + return xerrors.Errorf("getting block: %w", err) + } + + var b types.BlockHeader + if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { + return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) + } + + if currentMinHeight > b.Height { + currentMinHeight = b.Height + if currentMinHeight%builtin.EpochsInDay == 0 { + log.Infow("export", "height", currentMinHeight) + } + } + + var cids []cid.Cid + if !skipOldMsgs || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.Messages) { + mcids, err := recurseLinks(cs.chainBlockstore, walked, b.Messages, []cid.Cid{b.Messages}) + if err != nil { + return xerrors.Errorf("recursing messages failed: %w", err) + } + cids = mcids + } + } + + if b.Height > 0 { + for _, p := range b.Parents { + blocksToWalk = append(blocksToWalk, p) + } + } else { + // include the genesis block + cids = append(cids, b.Parents...) + } + + out := cids + + if b.Height == 0 || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.ParentStateRoot) { + cids, err := recurseLinks(cs.stateBlockstore, walked, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) + if err != nil { + return xerrors.Errorf("recursing genesis state failed: %w", err) + } + + out = append(out, cids...) + } + + if !skipMsgReceipts && walked.Visit(b.ParentMessageReceipts) { + out = append(out, b.ParentMessageReceipts) + } + } + + for _, c := range out { + if seen.Visit(c) { + if c.Prefix().Codec != cid.DagCBOR { + continue + } + + if err := cb(c); err != nil { + return err + } + + } + } + + return nil + } + + log.Infow("export started") + exportStart := build.Clock.Now() + + for len(blocksToWalk) > 0 { + next := blocksToWalk[0] + blocksToWalk = blocksToWalk[1:] + if err := walkChain(next); err != nil { + return xerrors.Errorf("walk chain failed: %w", err) + } + } + + log.Infow("export finished", "duration", build.Clock.Now().Sub(exportStart).Seconds()) + + return nil +} + +func recurseLinks(bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { + if root.Prefix().Codec != cid.DagCBOR { + return in, nil + } + + data, err := bs.Get(root) + if err != nil { + return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) + } + + var rerr error + err = cbg.ScanForLinks(bytes.NewReader(data.RawData()), func(c cid.Cid) { + if rerr != nil { + // No error return on ScanForLinks :( + return + } + + // traversed this already... + if !walked.Visit(c) { + return + } + + in = append(in, c) + var err error + in, err = recurseLinks(bs, walked, c, in) + if err != nil { + rerr = err + } + }) + if err != nil { + return nil, xerrors.Errorf("scanning for links failed: %w", err) + } + + return in, rerr +} diff --git a/chain/store/store.go b/chain/store/store.go index 523726863..b81bcf61e 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1,35 +1,24 @@ package store import ( - "bytes" "context" - "encoding/binary" "encoding/json" "errors" - "io" "os" "strconv" "strings" "sync" "time" - "github.com/filecoin-project/lotus/chain/state" - "golang.org/x/sync/errgroup" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/minio/blake2b-simd" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/api" bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/metrics" @@ -48,9 +37,6 @@ import ( "github.com/ipfs/go-datastore/query" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" - "github.com/ipld/go-car" - carutil "github.com/ipld/go-car/util" - cbg "github.com/whyrusleeping/cbor-gen" "github.com/whyrusleeping/pubsub" "golang.org/x/xerrors" ) @@ -988,27 +974,6 @@ func (cs *ChainStore) PersistBlockHeaders(b ...*types.BlockHeader) error { return err } -type storable interface { - ToStorageBlock() (block.Block, error) -} - -func PutMessage(bs bstore.Blockstore, m storable) (cid.Cid, error) { - b, err := m.ToStorageBlock() - if err != nil { - return cid.Undef, err - } - - if err := bs.Put(b); err != nil { - return cid.Undef, err - } - - return b.Cid(), nil -} - -func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) { - return PutMessage(cs.chainBlockstore, m) -} - func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) { // Hold lock for the whole function for now, if it becomes a problem we can // fix pretty easily @@ -1080,203 +1045,6 @@ func (cs *ChainStore) GetGenesis() (*types.BlockHeader, error) { return cs.GetBlock(c) } -func (cs *ChainStore) GetCMessage(c cid.Cid) (types.ChainMsg, error) { - m, err := cs.GetMessage(c) - if err == nil { - return m, nil - } - if err != bstore.ErrNotFound { - log.Warnf("GetCMessage: unexpected error getting unsigned message: %s", err) - } - - return cs.GetSignedMessage(c) -} - -func (cs *ChainStore) GetMessage(c cid.Cid) (*types.Message, error) { - var msg *types.Message - err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { - msg, err = types.DecodeMessage(b) - return err - }) - return msg, err -} - -func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error) { - var msg *types.SignedMessage - err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { - msg, err = types.DecodeSignedMessage(b) - return err - }) - return msg, err -} - -func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { - ctx := context.TODO() - // block headers use adt0, for now. - a, err := blockadt.AsArray(cs.ActorStore(ctx), root) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var ( - cids []cid.Cid - cborCid cbg.CborCid - ) - if err := a.ForEach(&cborCid, func(i int64) error { - c := cid.Cid(cborCid) - cids = append(cids, c) - return nil - }); err != nil { - return nil, xerrors.Errorf("failed to traverse amt: %w", err) - } - - if uint64(len(cids)) != a.Length() { - return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length()) - } - - return cids, nil -} - -type BlockMessages struct { - Miner address.Address - BlsMessages []types.ChainMsg - SecpkMessages []types.ChainMsg - WinCount int64 -} - -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) { - 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 - } - - // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise - if _, ok := applied[sender]; !ok { - applied[sender] = m.Nonce - } - - if applied[sender] != m.Nonce { - return false, nil - } - - applied[sender]++ - - return true, nil - } - - var out []BlockMessages - for _, b := range ts.Blocks() { - - bms, sms, err := cs.MessagesForBlock(b) - if err != nil { - return nil, xerrors.Errorf("failed to get messages for block: %w", err) - } - - bm := BlockMessages{ - Miner: b.Miner, - BlsMessages: make([]types.ChainMsg, 0, len(bms)), - SecpkMessages: make([]types.ChainMsg, 0, len(sms)), - WinCount: b.ElectionProof.WinCount, - } - - for _, bmsg := range bms { - b, err := selectMsg(bmsg.VMMessage()) - if err != nil { - return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) - } - - if b { - bm.BlsMessages = append(bm.BlsMessages, bmsg) - } - } - - for _, smsg := range sms { - b, err := selectMsg(smsg.VMMessage()) - if err != nil { - return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) - } - - if b { - bm.SecpkMessages = append(bm.SecpkMessages, smsg) - } - } - - out = append(out, bm) - } - - return out, nil -} - -func (cs *ChainStore) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { - bmsgs, err := cs.BlockMsgsForTipset(ts) - if err != nil { - return nil, err - } - - var out []types.ChainMsg - for _, bm := range bmsgs { - for _, blsm := range bm.BlsMessages { - out = append(out, blsm) - } - - for _, secm := range bm.SecpkMessages { - out = append(out, secm) - } - } - - return out, nil -} - -type mmCids struct { - bls []cid.Cid - secpk []cid.Cid -} - -func (cs *ChainStore) ReadMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { - o, ok := cs.mmCache.Get(mmc) - if ok { - mmcids := o.(*mmCids) - return mmcids.bls, mmcids.secpk, nil - } - - cst := cbor.NewCborStore(cs.chainLocalBlockstore) - var msgmeta types.MsgMeta - if err := cst.Get(context.TODO(), mmc, &msgmeta); err != nil { - return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) - } - - blscids, err := cs.readAMTCids(msgmeta.BlsMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) - } - - secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) - } - - cs.mmCache.Add(mmc, &mmCids{ - bls: blscids, - secpk: secpkcids, - }) - - return blscids, secpkcids, nil -} - // GetPath returns the sequence of atomic head change operations that // need to be applied in order to switch the head of the chain from the `from` // tipset to the `to` tipset. @@ -1304,71 +1072,6 @@ func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to type return path, nil } -func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { - blscids, secpkcids, err := cs.ReadMsgMetaCids(b.Messages) - if err != nil { - return nil, nil, err - } - - blsmsgs, err := cs.LoadMessagesFromCids(blscids) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) - } - - secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) - } - - return blsmsgs, secpkmsgs, nil -} - -func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) { - ctx := context.TODO() - // block headers use adt0, for now. - a, err := blockadt.AsArray(cs.ActorStore(ctx), b.ParentMessageReceipts) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var r types.MessageReceipt - if found, err := a.Get(uint64(i), &r); err != nil { - return nil, err - } else if !found { - return nil, xerrors.Errorf("failed to find receipt %d", i) - } - - return &r, nil -} - -func (cs *ChainStore) LoadMessagesFromCids(cids []cid.Cid) ([]*types.Message, error) { - msgs := make([]*types.Message, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) - } - - msgs = append(msgs, m) - } - - return msgs, nil -} - -func (cs *ChainStore) LoadSignedMessagesFromCids(cids []cid.Cid) ([]*types.SignedMessage, error) { - msgs := make([]*types.SignedMessage, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetSignedMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) - } - - msgs = append(msgs, m) - } - - return msgs, nil -} - // ChainBlockstore returns the chain blockstore. Currently the chain and state // // stores are both backed by the same physical store, albeit with different // // caching policies, but in the future they will segregate. @@ -1417,108 +1120,6 @@ func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { return NewFullTipSet(out), nil } -func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - h := blake2b.New256() - if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - VRFDigest := blake2b.Sum256(rbase) - _, err := h.Write(VRFDigest[:]) - if err != nil { - return nil, xerrors.Errorf("hashing VRFDigest: %w", err) - } - if err := binary.Write(h, binary.BigEndian, round); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - _, err = h.Write(entropy) - if err != nil { - return nil, xerrors.Errorf("hashing entropy: %w", err) - } - - return h.Sum(nil), nil -} - -func (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))) - - ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) - if err != nil { - return nil, err - } - - if round > ts.Height() { - return nil, xerrors.Errorf("cannot draw randomness from the future") - } - - searchHeight := round - if searchHeight < 0 { - searchHeight = 0 - } - - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) - if err != nil { - return nil, err - } - - be, err := cs.GetLatestBeaconEntry(randTs) - if err != nil { - return nil, err - } - - // if at (or just past -- for null epochs) appropriate epoch - // or at genesis (works for negative epochs) - return DrawRandomness(be.Data, pers, round, entropy) -} - -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))) - - ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) - if err != nil { - return nil, err - } - - if round > ts.Height() { - return nil, xerrors.Errorf("cannot draw randomness from the future") - } - - searchHeight := round - if searchHeight < 0 { - searchHeight = 0 - } - - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) - if err != nil { - return nil, err - } - - mtb := randTs.MinTicketBlock() - - // if at (or just past -- for null epochs) appropriate epoch - // or at genesis (works for negative epochs) - return DrawRandomness(mtb.Ticket.VRFProof, pers, round, entropy) -} - // GetTipsetByHeight returns the tipset on the chain behind 'ts' at the given // height. In the case that the given height is a null round, the 'prev' flag // selects the tipset before the null round if true, and the tipset following @@ -1555,252 +1156,3 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return cs.LoadTipSet(lbts.Parents()) } - -func recurseLinks(bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { - if root.Prefix().Codec != cid.DagCBOR { - return in, nil - } - - data, err := bs.Get(root) - if err != nil { - return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) - } - - var rerr error - err = cbg.ScanForLinks(bytes.NewReader(data.RawData()), func(c cid.Cid) { - if rerr != nil { - // No error return on ScanForLinks :( - return - } - - // traversed this already... - if !walked.Visit(c) { - return - } - - in = append(in, c) - var err error - in, err = recurseLinks(bs, walked, c, in) - if err != nil { - rerr = err - } - }) - if err != nil { - return nil, xerrors.Errorf("scanning for links failed: %w", err) - } - - return in, rerr -} - -func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs bool, w io.Writer) error { - h := &car.CarHeader{ - Roots: ts.Cids(), - Version: 1, - } - - if err := car.WriteHeader(h, w); err != nil { - return xerrors.Errorf("failed to write car header: %s", err) - } - - unionBs := bstore.Union(cs.stateBlockstore, cs.chainBlockstore) - return cs.WalkSnapshot(ctx, ts, inclRecentRoots, skipOldMsgs, true, func(c cid.Cid) error { - blk, err := unionBs.Get(c) - if err != nil { - return xerrors.Errorf("writing object to car, bs.Get: %w", err) - } - - if err := carutil.LdWrite(w, c.Bytes(), blk.RawData()); err != nil { - return xerrors.Errorf("failed to write block to car output: %w", err) - } - - return nil - }) -} - -func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs, skipMsgReceipts bool, cb func(cid.Cid) error) error { - if ts == nil { - ts = cs.GetHeaviestTipSet() - } - - seen := cid.NewSet() - walked := cid.NewSet() - - blocksToWalk := ts.Cids() - currentMinHeight := ts.Height() - - walkChain := func(blk cid.Cid) error { - if !seen.Visit(blk) { - return nil - } - - if err := cb(blk); err != nil { - return err - } - - data, err := cs.chainBlockstore.Get(blk) - if err != nil { - return xerrors.Errorf("getting block: %w", err) - } - - var b types.BlockHeader - if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { - return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) - } - - if currentMinHeight > b.Height { - currentMinHeight = b.Height - if currentMinHeight%builtin.EpochsInDay == 0 { - log.Infow("export", "height", currentMinHeight) - } - } - - var cids []cid.Cid - if !skipOldMsgs || b.Height > ts.Height()-inclRecentRoots { - if walked.Visit(b.Messages) { - mcids, err := recurseLinks(cs.chainBlockstore, walked, b.Messages, []cid.Cid{b.Messages}) - if err != nil { - return xerrors.Errorf("recursing messages failed: %w", err) - } - cids = mcids - } - } - - if b.Height > 0 { - for _, p := range b.Parents { - blocksToWalk = append(blocksToWalk, p) - } - } else { - // include the genesis block - cids = append(cids, b.Parents...) - } - - out := cids - - if b.Height == 0 || b.Height > ts.Height()-inclRecentRoots { - if walked.Visit(b.ParentStateRoot) { - cids, err := recurseLinks(cs.stateBlockstore, walked, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) - if err != nil { - return xerrors.Errorf("recursing genesis state failed: %w", err) - } - - out = append(out, cids...) - } - - if !skipMsgReceipts && walked.Visit(b.ParentMessageReceipts) { - out = append(out, b.ParentMessageReceipts) - } - } - - for _, c := range out { - if seen.Visit(c) { - if c.Prefix().Codec != cid.DagCBOR { - continue - } - - if err := cb(c); err != nil { - return err - } - - } - } - - return nil - } - - log.Infow("export started") - exportStart := build.Clock.Now() - - for len(blocksToWalk) > 0 { - next := blocksToWalk[0] - blocksToWalk = blocksToWalk[1:] - if err := walkChain(next); err != nil { - return xerrors.Errorf("walk chain failed: %w", err) - } - } - - log.Infow("export finished", "duration", build.Clock.Now().Sub(exportStart).Seconds()) - - return nil -} - -func (cs *ChainStore) Import(r io.Reader) (*types.TipSet, error) { - // TODO: writing only to the state blockstore is incorrect. - // At this time, both the state and chain blockstores are backed by the - // universal store. When we physically segregate the stores, we will need - // to route state objects to the state blockstore, and chain objects to - // the chain blockstore. - header, err := car.LoadCar(cs.StateBlockstore(), r) - if err != nil { - return nil, xerrors.Errorf("loadcar failed: %w", err) - } - - root, err := cs.LoadTipSet(types.NewTipSetKey(header.Roots...)) - if err != nil { - return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) - } - - return root, nil -} - -func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry, error) { - cur := ts - for i := 0; i < 20; i++ { - cbe := cur.Blocks()[0].BeaconEntries - if len(cbe) > 0 { - return &cbe[len(cbe)-1], nil - } - - if cur.Height() == 0 { - return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") - } - - next, err := cs.LoadTipSet(cur.Parents()) - if err != nil { - return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) - } - cur = next - } - - if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { - return &types.BeaconEntry{ - Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, - }, nil - } - - return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") -} - -type chainRand struct { - cs *ChainStore - blks []cid.Cid -} - -func NewChainRand(cs *ChainStore, blks []cid.Cid) vm.Rand { - return &chainRand{ - cs: cs, - blks: blks, - } -} - -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) 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) { - if tsk.IsEmpty() { - return cs.GetHeaviestTipSet(), nil - } - return cs.LoadTipSet(tsk) -} From 50b217817e4d7e439b0f5ffd874d30ad222e254a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 14:48:30 +0200 Subject: [PATCH 70/98] stmgr: Split upgrade code from upgrade runtime --- chain/stmgr/forks.go | 1088 +------------------------------------- chain/stmgr/upgrades.go | 1094 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1102 insertions(+), 1080 deletions(-) create mode 100644 chain/stmgr/upgrades.go diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index fb8e407ed..212272a95 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -4,42 +4,27 @@ import ( "bytes" "context" "encoding/binary" - "runtime" "sort" "sync" "time" - "github.com/filecoin-project/specs-actors/v5/actors/migration/nv13" - - "github.com/filecoin-project/go-state-types/rt" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/go-state-types/rt" + + "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" - miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" - multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/migration/nv3" - adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/specs-actors/v2/actors/migration/nv4" - "github.com/filecoin-project/specs-actors/v2/actors/migration/nv7" - "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" - "github.com/filecoin-project/specs-actors/v4/actors/migration/nv12" - "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" - "golang.org/x/xerrors" ) // MigrationCache can be used to cache information used by a migration. This is primarily useful to @@ -125,121 +110,6 @@ func (ml migrationLogger) Log(level rt.LogLevel, msg string, args ...interface{} } } -func DefaultUpgradeSchedule() UpgradeSchedule { - var us UpgradeSchedule - - updates := []Upgrade{{ - Height: build.UpgradeBreezeHeight, - Network: network.Version1, - Migration: UpgradeFaucetBurnRecovery, - }, { - Height: build.UpgradeSmokeHeight, - Network: network.Version2, - Migration: nil, - }, { - Height: build.UpgradeIgnitionHeight, - Network: network.Version3, - Migration: UpgradeIgnition, - }, { - Height: build.UpgradeRefuelHeight, - Network: network.Version3, - Migration: UpgradeRefuel, - }, { - Height: build.UpgradeAssemblyHeight, - Network: network.Version4, - Expensive: true, - Migration: UpgradeActorsV2, - }, { - Height: build.UpgradeTapeHeight, - Network: network.Version5, - Migration: nil, - }, { - Height: build.UpgradeLiftoffHeight, - Network: network.Version5, - Migration: UpgradeLiftoff, - }, { - Height: build.UpgradeKumquatHeight, - Network: network.Version6, - Migration: nil, - }, { - Height: build.UpgradePricelistOopsHeight, - Network: network.Version6AndAHalf, - Migration: nil, - }, { - Height: build.UpgradeCalicoHeight, - Network: network.Version7, - Migration: UpgradeCalico, - }, { - Height: build.UpgradePersianHeight, - Network: network.Version8, - Migration: nil, - }, { - Height: build.UpgradeOrangeHeight, - Network: network.Version9, - Migration: nil, - }, { - Height: build.UpgradeTrustHeight, - Network: network.Version10, - Migration: UpgradeActorsV3, - PreMigrations: []PreMigration{{ - PreMigration: PreUpgradeActorsV3, - StartWithin: 120, - DontStartWithin: 60, - StopWithin: 35, - }, { - PreMigration: PreUpgradeActorsV3, - StartWithin: 30, - DontStartWithin: 15, - StopWithin: 5, - }}, - Expensive: true, - }, { - Height: build.UpgradeNorwegianHeight, - Network: network.Version11, - Migration: nil, - }, { - Height: build.UpgradeTurboHeight, - Network: network.Version12, - Migration: UpgradeActorsV4, - PreMigrations: []PreMigration{{ - PreMigration: PreUpgradeActorsV4, - StartWithin: 120, - DontStartWithin: 60, - StopWithin: 35, - }, { - PreMigration: PreUpgradeActorsV4, - StartWithin: 30, - DontStartWithin: 15, - StopWithin: 5, - }}, - Expensive: true, - }, { - Height: build.UpgradeHyperdriveHeight, - Network: network.Version13, - Migration: UpgradeActorsV5, - PreMigrations: []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 { - // upgrade disabled - continue - } - us = append(us, u) - } - return us -} - func (us UpgradeSchedule) Validate() error { // Make sure each upgrade is valid. for _, u := range us { @@ -488,469 +358,6 @@ func doTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmo return nil } -func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ MigrationCache, em ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - // Some initial parameters - FundsForMiners := types.FromFil(1_000_000) - LookbackEpoch := abi.ChainEpoch(32000) - AccountCap := types.FromFil(0) - BaseMinerBalance := types.FromFil(20) - DesiredReimbursementBalance := types.FromFil(5_000_000) - - isSystemAccount := func(addr address.Address) (bool, error) { - id, err := address.IDFromAddress(addr) - if err != nil { - return false, xerrors.Errorf("id address: %w", err) - } - - if id < 1000 { - return true, nil - } - return false, nil - } - - minerFundsAlloc := func(pow, tpow abi.StoragePower) abi.TokenAmount { - return types.BigDiv(types.BigMul(pow, FundsForMiners), tpow) - } - - // Grab lookback state for account checks - lbts, err := sm.ChainStore().GetTipsetByHeight(ctx, LookbackEpoch, ts, false) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to get tipset at lookback height: %w", err) - } - - lbtree, err := sm.ParentState(lbts) - if err != nil { - return cid.Undef, xerrors.Errorf("loading state tree failed: %w", err) - } - - tree, err := sm.StateTree(root) - if err != nil { - return cid.Undef, xerrors.Errorf("getting state tree: %w", err) - } - - type transfer struct { - From address.Address - To address.Address - Amt abi.TokenAmount - } - - var transfers []transfer - subcalls := make([]types.ExecutionTrace, 0) - transferCb := func(trace types.ExecutionTrace) { - subcalls = append(subcalls, trace) - } - - // Take all excess funds away, put them into the reserve account - err = tree.ForEach(func(addr address.Address, act *types.Actor) error { - switch act.Code { - case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: - sysAcc, err := isSystemAccount(addr) - if err != nil { - return xerrors.Errorf("checking system account: %w", err) - } - - if !sysAcc { - transfers = append(transfers, transfer{ - From: addr, - To: builtin.ReserveAddress, - Amt: act.Balance, - }) - } - case builtin0.StorageMinerActorCodeID: - var st miner0.State - if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { - return xerrors.Errorf("failed to load miner state: %w", err) - } - - var available abi.TokenAmount - { - defer func() { - if err := recover(); err != nil { - log.Warnf("Get available balance failed (%s, %s, %s): %s", addr, act.Head, act.Balance, err) - } - available = abi.NewTokenAmount(0) - }() - // this panics if the miner doesnt have enough funds to cover their locked pledge - available = st.GetAvailableBalance(act.Balance) - } - - if !available.IsZero() { - transfers = append(transfers, transfer{ - From: addr, - To: builtin.ReserveAddress, - Amt: available, - }) - } - } - return nil - }) - if err != nil { - return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) - } - - // Execute transfers from previous step - for _, t := range transfers { - if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { - return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) - } - } - - // pull up power table to give miners back some funds proportional to their power - var ps power0.State - powAct, err := tree.GetActor(builtin0.StoragePowerActorAddr) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to load power actor: %w", err) - } - - cst := cbor.NewCborStore(sm.ChainStore().StateBlockstore()) - if err := cst.Get(ctx, powAct.Head, &ps); err != nil { - return cid.Undef, xerrors.Errorf("failed to get power actor state: %w", err) - } - - totalPower := ps.TotalBytesCommitted - - var transfersBack []transfer - // Now, we return some funds to places where they are needed - err = tree.ForEach(func(addr address.Address, act *types.Actor) error { - lbact, err := lbtree.GetActor(addr) - if err != nil { - if !xerrors.Is(err, types.ErrActorNotFound) { - return xerrors.Errorf("failed to get actor in lookback state") - } - } - - prevBalance := abi.NewTokenAmount(0) - if lbact != nil { - prevBalance = lbact.Balance - } - - switch act.Code { - case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: - nbalance := big.Min(prevBalance, AccountCap) - if nbalance.Sign() != 0 { - transfersBack = append(transfersBack, transfer{ - From: builtin.ReserveAddress, - To: addr, - Amt: nbalance, - }) - } - case builtin0.StorageMinerActorCodeID: - var st miner0.State - if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { - return xerrors.Errorf("failed to load miner state: %w", err) - } - - var minfo miner0.MinerInfo - if err := cst.Get(ctx, st.Info, &minfo); err != nil { - return xerrors.Errorf("failed to get miner info: %w", err) - } - - sectorsArr, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), st.Sectors) - if err != nil { - return xerrors.Errorf("failed to load sectors array: %w", err) - } - - slen := sectorsArr.Length() - - power := types.BigMul(types.NewInt(slen), types.NewInt(uint64(minfo.SectorSize))) - - mfunds := minerFundsAlloc(power, totalPower) - transfersBack = append(transfersBack, transfer{ - From: builtin.ReserveAddress, - To: minfo.Worker, - Amt: mfunds, - }) - - // Now make sure to give each miner who had power at the lookback some FIL - lbact, err := lbtree.GetActor(addr) - if err == nil { - var lbst miner0.State - if err := sm.ChainStore().ActorStore(ctx).Get(ctx, lbact.Head, &lbst); err != nil { - return xerrors.Errorf("failed to load miner state: %w", err) - } - - lbsectors, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), lbst.Sectors) - if err != nil { - return xerrors.Errorf("failed to load lb sectors array: %w", err) - } - - if lbsectors.Length() > 0 { - transfersBack = append(transfersBack, transfer{ - From: builtin.ReserveAddress, - To: minfo.Worker, - Amt: BaseMinerBalance, - }) - } - - } else { - log.Warnf("failed to get miner in lookback state: %s", err) - } - } - return nil - }) - if err != nil { - return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) - } - - for _, t := range transfersBack { - if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { - return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) - } - } - - // transfer all burnt funds back to the reserve account - burntAct, err := tree.GetActor(builtin0.BurntFundsActorAddr) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to load burnt funds actor: %w", err) - } - if err := doTransfer(tree, builtin0.BurntFundsActorAddr, builtin.ReserveAddress, burntAct.Balance, transferCb); err != nil { - return cid.Undef, xerrors.Errorf("failed to unburn funds: %w", err) - } - - // Top up the reimbursement service - reimbAddr, err := address.NewFromString("t0111") - if err != nil { - return cid.Undef, xerrors.Errorf("failed to parse reimbursement service address") - } - - reimb, err := tree.GetActor(reimbAddr) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to load reimbursement account actor: %w", err) - } - - difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) - if err := doTransfer(tree, builtin.ReserveAddress, reimbAddr, difference, transferCb); err != nil { - return cid.Undef, xerrors.Errorf("failed to top up reimbursement account: %w", err) - } - - // Now, a final sanity check to make sure the balances all check out - total := abi.NewTokenAmount(0) - err = tree.ForEach(func(addr address.Address, act *types.Actor) error { - total = types.BigAdd(total, act.Balance) - return nil - }) - if err != nil { - return cid.Undef, xerrors.Errorf("checking final state balance failed: %w", err) - } - - exp := types.FromFil(build.FilBase) - if !exp.Equals(total) { - return cid.Undef, xerrors.Errorf("resultant state tree account balance was not correct: %s", total) - } - - if em != nil { - // record the transfer in execution traces - - fakeMsg := makeFakeMsg(builtin.SystemActorAddr, builtin.SystemActorAddr, big.Zero(), uint64(epoch)) - - if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ - MessageReceipt: *makeFakeRct(), - ActorErr: nil, - ExecutionTrace: types.ExecutionTrace{ - Msg: fakeMsg, - MsgRct: makeFakeRct(), - Error: "", - Duration: 0, - GasCharges: nil, - Subcalls: subcalls, - }, - Duration: 0, - GasCosts: nil, - }, false); err != nil { - return cid.Undef, xerrors.Errorf("recording transfers: %w", err) - } - } - - return tree.Flush(ctx) -} - -func UpgradeIgnition(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - store := sm.cs.ActorStore(ctx) - - if build.UpgradeLiftoffHeight <= epoch { - return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") - } - - nst, err := nv3.MigrateStateTree(ctx, store, root, epoch) - if err != nil { - return cid.Undef, xerrors.Errorf("migrating actors state: %w", err) - } - - tree, err := sm.StateTree(nst) - if err != nil { - return cid.Undef, xerrors.Errorf("getting state tree: %w", err) - } - - err = setNetworkName(ctx, store, tree, "ignition") - if err != nil { - return cid.Undef, xerrors.Errorf("setting network name: %w", err) - } - - split1, err := address.NewFromString("t0115") - if err != nil { - return cid.Undef, xerrors.Errorf("first split address: %w", err) - } - - split2, err := address.NewFromString("t0116") - if err != nil { - return cid.Undef, xerrors.Errorf("second split address: %w", err) - } - - err = resetGenesisMsigs0(ctx, sm, store, tree, build.UpgradeLiftoffHeight) - if err != nil { - return cid.Undef, xerrors.Errorf("resetting genesis msig start epochs: %w", err) - } - - err = splitGenesisMultisig0(ctx, cb, split1, store, tree, 50, epoch, ts) - if err != nil { - return cid.Undef, xerrors.Errorf("splitting first msig: %w", err) - } - - err = splitGenesisMultisig0(ctx, cb, split2, store, tree, 50, epoch, ts) - if err != nil { - return cid.Undef, xerrors.Errorf("splitting second msig: %w", err) - } - - err = nv3.CheckStateTree(ctx, store, nst, epoch, builtin0.TotalFilecoin) - if err != nil { - return cid.Undef, xerrors.Errorf("sanity check after ignition upgrade failed: %w", err) - } - - return tree.Flush(ctx) -} - -func UpgradeRefuel(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - - store := sm.cs.ActorStore(ctx) - tree, err := sm.StateTree(root) - if err != nil { - return cid.Undef, xerrors.Errorf("getting state tree: %w", err) - } - - err = resetMultisigVesting0(ctx, store, tree, builtin.SaftAddress, 0, 0, big.Zero()) - if err != nil { - return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) - } - - err = resetMultisigVesting0(ctx, store, tree, builtin.ReserveAddress, 0, 0, big.Zero()) - if err != nil { - return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) - } - - err = resetMultisigVesting0(ctx, store, tree, builtin.RootVerifierAddress, 0, 0, big.Zero()) - if err != nil { - return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) - } - - return tree.Flush(ctx) -} - -func UpgradeActorsV2(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - store := store.ActorStore(ctx, buf) - - info, err := store.Put(ctx, new(types.StateInfo0)) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) - } - - newHamtRoot, err := nv4.MigrateStateTree(ctx, store, root, epoch, nv4.DefaultConfig()) - if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v2: %w", err) - } - - newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: types.StateTreeVersion1, - Actors: newHamtRoot, - Info: info, - }) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) - } - - // perform some basic sanity checks to make sure everything still works. - if newSm, err := state.LoadStateTree(store, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) - } else if newRoot2, err := newSm.Flush(ctx); err != nil { - return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) - } else if newRoot2 != newRoot { - return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) - } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { - return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) - } - - { - from := buf - to := buf.Read() - - if err := vm.Copy(ctx, from, to, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) - } - } - - return newRoot, nil -} - -func UpgradeLiftoff(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - tree, err := sm.StateTree(root) - if err != nil { - return cid.Undef, xerrors.Errorf("getting state tree: %w", err) - } - - err = setNetworkName(ctx, sm.cs.ActorStore(ctx), tree, "mainnet") - if err != nil { - return cid.Undef, xerrors.Errorf("setting network name: %w", err) - } - - return tree.Flush(ctx) -} - -func UpgradeCalico(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - if build.BuildType != build.BuildMainnet { - return root, nil - } - - store := sm.cs.ActorStore(ctx) - var stateRoot types.StateRoot - if err := store.Get(ctx, root, &stateRoot); err != nil { - return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) - } - - if stateRoot.Version != types.StateTreeVersion1 { - return cid.Undef, xerrors.Errorf( - "expected state root version 1 for calico upgrade, got %d", - stateRoot.Version, - ) - } - - newHamtRoot, err := nv7.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, nv7.DefaultConfig()) - if err != nil { - return cid.Undef, xerrors.Errorf("running nv7 migration: %w", err) - } - - newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: stateRoot.Version, - Actors: newHamtRoot, - Info: stateRoot.Info, - }) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) - } - - // perform some basic sanity checks to make sure everything still works. - if newSm, err := state.LoadStateTree(store, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) - } else if newRoot2, err := newSm.Flush(ctx); err != nil { - return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) - } else if newRoot2 != newRoot { - return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) - } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { - return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) - } - - return newRoot, nil -} - func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Address, em ExecMonitor, epoch abi.ChainEpoch, ts *types.TipSet) error { a, err := tree.GetActor(addr) if xerrors.Is(err, types.ErrActorNotFound) { @@ -1011,282 +418,8 @@ func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Add return tree.SetActor(init_.Address, ia) } -func UpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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 := nv10.Config{ - MaxWorkers: uint(workerCount), - JobQueueSize: 1000, - ResultQueueSize: 100, - ProgressLogPeriod: 10 * time.Second, - } - newRoot, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) - if err != nil { - return cid.Undef, xerrors.Errorf("migrating actors v3 state: %w", err) - } - - tree, err := sm.StateTree(newRoot) - if err != nil { - return cid.Undef, xerrors.Errorf("getting state tree: %w", err) - } - - if build.BuildType == build.BuildMainnet { - err := terminateActor(ctx, tree, build.ZeroAddress, cb, epoch, ts) - if err != nil && !xerrors.Is(err, types.ErrActorNotFound) { - return cid.Undef, xerrors.Errorf("deleting zero bls actor: %w", err) - } - - newRoot, err = tree.Flush(ctx) - if err != nil { - return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) - } - } - - return newRoot, nil -} - -func PreUpgradeActorsV3(ctx context.Context, sm *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 := nv10.Config{MaxWorkers: uint(workerCount)} - _, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) - return err -} - -func upgradeActorsV3Common( - ctx context.Context, sm *StateManager, cache MigrationCache, - root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, - config nv10.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.StateTreeVersion1 { - return cid.Undef, xerrors.Errorf( - "expected state root version 1 for actors v3 upgrade, got %d", - stateRoot.Version, - ) - } - - // Perform the migration - newHamtRoot, err := nv10.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) - if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v3: %w", err) - } - - // Persist the result. - newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: types.StateTreeVersion2, - Actors: newHamtRoot, - Info: stateRoot.Info, - }) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) - } - - // Persist the new tree. - - { - from := buf - to := buf.Read() - - if err := vm.Copy(ctx, from, to, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) - } - } - - return newRoot, nil -} - -func UpgradeActorsV4(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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 := nv12.Config{ - MaxWorkers: uint(workerCount), - JobQueueSize: 1000, - ResultQueueSize: 100, - ProgressLogPeriod: 10 * time.Second, - } - - newRoot, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) - if err != nil { - return cid.Undef, xerrors.Errorf("migrating actors v4 state: %w", err) - } - - return newRoot, nil -} - -func PreUpgradeActorsV4(ctx context.Context, sm *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 := nv12.Config{MaxWorkers: uint(workerCount)} - _, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) - return err -} - -func upgradeActorsV4Common( - ctx context.Context, sm *StateManager, cache MigrationCache, - root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, - config nv12.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.StateTreeVersion2 { - return cid.Undef, xerrors.Errorf( - "expected state root version 2 for actors v4 upgrade, got %d", - stateRoot.Version, - ) - } - - // Perform the migration - newHamtRoot, err := nv12.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) - if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v4: %w", err) - } - - // Persist the result. - newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: types.StateTreeVersion3, - Actors: newHamtRoot, - Info: stateRoot.Info, - }) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) - } - - // Persist the new tree. - - { - from := buf - to := buf.Read() - - if err := vm.Copy(ctx, from, to, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) - } - } - - return newRoot, nil -} - -func UpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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. - newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: types.StateTreeVersion4, - Actors: newHamtRoot, - Info: stateRoot.Info, - }) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) - } - - // Persist the new tree. - - { - from := buf - to := buf.Read() - - if err := vm.Copy(ctx, from, to, newRoot); err != nil { - return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) - } - } - - return newRoot, nil -} - func setNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, name string) error { - ia, err := tree.GetActor(builtin0.InitActorAddr) + ia, err := tree.GetActor(init_.Address) if err != nil { return xerrors.Errorf("getting init actor: %w", err) } @@ -1305,136 +438,13 @@ func setNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, return xerrors.Errorf("writing new init state: %w", err) } - if err := tree.SetActor(builtin0.InitActorAddr, ia); err != nil { + if err := tree.SetActor(init_.Address, ia); err != nil { return xerrors.Errorf("setting init actor: %w", err) } return nil } -func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Address, store adt0.Store, tree *state.StateTree, portions uint64, epoch abi.ChainEpoch, ts *types.TipSet) error { - if portions < 1 { - return xerrors.Errorf("cannot split into 0 portions") - } - - mact, err := tree.GetActor(addr) - if err != nil { - return xerrors.Errorf("getting msig actor: %w", err) - } - - mst, err := multisig.Load(store, mact) - if err != nil { - return xerrors.Errorf("getting msig state: %w", err) - } - - signers, err := mst.Signers() - if err != nil { - return xerrors.Errorf("getting msig signers: %w", err) - } - - thresh, err := mst.Threshold() - if err != nil { - return xerrors.Errorf("getting msig threshold: %w", err) - } - - ibal, err := mst.InitialBalance() - if err != nil { - return xerrors.Errorf("getting msig initial balance: %w", err) - } - - se, err := mst.StartEpoch() - if err != nil { - return xerrors.Errorf("getting msig start epoch: %w", err) - } - - ud, err := mst.UnlockDuration() - if err != nil { - return xerrors.Errorf("getting msig unlock duration: %w", err) - } - - pending, err := adt0.MakeEmptyMap(store).Root() - if err != nil { - return xerrors.Errorf("failed to create empty map: %w", err) - } - - newIbal := big.Div(ibal, types.NewInt(portions)) - newState := &multisig0.State{ - Signers: signers, - NumApprovalsThreshold: thresh, - NextTxnID: 0, - InitialBalance: newIbal, - StartEpoch: se, - UnlockDuration: ud, - PendingTxns: pending, - } - - scid, err := store.Put(ctx, newState) - if err != nil { - return xerrors.Errorf("storing new state: %w", err) - } - - newActor := types.Actor{ - Code: builtin0.MultisigActorCodeID, - Head: scid, - Nonce: 0, - Balance: big.Zero(), - } - - i := uint64(0) - subcalls := make([]types.ExecutionTrace, 0, portions) - transferCb := func(trace types.ExecutionTrace) { - subcalls = append(subcalls, trace) - } - - for i < portions { - keyAddr, err := makeKeyAddr(addr, i) - if err != nil { - return xerrors.Errorf("creating key address: %w", err) - } - - idAddr, err := tree.RegisterNewAddress(keyAddr) - if err != nil { - return xerrors.Errorf("registering new address: %w", err) - } - - err = tree.SetActor(idAddr, &newActor) - if err != nil { - return xerrors.Errorf("setting new msig actor state: %w", err) - } - - if err := doTransfer(tree, addr, idAddr, newIbal, transferCb); err != nil { - return xerrors.Errorf("transferring split msig balance: %w", err) - } - - i++ - } - - if em != nil { - // record the transfer in execution traces - - fakeMsg := makeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) - - if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ - MessageReceipt: *makeFakeRct(), - ActorErr: nil, - ExecutionTrace: types.ExecutionTrace{ - Msg: fakeMsg, - MsgRct: makeFakeRct(), - Error: "", - Duration: 0, - GasCharges: nil, - Subcalls: subcalls, - }, - Duration: 0, - GasCosts: nil, - }, false); err != nil { - return xerrors.Errorf("recording transfers: %w", err) - } - } - - return nil -} - func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, error) { var b bytes.Buffer if err := splitAddr.MarshalCBOR(&b); err != nil { @@ -1457,88 +467,6 @@ func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, erro return addr, nil } -// TODO: After the Liftoff epoch, refactor this to use resetMultisigVesting -func resetGenesisMsigs0(ctx context.Context, sm *StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { - gb, err := sm.cs.GetGenesis() - if err != nil { - return xerrors.Errorf("getting genesis block: %w", err) - } - - gts, err := types.NewTipSet([]*types.BlockHeader{gb}) - if err != nil { - return xerrors.Errorf("getting genesis tipset: %w", err) - } - - cst := cbor.NewCborStore(sm.cs.StateBlockstore()) - genesisTree, err := state.LoadStateTree(cst, gts.ParentState()) - if err != nil { - return xerrors.Errorf("loading state tree: %w", err) - } - - err = genesisTree.ForEach(func(addr address.Address, genesisActor *types.Actor) error { - if genesisActor.Code == builtin0.MultisigActorCodeID { - currActor, err := tree.GetActor(addr) - if err != nil { - return xerrors.Errorf("loading actor: %w", err) - } - - var currState multisig0.State - if err := store.Get(ctx, currActor.Head, &currState); err != nil { - return xerrors.Errorf("reading multisig state: %w", err) - } - - currState.StartEpoch = startEpoch - - currActor.Head, err = store.Put(ctx, &currState) - if err != nil { - return xerrors.Errorf("writing new multisig state: %w", err) - } - - if err := tree.SetActor(addr, currActor); err != nil { - return xerrors.Errorf("setting multisig actor: %w", err) - } - } - return nil - }) - - if err != nil { - return xerrors.Errorf("iterating over genesis actors: %w", err) - } - - return nil -} - -func resetMultisigVesting0(ctx context.Context, store adt0.Store, tree *state.StateTree, addr address.Address, startEpoch abi.ChainEpoch, duration abi.ChainEpoch, balance abi.TokenAmount) error { - act, err := tree.GetActor(addr) - if err != nil { - return xerrors.Errorf("getting actor: %w", err) - } - - if !builtin.IsMultisigActor(act.Code) { - return xerrors.Errorf("actor wasn't msig: %w", err) - } - - var msigState multisig0.State - if err := store.Get(ctx, act.Head, &msigState); err != nil { - return xerrors.Errorf("reading multisig state: %w", err) - } - - msigState.StartEpoch = startEpoch - msigState.UnlockDuration = duration - msigState.InitialBalance = balance - - act.Head, err = store.Put(ctx, &msigState) - if err != nil { - return xerrors.Errorf("writing new multisig state: %w", err) - } - - if err := tree.SetActor(addr, act); err != nil { - return xerrors.Errorf("setting multisig actor: %w", err) - } - - return nil -} - func makeFakeMsg(from address.Address, to address.Address, amt abi.TokenAmount, nonce uint64) *types.Message { return &types.Message{ From: from, diff --git a/chain/stmgr/upgrades.go b/chain/stmgr/upgrades.go new file mode 100644 index 000000000..968a0e273 --- /dev/null +++ b/chain/stmgr/upgrades.go @@ -0,0 +1,1094 @@ +package stmgr + +import ( + "context" + "runtime" + "time" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/network" + + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/migration/nv3" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/specs-actors/v2/actors/migration/nv4" + "github.com/filecoin-project/specs-actors/v2/actors/migration/nv7" + "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" + "github.com/filecoin-project/specs-actors/v4/actors/migration/nv12" + "github.com/filecoin-project/specs-actors/v5/actors/migration/nv13" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +func DefaultUpgradeSchedule() UpgradeSchedule { + var us UpgradeSchedule + + updates := []Upgrade{{ + Height: build.UpgradeBreezeHeight, + Network: network.Version1, + Migration: UpgradeFaucetBurnRecovery, + }, { + Height: build.UpgradeSmokeHeight, + Network: network.Version2, + Migration: nil, + }, { + Height: build.UpgradeIgnitionHeight, + Network: network.Version3, + Migration: UpgradeIgnition, + }, { + Height: build.UpgradeRefuelHeight, + Network: network.Version3, + Migration: UpgradeRefuel, + }, { + Height: build.UpgradeAssemblyHeight, + Network: network.Version4, + Expensive: true, + Migration: UpgradeActorsV2, + }, { + Height: build.UpgradeTapeHeight, + Network: network.Version5, + Migration: nil, + }, { + Height: build.UpgradeLiftoffHeight, + Network: network.Version5, + Migration: UpgradeLiftoff, + }, { + Height: build.UpgradeKumquatHeight, + Network: network.Version6, + Migration: nil, + }, { + Height: build.UpgradePricelistOopsHeight, + Network: network.Version6AndAHalf, + Migration: nil, + }, { + Height: build.UpgradeCalicoHeight, + Network: network.Version7, + Migration: UpgradeCalico, + }, { + Height: build.UpgradePersianHeight, + Network: network.Version8, + Migration: nil, + }, { + Height: build.UpgradeOrangeHeight, + Network: network.Version9, + Migration: nil, + }, { + Height: build.UpgradeTrustHeight, + Network: network.Version10, + Migration: UpgradeActorsV3, + PreMigrations: []PreMigration{{ + PreMigration: PreUpgradeActorsV3, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV3, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeNorwegianHeight, + Network: network.Version11, + Migration: nil, + }, { + Height: build.UpgradeTurboHeight, + Network: network.Version12, + Migration: UpgradeActorsV4, + PreMigrations: []PreMigration{{ + PreMigration: PreUpgradeActorsV4, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV4, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeHyperdriveHeight, + Network: network.Version13, + Migration: UpgradeActorsV5, + PreMigrations: []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 { + // upgrade disabled + continue + } + us = append(us, u) + } + return us +} + +func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ MigrationCache, em ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Some initial parameters + FundsForMiners := types.FromFil(1_000_000) + LookbackEpoch := abi.ChainEpoch(32000) + AccountCap := types.FromFil(0) + BaseMinerBalance := types.FromFil(20) + DesiredReimbursementBalance := types.FromFil(5_000_000) + + isSystemAccount := func(addr address.Address) (bool, error) { + id, err := address.IDFromAddress(addr) + if err != nil { + return false, xerrors.Errorf("id address: %w", err) + } + + if id < 1000 { + return true, nil + } + return false, nil + } + + minerFundsAlloc := func(pow, tpow abi.StoragePower) abi.TokenAmount { + return types.BigDiv(types.BigMul(pow, FundsForMiners), tpow) + } + + // Grab lookback state for account checks + lbts, err := sm.ChainStore().GetTipsetByHeight(ctx, LookbackEpoch, ts, false) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to get tipset at lookback height: %w", err) + } + + lbtree, err := sm.ParentState(lbts) + if err != nil { + return cid.Undef, xerrors.Errorf("loading state tree failed: %w", err) + } + + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + type transfer struct { + From address.Address + To address.Address + Amt abi.TokenAmount + } + + var transfers []transfer + subcalls := make([]types.ExecutionTrace, 0) + transferCb := func(trace types.ExecutionTrace) { + subcalls = append(subcalls, trace) + } + + // Take all excess funds away, put them into the reserve account + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + switch act.Code { + case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: + sysAcc, err := isSystemAccount(addr) + if err != nil { + return xerrors.Errorf("checking system account: %w", err) + } + + if !sysAcc { + transfers = append(transfers, transfer{ + From: addr, + To: builtin.ReserveAddress, + Amt: act.Balance, + }) + } + case builtin0.StorageMinerActorCodeID: + var st miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var available abi.TokenAmount + { + defer func() { + if err := recover(); err != nil { + log.Warnf("Get available balance failed (%s, %s, %s): %s", addr, act.Head, act.Balance, err) + } + available = abi.NewTokenAmount(0) + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available = st.GetAvailableBalance(act.Balance) + } + + if !available.IsZero() { + transfers = append(transfers, transfer{ + From: addr, + To: builtin.ReserveAddress, + Amt: available, + }) + } + } + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) + } + + // Execute transfers from previous step + for _, t := range transfers { + if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // pull up power table to give miners back some funds proportional to their power + var ps power0.State + powAct, err := tree.GetActor(builtin0.StoragePowerActorAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load power actor: %w", err) + } + + cst := cbor.NewCborStore(sm.ChainStore().StateBlockstore()) + if err := cst.Get(ctx, powAct.Head, &ps); err != nil { + return cid.Undef, xerrors.Errorf("failed to get power actor state: %w", err) + } + + totalPower := ps.TotalBytesCommitted + + var transfersBack []transfer + // Now, we return some funds to places where they are needed + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + lbact, err := lbtree.GetActor(addr) + if err != nil { + if !xerrors.Is(err, types.ErrActorNotFound) { + return xerrors.Errorf("failed to get actor in lookback state") + } + } + + prevBalance := abi.NewTokenAmount(0) + if lbact != nil { + prevBalance = lbact.Balance + } + + switch act.Code { + case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: + nbalance := big.Min(prevBalance, AccountCap) + if nbalance.Sign() != 0 { + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: addr, + Amt: nbalance, + }) + } + case builtin0.StorageMinerActorCodeID: + var st miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var minfo miner0.MinerInfo + if err := cst.Get(ctx, st.Info, &minfo); err != nil { + return xerrors.Errorf("failed to get miner info: %w", err) + } + + sectorsArr, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), st.Sectors) + if err != nil { + return xerrors.Errorf("failed to load sectors array: %w", err) + } + + slen := sectorsArr.Length() + + power := types.BigMul(types.NewInt(slen), types.NewInt(uint64(minfo.SectorSize))) + + mfunds := minerFundsAlloc(power, totalPower) + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: minfo.Worker, + Amt: mfunds, + }) + + // Now make sure to give each miner who had power at the lookback some FIL + lbact, err := lbtree.GetActor(addr) + if err == nil { + var lbst miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, lbact.Head, &lbst); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + lbsectors, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), lbst.Sectors) + if err != nil { + return xerrors.Errorf("failed to load lb sectors array: %w", err) + } + + if lbsectors.Length() > 0 { + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: minfo.Worker, + Amt: BaseMinerBalance, + }) + } + + } else { + log.Warnf("failed to get miner in lookback state: %s", err) + } + } + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) + } + + for _, t := range transfersBack { + if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // transfer all burnt funds back to the reserve account + burntAct, err := tree.GetActor(builtin0.BurntFundsActorAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load burnt funds actor: %w", err) + } + if err := doTransfer(tree, builtin0.BurntFundsActorAddr, builtin.ReserveAddress, burntAct.Balance, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("failed to unburn funds: %w", err) + } + + // Top up the reimbursement service + reimbAddr, err := address.NewFromString("t0111") + if err != nil { + return cid.Undef, xerrors.Errorf("failed to parse reimbursement service address") + } + + reimb, err := tree.GetActor(reimbAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load reimbursement account actor: %w", err) + } + + difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) + if err := doTransfer(tree, builtin.ReserveAddress, reimbAddr, difference, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("failed to top up reimbursement account: %w", err) + } + + // Now, a final sanity check to make sure the balances all check out + total := abi.NewTokenAmount(0) + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + total = types.BigAdd(total, act.Balance) + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("checking final state balance failed: %w", err) + } + + exp := types.FromFil(build.FilBase) + if !exp.Equals(total) { + return cid.Undef, xerrors.Errorf("resultant state tree account balance was not correct: %s", total) + } + + if em != nil { + // record the transfer in execution traces + + fakeMsg := makeFakeMsg(builtin.SystemActorAddr, builtin.SystemActorAddr, big.Zero(), uint64(epoch)) + + if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ + MessageReceipt: *makeFakeRct(), + ActorErr: nil, + ExecutionTrace: types.ExecutionTrace{ + Msg: fakeMsg, + MsgRct: makeFakeRct(), + Error: "", + Duration: 0, + GasCharges: nil, + Subcalls: subcalls, + }, + Duration: 0, + GasCosts: nil, + }, false); err != nil { + return cid.Undef, xerrors.Errorf("recording transfers: %w", err) + } + } + + return tree.Flush(ctx) +} + +func UpgradeIgnition(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + store := sm.cs.ActorStore(ctx) + + if build.UpgradeLiftoffHeight <= epoch { + return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") + } + + nst, err := nv3.MigrateStateTree(ctx, store, root, epoch) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors state: %w", err) + } + + tree, err := sm.StateTree(nst) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = setNetworkName(ctx, store, tree, "ignition") + if err != nil { + return cid.Undef, xerrors.Errorf("setting network name: %w", err) + } + + split1, err := address.NewFromString("t0115") + if err != nil { + return cid.Undef, xerrors.Errorf("first split address: %w", err) + } + + split2, err := address.NewFromString("t0116") + if err != nil { + return cid.Undef, xerrors.Errorf("second split address: %w", err) + } + + err = resetGenesisMsigs0(ctx, sm, store, tree, build.UpgradeLiftoffHeight) + if err != nil { + return cid.Undef, xerrors.Errorf("resetting genesis msig start epochs: %w", err) + } + + err = splitGenesisMultisig0(ctx, cb, split1, store, tree, 50, epoch, ts) + if err != nil { + return cid.Undef, xerrors.Errorf("splitting first msig: %w", err) + } + + err = splitGenesisMultisig0(ctx, cb, split2, store, tree, 50, epoch, ts) + if err != nil { + return cid.Undef, xerrors.Errorf("splitting second msig: %w", err) + } + + err = nv3.CheckStateTree(ctx, store, nst, epoch, builtin0.TotalFilecoin) + if err != nil { + return cid.Undef, xerrors.Errorf("sanity check after ignition upgrade failed: %w", err) + } + + return tree.Flush(ctx) +} + +func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Address, store adt0.Store, tree *state.StateTree, portions uint64, epoch abi.ChainEpoch, ts *types.TipSet) error { + if portions < 1 { + return xerrors.Errorf("cannot split into 0 portions") + } + + mact, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("getting msig actor: %w", err) + } + + mst, err := multisig.Load(store, mact) + if err != nil { + return xerrors.Errorf("getting msig state: %w", err) + } + + signers, err := mst.Signers() + if err != nil { + return xerrors.Errorf("getting msig signers: %w", err) + } + + thresh, err := mst.Threshold() + if err != nil { + return xerrors.Errorf("getting msig threshold: %w", err) + } + + ibal, err := mst.InitialBalance() + if err != nil { + return xerrors.Errorf("getting msig initial balance: %w", err) + } + + se, err := mst.StartEpoch() + if err != nil { + return xerrors.Errorf("getting msig start epoch: %w", err) + } + + ud, err := mst.UnlockDuration() + if err != nil { + return xerrors.Errorf("getting msig unlock duration: %w", err) + } + + pending, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return xerrors.Errorf("failed to create empty map: %w", err) + } + + newIbal := big.Div(ibal, types.NewInt(portions)) + newState := &multisig0.State{ + Signers: signers, + NumApprovalsThreshold: thresh, + NextTxnID: 0, + InitialBalance: newIbal, + StartEpoch: se, + UnlockDuration: ud, + PendingTxns: pending, + } + + scid, err := store.Put(ctx, newState) + if err != nil { + return xerrors.Errorf("storing new state: %w", err) + } + + newActor := types.Actor{ + Code: builtin0.MultisigActorCodeID, + Head: scid, + Nonce: 0, + Balance: big.Zero(), + } + + i := uint64(0) + subcalls := make([]types.ExecutionTrace, 0, portions) + transferCb := func(trace types.ExecutionTrace) { + subcalls = append(subcalls, trace) + } + + for i < portions { + keyAddr, err := makeKeyAddr(addr, i) + if err != nil { + return xerrors.Errorf("creating key address: %w", err) + } + + idAddr, err := tree.RegisterNewAddress(keyAddr) + if err != nil { + return xerrors.Errorf("registering new address: %w", err) + } + + err = tree.SetActor(idAddr, &newActor) + if err != nil { + return xerrors.Errorf("setting new msig actor state: %w", err) + } + + if err := doTransfer(tree, addr, idAddr, newIbal, transferCb); err != nil { + return xerrors.Errorf("transferring split msig balance: %w", err) + } + + i++ + } + + if em != nil { + // record the transfer in execution traces + + fakeMsg := makeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) + + if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ + MessageReceipt: *makeFakeRct(), + ActorErr: nil, + ExecutionTrace: types.ExecutionTrace{ + Msg: fakeMsg, + MsgRct: makeFakeRct(), + Error: "", + Duration: 0, + GasCharges: nil, + Subcalls: subcalls, + }, + Duration: 0, + GasCosts: nil, + }, false); err != nil { + return xerrors.Errorf("recording transfers: %w", err) + } + } + + return nil +} + +// TODO: After the Liftoff epoch, refactor this to use resetMultisigVesting +func resetGenesisMsigs0(ctx context.Context, sm *StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { + gb, err := sm.cs.GetGenesis() + if err != nil { + return xerrors.Errorf("getting genesis block: %w", err) + } + + gts, err := types.NewTipSet([]*types.BlockHeader{gb}) + if err != nil { + return xerrors.Errorf("getting genesis tipset: %w", err) + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + genesisTree, err := state.LoadStateTree(cst, gts.ParentState()) + if err != nil { + return xerrors.Errorf("loading state tree: %w", err) + } + + err = genesisTree.ForEach(func(addr address.Address, genesisActor *types.Actor) error { + if genesisActor.Code == builtin0.MultisigActorCodeID { + currActor, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("loading actor: %w", err) + } + + var currState multisig0.State + if err := store.Get(ctx, currActor.Head, &currState); err != nil { + return xerrors.Errorf("reading multisig state: %w", err) + } + + currState.StartEpoch = startEpoch + + currActor.Head, err = store.Put(ctx, &currState) + if err != nil { + return xerrors.Errorf("writing new multisig state: %w", err) + } + + if err := tree.SetActor(addr, currActor); err != nil { + return xerrors.Errorf("setting multisig actor: %w", err) + } + } + return nil + }) + + if err != nil { + return xerrors.Errorf("iterating over genesis actors: %w", err) + } + + return nil +} + +func resetMultisigVesting0(ctx context.Context, store adt0.Store, tree *state.StateTree, addr address.Address, startEpoch abi.ChainEpoch, duration abi.ChainEpoch, balance abi.TokenAmount) error { + act, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("getting actor: %w", err) + } + + if !builtin.IsMultisigActor(act.Code) { + return xerrors.Errorf("actor wasn't msig: %w", err) + } + + var msigState multisig0.State + if err := store.Get(ctx, act.Head, &msigState); err != nil { + return xerrors.Errorf("reading multisig state: %w", err) + } + + msigState.StartEpoch = startEpoch + msigState.UnlockDuration = duration + msigState.InitialBalance = balance + + act.Head, err = store.Put(ctx, &msigState) + if err != nil { + return xerrors.Errorf("writing new multisig state: %w", err) + } + + if err := tree.SetActor(addr, act); err != nil { + return xerrors.Errorf("setting multisig actor: %w", err) + } + + return nil +} + +func UpgradeRefuel(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + + store := sm.cs.ActorStore(ctx) + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.SaftAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.ReserveAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.RootVerifierAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + return tree.Flush(ctx) +} + +func UpgradeActorsV2(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + info, err := store.Put(ctx, new(types.StateInfo0)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) + } + + newHamtRoot, err := nv4.MigrateStateTree(ctx, store, root, epoch, nv4.DefaultConfig()) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v2: %w", err) + } + + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion1, + Actors: newHamtRoot, + Info: info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // perform some basic sanity checks to make sure everything still works. + if newSm, err := state.LoadStateTree(store, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) + } else if newRoot2, err := newSm.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) + } else if newRoot2 != newRoot { + return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) + } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { + return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) + } + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeLiftoff(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = setNetworkName(ctx, sm.cs.ActorStore(ctx), tree, "mainnet") + if err != nil { + return cid.Undef, xerrors.Errorf("setting network name: %w", err) + } + + return tree.Flush(ctx) +} + +func UpgradeCalico(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + if build.BuildType != build.BuildMainnet { + return root, nil + } + + store := sm.cs.ActorStore(ctx) + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion1 { + return cid.Undef, xerrors.Errorf( + "expected state root version 1 for calico upgrade, got %d", + stateRoot.Version, + ) + } + + newHamtRoot, err := nv7.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, nv7.DefaultConfig()) + if err != nil { + return cid.Undef, xerrors.Errorf("running nv7 migration: %w", err) + } + + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: stateRoot.Version, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // perform some basic sanity checks to make sure everything still works. + if newSm, err := state.LoadStateTree(store, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) + } else if newRoot2, err := newSm.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) + } else if newRoot2 != newRoot { + return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) + } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { + return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) + } + + return newRoot, nil +} + +func UpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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 := nv10.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + newRoot, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v3 state: %w", err) + } + + tree, err := sm.StateTree(newRoot) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + if build.BuildType == build.BuildMainnet { + err := terminateActor(ctx, tree, build.ZeroAddress, cb, epoch, ts) + if err != nil && !xerrors.Is(err, types.ErrActorNotFound) { + return cid.Undef, xerrors.Errorf("deleting zero bls actor: %w", err) + } + + newRoot, err = tree.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) + } + } + + return newRoot, nil +} + +func PreUpgradeActorsV3(ctx context.Context, sm *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 := nv10.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV3Common( + ctx context.Context, sm *StateManager, cache MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv10.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.StateTreeVersion1 { + return cid.Undef, xerrors.Errorf( + "expected state root version 1 for actors v3 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv10.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v3: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion2, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV4(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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 := nv12.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v4 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV4(ctx context.Context, sm *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 := nv12.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV4Common( + ctx context.Context, sm *StateManager, cache MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv12.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.StateTreeVersion2 { + return cid.Undef, xerrors.Errorf( + "expected state root version 2 for actors v4 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv12.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v4: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion3, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, 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. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} From ca1cd741c39ffbe3dae7d096b42f7d2d5f4522f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:15:50 +0200 Subject: [PATCH 71/98] stmgr: Split stmgr.go into smaller subfiles --- chain/stmgr/execute.go | 326 +++++++++++ chain/stmgr/read.go | 8 + chain/stmgr/searchwait.go | 279 +++++++++ chain/stmgr/stmgr.go | 1144 +------------------------------------ chain/stmgr/supply.go | 473 +++++++++++++++ chain/stmgr/utils.go | 91 +++ 6 files changed, 1178 insertions(+), 1143 deletions(-) create mode 100644 chain/stmgr/execute.go create mode 100644 chain/stmgr/searchwait.go create mode 100644 chain/stmgr/supply.go diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go new file mode 100644 index 000000000..9194f711e --- /dev/null +++ b/chain/stmgr/execute.go @@ -0,0 +1,326 @@ +package stmgr + +import ( + "context" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + "sync/atomic" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/cron" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/metrics" +) + +func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []store.BlockMessages, epoch abi.ChainEpoch, r vm.Rand, em ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { + done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) + defer done() + + partDone := metrics.Timer(ctx, metrics.VMApplyEarly) + defer func() { + partDone() + }() + + makeVmWithBaseState := func(base cid.Cid) (*vm.VM, error) { + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: epoch, + Rand: r, + Bstore: sm.cs.StateBlockstore(), + Syscalls: sm.cs.VMSys(), + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: baseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + } + + return sm.newVM(ctx, vmopt) + } + + vmi, err := makeVmWithBaseState(pstate) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) + } + + runCron := func(epoch abi.ChainEpoch) error { + cronMsg := &types.Message{ + To: cron.Address, + From: builtin.SystemActorAddr, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little + Method: cron.Methods.EpochTick, + Params: nil, + } + ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg) + if err != nil { + return err + } + if em != nil { + if err := em.MessageApplied(ctx, ts, cronMsg.Cid(), cronMsg, ret, true); err != nil { + return xerrors.Errorf("callback failed on cron message: %w", err) + } + } + if ret.ExitCode != 0 { + return xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode) + } + + return nil + } + + for i := parentEpoch; i < epoch; i++ { + if i > parentEpoch { + // run cron for null rounds if any + if err := runCron(i); err != nil { + return cid.Undef, cid.Undef, err + } + + pstate, err = vmi.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("flushing vm: %w", err) + } + } + + // handle state forks + // XXX: The state tree + newState, err := sm.handleStateForks(ctx, pstate, i, em, ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) + } + + if pstate != newState { + vmi, err = makeVmWithBaseState(newState) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) + } + } + + vmi.SetBlockHeight(i + 1) + pstate = newState + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyMessages) + + var receipts []cbg.CBORMarshaler + processedMsgs := make(map[cid.Cid]struct{}) + for _, b := range bms { + penalty := types.NewInt(0) + gasReward := big.Zero() + + for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { + m := cm.VMMessage() + if _, found := processedMsgs[m.Cid()]; found { + continue + } + r, err := vmi.ApplyMessage(ctx, cm) + if err != nil { + return cid.Undef, cid.Undef, err + } + + receipts = append(receipts, &r.MessageReceipt) + gasReward = big.Add(gasReward, r.GasCosts.MinerTip) + penalty = big.Add(penalty, r.GasCosts.MinerPenalty) + + if em != nil { + if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { + return cid.Undef, cid.Undef, err + } + } + processedMsgs[m.Cid()] = struct{}{} + } + + params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ + Miner: b.Miner, + Penalty: penalty, + GasReward: gasReward, + WinCount: b.WinCount, + }) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) + } + + rwMsg := &types.Message{ + From: builtin.SystemActorAddr, + To: reward.Address, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: 1 << 30, + Method: reward.Methods.AwardBlockReward, + Params: params, + } + ret, actErr := vmi.ApplyImplicitMessage(ctx, rwMsg) + if actErr != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, actErr) + } + if em != nil { + if err := em.MessageApplied(ctx, ts, rwMsg.Cid(), rwMsg, ret, true); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on reward message: %w", err) + } + } + + if ret.ExitCode != 0 { + return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) + } + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyCron) + + if err := runCron(epoch); err != nil { + return cid.Cid{}, cid.Cid{}, err + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyFlush) + + rectarr := blockadt.MakeEmptyArray(sm.cs.ActorStore(ctx)) + for i, receipt := range receipts { + if err := rectarr.Set(uint64(i), receipt); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + } + rectroot, err := rectarr.Root() + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + + st, err := vmi.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) + } + + stats.Record(ctx, metrics.VMSends.M(int64(atomic.LoadUint64(&vm.StatSends))), + metrics.VMApplied.M(int64(atomic.LoadUint64(&vm.StatApplied)))) + + return st, rectroot, nil +} + +func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { + ctx, span := trace.StartSpan(ctx, "tipSetState") + defer span.End() + if span.IsRecordingEvents() { + span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) + } + + ck := cidsToKey(ts.Cids()) + sm.stlk.Lock() + cw, cwok := sm.compWait[ck] + if cwok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("waited", true)) + select { + case <-cw: + sm.stlk.Lock() + case <-ctx.Done(): + return cid.Undef, cid.Undef, ctx.Err() + } + } + cached, ok := sm.stCache[ck] + if ok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("cache", true)) + return cached[0], cached[1], nil + } + ch := make(chan struct{}) + sm.compWait[ck] = ch + + defer func() { + sm.stlk.Lock() + delete(sm.compWait, ck) + if st != cid.Undef { + sm.stCache[ck] = []cid.Cid{st, rec} + } + sm.stlk.Unlock() + close(ch) + }() + + sm.stlk.Unlock() + + if ts.Height() == 0 { + // NB: This is here because the process that executes blocks requires that the + // block miner reference a valid miner in the state tree. Unless we create some + // magical genesis miner, this won't work properly, so we short circuit here + // This avoids the question of 'who gets paid the genesis block reward' + return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil + } + + st, rec, err = sm.computeTipSetState(ctx, ts, sm.tsExecMonitor) + if err != nil { + return cid.Undef, cid.Undef, err + } + + return st, rec, nil +} + +func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { + st, _, err := sm.computeTipSetState(ctx, ts, em) + return st, err +} + +func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { + var invocTrace []*api.InvocResult + st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) + if err != nil { + return cid.Undef, nil, err + } + return st, invocTrace, nil +} + +func (sm *StateManager) computeTipSetState(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, cid.Cid, error) { + ctx, span := trace.StartSpan(ctx, "computeTipSetState") + defer span.End() + + blks := ts.Blocks() + + for i := 0; i < len(blks); i++ { + for j := i + 1; j < len(blks); j++ { + if blks[i].Miner == blks[j].Miner { + return cid.Undef, cid.Undef, + xerrors.Errorf("duplicate miner in a tipset (%s %s)", + blks[i].Miner, blks[j].Miner) + } + } + } + + var parentEpoch abi.ChainEpoch + pstate := blks[0].ParentStateRoot + if blks[0].Height > 0 { + parent, err := sm.cs.GetBlock(blks[0].Parents[0]) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) + } + + parentEpoch = parent.Height + } + + r := store.NewChainRand(sm.cs, ts.Cids()) + + blkmsgs, err := sm.cs.BlockMsgsForTipset(ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) + } + + baseFee := blks[0].ParentBaseFee + + return sm.ApplyBlocks(ctx, parentEpoch, pstate, blkmsgs, blks[0].Height, r, em, baseFee, ts) +} diff --git a/chain/stmgr/read.go b/chain/stmgr/read.go index 3c7fb5d91..bc259f227 100644 --- a/chain/stmgr/read.go +++ b/chain/stmgr/read.go @@ -31,6 +31,14 @@ func (sm *StateManager) ParentState(ts *types.TipSet) (*state.StateTree, error) return state, nil } +func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + } + + return ts.ParentState() +} + func (sm *StateManager) StateTree(st cid.Cid) (*state.StateTree, error) { cst := cbor.NewCborStore(sm.cs.StateBlockstore()) state, err := state.LoadStateTree(cst, st) diff --git a/chain/stmgr/searchwait.go b/chain/stmgr/searchwait.go new file mode 100644 index 000000000..518180824 --- /dev/null +++ b/chain/stmgr/searchwait.go @@ -0,0 +1,279 @@ +package stmgr + +import ( + "context" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already +// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on +// chain for at least confidence epochs without being reverted before returning. +func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confidence uint64, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + msg, err := sm.cs.GetCMessage(mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + tsub := sm.cs.SubHeadChanges(ctx) + + head, ok := <-tsub + if !ok { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid") + } + + if len(head) != 1 { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item") + } + + if head[0].Type != store.HCCurrent { + return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(head[0].Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head[0].Val, r, foundMsg, nil + } + + var backTs *types.TipSet + var backRcp *types.MessageReceipt + var backFm cid.Cid + backSearchWait := make(chan struct{}) + go func() { + fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head[0].Val, msg, lookbackLimit, allowReplaced) + if err != nil { + log.Warnf("failed to look back through chain for message: %v", err) + return + } + + backTs = fts + backRcp = r + backFm = foundMsg + close(backSearchWait) + }() + + var candidateTs *types.TipSet + var candidateRcp *types.MessageReceipt + var candidateFm cid.Cid + heightOfHead := head[0].Val.Height() + reverts := map[types.TipSetKey]bool{} + + for { + select { + case notif, ok := <-tsub: + if !ok { + return nil, nil, cid.Undef, ctx.Err() + } + for _, val := range notif { + switch val.Type { + case store.HCRevert: + if val.Val.Equals(candidateTs) { + candidateTs = nil + candidateRcp = nil + candidateFm = cid.Undef + } + if backSearchWait != nil { + reverts[val.Val.Key()] = true + } + case store.HCApply: + if candidateTs != nil && val.Val.Height() >= candidateTs.Height()+abi.ChainEpoch(confidence) { + return candidateTs, candidateRcp, candidateFm, nil + } + r, foundMsg, err := sm.tipsetExecutedMessage(val.Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + if r != nil { + if confidence == 0 { + return val.Val, r, foundMsg, err + } + candidateTs = val.Val + candidateRcp = r + candidateFm = foundMsg + } + heightOfHead = val.Val.Height() + } + } + case <-backSearchWait: + // check if we found the message in the chain and that is hasn't been reverted since we started searching + if backTs != nil && !reverts[backTs.Key()] { + // if head is at or past confidence interval, return immediately + if heightOfHead >= backTs.Height()+abi.ChainEpoch(confidence) { + return backTs, backRcp, backFm, nil + } + + // wait for confidence interval + candidateTs = backTs + candidateRcp = backRcp + candidateFm = backFm + } + reverts = nil + backSearchWait = nil + case <-ctx.Done(): + return nil, nil, cid.Undef, ctx.Err() + } + } +} + +func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + msg, err := sm.cs.GetCMessage(mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(head, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head, r, foundMsg, nil + } + + fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head, msg, lookbackLimit, allowReplaced) + + if err != nil { + log.Warnf("failed to look back through chain for message %s", mcid) + return nil, nil, cid.Undef, err + } + + if fts == nil { + return nil, nil, cid.Undef, nil + } + + return fts, r, foundMsg, nil +} + +// searchBackForMsg searches up to limit tipsets backwards from the given +// tipset for a message receipt. +// If limit is +// - 0 then no tipsets are searched +// - 5 then five tipset are searched +// - LookbackNoLimit then there is no limit +func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg, limit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + limitHeight := from.Height() - limit + noLimit := limit == LookbackNoLimit + + cur := from + curActor, err := sm.LoadActor(ctx, m.VMMessage().From, cur) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load initital tipset") + } + + mFromId, err := sm.LookupID(ctx, m.VMMessage().From, from) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("looking up From id address: %w", err) + } + + mNonce := m.VMMessage().Nonce + + for { + // If we've reached the genesis block, or we've reached the limit of + // how far back to look + if cur.Height() == 0 || !noLimit && cur.Height() <= limitHeight { + // it ain't here! + return nil, nil, cid.Undef, nil + } + + select { + case <-ctx.Done(): + return nil, nil, cid.Undef, nil + default: + } + + // we either have no messages from the sender, or the latest message we found has a lower nonce than the one being searched for, + // either way, no reason to lookback, it ain't there + if curActor == nil || curActor.Nonce == 0 || curActor.Nonce < mNonce { + return nil, nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(cur.Parents()) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load tipset during msg wait searchback: %w", err) + } + + act, err := sm.LoadActor(ctx, mFromId, pts) + actorNoExist := errors.Is(err, types.ErrActorNotFound) + if err != nil && !actorNoExist { + return nil, nil, cid.Cid{}, xerrors.Errorf("failed to load the actor: %w", err) + } + + // check that between cur and parent tipset the nonce fell into range of our message + if actorNoExist || (curActor.Nonce > mNonce && act.Nonce <= mNonce) { + r, foundMsg, err := sm.tipsetExecutedMessage(cur, m.Cid(), m.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("checking for message execution during lookback: %w", err) + } + + if r != nil { + return cur, r, foundMsg, nil + } + } + + cur = pts + curActor = act + } +} + +func (sm *StateManager) tipsetExecutedMessage(ts *types.TipSet, msg cid.Cid, vmm *types.Message, allowReplaced bool) (*types.MessageReceipt, cid.Cid, error) { + // The genesis block did not execute any messages + if ts.Height() == 0 { + return nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(ts.Parents()) + if err != nil { + return nil, cid.Undef, err + } + + cm, err := sm.cs.MessagesForTipset(pts) + if err != nil { + return nil, cid.Undef, err + } + + for ii := range cm { + // iterate in reverse because we going backwards through the chain + i := len(cm) - ii - 1 + m := cm[i] + + if m.VMMessage().From == vmm.From { // cheaper to just check origin first + if m.VMMessage().Nonce == vmm.Nonce { + if allowReplaced && m.VMMessage().EqualCall(vmm) { + if m.Cid() != msg { + log.Warnw("found message with equal nonce and call params but different CID", + "wanted", msg, "found", m.Cid(), "nonce", vmm.Nonce, "from", vmm.From) + } + + pr, err := sm.cs.GetParentReceipt(ts.Blocks()[0], i) + if err != nil { + return nil, cid.Undef, err + } + return pr, m.Cid(), nil + } + + // this should be that message + return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", + msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) + } + if m.VMMessage().Nonce < vmm.Nonce { + return nil, cid.Undef, nil // don't bother looking further + } + } + } + + return nil, cid.Undef, nil +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 4f1351d2c..8f03b6bf9 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -2,52 +2,30 @@ package stmgr import ( "context" - "errors" "fmt" "sync" - "sync/atomic" - - "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" - cbg "github.com/whyrusleeping/cbor-gen" - "go.opencensus.io/stats" - "go.opencensus.io/trace" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" // Used for genesis. msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" - // we use the same adt for all receipts - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/actors/builtin/cron" - _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/metrics" ) const LookbackNoLimit = api.LookbackNoLimit @@ -217,312 +195,6 @@ func (sm *StateManager) Stop(ctx context.Context) error { return nil } -func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { - ctx, span := trace.StartSpan(ctx, "tipSetState") - defer span.End() - if span.IsRecordingEvents() { - span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) - } - - ck := cidsToKey(ts.Cids()) - sm.stlk.Lock() - cw, cwok := sm.compWait[ck] - if cwok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("waited", true)) - select { - case <-cw: - sm.stlk.Lock() - case <-ctx.Done(): - return cid.Undef, cid.Undef, ctx.Err() - } - } - cached, ok := sm.stCache[ck] - if ok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("cache", true)) - return cached[0], cached[1], nil - } - ch := make(chan struct{}) - sm.compWait[ck] = ch - - defer func() { - sm.stlk.Lock() - delete(sm.compWait, ck) - if st != cid.Undef { - sm.stCache[ck] = []cid.Cid{st, rec} - } - sm.stlk.Unlock() - close(ch) - }() - - sm.stlk.Unlock() - - if ts.Height() == 0 { - // NB: This is here because the process that executes blocks requires that the - // block miner reference a valid miner in the state tree. Unless we create some - // magical genesis miner, this won't work properly, so we short circuit here - // This avoids the question of 'who gets paid the genesis block reward' - return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil - } - - st, rec, err = sm.computeTipSetState(ctx, ts, sm.tsExecMonitor) - if err != nil { - return cid.Undef, cid.Undef, err - } - - return st, rec, nil -} - -func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { - st, _, err := sm.computeTipSetState(ctx, ts, em) - return st, err -} - -func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { - var invocTrace []*api.InvocResult - st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) - if err != nil { - return cid.Undef, nil, err - } - return st, invocTrace, nil -} - -func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []store.BlockMessages, epoch abi.ChainEpoch, r vm.Rand, em ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { - done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) - defer done() - - partDone := metrics.Timer(ctx, metrics.VMApplyEarly) - defer func() { - partDone() - }() - - makeVmWithBaseState := func(base cid.Cid) (*vm.VM, error) { - vmopt := &vm.VMOpts{ - StateBase: base, - Epoch: epoch, - Rand: r, - Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), - CircSupplyCalc: sm.GetVMCirculatingSupply, - NtwkVersion: sm.GetNtwkVersion, - BaseFee: baseFee, - LookbackState: LookbackStateGetterForTipset(sm, ts), - } - - return sm.newVM(ctx, vmopt) - } - - vmi, err := makeVmWithBaseState(pstate) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) - } - - runCron := func(epoch abi.ChainEpoch) error { - cronMsg := &types.Message{ - To: cron.Address, - From: builtin.SystemActorAddr, - Nonce: uint64(epoch), - Value: types.NewInt(0), - GasFeeCap: types.NewInt(0), - GasPremium: types.NewInt(0), - GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little - Method: cron.Methods.EpochTick, - Params: nil, - } - ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg) - if err != nil { - return err - } - if em != nil { - if err := em.MessageApplied(ctx, ts, cronMsg.Cid(), cronMsg, ret, true); err != nil { - return xerrors.Errorf("callback failed on cron message: %w", err) - } - } - if ret.ExitCode != 0 { - return xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode) - } - - return nil - } - - for i := parentEpoch; i < epoch; i++ { - if i > parentEpoch { - // run cron for null rounds if any - if err := runCron(i); err != nil { - return cid.Undef, cid.Undef, err - } - - pstate, err = vmi.Flush(ctx) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("flushing vm: %w", err) - } - } - - // handle state forks - // XXX: The state tree - newState, err := sm.handleStateForks(ctx, pstate, i, em, ts) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) - } - - if pstate != newState { - vmi, err = makeVmWithBaseState(newState) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) - } - } - - vmi.SetBlockHeight(i + 1) - pstate = newState - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyMessages) - - var receipts []cbg.CBORMarshaler - processedMsgs := make(map[cid.Cid]struct{}) - for _, b := range bms { - penalty := types.NewInt(0) - gasReward := big.Zero() - - for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { - m := cm.VMMessage() - if _, found := processedMsgs[m.Cid()]; found { - continue - } - r, err := vmi.ApplyMessage(ctx, cm) - if err != nil { - return cid.Undef, cid.Undef, err - } - - receipts = append(receipts, &r.MessageReceipt) - gasReward = big.Add(gasReward, r.GasCosts.MinerTip) - penalty = big.Add(penalty, r.GasCosts.MinerPenalty) - - if em != nil { - if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { - return cid.Undef, cid.Undef, err - } - } - processedMsgs[m.Cid()] = struct{}{} - } - - params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ - Miner: b.Miner, - Penalty: penalty, - GasReward: gasReward, - WinCount: b.WinCount, - }) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) - } - - rwMsg := &types.Message{ - From: builtin.SystemActorAddr, - To: reward.Address, - Nonce: uint64(epoch), - Value: types.NewInt(0), - GasFeeCap: types.NewInt(0), - GasPremium: types.NewInt(0), - GasLimit: 1 << 30, - Method: reward.Methods.AwardBlockReward, - Params: params, - } - ret, actErr := vmi.ApplyImplicitMessage(ctx, rwMsg) - if actErr != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, actErr) - } - if em != nil { - if err := em.MessageApplied(ctx, ts, rwMsg.Cid(), rwMsg, ret, true); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on reward message: %w", err) - } - } - - if ret.ExitCode != 0 { - return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) - } - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyCron) - - if err := runCron(epoch); err != nil { - return cid.Cid{}, cid.Cid{}, err - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyFlush) - - rectarr := blockadt.MakeEmptyArray(sm.cs.ActorStore(ctx)) - for i, receipt := range receipts { - if err := rectarr.Set(uint64(i), receipt); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) - } - } - rectroot, err := rectarr.Root() - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) - } - - st, err := vmi.Flush(ctx) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) - } - - stats.Record(ctx, metrics.VMSends.M(int64(atomic.LoadUint64(&vm.StatSends))), - metrics.VMApplied.M(int64(atomic.LoadUint64(&vm.StatApplied)))) - - return st, rectroot, nil -} - -func (sm *StateManager) computeTipSetState(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, cid.Cid, error) { - ctx, span := trace.StartSpan(ctx, "computeTipSetState") - defer span.End() - - blks := ts.Blocks() - - for i := 0; i < len(blks); i++ { - for j := i + 1; j < len(blks); j++ { - if blks[i].Miner == blks[j].Miner { - return cid.Undef, cid.Undef, - xerrors.Errorf("duplicate miner in a tipset (%s %s)", - blks[i].Miner, blks[j].Miner) - } - } - } - - var parentEpoch abi.ChainEpoch - pstate := blks[0].ParentStateRoot - if blks[0].Height > 0 { - parent, err := sm.cs.GetBlock(blks[0].Parents[0]) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) - } - - parentEpoch = parent.Height - } - - r := store.NewChainRand(sm.cs, ts.Cids()) - - blkmsgs, err := sm.cs.BlockMsgsForTipset(ts) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) - } - - baseFee := blks[0].ParentBaseFee - - return sm.ApplyBlocks(ctx, parentEpoch, pstate, blkmsgs, blks[0].Height, r, em, baseFee, ts) -} - -func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - - return ts.ParentState() -} - func (sm *StateManager) ChainStore() *store.ChainStore { return sm.cs } @@ -637,338 +309,6 @@ func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts * return state.LookupID(addr) } -// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already -// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on -// chain for at least confidence epochs without being reverted before returning. -func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confidence uint64, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - msg, err := sm.cs.GetCMessage(mcid) - if err != nil { - return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) - } - - tsub := sm.cs.SubHeadChanges(ctx) - - head, ok := <-tsub - if !ok { - return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid") - } - - if len(head) != 1 { - return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item") - } - - if head[0].Type != store.HCCurrent { - return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) - } - - r, foundMsg, err := sm.tipsetExecutedMessage(head[0].Val, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - - if r != nil { - return head[0].Val, r, foundMsg, nil - } - - var backTs *types.TipSet - var backRcp *types.MessageReceipt - var backFm cid.Cid - backSearchWait := make(chan struct{}) - go func() { - fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head[0].Val, msg, lookbackLimit, allowReplaced) - if err != nil { - log.Warnf("failed to look back through chain for message: %v", err) - return - } - - backTs = fts - backRcp = r - backFm = foundMsg - close(backSearchWait) - }() - - var candidateTs *types.TipSet - var candidateRcp *types.MessageReceipt - var candidateFm cid.Cid - heightOfHead := head[0].Val.Height() - reverts := map[types.TipSetKey]bool{} - - for { - select { - case notif, ok := <-tsub: - if !ok { - return nil, nil, cid.Undef, ctx.Err() - } - for _, val := range notif { - switch val.Type { - case store.HCRevert: - if val.Val.Equals(candidateTs) { - candidateTs = nil - candidateRcp = nil - candidateFm = cid.Undef - } - if backSearchWait != nil { - reverts[val.Val.Key()] = true - } - case store.HCApply: - if candidateTs != nil && val.Val.Height() >= candidateTs.Height()+abi.ChainEpoch(confidence) { - return candidateTs, candidateRcp, candidateFm, nil - } - r, foundMsg, err := sm.tipsetExecutedMessage(val.Val, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - if r != nil { - if confidence == 0 { - return val.Val, r, foundMsg, err - } - candidateTs = val.Val - candidateRcp = r - candidateFm = foundMsg - } - heightOfHead = val.Val.Height() - } - } - case <-backSearchWait: - // check if we found the message in the chain and that is hasn't been reverted since we started searching - if backTs != nil && !reverts[backTs.Key()] { - // if head is at or past confidence interval, return immediately - if heightOfHead >= backTs.Height()+abi.ChainEpoch(confidence) { - return backTs, backRcp, backFm, nil - } - - // wait for confidence interval - candidateTs = backTs - candidateRcp = backRcp - candidateFm = backFm - } - reverts = nil - backSearchWait = nil - case <-ctx.Done(): - return nil, nil, cid.Undef, ctx.Err() - } - } -} - -func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - msg, err := sm.cs.GetCMessage(mcid) - if err != nil { - return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) - } - - r, foundMsg, err := sm.tipsetExecutedMessage(head, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - - if r != nil { - return head, r, foundMsg, nil - } - - fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head, msg, lookbackLimit, allowReplaced) - - if err != nil { - log.Warnf("failed to look back through chain for message %s", mcid) - return nil, nil, cid.Undef, err - } - - if fts == nil { - return nil, nil, cid.Undef, nil - } - - return fts, r, foundMsg, nil -} - -// searchBackForMsg searches up to limit tipsets backwards from the given -// tipset for a message receipt. -// If limit is -// - 0 then no tipsets are searched -// - 5 then five tipset are searched -// - LookbackNoLimit then there is no limit -func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg, limit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - limitHeight := from.Height() - limit - noLimit := limit == LookbackNoLimit - - cur := from - curActor, err := sm.LoadActor(ctx, m.VMMessage().From, cur) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("failed to load initital tipset") - } - - mFromId, err := sm.LookupID(ctx, m.VMMessage().From, from) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("looking up From id address: %w", err) - } - - mNonce := m.VMMessage().Nonce - - for { - // If we've reached the genesis block, or we've reached the limit of - // how far back to look - if cur.Height() == 0 || !noLimit && cur.Height() <= limitHeight { - // it ain't here! - return nil, nil, cid.Undef, nil - } - - select { - case <-ctx.Done(): - return nil, nil, cid.Undef, nil - default: - } - - // we either have no messages from the sender, or the latest message we found has a lower nonce than the one being searched for, - // either way, no reason to lookback, it ain't there - if curActor == nil || curActor.Nonce == 0 || curActor.Nonce < mNonce { - return nil, nil, cid.Undef, nil - } - - pts, err := sm.cs.LoadTipSet(cur.Parents()) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("failed to load tipset during msg wait searchback: %w", err) - } - - act, err := sm.LoadActor(ctx, mFromId, pts) - actorNoExist := errors.Is(err, types.ErrActorNotFound) - if err != nil && !actorNoExist { - return nil, nil, cid.Cid{}, xerrors.Errorf("failed to load the actor: %w", err) - } - - // check that between cur and parent tipset the nonce fell into range of our message - if actorNoExist || (curActor.Nonce > mNonce && act.Nonce <= mNonce) { - r, foundMsg, err := sm.tipsetExecutedMessage(cur, m.Cid(), m.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("checking for message execution during lookback: %w", err) - } - - if r != nil { - return cur, r, foundMsg, nil - } - } - - cur = pts - curActor = act - } -} - -func (sm *StateManager) tipsetExecutedMessage(ts *types.TipSet, msg cid.Cid, vmm *types.Message, allowReplaced bool) (*types.MessageReceipt, cid.Cid, error) { - // The genesis block did not execute any messages - if ts.Height() == 0 { - return nil, cid.Undef, nil - } - - pts, err := sm.cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, cid.Undef, err - } - - cm, err := sm.cs.MessagesForTipset(pts) - if err != nil { - return nil, cid.Undef, err - } - - for ii := range cm { - // iterate in reverse because we going backwards through the chain - i := len(cm) - ii - 1 - m := cm[i] - - if m.VMMessage().From == vmm.From { // cheaper to just check origin first - if m.VMMessage().Nonce == vmm.Nonce { - if allowReplaced && m.VMMessage().EqualCall(vmm) { - if m.Cid() != msg { - log.Warnw("found message with equal nonce and call params but different CID", - "wanted", msg, "found", m.Cid(), "nonce", vmm.Nonce, "from", vmm.From) - } - - pr, err := sm.cs.GetParentReceipt(ts.Blocks()[0], i) - if err != nil { - return nil, cid.Undef, err - } - return pr, m.Cid(), nil - } - - // this should be that message - return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", - msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) - } - if m.VMMessage().Nonce < vmm.Nonce { - return nil, cid.Undef, nil // don't bother looking further - } - } - } - - return nil, cid.Undef, nil -} - -func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - st := ts.ParentState() - - stateTree, err := sm.StateTree(st) - if err != nil { - return nil, err - } - - var out []address.Address - err = stateTree.ForEach(func(addr address.Address, act *types.Actor) error { - out = append(out, addr) - return nil - }) - if err != nil { - return nil, err - } - - return out, nil -} - -func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { - st, err := sm.ParentState(ts) - if err != nil { - return api.MarketBalance{}, err - } - - act, err := st.GetActor(market.Address) - if err != nil { - return api.MarketBalance{}, err - } - - mstate, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return api.MarketBalance{}, err - } - - addr, err = sm.LookupID(ctx, addr, ts) - if err != nil { - return api.MarketBalance{}, err - } - - var out api.MarketBalance - - et, err := mstate.EscrowTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Escrow, err = et.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) - } - - lt, err := mstate.LockedTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Locked, err = lt.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) - } - - return out, nil -} - func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) error { tschain := []*types.TipSet{ts} for ts.Height() != 0 { @@ -1002,450 +342,6 @@ func (sm *StateManager) SetVMConstructor(nvm func(context.Context, *vm.VMOpts) ( sm.newVM = nvm } -// sets up information about the vesting schedule -func (sm *StateManager) setupGenesisVestingSchedule(ctx context.Context) error { - - gb, err := sm.cs.GetGenesis() - if err != nil { - return xerrors.Errorf("getting genesis block: %w", err) - } - - gts, err := types.NewTipSet([]*types.BlockHeader{gb}) - if err != nil { - return xerrors.Errorf("getting genesis tipset: %w", err) - } - - st, _, err := sm.TipSetState(ctx, gts) - if err != nil { - return xerrors.Errorf("getting genesis tipset state: %w", err) - } - - cst := cbor.NewCborStore(sm.cs.StateBlockstore()) - sTree, err := state.LoadStateTree(cst, st) - if err != nil { - return xerrors.Errorf("loading state tree: %w", err) - } - - gmf, err := getFilMarketLocked(ctx, sTree) - if err != nil { - return xerrors.Errorf("setting up genesis market funds: %w", err) - } - - gp, err := getFilPowerLocked(ctx, sTree) - if err != nil { - return xerrors.Errorf("setting up genesis pledge: %w", err) - } - - sm.genesisMarketFunds = gmf - sm.genesisPledge = gp - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(49_929_341) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - - sm.preIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - InitialBalance: v, - UnlockDuration: k, - PendingTxns: cid.Undef, - } - sm.preIgnitionVesting = append(sm.preIgnitionVesting, ns) - } - - return nil -} - -// sets up information about the vesting schedule post the ignition upgrade -func (sm *StateManager) setupPostIgnitionVesting(ctx context.Context) error { - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(49_929_341) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - - sm.postIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - // In the pre-ignition logic, we incorrectly set this value in Fil, not attoFil, an off-by-10^18 error - InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), - UnlockDuration: k, - PendingTxns: cid.Undef, - // In the pre-ignition logic, the start epoch was 0. This changes in the fork logic of the Ignition upgrade itself. - StartEpoch: build.UpgradeLiftoffHeight, - } - sm.postIgnitionVesting = append(sm.postIgnitionVesting, ns) - } - - return nil -} - -// sets up information about the vesting schedule post the calico upgrade -func (sm *StateManager) setupPostCalicoVesting(ctx context.Context) error { - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 0 days - zeroDays := abi.ChainEpoch(0) - totalsByEpoch[zeroDays] = big.NewInt(10_632_000) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(19_015_887) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - totalsByEpoch[oneYear] = big.Add(totalsByEpoch[oneYear], big.NewInt(9_400_000)) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - totalsByEpoch[threeYears] = big.Add(totalsByEpoch[threeYears], big.NewInt(898_958)) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(9_805_053)) - - sm.postCalicoVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), - UnlockDuration: k, - PendingTxns: cid.Undef, - StartEpoch: build.UpgradeLiftoffHeight, - } - sm.postCalicoVesting = append(sm.postCalicoVesting, ns) - } - - return nil -} - -// GetVestedFunds returns all funds that have "left" actors that are in the genesis state: -// - For Multisigs, it counts the actual amounts that have vested at the given epoch -// - For Accounts, it counts max(currentBalance - genesisBalance, 0). -func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - vf := big.Zero() - if height <= build.UpgradeIgnitionHeight { - for _, v := range sm.preIgnitionVesting { - au := big.Sub(v.InitialBalance, v.AmountLocked(height)) - vf = big.Add(vf, au) - } - } else if height <= build.UpgradeCalicoHeight { - for _, v := range sm.postIgnitionVesting { - // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. - // The start epoch changed in the Ignition upgrade. - au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) - vf = big.Add(vf, au) - } - } else { - for _, v := range sm.postCalicoVesting { - // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. - // The start epoch changed in the Ignition upgrade. - au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) - vf = big.Add(vf, au) - } - } - - // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed - if height <= build.UpgradeAssemblyHeight { - // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch - vf = big.Add(vf, sm.genesisPledge) - // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch - vf = big.Add(vf, sm.genesisMarketFunds) - } - - return vf, nil -} - -func GetFilReserveDisbursed(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - ract, err := st.GetActor(builtin.ReserveAddress) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get reserve actor: %w", err) - } - - // If money enters the reserve actor, this could lead to a negative term - return big.Sub(big.NewFromGo(build.InitialFilReserved), ract.Balance), nil -} - -func GetFilMined(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - ractor, err := st.GetActor(reward.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load reward actor state: %w", err) - } - - rst, err := reward.Load(adt.WrapStore(ctx, st.Store), ractor) - if err != nil { - return big.Zero(), err - } - - return rst.TotalStoragePowerReward() -} - -func getFilMarketLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - act, err := st.GetActor(market.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load market actor: %w", err) - } - - mst, err := market.Load(adt.WrapStore(ctx, st.Store), act) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load market state: %w", err) - } - - return mst.TotalLocked() -} - -func getFilPowerLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - pactor, err := st.GetActor(power.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load power actor: %w", err) - } - - pst, err := power.Load(adt.WrapStore(ctx, st.Store), pactor) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load power state: %w", err) - } - - return pst.TotalLocked() -} - -func (sm *StateManager) GetFilLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - - filMarketLocked, err := getFilMarketLocked(ctx, st) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get filMarketLocked: %w", err) - } - - filPowerLocked, err := getFilPowerLocked(ctx, st) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get filPowerLocked: %w", err) - } - - return types.BigAdd(filMarketLocked, filPowerLocked), nil -} - -func GetFilBurnt(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - burnt, err := st.GetActor(builtin.BurntFundsActorAddr) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load burnt actor: %w", err) - } - - return burnt.Balance, nil -} - -func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - cs, err := sm.GetVMCirculatingSupplyDetailed(ctx, height, st) - if err != nil { - return types.EmptyInt, err - } - - return cs.FilCirculating, err -} - -func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { - sm.genesisMsigLk.Lock() - defer sm.genesisMsigLk.Unlock() - if sm.preIgnitionVesting == nil || sm.genesisPledge.IsZero() || sm.genesisMarketFunds.IsZero() { - err := sm.setupGenesisVestingSchedule(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup pre-ignition vesting schedule: %w", err) - } - } - if sm.postIgnitionVesting == nil { - err := sm.setupPostIgnitionVesting(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-ignition vesting schedule: %w", err) - } - } - if sm.postCalicoVesting == nil { - err := sm.setupPostCalicoVesting(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-calico vesting schedule: %w", err) - } - } - - filVested, err := sm.GetFilVested(ctx, height, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) - } - - filReserveDisbursed := big.Zero() - if height > build.UpgradeAssemblyHeight { - filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) - } - } - - filMined, err := GetFilMined(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filMined: %w", err) - } - - filBurnt, err := GetFilBurnt(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) - } - - filLocked, err := sm.GetFilLocked(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) - } - - ret := types.BigAdd(filVested, filMined) - ret = types.BigAdd(ret, filReserveDisbursed) - ret = types.BigSub(ret, filBurnt) - ret = types.BigSub(ret, filLocked) - - if ret.LessThan(big.Zero()) { - ret = big.Zero() - } - - return api.CirculatingSupply{ - FilVested: filVested, - FilMined: filMined, - FilBurnt: filBurnt, - FilLocked: filLocked, - FilCirculating: ret, - FilReserveDisbursed: filReserveDisbursed, - }, nil -} - -func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - circ := big.Zero() - unCirc := big.Zero() - err := st.ForEach(func(a address.Address, actor *types.Actor) error { - switch { - case actor.Balance.IsZero(): - // Do nothing for zero-balance actors - break - case a == _init.Address || - a == reward.Address || - a == verifreg.Address || - // The power actor itself should never receive funds - a == power.Address || - a == builtin.SystemActorAddr || - a == builtin.CronActorAddr || - a == builtin.BurntFundsActorAddr || - a == builtin.SaftAddress || - a == builtin.ReserveAddress: - - unCirc = big.Add(unCirc, actor.Balance) - - case a == market.Address: - mst, err := market.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - lb, err := mst.TotalLocked() - if err != nil { - return err - } - - circ = big.Add(circ, big.Sub(actor.Balance, lb)) - unCirc = big.Add(unCirc, lb) - - case builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code): - circ = big.Add(circ, actor.Balance) - - case builtin.IsStorageMinerActor(actor.Code): - mst, err := miner.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - ab, err := mst.AvailableBalance(actor.Balance) - - if err == nil { - circ = big.Add(circ, ab) - unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) - } else { - // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) - // In this case, the actor's entire balance is considered "uncirculating" - unCirc = big.Add(unCirc, actor.Balance) - } - - case builtin.IsMultisigActor(actor.Code): - mst, err := multisig.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - lb, err := mst.LockedBalance(height) - if err != nil { - return err - } - - ab := big.Sub(actor.Balance, lb) - circ = big.Add(circ, big.Max(ab, big.Zero())) - unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) - default: - return xerrors.Errorf("unexpected actor: %s", a) - } - - return nil - }) - - if err != nil { - return types.EmptyInt, err - } - - total := big.Add(circ, unCirc) - if !total.Equals(types.TotalFilecoinInt) { - return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) - } - - return circ, nil -} - func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { // The epochs here are the _last_ epoch for every version, or -1 if the // version is disabled. @@ -1456,41 +352,3 @@ func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoc } return sm.latestVersion } - -func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, nil, err - } - - act, err := st.GetActor(addr) - if err != nil { - return nil, nil, err - } - - actState, err := paych.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, nil, err - } - return act, actState, nil -} - -func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, err - } - - act, err := st.GetActor(market.Address) - if err != nil { - return nil, err - } - - actState, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, err - } - return actState, nil -} - -var _ StateManagerAPI = (*StateManager)(nil) diff --git a/chain/stmgr/supply.go b/chain/stmgr/supply.go new file mode 100644 index 000000000..c9475a51e --- /dev/null +++ b/chain/stmgr/supply.go @@ -0,0 +1,473 @@ +package stmgr + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +// sets up information about the vesting schedule +func (sm *StateManager) setupGenesisVestingSchedule(ctx context.Context) error { + + gb, err := sm.cs.GetGenesis() + if err != nil { + return xerrors.Errorf("getting genesis block: %w", err) + } + + gts, err := types.NewTipSet([]*types.BlockHeader{gb}) + if err != nil { + return xerrors.Errorf("getting genesis tipset: %w", err) + } + + st, _, err := sm.TipSetState(ctx, gts) + if err != nil { + return xerrors.Errorf("getting genesis tipset state: %w", err) + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + sTree, err := state.LoadStateTree(cst, st) + if err != nil { + return xerrors.Errorf("loading state tree: %w", err) + } + + gmf, err := getFilMarketLocked(ctx, sTree) + if err != nil { + return xerrors.Errorf("setting up genesis market funds: %w", err) + } + + gp, err := getFilPowerLocked(ctx, sTree) + if err != nil { + return xerrors.Errorf("setting up genesis pledge: %w", err) + } + + sm.genesisMarketFunds = gmf + sm.genesisPledge = gp + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.preIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: v, + UnlockDuration: k, + PendingTxns: cid.Undef, + } + sm.preIgnitionVesting = append(sm.preIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the ignition upgrade +func (sm *StateManager) setupPostIgnitionVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.postIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + // In the pre-ignition logic, we incorrectly set this value in Fil, not attoFil, an off-by-10^18 error + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + // In the pre-ignition logic, the start epoch was 0. This changes in the fork logic of the Ignition upgrade itself. + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postIgnitionVesting = append(sm.postIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the calico upgrade +func (sm *StateManager) setupPostCalicoVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 0 days + zeroDays := abi.ChainEpoch(0) + totalsByEpoch[zeroDays] = big.NewInt(10_632_000) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(19_015_887) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + totalsByEpoch[oneYear] = big.Add(totalsByEpoch[oneYear], big.NewInt(9_400_000)) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + totalsByEpoch[threeYears] = big.Add(totalsByEpoch[threeYears], big.NewInt(898_958)) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(9_805_053)) + + sm.postCalicoVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postCalicoVesting = append(sm.postCalicoVesting, ns) + } + + return nil +} + +// GetVestedFunds returns all funds that have "left" actors that are in the genesis state: +// - For Multisigs, it counts the actual amounts that have vested at the given epoch +// - For Accounts, it counts max(currentBalance - genesisBalance, 0). +func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + vf := big.Zero() + if height <= build.UpgradeIgnitionHeight { + for _, v := range sm.preIgnitionVesting { + au := big.Sub(v.InitialBalance, v.AmountLocked(height)) + vf = big.Add(vf, au) + } + } else if height <= build.UpgradeCalicoHeight { + for _, v := range sm.postIgnitionVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } else { + for _, v := range sm.postCalicoVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } + + // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed + if height <= build.UpgradeAssemblyHeight { + // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch + vf = big.Add(vf, sm.genesisPledge) + // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch + vf = big.Add(vf, sm.genesisMarketFunds) + } + + return vf, nil +} + +func GetFilReserveDisbursed(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ract, err := st.GetActor(builtin.ReserveAddress) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get reserve actor: %w", err) + } + + // If money enters the reserve actor, this could lead to a negative term + return big.Sub(big.NewFromGo(build.InitialFilReserved), ract.Balance), nil +} + +func GetFilMined(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ractor, err := st.GetActor(reward.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load reward actor state: %w", err) + } + + rst, err := reward.Load(adt.WrapStore(ctx, st.Store), ractor) + if err != nil { + return big.Zero(), err + } + + return rst.TotalStoragePowerReward() +} + +func getFilMarketLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + act, err := st.GetActor(market.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market actor: %w", err) + } + + mst, err := market.Load(adt.WrapStore(ctx, st.Store), act) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market state: %w", err) + } + + return mst.TotalLocked() +} + +func getFilPowerLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + pactor, err := st.GetActor(power.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, st.Store), pactor) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power state: %w", err) + } + + return pst.TotalLocked() +} + +func (sm *StateManager) GetFilLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + + filMarketLocked, err := getFilMarketLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filMarketLocked: %w", err) + } + + filPowerLocked, err := getFilPowerLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filPowerLocked: %w", err) + } + + return types.BigAdd(filMarketLocked, filPowerLocked), nil +} + +func GetFilBurnt(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + burnt, err := st.GetActor(builtin.BurntFundsActorAddr) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load burnt actor: %w", err) + } + + return burnt.Balance, nil +} + +func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + cs, err := sm.GetVMCirculatingSupplyDetailed(ctx, height, st) + if err != nil { + return types.EmptyInt, err + } + + return cs.FilCirculating, err +} + +func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { + sm.genesisMsigLk.Lock() + defer sm.genesisMsigLk.Unlock() + if sm.preIgnitionVesting == nil || sm.genesisPledge.IsZero() || sm.genesisMarketFunds.IsZero() { + err := sm.setupGenesisVestingSchedule(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup pre-ignition vesting schedule: %w", err) + } + } + if sm.postIgnitionVesting == nil { + err := sm.setupPostIgnitionVesting(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-ignition vesting schedule: %w", err) + } + } + if sm.postCalicoVesting == nil { + err := sm.setupPostCalicoVesting(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-calico vesting schedule: %w", err) + } + } + + filVested, err := sm.GetFilVested(ctx, height, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) + } + + filReserveDisbursed := big.Zero() + if height > build.UpgradeAssemblyHeight { + filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) + } + } + + filMined, err := GetFilMined(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filMined: %w", err) + } + + filBurnt, err := GetFilBurnt(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) + } + + filLocked, err := sm.GetFilLocked(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) + } + + ret := types.BigAdd(filVested, filMined) + ret = types.BigAdd(ret, filReserveDisbursed) + ret = types.BigSub(ret, filBurnt) + ret = types.BigSub(ret, filLocked) + + if ret.LessThan(big.Zero()) { + ret = big.Zero() + } + + return api.CirculatingSupply{ + FilVested: filVested, + FilMined: filMined, + FilBurnt: filBurnt, + FilLocked: filLocked, + FilCirculating: ret, + FilReserveDisbursed: filReserveDisbursed, + }, nil +} + +func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + circ := big.Zero() + unCirc := big.Zero() + err := st.ForEach(func(a address.Address, actor *types.Actor) error { + switch { + case actor.Balance.IsZero(): + // Do nothing for zero-balance actors + break + case a == _init.Address || + a == reward.Address || + a == verifreg.Address || + // The power actor itself should never receive funds + a == power.Address || + a == builtin.SystemActorAddr || + a == builtin.CronActorAddr || + a == builtin.BurntFundsActorAddr || + a == builtin.SaftAddress || + a == builtin.ReserveAddress: + + unCirc = big.Add(unCirc, actor.Balance) + + case a == market.Address: + mst, err := market.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.TotalLocked() + if err != nil { + return err + } + + circ = big.Add(circ, big.Sub(actor.Balance, lb)) + unCirc = big.Add(unCirc, lb) + + case builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code): + circ = big.Add(circ, actor.Balance) + + case builtin.IsStorageMinerActor(actor.Code): + mst, err := miner.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + ab, err := mst.AvailableBalance(actor.Balance) + + if err == nil { + circ = big.Add(circ, ab) + unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) + } else { + // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) + // In this case, the actor's entire balance is considered "uncirculating" + unCirc = big.Add(unCirc, actor.Balance) + } + + case builtin.IsMultisigActor(actor.Code): + mst, err := multisig.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.LockedBalance(height) + if err != nil { + return err + } + + ab := big.Sub(actor.Balance, lb) + circ = big.Add(circ, big.Max(ab, big.Zero())) + unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) + default: + return xerrors.Errorf("unexpected actor: %s", a) + } + + return nil + }) + + if err != nil { + return types.EmptyInt, err + } + + total := big.Add(circ, unCirc) + if !total.Equals(types.TotalFilecoinInt) { + return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) + } + + return circ, nil +} diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index d2a2c6e60..8bcdbd755 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -35,6 +35,7 @@ import ( init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/beacon" @@ -729,3 +730,93 @@ func MakeMsgGasCost(msg *types.Message, ret *vm.ApplyRet) api.MsgGasCost { TotalCost: big.Sub(msg.RequiredFunds(), ret.GasCosts.Refund), } } + +func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { + stateTree, err := sm.StateTree(sm.parentState(ts)) + if err != nil { + return nil, err + } + + var out []address.Address + err = stateTree.ForEach(func(addr address.Address, act *types.Actor) error { + out = append(out, addr) + return nil + }) + if err != nil { + return nil, err + } + + return out, nil +} + +func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, nil, err + } + + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil +} + +func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + + act, err := st.GetActor(market.Address) + if err != nil { + return nil, err + } + + actState, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, err + } + return actState, nil +} + +func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { + mstate, err := sm.GetMarketState(ctx, ts) + if err != nil { + return api.MarketBalance{}, err + } + + addr, err = sm.LookupID(ctx, addr, ts) + if err != nil { + return api.MarketBalance{}, err + } + + var out api.MarketBalance + + et, err := mstate.EscrowTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Escrow, err = et.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) + } + + lt, err := mstate.LockedTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Locked, err = lt.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) + } + + return out, nil +} + +var _ StateManagerAPI = (*StateManager)(nil) From 48cddd3644f93a7f5069dc82893498b22f798ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 27 Jul 2021 14:25:28 +0100 Subject: [PATCH 72/98] add a super verbose -vv flag to lotus and lotus-miner. --- cli/util/api.go | 28 ++++++++++++++++++++++++++++ cli/util/verbose.go | 16 ++++++++++++++++ cmd/lotus-miner/main.go | 2 ++ cmd/lotus/main.go | 2 ++ 4 files changed, 48 insertions(+) create mode 100644 cli/util/verbose.go diff --git a/cli/util/api.go b/cli/util/api.go index ecd2e927f..b7210a18f 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -146,6 +146,10 @@ func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http. return "", nil, xerrors.Errorf("could not get DialArgs: %w", err) } + if IsSuperVerbose { + _, _ = fmt.Fprintf(ctx.App.Writer, "using raw API %s endpoint: %s\n", version, addr) + } + return addr, ainfo.AuthHeader(), nil } @@ -185,6 +189,10 @@ func GetFullNodeAPI(ctx *cli.Context) (v0api.FullNode, jsonrpc.ClientCloser, err return nil, nil, err } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v0 endpoint:", addr) + } + return client.NewFullNodeRPCV0(ctx.Context, addr, headers) } @@ -198,6 +206,10 @@ func GetFullNodeAPIV1(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientCloser, e return nil, nil, err } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v1 endpoint:", addr) + } + return client.NewFullNodeRPCV1(ctx.Context, addr, headers) } @@ -242,6 +254,10 @@ func GetStorageMinerAPI(ctx *cli.Context, opts ...GetStorageMinerOption) (api.St addr = u.String() } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using miner API v0 endpoint:", addr) + } + return client.NewStorageMinerRPCV0(ctx.Context, addr, headers) } @@ -251,6 +267,10 @@ func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) { return nil, nil, err } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using worker API v0 endpoint:", addr) + } + return client.NewWorkerRPCV0(ctx.Context, addr, headers) } @@ -260,6 +280,10 @@ func GetGatewayAPI(ctx *cli.Context) (api.Gateway, jsonrpc.ClientCloser, error) return nil, nil, err } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using gateway API v1 endpoint:", addr) + } + return client.NewGatewayRPCV1(ctx.Context, addr, headers) } @@ -269,6 +293,10 @@ func GetGatewayAPIV0(ctx *cli.Context) (v0api.Gateway, jsonrpc.ClientCloser, err return nil, nil, err } + if IsSuperVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using gateway API v0 endpoint:", addr) + } + return client.NewGatewayRPCV0(ctx.Context, addr, headers) } diff --git a/cli/util/verbose.go b/cli/util/verbose.go new file mode 100644 index 000000000..b1eb1a9be --- /dev/null +++ b/cli/util/verbose.go @@ -0,0 +1,16 @@ +package cliutil + +import "github.com/urfave/cli/v2" + +// IsSuperVerbose is a global var signalling if we're running in super verbose +// mode or not (default: false). +var IsSuperVerbose bool + +// FlagSuperVerbose enables super verbose mode, which is useful when debugging +// the CLI itself. It should be included as a flag on the top-level command +// (e.g. lotus -vv, lotus-miner -vv). +var FlagSuperVerbose = &cli.BoolFlag{ + Name: "vv", + Usage: "enables super verbose mode, useful for debugging the CLI", + Destination: &IsSuperVerbose, +} diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index c555531d6..c14f85b93 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/fatih/color" + cliutil "github.com/filecoin-project/lotus/cli/util" logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" "go.opencensus.io/trace" @@ -105,6 +106,7 @@ func main() { Value: "~/.lotusminer", // TODO: Consider XDG_DATA_HOME Usage: fmt.Sprintf("Specify miner repo path. flag(%s) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON", FlagMinerRepoDeprecation), }, + cliutil.FlagSuperVerbose, }, Commands: append(local, lcli.CommonCommands...), diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index d803cce1e..6719116ce 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" lcli "github.com/filecoin-project/lotus/cli" + cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/filecoin-project/lotus/lib/lotuslog" "github.com/filecoin-project/lotus/lib/tracing" "github.com/filecoin-project/lotus/node/repo" @@ -81,6 +82,7 @@ func main() { Name: "force-send", Usage: "if true, will ignore pre-send checks", }, + cliutil.FlagSuperVerbose, }, Commands: append(local, lcli.Commands...), From 9bd312881dd3135c8506a8258963f66ec67d6df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:30:23 +0200 Subject: [PATCH 73/98] VMSys doesn't belong in chainstore --- chain/gen/gen.go | 4 ++-- chain/gen/genesis/genesis.go | 10 +++++----- chain/gen/genesis/miners.go | 4 ++-- chain/stmgr/call.go | 4 ++-- chain/stmgr/execute.go | 2 +- chain/stmgr/stmgr.go | 12 +++++++----- chain/stmgr/utils.go | 6 +++--- chain/store/index_test.go | 2 +- chain/store/store.go | 10 +--------- chain/store/store_test.go | 6 +++--- cmd/lotus-bench/import.go | 4 ++-- cmd/lotus-shed/balances.go | 8 ++++---- cmd/lotus-shed/export.go | 2 +- cmd/lotus-shed/genesis-verify.go | 6 ++---- cmd/lotus-shed/miner-types.go | 4 +--- cmd/lotus-shed/pruning.go | 4 +--- cmd/lotus-sim/simulation/node.go | 6 +++--- cmd/lotus/daemon.go | 4 ++-- conformance/driver.go | 6 +++--- node/modules/chain.go | 8 ++++---- node/modules/stmgr.go | 5 +++-- 21 files changed, 53 insertions(+), 64 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 424ee6edc..6b30f99ee 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -233,7 +233,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS return nil, xerrors.Errorf("make genesis block failed: %w", err) } - cs := store.NewChainStore(bs, bs, ds, sys, j) + cs := store.NewChainStore(bs, bs, ds, j) genfb := &types.FullBlock{Header: genb.Genesis} gents := store.NewFullTipSet([]*types.FullBlock{genfb}) @@ -247,7 +247,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, xerrors.Errorf("initing stmgr: %w", err) } diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 6dec3fea6..0f863ff1d 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -471,7 +471,7 @@ func createMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state return nil } -func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address, nv network.Version) (cid.Cid, error) { +func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address, nv network.Version) (cid.Cid, error) { verifNeeds := make(map[address.Address]abi.PaddedPieceSize) var sum abi.PaddedPieceSize @@ -480,7 +480,7 @@ func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot ci Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), - Syscalls: mkFakedSigSyscalls(cs.VMSys()), + Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: nil, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { return nv @@ -559,15 +559,15 @@ func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blocksto } // temp chainstore - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), sys, j) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), j) // Verify PreSealed Data - stateroot, err = VerifyPreSealedData(ctx, cs, stateroot, template, keyIDs, template.NetworkVersion) + stateroot, err = VerifyPreSealedData(ctx, cs, sys, stateroot, template, keyIDs, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("failed to verify presealed data: %w", err) } - stateroot, err = SetupStorageMiners(ctx, cs, stateroot, template.Miners, template.NetworkVersion) + stateroot, err = SetupStorageMiners(ctx, cs, sys, stateroot, template.Miners, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("setup miners failed: %w", err) } diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index e6f17d677..38c769696 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -78,7 +78,7 @@ func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder { } // Note: Much of this is brittle, if the methodNum / param / return changes, it will break things -func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid, miners []genesis.Miner, nv network.Version) (cid.Cid, error) { +func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, sroot cid.Cid, miners []genesis.Miner, nv network.Version) (cid.Cid, error) { cst := cbor.NewCborStore(cs.StateBlockstore()) av := actors.VersionForNetwork(nv) @@ -92,7 +92,7 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), - Syscalls: mkFakedSigSyscalls(cs.VMSys()), + Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: csc, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { return nv diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index dc6da0f9c..caa815132 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -64,7 +64,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. Epoch: pheight + 1, Rand: store.NewChainRand(sm.cs, ts.Cids()), Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: types.NewInt(0), @@ -179,7 +179,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri Epoch: ts.Height() + 1, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index 9194f711e..de7ca2894 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -42,7 +42,7 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEp Epoch: epoch, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: baseFee, diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 8f03b6bf9..09b8284dd 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -75,6 +75,7 @@ type StateManager struct { stlk sync.Mutex genesisMsigLk sync.Mutex newVM func(context.Context, *vm.VMOpts) (*vm.VM, error) + syscalls vm.SyscallBuilder preIgnitionVesting []msig0.State postIgnitionVesting []msig0.State postCalicoVesting []msig0.State @@ -91,15 +92,15 @@ type treeCache struct { tree *state.StateTree } -func NewStateManager(cs *store.ChainStore) *StateManager { - sm, err := NewStateManagerWithUpgradeSchedule(cs, DefaultUpgradeSchedule()) +func NewStateManager(cs *store.ChainStore, sys vm.SyscallBuilder) *StateManager { + sm, err := NewStateManagerWithUpgradeSchedule(cs, sys, DefaultUpgradeSchedule()) if err != nil { panic(fmt.Sprintf("default upgrade schedule is invalid: %s", err)) } return sm } -func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule) (*StateManager, error) { +func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, sys vm.SyscallBuilder, us UpgradeSchedule) (*StateManager, error) { // If we have upgrades, make sure they're in-order and make sense. if err := us.Validate(); err != nil { return nil, err @@ -141,6 +142,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule stateMigrations: stateMigrations, expensiveUpgrades: expensiveUpgrades, newVM: vm.NewVM, + syscalls: sys, cs: cs, stCache: make(map[string][]cid.Cid), tCache: treeCache{ @@ -151,8 +153,8 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule }, nil } -func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { - sm, err := NewStateManagerWithUpgradeSchedule(cs, us) +func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, sys vm.SyscallBuilder, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { + sm, err := NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, err } diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 8bcdbd755..f54904aaa 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -357,7 +357,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, Epoch: height, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, @@ -700,8 +700,8 @@ func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Add return true, nil } -func CheckTotalFIL(ctx context.Context, sm *StateManager, ts *types.TipSet) (abi.TokenAmount, error) { - str, err := state.LoadStateTree(sm.ChainStore().ActorStore(ctx), ts.ParentState()) +func CheckTotalFIL(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) (abi.TokenAmount, error) { + str, err := state.LoadStateTree(cs.ActorStore(ctx), ts.ParentState()) if err != nil { return abi.TokenAmount{}, err } diff --git a/chain/store/index_test.go b/chain/store/index_test.go index 447071901..b74bc835b 100644 --- a/chain/store/index_test.go +++ b/chain/store/index_test.go @@ -31,7 +31,7 @@ func TestIndexSeeks(t *testing.T) { ctx := context.TODO() nbs := blockstore.NewMemorySync() - cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil, nil) + cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil) defer cs.Close() //nolint:errcheck _, err = cs.Import(bytes.NewReader(gencar)) diff --git a/chain/store/store.go b/chain/store/store.go index b81bcf61e..9715569ac 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -19,7 +19,6 @@ import ( bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/metrics" @@ -123,8 +122,6 @@ type ChainStore struct { mmCache *lru.ARCCache tsCache *lru.ARCCache - vmcalls vm.SyscallBuilder - evtTypes [1]journal.EventType journal journal.Journal @@ -132,7 +129,7 @@ type ChainStore struct { wg sync.WaitGroup } -func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, vmcalls vm.SyscallBuilder, j journal.Journal) *ChainStore { +func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, j journal.Journal) *ChainStore { c, _ := lru.NewARC(DefaultMsgMetaCacheSize) tsc, _ := lru.NewARC(DefaultTipSetCacheSize) if j == nil { @@ -152,7 +149,6 @@ func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dsto tipsets: make(map[abi.ChainEpoch][]cid.Cid), mmCache: c, tsCache: tsc, - vmcalls: vmcalls, cancelFn: cancel, journal: j, } @@ -1094,10 +1090,6 @@ func (cs *ChainStore) ActorStore(ctx context.Context) adt.Store { return ActorStore(ctx, cs.stateBlockstore) } -func (cs *ChainStore) VMSys() vm.SyscallBuilder { - return cs.vmcalls -} - func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { var out []*types.FullBlock diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 62a0430e3..898d590d0 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -70,7 +70,7 @@ func BenchmarkGetRandomness(b *testing.B) { b.Fatal(err) } - cs := store.NewChainStore(bs, bs, mds, nil, nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck b.ResetTimer() @@ -105,7 +105,7 @@ func TestChainExportImport(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) @@ -140,7 +140,7 @@ func TestChainExportImportFull(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 4b464bebe..d8ef57138 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -253,10 +253,10 @@ var importBenchCmd = &cli.Command{ } metadataDs := datastore.NewMapDatastore() - cs := store.NewChainStore(bs, bs, metadataDs, vm.Syscalls(verifier), nil) + cs := store.NewChainStore(bs, bs, metadataDs, nil) defer cs.Close() //nolint:errcheck - stm := stmgr.NewStateManager(cs) + stm := stmgr.NewStateManager(cs, vm.Syscalls(verifier)) var carFile *os.File // open the CAR file if one is provided. diff --git a/cmd/lotus-shed/balances.go b/cmd/lotus-shed/balances.go index 87530c666..3a158483f 100644 --- a/cmd/lotus-shed/balances.go +++ b/cmd/lotus-shed/balances.go @@ -510,13 +510,13 @@ var chainBalanceStateCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) tree, err := state.LoadStateTree(cst, sroot) if err != nil { @@ -731,13 +731,13 @@ var chainPledgeCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) state, err := state.LoadStateTree(cst, sroot) if err != nil { diff --git a/cmd/lotus-shed/export.go b/cmd/lotus-shed/export.go index e711ba2bb..dc5cc3bd2 100644 --- a/cmd/lotus-shed/export.go +++ b/cmd/lotus-shed/export.go @@ -90,7 +90,7 @@ var exportChainCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, nil, nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck if err := cs.Load(); err != nil { diff --git a/cmd/lotus-shed/genesis-verify.go b/cmd/lotus-shed/genesis-verify.go index 32e4e14ad..c00ce2c7f 100644 --- a/cmd/lotus-shed/genesis-verify.go +++ b/cmd/lotus-shed/genesis-verify.go @@ -52,7 +52,7 @@ var genesisVerifyCmd = &cli.Command{ } bs := blockstore.FromDatastore(datastore.NewMapDatastore()) - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck cf := cctx.Args().Get(0) @@ -66,9 +66,7 @@ var genesisVerifyCmd = &cli.Command{ return err } - sm := stmgr.NewStateManager(cs) - - total, err := stmgr.CheckTotalFIL(context.TODO(), sm, ts) + total, err := stmgr.CheckTotalFIL(context.TODO(), cs, ts) if err != nil { return err } diff --git a/cmd/lotus-shed/miner-types.go b/cmd/lotus-shed/miner-types.go index 19a30c4b9..491a77aa0 100644 --- a/cmd/lotus-shed/miner-types.go +++ b/cmd/lotus-shed/miner-types.go @@ -15,8 +15,6 @@ import ( "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/repo" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" "github.com/filecoin-project/specs-actors/v4/actors/util/adt" @@ -76,7 +74,7 @@ var minerTypesCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) diff --git a/cmd/lotus-shed/pruning.go b/cmd/lotus-shed/pruning.go index 1afe76c4d..1b57a1cd4 100644 --- a/cmd/lotus-shed/pruning.go +++ b/cmd/lotus-shed/pruning.go @@ -13,8 +13,6 @@ import ( badgerbs "github.com/filecoin-project/lotus/blockstore/badger" "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/repo" ) @@ -169,7 +167,7 @@ var stateTreePruneCmd = &cli.Command{ return nil } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck if err := cs.Load(); err != nil { diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 5b8bf2bf9..c2a497bcb 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -61,7 +61,7 @@ func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { } return &Node{ repo: lr, - Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), + Chainstore: store.NewChainStore(bs, bs, ds, nil), MetadataDS: ds, Blockstore: bs, }, err @@ -105,7 +105,7 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { if err != nil { return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err) } - sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) + sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, vm.Syscalls(mock.Verifier), us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -127,7 +127,7 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) sim := &Simulation{ name: name, Node: nd, - StateManager: stmgr.NewStateManager(nd.Chainstore), + StateManager: stmgr.NewStateManager(nd.Chainstore, vm.Syscalls(mock.Verifier)), stages: stages, } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 0d5961aae..486ac8ed7 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -481,7 +481,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return xerrors.Errorf("failed to open journal: %w", err) } - cst := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), j) + cst := store.NewChainStore(bs, bs, mds, j) defer cst.Close() //nolint:errcheck log.Infof("importing chain from %s...", fname) @@ -517,7 +517,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return err } - stm := stmgr.NewStateManager(cst) + stm := stmgr.NewStateManager(cst, vm.Syscalls(ffiwrapper.ProofVerifier)) if !snapshot { log.Infof("validating imported chain...") diff --git a/conformance/driver.go b/conformance/driver.go index c7fc0d6c4..0b3d42644 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -101,8 +101,8 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params tipset = params.Tipset syscalls = vm.Syscalls(ffiwrapper.ProofVerifier) - cs = store.NewChainStore(bs, bs, ds, syscalls, nil) - sm = stmgr.NewStateManager(cs) + cs = store.NewChainStore(bs, bs, ds, nil) + sm = stmgr.NewStateManager(cs, syscalls) ) if params.Rand == nil { @@ -196,7 +196,7 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP // dummy state manager; only to reference the GetNetworkVersion method, // which does not depend on state. - sm := stmgr.NewStateManager(nil) + sm := stmgr.NewStateManager(nil, nil) vmOpts := &vm.VMOpts{ StateBase: params.Preroot, diff --git a/node/modules/chain.go b/node/modules/chain.go index c4017b8c0..a0e7f2f51 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -72,8 +72,8 @@ func MessagePool(lc fx.Lifecycle, mpp messagepool.Provider, ds dtypes.MetadataDS return mp, nil } -func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlockstore, ds dtypes.MetadataDS, basebs dtypes.BaseBlockstore, syscalls vm.SyscallBuilder, j journal.Journal) *store.ChainStore { - chain := store.NewChainStore(cbs, sbs, ds, syscalls, j) +func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlockstore, ds dtypes.MetadataDS, basebs dtypes.BaseBlockstore, j journal.Journal) *store.ChainStore { + chain := store.NewChainStore(cbs, sbs, ds, j) if err := chain.Load(); err != nil { log.Warnf("loading chain state from disk: %s", err) @@ -100,14 +100,14 @@ func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlo return chain } -func NetworkName(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, us stmgr.UpgradeSchedule, _ dtypes.AfterGenesisSet) (dtypes.NetworkName, error) { +func NetworkName(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, syscalls vm.SyscallBuilder, us stmgr.UpgradeSchedule, _ dtypes.AfterGenesisSet) (dtypes.NetworkName, error) { if !build.Devnet { return "testnetnet", nil } ctx := helpers.LifecycleCtx(mctx, lc) - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, syscalls, us) if err != nil { return "", err } diff --git a/node/modules/stmgr.go b/node/modules/stmgr.go index 9d3917b85..af53457f9 100644 --- a/node/modules/stmgr.go +++ b/node/modules/stmgr.go @@ -1,14 +1,15 @@ package modules import ( + "github.com/filecoin-project/lotus/chain/vm" "go.uber.org/fx" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" ) -func StateManager(lc fx.Lifecycle, cs *store.ChainStore, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) +func StateManager(lc fx.Lifecycle, cs *store.ChainStore, sys vm.SyscallBuilder, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, err } From e7d73cbe56eb328994c60a5206d3d2c7ea38c853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:34:39 +0200 Subject: [PATCH 74/98] vm: Remove unused ActorBalance --- chain/store/store.go | 2 +- chain/vm/invoker.go | 2 +- chain/vm/vm.go | 9 --------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/chain/store/store.go b/chain/store/store.go index 9715569ac..df5936c37 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -119,7 +119,7 @@ type ChainStore struct { reorgCh chan<- reorg reorgNotifeeCh chan ReorgNotifee - mmCache *lru.ARCCache + mmCache *lru.ARCCache // msg meta cache (mh.Messages -> secp, bls []cid) tsCache *lru.ARCCache evtTypes [1]journal.EventType diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index e4b154031..6bca8e9ac 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -228,7 +228,7 @@ func DumpActorState(act *types.Actor, b []byte) (interface{}, error) { return nil, nil } - i := NewActorRegistry() // TODO: register builtins in init block + i := NewActorRegistry() actInfo, ok := i.actors[act.Code] if !ok { diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 2746d5f17..84f57ec9b 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -641,15 +641,6 @@ func (vm *VM) ShouldBurn(ctx context.Context, st *state.StateTree, msg *types.Me return true, nil } -func (vm *VM) ActorBalance(addr address.Address) (types.BigInt, aerrors.ActorError) { - act, err := vm.cstate.GetActor(addr) - if err != nil { - return types.EmptyInt, aerrors.Absorb(err, 1, "failed to find actor") - } - - return act.Balance, nil -} - type vmFlushKey struct{} func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { From ce82f2827b18db0eb706e0b4e46a3885f9285db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:43:03 +0200 Subject: [PATCH 75/98] stmgr: Put actor state accessors in actors.go --- chain/stmgr/actors.go | 551 ++++++++++++++++++++++++++++++++ chain/stmgr/utils.go | 715 ++++++------------------------------------ 2 files changed, 640 insertions(+), 626 deletions(-) create mode 100644 chain/stmgr/actors.go diff --git a/chain/stmgr/actors.go b/chain/stmgr/actors.go new file mode 100644 index 000000000..0c1e219c8 --- /dev/null +++ b/chain/stmgr/actors.go @@ -0,0 +1,551 @@ +package stmgr + +import ( + "bytes" + "context" + "os" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + cid "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" +) + +func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { + state, err := sm.StateTree(st) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load state tree: %w", err) + } + act, err := state.GetActor(maddr) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + info, err := mas.Info() + if err != nil { + return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) + } + + return vm.ResolveToKeyAddr(state, sm.cs.ActorStore(ctx), info.Worker) +} + +func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { + return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) +} + +func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, bool, error) { + act, err := sm.LoadActorRaw(ctx, power.Address, st) + if err != nil { + return power.Claim{}, power.Claim{}, false, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) + } + + pas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + tpow, err := pas.TotalPower() + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + var mpow power.Claim + var minpow bool + if maddr != address.Undef { + var found bool + mpow, found, err = pas.MinerPower(maddr) + if err != nil || !found { + return power.Claim{}, tpow, false, err + } + + minpow, err = pas.MinerNominalPowerMeetsConsensusMinimum(maddr) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + } + + return mpow, tpow, minpow, nil +} + +func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorPreCommitOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetPrecommittedSector(sid) +} + +func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetSector(sid) +} + +func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) { + act, err := sm.LoadActorRaw(ctx, maddr, st) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + var provingSectors bitfield.BitField + if nv < network.Version7 { + allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) + if err != nil { + return nil, xerrors.Errorf("get all sectors: %w", err) + } + + faultySectors, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) + if err != nil { + return nil, xerrors.Errorf("get faulty sectors: %w", err) + } + + provingSectors, err = bitfield.SubtractBitField(allSectors, faultySectors) + if err != nil { + return nil, xerrors.Errorf("calc proving sectors: %w", err) + } + } else { + provingSectors, err = miner.AllPartSectors(mas, miner.Partition.ActiveSectors) + if err != nil { + return nil, xerrors.Errorf("get active sectors sectors: %w", err) + } + } + + numProvSect, err := provingSectors.Count() + if err != nil { + return nil, xerrors.Errorf("failed to count bits: %w", err) + } + + // TODO(review): is this right? feels fishy to me + if numProvSect == 0 { + return nil, nil + } + + info, err := mas.Info() + if err != nil { + return nil, xerrors.Errorf("getting miner info: %w", err) + } + + mid, err := address.IDFromAddress(maddr) + if err != nil { + return nil, xerrors.Errorf("getting miner ID: %w", err) + } + + proofType, err := miner.WinningPoStProofTypeFromWindowPoStProofType(nv, info.WindowPoStProofType) + if err != nil { + return nil, xerrors.Errorf("determining winning post proof type: %w", err) + } + + ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, proofType, abi.ActorID(mid), rand, numProvSect) + if err != nil { + return nil, xerrors.Errorf("generating winning post challenges: %w", err) + } + + iter, err := provingSectors.BitIterator() + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + + // Select winning sectors by _index_ in the all-sectors bitfield. + selectedSectors := bitfield.New() + prev := uint64(0) + for _, n := range ids { + sno, err := iter.Nth(n - prev) + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + selectedSectors.Set(sno) + prev = n + } + + sectors, err := mas.LoadSectors(&selectedSectors) + if err != nil { + return nil, xerrors.Errorf("loading proving sectors: %w", err) + } + + out := make([]builtin.SectorInfo, len(sectors)) + for i, sinfo := range sectors { + out[i] = builtin.SectorInfo{ + SealProof: sinfo.SealProof, + SectorNumber: sinfo.SectorNumber, + SealedCID: sinfo.SealedCID, + } + } + + return out, nil +} + +func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("failed to load power actor: %w", err) + } + + spas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return false, xerrors.Errorf("failed to load power actor state: %w", err) + } + + _, ok, err := spas.MinerPower(maddr) + if err != nil { + return false, xerrors.Errorf("getting miner power: %w", err) + } + + if !ok { + return true, nil + } + + return false, nil +} + +func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { + act, err := sm.LoadActor(ctx, market.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor: %w", err) + } + + state, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor state: %w", err) + } + + proposals, err := state.Proposals() + if err != nil { + return nil, err + } + + proposal, found, err := proposals.Get(dealID) + + if err != nil { + return nil, err + } else if !found { + return nil, xerrors.Errorf( + "deal %d not found "+ + "- deal may not have completed sealing before deal proposal "+ + "start epoch, or deal may have been slashed", + dealID) + } + + states, err := state.States() + if err != nil { + return nil, err + } + + st, found, err := states.Get(dealID) + if err != nil { + return nil, err + } + + if !found { + st = market.EmptyDealState() + } + + return &api.MarketDeal{ + Proposal: *proposal, + State: *st, + }, nil +} + +func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor: %w", err) + } + + powState, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor state: %w", err) + } + + return powState.ListAllMiners() +} + +func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv ffiwrapper.Verifier) (*api.MiningBaseInfo, error) { + ts, err := sm.ChainStore().LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) + } + + prev, err := sm.ChainStore().GetLatestBeaconEntry(ts) + if err != nil { + if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { + return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) + } + + prev = &types.BeaconEntry{} + } + + entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, round, ts.Height(), *prev) + if err != nil { + return nil, err + } + + rbase := *prev + if len(entries) > 0 { + rbase = entries[len(entries)-1] + } + + lbts, lbst, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + if err != nil { + return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) + } + + act, err := sm.LoadActorRaw(ctx, maddr, lbst) + if xerrors.Is(err, types.ErrActorNotFound) { + _, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("loading miner in current state: %w", err) + } + + return nil, nil + } + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + buf := new(bytes.Buffer) + if err := maddr.MarshalCBOR(buf); err != nil { + return nil, xerrors.Errorf("failed to marshal miner address: %w", err) + } + + prand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) + if err != nil { + return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) + } + + nv := sm.GetNtwkVersion(ctx, ts.Height()) + + sectors, err := GetSectorsForWinningPoSt(ctx, nv, pv, sm, lbst, maddr, prand) + if err != nil { + return nil, xerrors.Errorf("getting winning post proving set: %w", err) + } + + if len(sectors) == 0 { + return nil, nil + } + + mpow, tpow, _, err := GetPowerRaw(ctx, sm, lbst, maddr) + if err != nil { + return nil, xerrors.Errorf("failed to get power: %w", err) + } + + info, err := mas.Info() + if err != nil { + return nil, err + } + + worker, err := sm.ResolveToKeyAddress(ctx, info.Worker, ts) + if err != nil { + return nil, xerrors.Errorf("resolving worker address: %w", err) + } + + // TODO: Not ideal performance...This method reloads miner and power state (already looked up here and in GetPowerRaw) + eligible, err := MinerEligibleToMine(ctx, sm, maddr, ts, lbts) + if err != nil { + return nil, xerrors.Errorf("determining miner eligibility: %w", err) + } + + return &api.MiningBaseInfo{ + MinerPower: mpow.QualityAdjPower, + NetworkPower: tpow.QualityAdjPower, + Sectors: sectors, + WorkerKey: worker, + SectorSize: info.SectorSize, + PrevBeaconEntry: *prev, + BeaconEntries: entries, + EligibleForMining: eligible, + }, nil +} + +func minerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { + pact, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + ps, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + return ps.MinerNominalPowerMeetsConsensusMinimum(addr) +} + +func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Address, baseTs *types.TipSet, lookbackTs *types.TipSet) (bool, error) { + hmp, err := minerHasMinPower(ctx, sm, addr, lookbackTs) + + // TODO: We're blurring the lines between a "runtime network version" and a "Lotus upgrade epoch", is that unavoidable? + if sm.GetNtwkVersion(ctx, baseTs.Height()) <= network.Version3 { + return hmp, err + } + + if err != nil { + return false, err + } + + if !hmp { + return false, nil + } + + // Post actors v2, also check MinerEligibleForElection with base ts + + pact, err := sm.LoadActor(ctx, power.Address, baseTs) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + pstate, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + mact, err := sm.LoadActor(ctx, addr, baseTs) + if err != nil { + return false, xerrors.Errorf("loading miner actor state: %w", err) + } + + mstate, err := miner.Load(sm.cs.ActorStore(ctx), mact) + if err != nil { + return false, err + } + + // Non-empty power claim. + if claim, found, err := pstate.MinerPower(addr); err != nil { + return false, err + } else if !found { + return false, err + } else if claim.QualityAdjPower.LessThanEqual(big.Zero()) { + return false, err + } + + // No fee debt. + if debt, err := mstate.FeeDebt(); err != nil { + return false, err + } else if !debt.IsZero() { + return false, err + } + + // No active consensus faults. + if mInfo, err := mstate.Info(); err != nil { + return false, err + } else if baseTs.Height() <= mInfo.ConsensusFaultElapsed { + return false, nil + } + + return true, nil +} + +func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, nil, err + } + + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil +} + +func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + + act, err := st.GetActor(market.Address) + if err != nil { + return nil, err + } + + actState, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, err + } + return actState, nil +} + +func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { + mstate, err := sm.GetMarketState(ctx, ts) + if err != nil { + return api.MarketBalance{}, err + } + + addr, err = sm.LookupID(ctx, addr, ts) + if err != nil { + return api.MarketBalance{}, err + } + + var out api.MarketBalance + + et, err := mstate.EscrowTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Escrow, err = et.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) + } + + lt, err := mstate.LockedTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Locked, err = lt.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) + } + + return out, nil +} + +var _ StateManagerAPI = (*StateManager)(nil) diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index f54904aaa..a4d78f997 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -1,52 +1,124 @@ package stmgr import ( - "bytes" "context" "fmt" - "os" "reflect" "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" - - cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/rt" exported0 "github.com/filecoin-project/specs-actors/actors/builtin/exported" exported2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/exported" exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" exported4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/exported" + exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/paych" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/modules/dtypes" ) +type MethodMeta struct { + Name string + + Params reflect.Type + Ret reflect.Type +} + +var MethodsMap = map[cid.Cid]map[abi.MethodNum]MethodMeta{} + +func init() { + // TODO: combine with the runtime actor registry. + var actors []rt.VMActor + actors = append(actors, exported0.BuiltinActors()...) + 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() + methods := make(map[abi.MethodNum]MethodMeta, len(exports)) + + // Explicitly add send, it's special. + methods[builtin.MethodSend] = MethodMeta{ + Name: "Send", + Params: reflect.TypeOf(new(abi.EmptyValue)), + Ret: reflect.TypeOf(new(abi.EmptyValue)), + } + + // Iterate over exported methods. Some of these _may_ be nil and + // must be skipped. + for number, export := range exports { + if export == nil { + continue + } + + ev := reflect.ValueOf(export) + et := ev.Type() + + // Extract the method names using reflection. These + // method names always match the field names in the + // `builtin.Method*` structs (tested in the specs-actors + // tests). + fnName := runtime.FuncForPC(ev.Pointer()).Name() + fnName = strings.TrimSuffix(fnName[strings.LastIndexByte(fnName, '.')+1:], "-fm") + + switch abi.MethodNum(number) { + case builtin.MethodSend: + panic("method 0 is reserved for Send") + case builtin.MethodConstructor: + if fnName != "Constructor" { + panic("method 1 is reserved for Constructor") + } + } + + methods[abi.MethodNum(number)] = MethodMeta{ + Name: fnName, + Params: et.In(1), + Ret: et.Out(0), + } + } + MethodsMap[actor.Code()] = methods + } +} + +func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, method abi.MethodNum, ts *types.TipSet) (cbg.CBORUnmarshaler, error) { + act, err := sm.LoadActor(ctx, to, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + m, found := MethodsMap[act.Code][method] + if !found { + return nil, fmt.Errorf("unknown method %d for actor %s", method, act.Code) + } + return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil +} + +func GetParamType(actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) { + m, found := MethodsMap[actCode][method] + if !found { + return nil, fmt.Errorf("unknown method %d for actor %s", method, actCode) + } + return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil +} + func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.NetworkName, error) { act, err := sm.LoadActorRaw(ctx, init_.Address, st) if err != nil { @@ -60,277 +132,6 @@ func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.N return ias.NetworkName() } -func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { - state, err := sm.StateTree(st) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load state tree: %w", err) - } - act, err := state.GetActor(maddr) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - info, err := mas.Info() - if err != nil { - return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) - } - - return vm.ResolveToKeyAddr(state, sm.cs.ActorStore(ctx), info.Worker) -} - -func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { - return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) -} - -func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, bool, error) { - act, err := sm.LoadActorRaw(ctx, power.Address, st) - if err != nil { - return power.Claim{}, power.Claim{}, false, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) - } - - pas, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - - tpow, err := pas.TotalPower() - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - - var mpow power.Claim - var minpow bool - if maddr != address.Undef { - var found bool - mpow, found, err = pas.MinerPower(maddr) - if err != nil || !found { - return power.Claim{}, tpow, false, err - } - - minpow, err = pas.MinerNominalPowerMeetsConsensusMinimum(maddr) - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - } - - return mpow, tpow, minpow, nil -} - -func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorPreCommitOnChainInfo, error) { - act, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return mas.GetPrecommittedSector(sid) -} - -func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorOnChainInfo, error) { - act, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return mas.GetSector(sid) -} - -func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) { - act, err := sm.LoadActorRaw(ctx, maddr, st) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor state: %w", err) - } - - var provingSectors bitfield.BitField - if nv < network.Version7 { - allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) - if err != nil { - return nil, xerrors.Errorf("get all sectors: %w", err) - } - - faultySectors, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) - if err != nil { - return nil, xerrors.Errorf("get faulty sectors: %w", err) - } - - provingSectors, err = bitfield.SubtractBitField(allSectors, faultySectors) - if err != nil { - return nil, xerrors.Errorf("calc proving sectors: %w", err) - } - } else { - provingSectors, err = miner.AllPartSectors(mas, miner.Partition.ActiveSectors) - if err != nil { - return nil, xerrors.Errorf("get active sectors sectors: %w", err) - } - } - - numProvSect, err := provingSectors.Count() - if err != nil { - return nil, xerrors.Errorf("failed to count bits: %w", err) - } - - // TODO(review): is this right? feels fishy to me - if numProvSect == 0 { - return nil, nil - } - - info, err := mas.Info() - if err != nil { - return nil, xerrors.Errorf("getting miner info: %w", err) - } - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return nil, xerrors.Errorf("getting miner ID: %w", err) - } - - proofType, err := miner.WinningPoStProofTypeFromWindowPoStProofType(nv, info.WindowPoStProofType) - if err != nil { - return nil, xerrors.Errorf("determining winning post proof type: %w", err) - } - - ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, proofType, abi.ActorID(mid), rand, numProvSect) - if err != nil { - return nil, xerrors.Errorf("generating winning post challenges: %w", err) - } - - iter, err := provingSectors.BitIterator() - if err != nil { - return nil, xerrors.Errorf("iterating over proving sectors: %w", err) - } - - // Select winning sectors by _index_ in the all-sectors bitfield. - selectedSectors := bitfield.New() - prev := uint64(0) - for _, n := range ids { - sno, err := iter.Nth(n - prev) - if err != nil { - return nil, xerrors.Errorf("iterating over proving sectors: %w", err) - } - selectedSectors.Set(sno) - prev = n - } - - sectors, err := mas.LoadSectors(&selectedSectors) - if err != nil { - return nil, xerrors.Errorf("loading proving sectors: %w", err) - } - - out := make([]builtin.SectorInfo, len(sectors)) - for i, sinfo := range sectors { - out[i] = builtin.SectorInfo{ - SealProof: sinfo.SealProof, - SectorNumber: sinfo.SectorNumber, - SealedCID: sinfo.SealedCID, - } - } - - return out, nil -} - -func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { - act, err := sm.LoadActor(ctx, power.Address, ts) - if err != nil { - return false, xerrors.Errorf("failed to load power actor: %w", err) - } - - spas, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return false, xerrors.Errorf("failed to load power actor state: %w", err) - } - - _, ok, err := spas.MinerPower(maddr) - if err != nil { - return false, xerrors.Errorf("getting miner power: %w", err) - } - - if !ok { - return true, nil - } - - return false, nil -} - -func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { - act, err := sm.LoadActor(ctx, market.Address, ts) - if err != nil { - return nil, xerrors.Errorf("failed to load market actor: %w", err) - } - - state, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load market actor state: %w", err) - } - - proposals, err := state.Proposals() - if err != nil { - return nil, err - } - - proposal, found, err := proposals.Get(dealID) - - if err != nil { - return nil, err - } else if !found { - return nil, xerrors.Errorf( - "deal %d not found "+ - "- deal may not have completed sealing before deal proposal "+ - "start epoch, or deal may have been slashed", - dealID) - } - - states, err := state.States() - if err != nil { - return nil, err - } - - st, found, err := states.Get(dealID) - if err != nil { - return nil, err - } - - if !found { - st = market.EmptyDealState() - } - - return &api.MarketDeal{ - Proposal: *proposal, - State: *st, - }, nil -} - -func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { - act, err := sm.LoadActor(ctx, power.Address, ts) - if err != nil { - return nil, xerrors.Errorf("failed to load power actor: %w", err) - } - - powState, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load power actor state: %w", err) - } - - return powState.ListAllMiners() -} - func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, msgs []*types.Message, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { if ts == nil { ts = sm.cs.GetHeaviestTipSet() @@ -434,272 +235,6 @@ func GetLookbackTipSetForRound(ctx context.Context, sm *StateManager, ts *types. return lbts, nextTs.ParentState(), nil } -func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv ffiwrapper.Verifier) (*api.MiningBaseInfo, error) { - ts, err := sm.ChainStore().LoadTipSet(tsk) - if err != nil { - return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) - } - - prev, err := sm.ChainStore().GetLatestBeaconEntry(ts) - if err != nil { - if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { - return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) - } - - prev = &types.BeaconEntry{} - } - - entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, round, ts.Height(), *prev) - if err != nil { - return nil, err - } - - rbase := *prev - if len(entries) > 0 { - rbase = entries[len(entries)-1] - } - - lbts, lbst, err := GetLookbackTipSetForRound(ctx, sm, ts, round) - if err != nil { - return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) - } - - act, err := sm.LoadActorRaw(ctx, maddr, lbst) - if xerrors.Is(err, types.ErrActorNotFound) { - _, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("loading miner in current state: %w", err) - } - - return nil, nil - } - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor state: %w", err) - } - - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal miner address: %w", err) - } - - prand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) - if err != nil { - return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) - } - - nv := sm.GetNtwkVersion(ctx, ts.Height()) - - sectors, err := GetSectorsForWinningPoSt(ctx, nv, pv, sm, lbst, maddr, prand) - if err != nil { - return nil, xerrors.Errorf("getting winning post proving set: %w", err) - } - - if len(sectors) == 0 { - return nil, nil - } - - mpow, tpow, _, err := GetPowerRaw(ctx, sm, lbst, maddr) - if err != nil { - return nil, xerrors.Errorf("failed to get power: %w", err) - } - - info, err := mas.Info() - if err != nil { - return nil, err - } - - worker, err := sm.ResolveToKeyAddress(ctx, info.Worker, ts) - if err != nil { - return nil, xerrors.Errorf("resolving worker address: %w", err) - } - - // TODO: Not ideal performance...This method reloads miner and power state (already looked up here and in GetPowerRaw) - eligible, err := MinerEligibleToMine(ctx, sm, maddr, ts, lbts) - if err != nil { - return nil, xerrors.Errorf("determining miner eligibility: %w", err) - } - - return &api.MiningBaseInfo{ - MinerPower: mpow.QualityAdjPower, - NetworkPower: tpow.QualityAdjPower, - Sectors: sectors, - WorkerKey: worker, - SectorSize: info.SectorSize, - PrevBeaconEntry: *prev, - BeaconEntries: entries, - EligibleForMining: eligible, - }, nil -} - -type MethodMeta struct { - Name string - - Params reflect.Type - Ret reflect.Type -} - -var MethodsMap = map[cid.Cid]map[abi.MethodNum]MethodMeta{} - -func init() { - // TODO: combine with the runtime actor registry. - var actors []rt.VMActor - actors = append(actors, exported0.BuiltinActors()...) - 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() - methods := make(map[abi.MethodNum]MethodMeta, len(exports)) - - // Explicitly add send, it's special. - methods[builtin.MethodSend] = MethodMeta{ - Name: "Send", - Params: reflect.TypeOf(new(abi.EmptyValue)), - Ret: reflect.TypeOf(new(abi.EmptyValue)), - } - - // Iterate over exported methods. Some of these _may_ be nil and - // must be skipped. - for number, export := range exports { - if export == nil { - continue - } - - ev := reflect.ValueOf(export) - et := ev.Type() - - // Extract the method names using reflection. These - // method names always match the field names in the - // `builtin.Method*` structs (tested in the specs-actors - // tests). - fnName := runtime.FuncForPC(ev.Pointer()).Name() - fnName = strings.TrimSuffix(fnName[strings.LastIndexByte(fnName, '.')+1:], "-fm") - - switch abi.MethodNum(number) { - case builtin.MethodSend: - panic("method 0 is reserved for Send") - case builtin.MethodConstructor: - if fnName != "Constructor" { - panic("method 1 is reserved for Constructor") - } - } - - methods[abi.MethodNum(number)] = MethodMeta{ - Name: fnName, - Params: et.In(1), - Ret: et.Out(0), - } - } - MethodsMap[actor.Code()] = methods - } -} - -func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, method abi.MethodNum, ts *types.TipSet) (cbg.CBORUnmarshaler, error) { - act, err := sm.LoadActor(ctx, to, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - - m, found := MethodsMap[act.Code][method] - if !found { - return nil, fmt.Errorf("unknown method %d for actor %s", method, act.Code) - } - return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil -} - -func GetParamType(actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) { - m, found := MethodsMap[actCode][method] - if !found { - return nil, fmt.Errorf("unknown method %d for actor %s", method, actCode) - } - return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil -} - -func minerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { - pact, err := sm.LoadActor(ctx, power.Address, ts) - if err != nil { - return false, xerrors.Errorf("loading power actor state: %w", err) - } - - ps, err := power.Load(sm.cs.ActorStore(ctx), pact) - if err != nil { - return false, err - } - - return ps.MinerNominalPowerMeetsConsensusMinimum(addr) -} - -func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Address, baseTs *types.TipSet, lookbackTs *types.TipSet) (bool, error) { - hmp, err := minerHasMinPower(ctx, sm, addr, lookbackTs) - - // TODO: We're blurring the lines between a "runtime network version" and a "Lotus upgrade epoch", is that unavoidable? - if sm.GetNtwkVersion(ctx, baseTs.Height()) <= network.Version3 { - return hmp, err - } - - if err != nil { - return false, err - } - - if !hmp { - return false, nil - } - - // Post actors v2, also check MinerEligibleForElection with base ts - - pact, err := sm.LoadActor(ctx, power.Address, baseTs) - if err != nil { - return false, xerrors.Errorf("loading power actor state: %w", err) - } - - pstate, err := power.Load(sm.cs.ActorStore(ctx), pact) - if err != nil { - return false, err - } - - mact, err := sm.LoadActor(ctx, addr, baseTs) - if err != nil { - return false, xerrors.Errorf("loading miner actor state: %w", err) - } - - mstate, err := miner.Load(sm.cs.ActorStore(ctx), mact) - if err != nil { - return false, err - } - - // Non-empty power claim. - if claim, found, err := pstate.MinerPower(addr); err != nil { - return false, err - } else if !found { - return false, err - } else if claim.QualityAdjPower.LessThanEqual(big.Zero()) { - return false, err - } - - // No fee debt. - if debt, err := mstate.FeeDebt(); err != nil { - return false, err - } else if !debt.IsZero() { - return false, err - } - - // No active consensus faults. - if mInfo, err := mstate.Info(); err != nil { - return false, err - } else if baseTs.Height() <= mInfo.ConsensusFaultElapsed { - return false, nil - } - - return true, nil -} - func CheckTotalFIL(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) (abi.TokenAmount, error) { str, err := state.LoadStateTree(cs.ActorStore(ctx), ts.ParentState()) if err != nil { @@ -748,75 +283,3 @@ func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([] return out, nil } - -func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, nil, err - } - - act, err := st.GetActor(addr) - if err != nil { - return nil, nil, err - } - - actState, err := paych.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, nil, err - } - return act, actState, nil -} - -func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, err - } - - act, err := st.GetActor(market.Address) - if err != nil { - return nil, err - } - - actState, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, err - } - return actState, nil -} - -func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { - mstate, err := sm.GetMarketState(ctx, ts) - if err != nil { - return api.MarketBalance{}, err - } - - addr, err = sm.LookupID(ctx, addr, ts) - if err != nil { - return api.MarketBalance{}, err - } - - var out api.MarketBalance - - et, err := mstate.EscrowTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Escrow, err = et.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) - } - - lt, err := mstate.LockedTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Locked, err = lt.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) - } - - return out, nil -} - -var _ StateManagerAPI = (*StateManager)(nil) From ae63a4b33bb731a9d1af312a5cbac96cd6155ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:49:01 +0200 Subject: [PATCH 76/98] fix lotus-sim build --- chain/stmgr/stmgr.go | 4 ++++ cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go | 2 +- cmd/lotus-sim/simulation/simulation.go | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 09b8284dd..b76dd0faf 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -354,3 +354,7 @@ func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoc } return sm.latestVersion } + +func (sm *StateManager) VMSys() vm.SyscallBuilder { + return sm.syscalls +} diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index 2ffc0bf14..36b9cee75 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -83,7 +83,7 @@ func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.S Epoch: parentTs.Height() + 1, Rand: r, Bstore: sm.ChainStore().StateBlockstore(), - Syscalls: sm.ChainStore().VMSys(), + Syscalls: sm.VMSys(), CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: abi.NewTokenAmount(0), diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index d91d30eda..83b45f942 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -18,6 +18,8 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" ) @@ -198,7 +200,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, newUpgradeSchedule) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, vm.Syscalls(mock.Verifier), newUpgradeSchedule) if err != nil { return err } From 05a3710288daf867f712a342d7fc7ae8266ef58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:53:47 +0200 Subject: [PATCH 77/98] chainstore: Fix test build --- chain/stmgr/forks_test.go | 6 +++--- chain/store/store_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 9caeee51f..0df6ce397 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -121,7 +121,7 @@ func TestForkHeightTriggers(t *testing.T) { } sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, @@ -250,7 +250,7 @@ func TestForkRefuseCall(t *testing.T) { } sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Expensive: true, Height: testForkHeight, @@ -365,7 +365,7 @@ func TestForkPreMigration(t *testing.T) { counter := make(chan struct{}, 10) sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 898d590d0..2db2f061b 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -157,7 +157,7 @@ func TestChainExportImportFull(t *testing.T) { t.Fatal("imported chain differed from exported chain") } - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, nil) for i := 0; i < 100; i++ { ts, err := cs.GetTipsetByHeight(context.TODO(), abi.ChainEpoch(i), nil, false) if err != nil { From c57c20c6e43a1e86f650a2ddd600dc4b0589afad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Jul 2021 15:58:18 +0200 Subject: [PATCH 78/98] fix lint --- chain/stmgr/execute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index de7ca2894..3191a45db 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -3,13 +3,13 @@ package stmgr import ( "context" "fmt" + "sync/atomic" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "go.opencensus.io/stats" "go.opencensus.io/trace" "golang.org/x/xerrors" - "sync/atomic" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" From 3451acbc0313df454aa0cf97108624ca72dfeb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 27 Jul 2021 15:28:10 +0100 Subject: [PATCH 79/98] docgen. --- documentation/en/cli-lotus-miner.md | 1 + documentation/en/cli-lotus.md | 1 + 2 files changed, 2 insertions(+) diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 2ba693bff..9884edf27 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -43,6 +43,7 @@ GLOBAL OPTIONS: --actor value, -a value specify other actor to check state for (read only) --color use color in display output (default: depends on output being a TTY) --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] + --vv enables super verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) --version, -v print the version (default: false) ``` diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index cae648a0d..7418f433d 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -39,6 +39,7 @@ COMMANDS: GLOBAL OPTIONS: --interactive setting to false will disable interactive functionality of commands (default: false) --force-send if true, will ignore pre-send checks (default: false) + --vv enables super verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) --version, -v print the version (default: false) ``` From 2f03b456de77509bc7d4fe1d1b286bf6e2388869 Mon Sep 17 00:00:00 2001 From: Jiaying Wang <42981373+jennijuju@users.noreply.github.com> Date: Tue, 27 Jul 2021 12:00:08 -0400 Subject: [PATCH 80/98] Update RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 8eec88f88..53cfd0418 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -63,13 +63,8 @@ Testing an RC: - [ ] (optional) let a sector go faulty, and see it be recovered - [ ] **Stage 2 - Community Testing** - - [ ] Inform beta miners (@lotus-early-testers-miner in Filecoin Slack #fil-lotus) - - [ ] Inform close ecosystem partners to test their projects (@lotus-early-testers-eco-dev in Filecoin slack #fil-lotus-dev) - - [ ] Powergate - - [ ] Glif - - [ ] Stats dashboard - - [ ] Sentinel - - [ ] Protofire + - [ ] Inform beta lotus users (@lotus-early-testers in Filecoin Slack #fil-lotus) + - [ ] **Stage 3 - Community Prod Testing** - [ ] Documentation From 2790c9439f904af871ddb66594abafa72970223e Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 27 Jul 2021 13:14:49 -0400 Subject: [PATCH 81/98] Update to proof v8.0.3 --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index d60fc680a..a7b3c2e69 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit d60fc680aa8abeafba698f738fed5b94c9bda33d +Subproject commit a7b3c2e695393fd716e9265ff8cba932a3e38dd4 From b04fb75a924370c81423de62f24036cb5da4ad47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 27 Jul 2021 20:46:02 +0100 Subject: [PATCH 82/98] rename flag to very verbose. --- cli/util/api.go | 14 +++++++------- cli/util/verbose.go | 14 +++++++------- cmd/lotus-miner/main.go | 2 +- cmd/lotus/main.go | 2 +- documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus.md | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/util/api.go b/cli/util/api.go index b7210a18f..730b75d9d 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -146,7 +146,7 @@ func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http. return "", nil, xerrors.Errorf("could not get DialArgs: %w", err) } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintf(ctx.App.Writer, "using raw API %s endpoint: %s\n", version, addr) } @@ -189,7 +189,7 @@ func GetFullNodeAPI(ctx *cli.Context) (v0api.FullNode, jsonrpc.ClientCloser, err return nil, nil, err } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v0 endpoint:", addr) } @@ -206,7 +206,7 @@ func GetFullNodeAPIV1(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientCloser, e return nil, nil, err } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v1 endpoint:", addr) } @@ -254,7 +254,7 @@ func GetStorageMinerAPI(ctx *cli.Context, opts ...GetStorageMinerOption) (api.St addr = u.String() } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using miner API v0 endpoint:", addr) } @@ -267,7 +267,7 @@ func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) { return nil, nil, err } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using worker API v0 endpoint:", addr) } @@ -280,7 +280,7 @@ func GetGatewayAPI(ctx *cli.Context) (api.Gateway, jsonrpc.ClientCloser, error) return nil, nil, err } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using gateway API v1 endpoint:", addr) } @@ -293,7 +293,7 @@ func GetGatewayAPIV0(ctx *cli.Context) (v0api.Gateway, jsonrpc.ClientCloser, err return nil, nil, err } - if IsSuperVerbose { + if IsVeryVerbose { _, _ = fmt.Fprintln(ctx.App.Writer, "using gateway API v0 endpoint:", addr) } diff --git a/cli/util/verbose.go b/cli/util/verbose.go index b1eb1a9be..efcad0962 100644 --- a/cli/util/verbose.go +++ b/cli/util/verbose.go @@ -2,15 +2,15 @@ package cliutil import "github.com/urfave/cli/v2" -// IsSuperVerbose is a global var signalling if we're running in super verbose -// mode or not (default: false). -var IsSuperVerbose bool +// IsVeryVerbose is a global var signalling if the CLI is running in very +// verbose mode or not (default: false). +var IsVeryVerbose bool -// FlagSuperVerbose enables super verbose mode, which is useful when debugging +// FlagVeryVerbose enables very verbose mode, which is useful when debugging // the CLI itself. It should be included as a flag on the top-level command // (e.g. lotus -vv, lotus-miner -vv). -var FlagSuperVerbose = &cli.BoolFlag{ +var FlagVeryVerbose = &cli.BoolFlag{ Name: "vv", - Usage: "enables super verbose mode, useful for debugging the CLI", - Destination: &IsSuperVerbose, + Usage: "enables very verbose mode, useful for debugging the CLI", + Destination: &IsVeryVerbose, } diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index c14f85b93..c697de0c9 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -106,7 +106,7 @@ func main() { Value: "~/.lotusminer", // TODO: Consider XDG_DATA_HOME Usage: fmt.Sprintf("Specify miner repo path. flag(%s) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON", FlagMinerRepoDeprecation), }, - cliutil.FlagSuperVerbose, + cliutil.FlagVeryVerbose, }, Commands: append(local, lcli.CommonCommands...), diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index 6719116ce..66eae0f1e 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -82,7 +82,7 @@ func main() { Name: "force-send", Usage: "if true, will ignore pre-send checks", }, - cliutil.FlagSuperVerbose, + cliutil.FlagVeryVerbose, }, Commands: append(local, lcli.Commands...), diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 9884edf27..29593fbd4 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -43,7 +43,7 @@ GLOBAL OPTIONS: --actor value, -a value specify other actor to check state for (read only) --color use color in display output (default: depends on output being a TTY) --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] - --vv enables super verbose mode, useful for debugging the CLI (default: false) + --vv enables very verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) --version, -v print the version (default: false) ``` diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 7418f433d..5faf5c58b 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -39,7 +39,7 @@ COMMANDS: GLOBAL OPTIONS: --interactive setting to false will disable interactive functionality of commands (default: false) --force-send if true, will ignore pre-send checks (default: false) - --vv enables super verbose mode, useful for debugging the CLI (default: false) + --vv enables very verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) --version, -v print the version (default: false) ``` From 01c786c141ffd61b274271e770ba464d4704eb80 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 27 Jul 2021 18:48:08 -0700 Subject: [PATCH 83/98] chore: fixup issue templates 1. Render logs/version as text, body as markdown. Previously, the _body_ was rendered as bash. 2. Fix spelling of "required". --- .github/ISSUE_TEMPLATE/bug_report.yml | 9 ++++----- .github/ISSUE_TEMPLATE/m1_bug_report_deal.yml | 8 ++++---- .github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml | 9 ++++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7876715e2..244be5078 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -43,13 +43,14 @@ body: id: version attributes: label: Lotus Version + render: text description: Enter the output of `lotus version` and `lotus-miner version` if applicable. placeholder: | e.g. Daemon:1.11.0-rc2+debug+git.0519cd371.dirty+api1.3.0 Local: lotus version 1.11.0-rc2+debug+git.0519cd371.dirty validations: - reuiqred: true + required: true - type: textarea id: Description attributes: @@ -62,19 +63,18 @@ body: * For sealing issues, include the output of `lotus-miner sectors status --log ` for the failed sector(s). * For proving issues, include the output of `lotus-miner proving` info. * For deal making issues, include the output of `lotus client list-deals -v` and/or `lotus-miner storage-deals|retrieval-deals|data-transfers list [-v]` commands for the deal(s) in question. - render: bash validations: required: true - type: textarea id: extraInfo attributes: label: Logging Information + render: text description: | Please provide debug logs of the problem, remember you can get set log level control for: * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#log-level-control). * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level If you don't provide detailed logs when you raise the issue it will almost certainly be the first request I make before furthur diagnosing the problem. - render: bash validations: required: true - type: textarea @@ -87,6 +87,5 @@ body: 2. Do '...' 3. See error '...' ... - render: bash validations: - required: false \ No newline at end of file + required: false diff --git a/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml b/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml index 4402e97da..3a24d9564 100644 --- a/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml +++ b/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml @@ -35,10 +35,11 @@ body: - type: textarea id: version attributes: + render: text label: Lotus Tag and Version description: Enter the lotus tag, output of `lotus version` and `lotus-miner version`. validations: - reuiqred: true + required: true - type: textarea id: Description attributes: @@ -48,7 +49,6 @@ body: * What you were doding when you experienced the bug? * Any *error* messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). * What is the expected behaviour? - render: bash validations: required: true - type: textarea @@ -72,6 +72,7 @@ body: - type: textarea id: logging attributes: + render: text label: Logging Information description: Please link to the whole of the miner logs on your side of the transaction. You can upload the logs to a [gist](https://gist.github.com). validations: @@ -86,6 +87,5 @@ body: 2. Do '...' 3. See error '...' ... - render: bash validations: - required: false \ No newline at end of file + required: false diff --git a/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml b/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml index ede3593e5..363c3a1ab 100644 --- a/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml +++ b/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml @@ -36,10 +36,11 @@ body: - type: textarea id: version attributes: + render: text label: Lotus Tag and Version description: Enter the lotus tag, output of `lotus version` and `lotus-miner version`. validations: - reuiqred: true + required: true - type: textarea id: Description attributes: @@ -51,19 +52,18 @@ body: * What is the expected behaviour? * For sealing issues, include the output of `lotus-miner sectors status --log ` for the failed sector(s). * For proving issues, include the output of `lotus-miner proving` info. - render: bash validations: required: true - type: textarea id: extraInfo attributes: label: Logging Information + render: text description: | Please provide debug logs of the problem, remember you can get set log level control for: * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#log-level-control). * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level If you don't provide detailed logs when you raise the issue it will almost certainly be the first request I make before furthur diagnosing the problem. - render: bash validations: required: true - type: textarea @@ -76,6 +76,5 @@ body: 2. Do '...' 3. See error '...' ... - render: bash validations: - required: false \ No newline at end of file + required: false From e49c77e29c0448dca5b0d763909c70dc4a2d0471 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 27 Jul 2021 22:54:23 -0400 Subject: [PATCH 84/98] bump master version to v1.11.2-dev --- build/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.go b/build/version.go index c6a1be3e2..4e155c98a 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.1-dev" +const BuildVersion = "1.11.2-dev" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { From f9595b58ee89567a6a9ed1728ae801a3d4f160b4 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 27 Jul 2021 23:02:54 -0400 Subject: [PATCH 85/98] make gen --- build/openrpc/full.json.gz | Bin 25240 -> 25240 bytes build/openrpc/miner.json.gz | Bin 9479 -> 9479 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2710 bytes documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus-worker.md | 2 +- documentation/en/cli-lotus.md | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index c940d921b0af50de4f243bc595a80f87b709ce86..4708ec159750bc2464b61b01bbcdfb1e270b2780 100644 GIT binary patch delta 25105 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 delta 25059 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�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 E0APQLuK)l5 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 41e51a13ec002908072c639cd2136778bea75f3e..58a76714dc5e83fc937928a04c9da6ad108678c7 100644 GIT binary patch delta 21 ccmZqoYWJGZ%J^YpTcrvIXKC Date: Wed, 28 Jul 2021 15:59:16 +0300 Subject: [PATCH 86/98] update go-libp2p-pubsub to v0.5.3 --- go.mod | 22 ++++----- go.sum | 146 ++++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 119 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 5f968f6e0..080403c20 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/gdamore/tcell/v2 v2.2.0 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect - github.com/golang/mock v1.5.0 + github.com/golang/mock v1.6.0 github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.2 @@ -105,17 +105,17 @@ require ( github.com/libp2p/go-eventbus v0.2.1 github.com/libp2p/go-libp2p v0.14.2 github.com/libp2p/go-libp2p-connmgr v0.2.4 - github.com/libp2p/go-libp2p-core v0.8.5 - github.com/libp2p/go-libp2p-discovery v0.5.0 + github.com/libp2p/go-libp2p-core v0.8.6 + github.com/libp2p/go-libp2p-discovery v0.5.1 github.com/libp2p/go-libp2p-kad-dht v0.11.0 github.com/libp2p/go-libp2p-mplex v0.4.1 github.com/libp2p/go-libp2p-noise v0.2.0 - github.com/libp2p/go-libp2p-peerstore v0.2.7 - github.com/libp2p/go-libp2p-pubsub v0.5.0 - github.com/libp2p/go-libp2p-quic-transport v0.10.0 + github.com/libp2p/go-libp2p-peerstore v0.2.8 + github.com/libp2p/go-libp2p-pubsub v0.5.3 + github.com/libp2p/go-libp2p-quic-transport v0.11.2 github.com/libp2p/go-libp2p-record v0.1.3 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 - github.com/libp2p/go-libp2p-swarm v0.5.0 + github.com/libp2p/go-libp2p-swarm v0.5.3 github.com/libp2p/go-libp2p-tls v0.1.3 github.com/libp2p/go-libp2p-yamux v0.5.4 github.com/libp2p/go-maddr-filter v0.1.0 @@ -124,14 +124,14 @@ require ( github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.0.3 - github.com/multiformats/go-multiaddr v0.3.1 + github.com/multiformats/go-multiaddr v0.3.3 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multibase v0.0.3 github.com/multiformats/go-multihash v0.0.15 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/opentracing/opentracing-go v1.2.0 github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a - github.com/prometheus/client_golang v1.6.0 + github.com/prometheus/client_golang v1.10.0 github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 @@ -150,9 +150,9 @@ require ( go.uber.org/fx v1.9.0 go.uber.org/multierr v1.6.0 go.uber.org/zap v1.16.0 - golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210510120138-977fb7262007 + golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/tools v0.1.5 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 diff --git a/go.sum b/go.sum index b22f3dc15..69dabfd3b 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,9 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -199,8 +200,9 @@ github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e/go.mod h1: github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.1 h1:w9pSFNSdq/JPM1N12Fz/F/bzo993Is1W+Q7HjPzi7yg= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= @@ -397,6 +399,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968 h1:s+PDl6lozQ+dEUtUtQnO7+A2iPG3sK1pI4liU+jxn90= github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= @@ -429,8 +433,8 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -443,8 +447,10 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= @@ -457,8 +463,10 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -609,8 +617,9 @@ github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjv github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= -github.com/ipfs/go-ds-badger v0.2.3 h1:J27YvAcpuA5IvZUbeBxOcQgqnYHUPxoygc6QxxkodZ4= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-badger v0.2.7 h1:ju5REfIm+v+wgVnQ19xGLYPHYHbYLR6qJfmMbCDSK1I= +github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= github.com/ipfs/go-ds-badger2 v0.1.0/go.mod h1:pbR1p817OZbdId9EvLOhKBgUVTM3BMCSTan78lDDVaw= github.com/ipfs/go-ds-badger2 v0.1.1-0.20200708190120-187fc06f714e h1:Xi1nil8K2lBOorBS6Ys7+hmUCzH8fr3U9ipdL/IrcEI= github.com/ipfs/go-ds-badger2 v0.1.1-0.20200708190120-187fc06f714e/go.mod h1:lJnws7amT9Ehqzta0gwMrRsURU04caT0iRPr1W8AsOU= @@ -698,8 +707,9 @@ github.com/ipfs/go-log v1.0.0/go.mod h1:JO7RzlMK6rA+CIxFMLOuB6Wf5b81GDiKElL7UPSI github.com/ipfs/go-log v1.0.1/go.mod h1:HuWlQttfN6FWNHRhlY5yMk/lW7evQC0HHGOxEwMRR8I= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= -github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.0.1/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= @@ -800,12 +810,14 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kabukky/httpscerts v0.0.0-20150320125433-617593d7dcb3/go.mod h1:BYpt4ufZiIGv2nXn4gMxnfKV306n3mWXgNu/d2TqdTU= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= @@ -819,6 +831,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -838,8 +852,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= -github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= +github.com/libp2p/go-addr-util v0.1.0 h1:acKsntI33w2bTU7tC9a0SaPimJGfSI0bFKC18ChxeVI= +github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= @@ -940,9 +955,9 @@ github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.3/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.5 h1:aEgbIcPGsKy6zYcC+5AJivYFedhYa4sW7mIpWpUaLKw= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.6 h1:3S8g006qG6Tjpj1JdRK2S+TWc2DJQKX/RG9fdLeiLSU= +github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT72ImHNUqh5D/dBE= github.com/libp2p/go-libp2p-crypto v0.0.2/go.mod h1:eETI5OUfBnvARGOHrJz2eWNyTUxEGZnBxMcbUjfIj4I= github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ= @@ -954,8 +969,9 @@ github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFT github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.4.0/go.mod h1:bZ0aJSrFc/eX2llP0ryhb1kpgkPyTo23SJ5b7UQCMh4= -github.com/libp2p/go-libp2p-discovery v0.5.0 h1:Qfl+e5+lfDgwdrXdu4YNCWyEo3fWuP+WgN9mN0iWviQ= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-discovery v0.5.1 h1:CJylx+h2+4+s68GvrM4pGNyfNhOYviWBPtVv5PA7sfo= +github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-host v0.0.1/go.mod h1:qWd+H1yuU0m5CwzAkvbSjqKairayEHdR5MMl7Cwa7Go= github.com/libp2p/go-libp2p-host v0.0.3/go.mod h1:Y/qPyA6C8j2coYyos1dfRm0I8+nvd4TGrDGt4tA7JR8= github.com/libp2p/go-libp2p-interface-connmgr v0.0.1/go.mod h1:GarlRLH0LdeWcLnYM/SaBykKFl9U5JFnbBGruAk/D5k= @@ -1009,20 +1025,22 @@ github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRj github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.7 h1:83JoLxyR9OYTnNfB5vvFqvMUv/xDNa6NoPHnENhBsGw= github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.8 h1:nJghUlUkFVvyk7ccsM67oFA6kqUkwyCM1G4WPVMCWYA= +github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s= github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEXfQqUgC/1adR2Xtflk= github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q= github.com/libp2p/go-libp2p-pubsub v0.3.2-0.20200527132641-c0712c6e92cf/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= -github.com/libp2p/go-libp2p-pubsub v0.5.0 h1:OzcIuCWyJpOrWH0PTOfvxTzqFur4tiXpY5jXC8OxjyE= -github.com/libp2p/go-libp2p-pubsub v0.5.0/go.mod h1:MKnrsQkFgPcrQs1KVmOXy6Uz2RDQ1xO7dQo/P0Ba+ig= +github.com/libp2p/go-libp2p-pubsub v0.5.3 h1:XCn5xvgA/AKpbbaeqbomfKtQCbT9QsU39tYsVj0IndQ= +github.com/libp2p/go-libp2p-pubsub v0.5.3/go.mod h1:gVOzwebXVdSMDQBTfH8ACO5EJ4SQrvsHqCmYsCZpD0E= github.com/libp2p/go-libp2p-quic-transport v0.1.1/go.mod h1:wqG/jzhF3Pu2NrhJEvE+IE0NTHNXslOPn9JQzyCAxzU= github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M= -github.com/libp2p/go-libp2p-quic-transport v0.10.0 h1:koDCbWD9CCHwcHZL3/WEvP2A+e/o5/W5L3QS/2SPMA0= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= +github.com/libp2p/go-libp2p-quic-transport v0.11.2 h1:p1YQDZRHH4Cv2LPtHubqlQ9ggz4CKng/REZuXZbZMhM= +github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-record v0.0.1/go.mod h1:grzqg263Rug/sRex85QrDOLntdFAymLDLm7lxMgU79Q= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.1.1/go.mod h1:VRgKajOyMVgP/F0L5g3kH7SVskp17vFi2xheb5uMJtg= @@ -1050,9 +1068,9 @@ github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYR github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.3.1/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= -github.com/libp2p/go-libp2p-swarm v0.4.3/go.mod h1:mmxP1pGBSc1Arw4F5DIjcpjFAmsRzA1KADuMtMuCT4g= -github.com/libp2p/go-libp2p-swarm v0.5.0 h1:HIK0z3Eqoo8ugmN8YqWAhD2RORgR+3iNXYG4U2PFd1E= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= +github.com/libp2p/go-libp2p-swarm v0.5.3 h1:hsYaD/y6+kZff1o1Mc56NcuwSg80lIphTS/zDk3mO4M= +github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= github.com/libp2p/go-libp2p-testing v0.0.1/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -1061,8 +1079,9 @@ github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eq github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= -github.com/libp2p/go-libp2p-testing v0.4.0 h1:PrwHRi0IGqOwVQWR3xzgigSlhlLfxgfXgkHxr77EghQ= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-testing v0.4.2 h1:IOiA5mMigi+eEjf4J+B7fepDhsjtsoWA9QbsCqbNp5U= +github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-tls v0.1.3 h1:twKMhMu44jQO+HgQK9X8NHO5HkeJu2QbhLzLJpa8oNM= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport v0.0.1/go.mod h1:UzbUs9X+PHOSw7S3ZmeOxfnwaQY5vGDzZmKPod3N3tk= @@ -1073,8 +1092,9 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.0.4/go.mod h1:RGq+tupk+oj7PzL2 github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 h1:4JsnbfJzgZeRS9AWN7B9dPqn/LY/HoQTlO9gtdJTIYM= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.6 h1:SHt3g0FslnqIkEWF25YOB8UCOCTpGAVvHRWQYJ+veiI= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= github.com/libp2p/go-libp2p-yamux v0.5.1 h1:sX4WQPHMhRxJE5UZTfjEuBvlQWXB5Bo3A2JK9ZJ9EM0= github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-maddr-filter v0.0.1/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= @@ -1103,6 +1123,7 @@ github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= github.com/libp2p/go-netroute v0.1.6 h1:ruPJStbYyXVYGQ81uzEDzuvbYRLKRrLvTYd33yomC38= github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= @@ -1117,8 +1138,9 @@ github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQy github.com/libp2p/go-reuseport-transport v0.0.1/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-reuseport-transport v0.0.4 h1:OZGz0RB620QDGpv300n1zaOcKGGAoGVf8h9txtt/1uM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= +github.com/libp2p/go-reuseport-transport v0.0.5 h1:lJzi+vSYbyJj2faPKLxNGWEIBcaV/uJmyvsUxXy2mLw= +github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.1 h1:yD80l2ZOdGksnOyHrhxDdTDFrf7Oy+v3FMVArIRgZxQ= @@ -1134,8 +1156,9 @@ github.com/libp2p/go-tcp-transport v0.0.4/go.mod h1:+E8HvC8ezEVOxIo3V5vCK9l1y/19 github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= -github.com/libp2p/go-tcp-transport v0.2.1 h1:ExZiVQV+h+qL16fzCWtd1HSzPsqWottJ8KXwWaVi8Ns= github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= +github.com/libp2p/go-tcp-transport v0.2.7 h1:Z8Kc/Kb8tD84WiaH55xAlaEnkqzrp88jSEySCKV4+gg= +github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= github.com/libp2p/go-testutil v0.0.1/go.mod h1:iAcJc/DKJQanJ5ws2V+u5ywdL2n12X1WbbEG+Jjy69I= github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= github.com/libp2p/go-ws-transport v0.0.1/go.mod h1:p3bKjDWHEgtuKKj+2OdPYs5dAPIjtpQGHF2tJfGz7Ww= @@ -1156,8 +1179,9 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= -github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= +github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg= @@ -1175,10 +1199,17 @@ github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGD github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk= -github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.5 h1:Ci4EIUN6Rlb+D6GmLdej/bCQ4nPYNtVXQB+xjiXE1nk= +github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= +github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1 h1:/rpmWuGvceLwwWuaKPdjpR4JJEUH0tq64/I3hvzaNLM= +github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1215,6 +1246,12 @@ github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= @@ -1256,8 +1293,9 @@ github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y9 github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= -github.com/multiformats/go-multiaddr v0.3.1 h1:1bxa+W7j9wZKTZREySx1vPMs2TqrYWjVZ7zE6/XLG1I= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= +github.com/multiformats/go-multiaddr v0.3.3 h1:vo2OTSAqnENB2rLk79pLtr+uhj+VAzSe3uef5q0lRSs= +github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.3/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= @@ -1305,6 +1343,7 @@ github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -1319,8 +1358,9 @@ github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c h1:5bFTChQxSKNwy github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c/go.mod h1:7qN3Y0BvzRUf4LofcoJplQL10lsFDb4PYlePTVwrP28= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -1330,16 +1370,19 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1390,8 +1433,11 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1406,8 +1452,10 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289/go.mod h1:FGbBv5OPKjch+jNUJmEQpMZytIdyW0NdBtWFcfSKusc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1418,8 +1466,11 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.0 h1:jhMy6QXfi3y2HEzFoyuCj40z4OZIIHHPtFyCMftmvKA= github.com/prometheus/procfs v0.1.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/raulk/clock v1.1.0 h1:dpb29+UKMbLqiU/jqIJptgLR1nn23HLgMY0sTCDza5Y= github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= github.com/raulk/go-watchdog v1.0.1 h1:qgm3DIJAeb+2byneLrQJ7kvmDLGxN2vy3apXyGaDKN4= @@ -1661,8 +1712,9 @@ go.uber.org/dig v1.10.0 h1:yLmDDj9/zuDjv3gz8GQGviXMs9TfysIUMUilCpgzUJY= go.uber.org/dig v1.10.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= go.uber.org/fx v1.9.0 h1:7OAz8ucp35AU8eydejpYG7QrbE8rLKzGhHbZlJi5LYY= go.uber.org/fx v1.9.0/go.mod h1:mFdUyAUuJ3w4jAckiKSKbldsxy1ojpAMJ+dVZg5Y0Aw= -go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1714,8 +1766,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1798,10 +1851,13 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1819,6 +1875,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1884,17 +1941,25 @@ golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= @@ -1953,7 +2018,9 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200827010519-17fd2f27a9e3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201112185108-eeaa07dd7696/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2035,8 +2102,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2063,8 +2132,9 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= From de4a847078561d4bab53f15bc2474063a1a01140 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 12:10:17 +0300 Subject: [PATCH 87/98] add RuntimeSubsystems API method; use it in `lotus-miner info` --- api/api_storage.go | 2 + api/api_subsystems.go | 63 +++ api/docgen/docgen.go | 11 +- api/proxy_gen.go | 13 + build/openrpc/full.json.gz | Bin 25240 -> 25240 bytes build/openrpc/miner.json.gz | Bin 9479 -> 9540 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2710 bytes cmd/lotus-miner/info.go | 562 ++++++++++++----------- documentation/en/api-v0-methods-miner.md | 14 + node/builder_miner.go | 2 + node/impl/storminer.go | 6 + node/modules/storageminer.go | 17 + 12 files changed, 410 insertions(+), 280 deletions(-) create mode 100644 api/api_subsystems.go diff --git a/api/api_storage.go b/api/api_storage.go index 154abcea7..d52032650 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -166,6 +166,8 @@ type StorageMiner interface { MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write MarketPublishPendingDeals(ctx context.Context) error //perm:admin + RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read + DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin DealsList(ctx context.Context) ([]MarketDeal, error) //perm:admin DealsConsiderOnlineStorageDeals(context.Context) (bool, error) //perm:admin diff --git a/api/api_subsystems.go b/api/api_subsystems.go new file mode 100644 index 000000000..1894bbdd8 --- /dev/null +++ b/api/api_subsystems.go @@ -0,0 +1,63 @@ +package api + +import ( + "bytes" + "encoding/json" +) + +type MinerSubsystems []MinerSubsystem + +func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { + for _, v := range ms { + if v == entry { + return true + } + + } + return false +} + +type MinerSubsystem int + +const ( + MarketsSubsystem MinerSubsystem = iota + MiningSubsystem + SealingSubsystem + SectorStorageSubsystem +) + +func (ms MinerSubsystem) String() string { + return MinerSubsystemToString[ms] +} + +var MinerSubsystemToString = map[MinerSubsystem]string{ + MarketsSubsystem: "Markets", + MiningSubsystem: "Mining", + SealingSubsystem: "Sealing", + SectorStorageSubsystem: "SectorStorage", +} + +var MinerSubsystemToID = map[string]MinerSubsystem{ + "Markets": MarketsSubsystem, + "Mining": MiningSubsystem, + "Sealing": SealingSubsystem, + "SectorStorage": SectorStorageSubsystem, +} + +func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(MinerSubsystemToString[ms]) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + // TODO: handle zero value + *ms = MinerSubsystemToID[j] + return nil +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 39980023f..1e712a0ae 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -46,11 +46,12 @@ import ( ) var ExampleValues = map[reflect.Type]interface{}{ - reflect.TypeOf(auth.Permission("")): auth.Permission("write"), - reflect.TypeOf(""): "string value", - reflect.TypeOf(uint64(42)): uint64(42), - reflect.TypeOf(byte(7)): byte(7), - reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(api.MinerSubsystem(0)): api.MinerSubsystem(1), + reflect.TypeOf(auth.Permission("")): auth.Permission("write"), + reflect.TypeOf(""): "string value", + reflect.TypeOf(uint64(42)): uint64(42), + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf([]byte{}): []byte("byte array"), } func addExample(v interface{}) { diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 7d96425ff..a4feb7be1 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -699,6 +699,8 @@ type StorageMinerStruct struct { ReturnUnsealPiece func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + RuntimeSubsystems func(p0 context.Context) (MinerSubsystems, error) `perm:"read"` + SealingAbort func(p0 context.Context, p1 storiface.CallID) error `perm:"admin"` SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` @@ -4095,6 +4097,17 @@ func (s *StorageMinerStub) ReturnUnsealPiece(p0 context.Context, p1 storiface.Ca return ErrNotSupported } +func (s *StorageMinerStruct) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + if s.Internal.RuntimeSubsystems == nil { + return *new(MinerSubsystems), ErrNotSupported + } + return s.Internal.RuntimeSubsystems(p0) +} + +func (s *StorageMinerStub) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + return *new(MinerSubsystems), ErrNotSupported +} + func (s *StorageMinerStruct) SealingAbort(p0 context.Context, p1 storiface.CallID) error { if s.Internal.SealingAbort == nil { return ErrNotSupported diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 4708ec159750bc2464b61b01bbcdfb1e270b2780..db41fb2d3a18922fe3b5b9960853ec350fb7ee91 100644 GIT binary patch delta 20847 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 KonJWA>jVIUfNw_t delta 20863 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 acE!D4?FyCb=l=@;0RR88YkDKp>jVICf>(b4 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 58a76714dc5e83fc937928a04c9da6ad108678c7..69d471dbd5673f06dc006a4d21f0a193b36bbb30 100644 GIT binary patch delta 6074 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 A7ytkO delta 6021 zcmV;07kcQ#N{33Y6#@ZclNJK50VtEj0%`%slQ0A70kM-C1h#+iR3YwN1SxtA&D-6! znz)YvP>Flr2p2M2Dzalh=W+Z~lLMb$0U$K;#a)4%skic?Vt0H$5JrfI7f)HVAAXUH1Sa79Z;qVS}+} zF(kjN5dkK&Z8l!MbMdPlHNLa>wmNP}QM zkUQk^y-%}F1_S~sJW|A;aEhEG+R`a?5oFcIQ$(!iZ#9cxH?jfMm_X}!C*Lky_=nr= zO!BjQ2topXm?&aALa`YhJk?egaYzci;Ra27$*8L=;-H(=8Y!gPQ6G|NXLW|WkzTcB z5t8}(fd)$91Mzr;s7Os*!G{eZl&`=Yw3%4XJiH*Li=cl77O0|>+Y=FSqBy`8JmP2DAZS-kERq3gB)dp|2Dq8+aR%8gY>hg@9Rx(Zd zins~EqnFsDaUWE*LoNj@Ror;I(QFL~LAl9D3h22(iBCIEbDtl1x=Xd^ib2aW;Oxw`lxp`se`z{Ugek>jZRIeF6pVV=uPI<{9qQCati@5#I<7S*!b(Tc~Bxgq0J=WU&o8&bXAKY(>TQX~LG z7Gnbm$SPne+d6%+ABbeuiPNW^m93M1hU^>Jg(Frpx1Xj&xCZ_byIZGEyB2sL3(8)6 z_$_2X&zS98w{*mYZ=IS5SF%oZXL^ZN(0sUZ9qO2l!cfk$bg$rp?Z9dmo_&KzmU>ns zzi_VPR*TENipMf#HawSztf;1{{(ZUh5G~`L(!)aM%%|!nklUbj0+W2^Qk)KdBAZXV zz+!Lv#A^k?Rnr_C~V$v2-lujLgU=js4GO+r>|=R#lgs}pI8fVp=gn% z69<`NTWSqd>FJq7nK)=Uey!-rA1=CeqNs^1R{k1!j0qdc zUY9_7lJs@SxXlozd_uYj>@u_9$^b)Ofex^tx>OX${71jV`T6*NK$;nwTEb=mCup3UBgoXgQkUzIwK2_~!8?CV#O zGhuf{9Svc_+~DmT(cB}I5KuE!Gvau)Qz|1qv#eA}z10yXGi-%tB|Ho+{PhdMt2H%F ziw{5c$-Q|pP$X4z3&(qh=EpK@j>bU68@03T;=>Zy{YYEn^ zYsjwF!#Aw;AQe4XLd*&vX&WsuB+k#8n6!Vc#)a9XWAfAmU#OKdbE+Ter3bHe5?nGY zx%xrmHL~@ii%R5b?}nzy3JJTT z8so(P$e9i<@L^!s1QA}2)f}&)M7)M!p zRvUY%V{`lU4hH>xzh?wl$R{O@<)~;vp;@Y*=)7-+<3tftd(Elq%o6A8l^7=pLgiUt zn=-tFCOVu!+xEj5`SE__T-c7lPsw&~1d}NbZIffyJ*~~mq&S>^F+%qb$Gt_a0MS_V zVS3dx`Dz{}sVO*2WP%#~WAX2Sn~eUB`6;tDcPylL;!_qX@@B#%Vv6 zL7gmFUG@-xgVVZyBSo39-31}k(5=UcJOZH$-XhB52dgSp-oKPqt)xTwFwH2?0OKbq z+23i%PD2)H$WB&wvf7u`Q$?DCFS#&*HvYSg8$kx>38Z&oyoSVhdJdgD@8o$W&pUbk zoaFh5BIiJeu=ma#@`ZE_VIG6hz9f$g){GcWV&BQ|PKI}XGQ5-F&qs!j`vXN1hmc>_ z2-ZHF2w{|x-wVgFs~<{)V>Tnk<5MU;mgod|C&-^jkUve0hRzq?=@K1XGE^j21wzzD z&_g0y6J^7R%4n1Xr1tj(sR5f(D(P8t`-4ucJPWnb$@EU9`!ao`$c_>+Y>uEkb2b}H zvw}unp2whnl#=ECWmAWK-`{t%`i>!nLo?z%IjL^f(24hFBi^%oQ!j$MOwFH6g`cV+ z(%qh<(_b3;Yph7xN(<>MmsHVT!%F(=%5|J&6RK<2wCWmms;g67&rx-8mg%%sr?vJ| zYaJ=F93%OB<)UUbUJ;;*x(hY6{fa;}r^XVq>oi$^r^%k7CPUpmtkY=wuF)on)M1IJ zuDcbB09DdxLK4leP& zFT;}*S?ct5r?)lq_Hl0Q0>AWj%?QC5l=fpeVuLlKmy_6cF&v#jeijP3li{5V_htB0 zkt5H4Y!ysaXA8&8mYdfR-*EuCk~>F0$HCQ=%_-DcFIwA)a7-?=&Q;S_TS#l&Tq8&yMJJUkgP#1wHxP*JXGYh z3>+1^!3942K(?7HAtS%)Ii!$#RQc=R*8i8_g&0LtxX$O%3^q9x9^+6;Rd|$7y4cxz zW?6ujKnK(TkN{_k2=F+tLvXPK#PNT5;wD{0oZV^-ye%{lfG*-4dY=fw{32Zl2>RlG z_W)n8ZG!~@ri&O2_JHF8y2c(bp>6wbZO6O?J3_GyHjaxvkzh0vl33rv#w3RHlec(7 z;Bef^^}KQVY(mD=cfTbVM7M&zxT}1It>A{IpcMVlcr+Bh7^aRVZggwD;(kN|er>zt z?fzmVW;RBI8mGpxKNyb2M$fo%ojYuQF*_}Kb_o~APVYK<2EUkJTnT#0@9yjwZ-|wA z;ES=~MmhhbxHiupY3A5Aq{wAh@fQNA)MNh+fw`}4xo@{{{Kua0D?;1Inac^H{QU#E zR|urPYMEWR|0T-awwGP3vJj#*)4C-=&_c!|Q0GRR7^&)APZ*GB*{RA%t}KjyIMr!g z{~+anvZE%MH(dR|+LK-!k>-~Y)L4!t$}Ncu)g+f@r`Jme)TcR;k=e#^D<-2SQZLK+ z8&0AtMVPQIDWb~XMpEiK?TK|hRFqcmk4X7B{#$$JRVz@Lfug#sfNvayXNos|4XL@l zMqaV09Rr?weSvC(3TAh5KB}FQX%-KEyskfc4>rQ3EKKgeB8ID#i&l(Yc!hS+^zaiA z2Ra^L1{A={s5A7)|4;^+)-L0&UM|T63pq_I#EF%W$NacJrgh&!Y$Hp21KxBCDf@(1 ziT;N_00-=)?dO_*p%hJkC~s;kiQ8;~b0pw}9cttqK@8@2JC__qDk@#Xi^BH${-Wd%nl7WvI2E*0W^Q@-yB33%@OJnSn&rrD>PH}2C_KvZ#j(tT9K?h4?| z@V^@KB7vYn%WKJpo5z;ljf1J<&dc#|kQ-+`Z^kt|&vy-1B##;r~{~rS1aU(nMnw`_Csg5{#a}{&J7?6_eM2%Foe+<1BMWr z;l4P6MXVPM9qu&~Thn0E30|;mAz3kXM;!p8ouos2htLv#yB>YnW4*~I0(aEeK#Cb3 zKZKiJOcR^kjyZoU<~+)c6bfmv?$J^bkle@nlZmav8McbOyGdvbCE`la)M8ZiDN*Dt zDm*2MaqknI69wy?tqT&pC}3IXsZ0{s3|qiuq|b2n_;%S|9*8yzZe92=pk=wp^N5u2LQo8k?Zle7YJsKg-q~5INva_u*rob1e={rvB};ZY@vCJtZ)>@ zx-Yr&4TATq)f#&1!=yy(LzhBQu794B2Gz`YP8w>P=Q)xYXo-R1 z^PIp_6tQ}DqSKw|X!G!KZp4&#iM^^ddcyw2+$z2|NHNE9aGGrD8|CF$HNaWUpBw{d z+iXX=3VlajqulhH+-Q|HDdwt=gW;$*7>++bpW@BWlozIxBO5w@<2-GzRV4Y2<)0_Z zpX7!ldDm!Ji`7|wx%%6{?MFdWpBb4#8(G(Asln6?WDYaS;Z|X2ANSg^!ZOmAW0_fL zWiy?e^OI@6-;ZUVU!3zYckvAL@$~3;((f5T`4Im5krR1NdTAI1*lOy!ZY>VodJ?iM z*y0Gp@0hno{oG)GC68%^T`lew5On>+yegGn9?t*7Wa(&dycnji<`ge$Re{gLS>)Fc zeut>=7z=EiC4gXs@4{kJVDSJh&VL|R?=!d|UtPMxFX1FB+)<*sQzVH_c=Z#?2H?eI zxI1U&Y&PdX-6!_rI`|>(Njo+EMeP2c^Ve&W{9}#&_vfE~4Ne2}zh}$su zLvLSobam|JilUM$#XE2#kQF^*dUL>%~o&bH!d`Dt9C0*ortaU3UQ$t)ai?h zuLeKLVSia*0^Nr<#B{h{{M1-L4^74~G^5_3$kMZa97E~Uf}!b4;WC*x`n9!u+5SX1 zHpx0?R7H(}a@w;g$26a;1er`dm6Iy_wv!m-hDm#K-;}Del@K7*zJi!1>oX=A1#1J4 z1-^n5+)5jW5O?*?!T$v?SvqX*(3IO;G6ks@A=^f)x~XsKo(_in0;fO&th;YqGt3QN z_Uc}L;*$lFcNau-Nf>vt`30gyifT|z`1J!t7sNvMyk4}bwDj2D*+csp63f{Tnb%A(TK3d(P>j`p}enl@2NJ6pK=yQ~O*K32HA;$r2yzL)o?ivepe2CNG#y&R9m zxycvahpkmF=G%u*%A=n+r!Q3e9DY+eKS1_LF8}+^9r9%(0YclqW7rxDd!$O!5G;6R zS(lCpvumQovPl?f?gpkr#|6j*_=Su=I8m+lO!Yv91Qy^D&yD`2gX3g%n`!pGI2fzOhr|?4p#| zh#JR}<=|vGT^yk0sDCiFj?uwlgv^5zI2j%<#|wA_r$*2CaD5pbUl%k#rKXdq#)QKj2iLRd0~Z3J0s_SX0BXU<__*y?P0#jIn_>I}s65liGd{wBNJ z1SjZTK?}Fg#Poq5c%R)tY{Lb^{G?~RohN@^BM-TE43`tWnEcNV9)D2wU^W7*buJqs zsna@vx7KaAY%V$tqZQDiA5X7kcLin*oph{|z8`&J`xPZWnyQk-#5XucrVU*r__>a1 zNdPL?_*|T)^a+13%F{k$Km4|6Q}wGXPC%&eytw6C&P z93Galz@IF8v_XyY9qIqh=>!VO+r9P5w4L1s@@FX-7+TJrHHPTAz3RS(S0M{;dzGm!v?MghI$i5tpkCR{` zFE&r}GlFj1hr0W9A4J<*_7@As(Q#(`=*R`2_%7u|UNpyUAK&fcyM2751pSzlZxN{S zTt)7Qlg1)G1?0SN)K{|zBOL(&2D2t42>}&6HXa{8KmPv!0096033XH5DZv2%;R>3f diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index fbf25f4ea060a5e70be819d3d1e2acb5328a62e6..07740e5e2ccfab4bbc3b6ce6333159c762d09d90 100644 GIT binary patch delta 1731 zcmV;!20Zze6_ypS`vL=W&a{*M0zZH5hhkOw6_h>%RE;!iq`7ZM^SD(44=5up{SNV57~+Hsv1xz$YgT?u z*vL5SXL{aR{9lxB1_WvVuoFhQ3jkn2kJn3B84T_3!=VIKZEVBr(wYOaX8Nm+optQo zx7c~oE{>g-Y{(5GrY0%Q&n!L+(KVMvKz+Yb+)J7>ihVJsTS?y(F}K7iWGYp04FBR% z&AZaTA3YK`(7yR{#JGTKkUW17_ALEu;Z(s3{tVvLcp|AyT-@(1HAoPT zT1R!k`PEk${qOem8)%5~Q~iH$3*u)*tnvL$ z0f}9}_irb`S3~a_dhc6IJL?w5v@4><(lDBZG{xXMTEv5F#Gyb}T^5+arj~@HqV|l! z(u~)w!0#gNO7a@HO506toD+5fFYNrEmtK{hbI+qHuRY5xEJbMsN+YEt z?sd3+Vj$)GXk2JB#8&hXay5GK!MgBxP4p1eKVInKoI27Ff7QoGp)Z(6FEk+G= zcOs(h0?^G{#~vP)E4A}CEo*+G^Mc3P`l==D={(Mu#)A?%WTw!i15D8&O>1(JF#SJ& ztYK0kcf7)BX}@04Yt7Rv{TJNS4wDQgoxyiv1L;0VXM8g0lJ1Ebbmaqh{$Kq0Z$FrD z&;M)S4Pi3u_J*_@OiqFTdY#8!vD+8S)5Bmwxk->!FG%YJ>08Fk-I7C` z37*S`@2Z%&W4`Z3m&P#i_du-;b35CAhq;|Phr4UGxeL%Xuf%T;?$vRwj&u7K=Xxb4 z%(X+m60m-HC?VcRhG=0mwMGO?7}{nK%!r*%_tdd)C+v3@V&UGWdFs?hjUs9k@s3c$ zNy$Ot*!K{_RxQ#53q19z(RvSq50-|7Vou6?U>hA;7}R9Rj}SI|9*DMpOqYH zz5%t~OEMF}GMG5v++DKUFKOVlJN`1PEZOy!bm$TBAdwow%qoaP5Z!CetfPUxaD~*E z=pQPP1f?h&-7=3%kRlI&2pYnF6Ov$a=18PC((av%hys0~h4%a-lkxV^kB$?biZeBwEOq%JOHejiWUTs7OE(y*6w$q-JOziJF_`4 zeVxOcu6POT5&v6$;_qZg{W4v)FrK%(WNEyjd7}k$T9RTznAJc>w?V7b1r)uw;MEJ^ z6;kA8WOe;23AdSDX)T?9nO6TL-16!Zr3d9IlW%JLv@$`p->IXsZppEnX#4Iu->gbe zuKQ}Q*@tZYIZ<%Q9C%*w>8ir*8pp_{PUD<9hEH5l{BP@VOLmCr^EmZ$mrAb*%bCR% zzwgv*b9(LkX^#nR^G2yHdIOZmZE64-R*o>JC5$CLLQfFJO7hV_3wa! zGE~VqU7=EzCcoxnug!g>`!80%q^p6;?FWehrl^b57z7|90I}#PokSvFEPS^2N{xMY Z$*9v7o=)$k{|f*B|NqnoW^P$}000n$fOY@? delta 1731 zcmV;!20Zze6_ypS`vL>BkF}Hj0zZGwNwF&Z3d$ZEDpE*ehGr8-8sz#bM(%d(s**z_R2b2+)eusE23~@q+*ff9rH7ma+ zY-AkvGd*uD{x8Zm0|GSw*a;)u1pu(1$Ll4m42Jgi;ZTCAHnw4QY0ZIIGyPS^&N_DP zTkPze701p?Hspp8Q?m|Nl$GL@=0hJSIX z=3Qyvj~>uZG?=^xn6acG@bAX;(yzrC~G+X^O#jw1@}Uh(m#{x-2k-O)UvYMeP}d zr5UeVf!{^kmE<*YmA0GQI4A4|Vziipxca7MsrYW>6>^yL6Ss*Q@?LcJPoSf5rDVzm zQzW};YiI2eTNB&{DQ}ZG1!gygnSJOKo7pwEKqP!+5{UWUp9Sh4`7CCk>us&n@)Fjo zwVlGAyRcfTpT^Wn!g@)#&r8D7R=1Pa1yz4)_lk{6ugcH4=TVi{p5+#nqBH}gkx~-( zI$S?7kaB)BF0>h9EBXkz8ol^nU3k1EdWh;DFLZHE9cc)1Q(|lj;ATf^anG?9qXxP= z5m9#m=;p0s50A=~+WDK7HNVk$!DDTG)e`n}9%oGBK?xl)Q|QtGrs$BSH91L`{vUtV zFsYF{Ug5N~U$5x3=4qDx3vOzMNrsco;5)H_bf2U%KACh$_e2f4@&P>mFaG?uA56IC z|26Q2Fd24xL)r}{CqV$c&SNiH18lNALsyw}ZZ@CkVKAZGB*>~4r1gUIEo0_R$sx`J z&*j5+Rm|Km-*=-+V;K2+pw@=Do$Y_a+)kat-8I|X1!$XB;Nr=&xqXXs-I5dL z+M!R7lF_PYzQaPQMRb?T!=5jBc&D~zPX;od#zGC=Cu_#REBRAtar=5o89uKCnblP zZ$Pd0lFWp#3?>dZcbDw;OB#6Xj=u~mOLqMw9ePAONTkLvvkD>+ME9CA>u8`aTp=|k z`iDv+K`F{cx6C6Gq{ssxf`)(age2IUIT9(3w0kEbqCj6LA|H@ecPDT+xX19vyq&Y& z#P_LpdRB61GTuJ=(Q(34ai)for7mA&3Cc#x>?hmKtBokfR=Fi@Y(*62gsQzDG5!9L zO}>!u-D&lQlH-Yg5czNs`94F9PEnR>GZ0rhL>YDyy~3hYvs3N8R;z!rtZiSTb7EG1 z{b%#B)wIK29W}QJ^`0|UYxld*?smz!o!Ok2 zzRqDzSG)xFi2p4=@pm$$ewi*?7|&Z?vNT@NywQR=ElIH<%xa*c+o09z0*YQ-@al!| z3Mq0kvbuhigxk!nw3dI)OsoGAZh7^I(t~o9$u~8ATA85Q@6^#*r{q{pw0(D-Z&sx! z*L}6u>_ayHoG7?t4m_{;bXDPYjbmg}r*Tdl!zV5&{ z-*@V@x@VjC#M9n>scO&m&*Trl#NKoD%oJ{N$+mS)9VB%35}1F|qDXLuM^T*tWuquc zFolTY2#^vSMCYwF(52D?Fyz7m2Ms2Op#MbQV6+fH=iC7s9XySxJw}u{REwzl@B&v+ zTi-qMiTwc1ycZR&MhJ5dL7#Q4F_Gg&R&h3S*rxtaME#Vqeo7fZ&2GtQme`gJVP;HK zyhNM3hjN$~S6Ooc@!}dr$S&b(EAQ7U8>*yyOHs7j?sh(`9Y=RmA$uj;&i^D-{X3wb z3{`SYSE!Vw$*(!tYja=e{)^Qw>1yC|`$3|BDe59M1_6i&KrDJnCy@vk3!m-1Qe)p; ZGV1sgPp5a&{{;X5|Nl>=ppaR5000ghVSxYu diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 3941ce563..4c409788f 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -55,7 +55,7 @@ func infoCmdAct(cctx *cli.Context) error { } defer closer() - api, acloser, err := lcli.GetFullNodeAPI(cctx) + fullapi, acloser, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err } @@ -63,9 +63,16 @@ func infoCmdAct(cctx *cli.Context) error { ctx := lcli.ReqContext(cctx) + subsystems, err := nodeApi.RuntimeSubsystems(ctx) + if err != nil { + return err + } + + fmt.Println("Enabled subsystems:", subsystems) + fmt.Print("Chain: ") - head, err := api.ChainHead(ctx) + head, err := fullapi.ChainHead(ctx) if err != nil { return err } @@ -95,284 +102,289 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(api), blockstore.NewMemory()) - mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) - if err != nil { - return err - } - - // Sector size - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) - fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) - - pow, err := api.StateMinerPower(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - fmt.Printf("Power: %s / %s (%0.4f%%)\n", - color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), - types.DeciStr(pow.TotalPower.QualityAdjPower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), - pow.TotalPower.QualityAdjPower, - ), - ) - - fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", - color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), - types.SizeStr(pow.TotalPower.RawBytePower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), - pow.TotalPower.RawBytePower, - ), - ) - secCounts, err := api.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - proving := secCounts.Active + secCounts.Faulty - nfaults := secCounts.Faulty - fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) - if nfaults == 0 { - fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) - } else { - var faultyPercentage float64 - if secCounts.Live != 0 { - faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) - } - fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", - types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), - types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), - faultyPercentage) - } - - if !pow.HasMinPower { - fmt.Print("Below minimum power threshold, no blocks will be won") - } else { - - winRatio := new(corebig.Rat).SetFrac( - types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, - pow.TotalPower.QualityAdjPower.Int, - ) - - if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { - - // if the corresponding poisson distribution isn't infinitely small then - // throw it into the mix as well, accounting for multi-wins - winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) - winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) - if winRationWithPoisson != nil { - winRatio = winRationWithPoisson - winRatioFloat = winRationWithPoissonFloat - } - - weekly, _ := new(corebig.Rat).Mul( - winRatio, - new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), - ).Float64() - - avgDuration, _ := new(corebig.Rat).Mul( - new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), - new(corebig.Rat).Inv(winRatio), - ).Float64() - - fmt.Print("Projected average block win rate: ") - color.Blue( - "%.02f/week (every %s)", - weekly, - (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), - ) - - // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples - // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t - // t == how many dice-rolls (epochs) before win - // p == winRate == ( minerPower / netPower ) - // c == target probability of win ( 99.9% in this case ) - fmt.Print("Projected block win with ") - color.Green( - "99.9%% probability every %s", - (time.Second * time.Duration( - builtin.EpochDurationSeconds*math.Log(1-0.999)/ - math.Log(1-winRatioFloat), - )).Truncate(time.Second).String(), - ) - fmt.Println("(projections DO NOT account for future network and miner growth)") - } - } - - fmt.Println() - - deals, err := nodeApi.MarketListIncompleteDeals(ctx) - if err != nil { - return err - } - - type dealStat struct { - count, verifCount int - bytes, verifBytes uint64 - } - dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { - ds.count++ - ds.bytes += uint64(deal.Proposal.PieceSize) - if deal.Proposal.VerifiedDeal { - ds.verifCount++ - ds.verifBytes += uint64(deal.Proposal.PieceSize) - } - } - - showDealStates := map[storagemarket.StorageDealStatus]struct{}{ - storagemarket.StorageDealActive: {}, - storagemarket.StorageDealTransferring: {}, - storagemarket.StorageDealStaged: {}, - storagemarket.StorageDealAwaitingPreCommit: {}, - storagemarket.StorageDealSealing: {}, - storagemarket.StorageDealPublish: {}, - storagemarket.StorageDealCheckForAcceptance: {}, - storagemarket.StorageDealPublishing: {}, - } - - var total dealStat - perState := map[storagemarket.StorageDealStatus]*dealStat{} - for _, deal := range deals { - if _, ok := showDealStates[deal.State]; !ok { - continue - } - if perState[deal.State] == nil { - perState[deal.State] = new(dealStat) - } - - dsAdd(&total, deal) - dsAdd(perState[deal.State], deal) - } - - type wstr struct { - str string - status storagemarket.StorageDealStatus - } - sorted := make([]wstr, 0, len(perState)) - for status, stat := range perState { - st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") - sorted = append(sorted, wstr{ - str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), - status: status, - }, - ) - } - sort.Slice(sorted, func(i, j int) bool { - if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { - return sorted[i].status == storagemarket.StorageDealActive - } - return sorted[i].status > sorted[j].status - }) - - fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) - - tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - for _, e := range sorted { - _, _ = tw.Write([]byte(e.str)) - } - - _ = tw.Flush() - fmt.Println() - - retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) - if err != nil { - return xerrors.Errorf("getting retrieval deal list: %w", err) - } - - var retrComplete dealStat - for _, retrieval := range retrievals { - if retrieval.Status == retrievalmarket.DealStatusCompleted { - retrComplete.count++ - retrComplete.bytes += retrieval.TotalSent - } - } - - fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) - - fmt.Println() - - spendable := big.Zero() - - // NOTE: there's no need to unlock anything here. Funds only - // vest on deadline boundaries, and they're unlocked by cron. - lockedFunds, err := mas.LockedFunds() - if err != nil { - return xerrors.Errorf("getting locked funds: %w", err) - } - availBalance, err := mas.AvailableBalance(mact.Balance) - if err != nil { - return xerrors.Errorf("getting available balance: %w", err) - } - spendable = big.Add(spendable, availBalance) - - fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) - fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) - fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) - fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) - colorTokenAmount(" Available: %s\n", availBalance) - - mb, err := api.StateMarketBalance(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting market balance: %w", err) - } - spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) - - fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) - fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) - colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) - - wb, err := api.WalletBalance(ctx, mi.Worker) - if err != nil { - return xerrors.Errorf("getting worker balance: %w", err) - } - spendable = big.Add(spendable, wb) - color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) - if len(mi.ControlAddresses) > 0 { - cbsum := big.Zero() - for _, ca := range mi.ControlAddresses { - b, err := api.WalletBalance(ctx, ca) - if err != nil { - return xerrors.Errorf("getting control address balance: %w", err) - } - cbsum = big.Add(cbsum, b) - } - spendable = big.Add(spendable, cbsum) - - fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) - } - colorTokenAmount("Total Spendable: %s\n", spendable) - - fmt.Println() - - if !cctx.Bool("hide-sectors-info") { - fmt.Println("Sectors:") - err = sectorsInfo(ctx, nodeApi) + if subsystems.Has(api.SectorStorageSubsystem) { + maddr, err := getActorAddress(ctx, cctx) if err != nil { return err } + + mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } + + // Sector size + mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) + fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) + + pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + fmt.Printf("Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) + + fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) + secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + proving := secCounts.Active + secCounts.Faulty + nfaults := secCounts.Faulty + fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) + if nfaults == 0 { + fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) + } else { + var faultyPercentage float64 + if secCounts.Live != 0 { + faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) + } + fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", + types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), + types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), + faultyPercentage) + } + + if !pow.HasMinPower { + fmt.Print("Below minimum power threshold, no blocks will be won") + } else { + + winRatio := new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, + ) + + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + + // if the corresponding poisson distribution isn't infinitely small then + // throw it into the mix as well, accounting for multi-wins + winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) + winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) + if winRationWithPoisson != nil { + winRatio = winRationWithPoisson + winRatioFloat = winRationWithPoissonFloat + } + + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Print("Projected average block win rate: ") + color.Blue( + "%.02f/week (every %s)", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) + + // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t + // t == how many dice-rolls (epochs) before win + // p == winRate == ( minerPower / netPower ) + // c == target probability of win ( 99.9% in this case ) + fmt.Print("Projected block win with ") + color.Green( + "99.9%% probability every %s", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Println("(projections DO NOT account for future network and miner growth)") + } + } + + fmt.Println() + + spendable := big.Zero() + + // NOTE: there's no need to unlock anything here. Funds only + // vest on deadline boundaries, and they're unlocked by cron. + lockedFunds, err := mas.LockedFunds() + if err != nil { + return xerrors.Errorf("getting locked funds: %w", err) + } + availBalance, err := mas.AvailableBalance(mact.Balance) + if err != nil { + return xerrors.Errorf("getting available balance: %w", err) + } + spendable = big.Add(spendable, availBalance) + + fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) + fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) + fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) + fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) + colorTokenAmount(" Available: %s\n", availBalance) + + mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting market balance: %w", err) + } + spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) + + fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) + fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) + colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) + + wb, err := fullapi.WalletBalance(ctx, mi.Worker) + if err != nil { + return xerrors.Errorf("getting worker balance: %w", err) + } + spendable = big.Add(spendable, wb) + color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) + if len(mi.ControlAddresses) > 0 { + cbsum := big.Zero() + for _, ca := range mi.ControlAddresses { + b, err := fullapi.WalletBalance(ctx, ca) + if err != nil { + return xerrors.Errorf("getting control address balance: %w", err) + } + cbsum = big.Add(cbsum, b) + } + spendable = big.Add(spendable, cbsum) + + fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) + } + colorTokenAmount("Total Spendable: %s\n", spendable) + + fmt.Println() + + if !cctx.Bool("hide-sectors-info") { + fmt.Println("Sectors:") + err = sectorsInfo(ctx, nodeApi) + if err != nil { + return err + } + } + + // TODO: grab actr state / info + // * Sealed sectors (count / bytes) + // * Power + } + + if subsystems.Has(api.MarketsSubsystem) { + deals, err := nodeApi.MarketListIncompleteDeals(ctx) + if err != nil { + return err + } + + type dealStat struct { + count, verifCount int + bytes, verifBytes uint64 + } + dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { + ds.count++ + ds.bytes += uint64(deal.Proposal.PieceSize) + if deal.Proposal.VerifiedDeal { + ds.verifCount++ + ds.verifBytes += uint64(deal.Proposal.PieceSize) + } + } + + showDealStates := map[storagemarket.StorageDealStatus]struct{}{ + storagemarket.StorageDealActive: {}, + storagemarket.StorageDealTransferring: {}, + storagemarket.StorageDealStaged: {}, + storagemarket.StorageDealAwaitingPreCommit: {}, + storagemarket.StorageDealSealing: {}, + storagemarket.StorageDealPublish: {}, + storagemarket.StorageDealCheckForAcceptance: {}, + storagemarket.StorageDealPublishing: {}, + } + + var total dealStat + perState := map[storagemarket.StorageDealStatus]*dealStat{} + for _, deal := range deals { + if _, ok := showDealStates[deal.State]; !ok { + continue + } + if perState[deal.State] == nil { + perState[deal.State] = new(dealStat) + } + + dsAdd(&total, deal) + dsAdd(perState[deal.State], deal) + } + + type wstr struct { + str string + status storagemarket.StorageDealStatus + } + sorted := make([]wstr, 0, len(perState)) + for status, stat := range perState { + st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") + sorted = append(sorted, wstr{ + str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), + status: status, + }, + ) + } + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { + return sorted[i].status == storagemarket.StorageDealActive + } + return sorted[i].status > sorted[j].status + }) + + fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) + + tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + for _, e := range sorted { + _, _ = tw.Write([]byte(e.str)) + } + + _ = tw.Flush() + fmt.Println() + + retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) + if err != nil { + return xerrors.Errorf("getting retrieval deal list: %w", err) + } + + var retrComplete dealStat + for _, retrieval := range retrievals { + if retrieval.Status == retrievalmarket.DealStatusCompleted { + retrComplete.count++ + retrComplete.bytes += retrieval.TotalSent + } + } + + fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) + + fmt.Println() } - // TODO: grab actr state / info - // * Sealed sectors (count / bytes) - // * Power return nil } diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index e8598ff0c..dcd3abd4d 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -94,6 +94,8 @@ * [ReturnSealPreCommit1](#ReturnSealPreCommit1) * [ReturnSealPreCommit2](#ReturnSealPreCommit2) * [ReturnUnsealPiece](#ReturnUnsealPiece) +* [Runtime](#Runtime) + * [RuntimeSubsystems](#RuntimeSubsystems) * [Sealing](#Sealing) * [SealingAbort](#SealingAbort) * [SealingSchedDiag](#SealingSchedDiag) @@ -1522,6 +1524,18 @@ Inputs: Response: `{}` +## Runtime + + +### RuntimeSubsystems + + +Perms: read + +Inputs: `null` + +Response: `null` + ## Sealing diff --git a/node/builder_miner.go b/node/builder_miner.go index 0c0f9d15a..d62cbcad9 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,6 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), + Override(new([]api.MinerSubsystem), modules.AddMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), @@ -215,6 +216,7 @@ func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConf func(s *Settings) error { resAPI := &impl.StorageMinerAPI{} + s.invokes[ExtractApiKey] = fx.Populate(resAPI) *out = resAPI return nil diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 9db6a3775..86c84fee7 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -48,6 +48,8 @@ import ( type StorageMinerAPI struct { fx.In + Subsystems api.MinerSubsystems + api.Common api.Net @@ -703,4 +705,8 @@ func (sm *StorageMinerAPI) ComputeProof(ctx context.Context, ssi []builtin.Secto return sm.Epp.ComputeProof(ctx, ssi, rand) } +func (sm *StorageMinerAPI) RuntimeSubsystems(context.Context) (res api.MinerSubsystems, err error) { + return sm.Subsystems, nil +} + var _ api.StorageMiner = &StorageMinerAPI{} diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 3a3914e0c..06ef78ca0 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1007,3 +1007,20 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } + +func AddMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { + if cfg.EnableMining { + res = append(res, api.MiningSubsystem) + } + if cfg.EnableSealing { + res = append(res, api.SealingSubsystem) + } + if cfg.EnableSectorStorage { + res = append(res, api.SectorStorageSubsystem) + } + if cfg.EnableMarkets { + res = append(res, api.MarketsSubsystem) + } + + return +} From 4cf5c8f656f58f81f69b85fe5b8180592a810e57 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 16:23:04 +0300 Subject: [PATCH 88/98] fixup --- node/builder_miner.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/node/builder_miner.go b/node/builder_miner.go index d62cbcad9..830e6d075 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,7 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(new([]api.MinerSubsystem), modules.AddMinerSubsystems(cfg.Subsystems)), + Override(new(api.MinerSubsystems), modules.AddMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), @@ -216,7 +216,6 @@ func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConf func(s *Settings) error { resAPI := &impl.StorageMinerAPI{} - s.invokes[ExtractApiKey] = fx.Populate(resAPI) *out = resAPI return nil From 54c6a3a45413f1fd4bb0857f453d2820ce80400e Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 17:30:59 +0300 Subject: [PATCH 89/98] extract outputs for mining node and markets node in separate functions --- cmd/lotus-miner/info.go | 561 +++++++++++++++++++++------------------- 1 file changed, 290 insertions(+), 271 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 4c409788f..2a3627959 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -21,6 +21,7 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/api/v0api" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/api" @@ -103,291 +104,309 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() if subsystems.Has(api.SectorStorageSubsystem) { - maddr, err := getActorAddress(ctx, cctx) + err := handleMiningInfo(cctx, ctx, fullapi, nodeApi) if err != nil { return err } - - mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) - mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) - if err != nil { - return err - } - - // Sector size - mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) - fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) - - pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - fmt.Printf("Power: %s / %s (%0.4f%%)\n", - color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), - types.DeciStr(pow.TotalPower.QualityAdjPower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), - pow.TotalPower.QualityAdjPower, - ), - ) - - fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", - color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), - types.SizeStr(pow.TotalPower.RawBytePower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), - pow.TotalPower.RawBytePower, - ), - ) - secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - proving := secCounts.Active + secCounts.Faulty - nfaults := secCounts.Faulty - fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) - if nfaults == 0 { - fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) - } else { - var faultyPercentage float64 - if secCounts.Live != 0 { - faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) - } - fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", - types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), - types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), - faultyPercentage) - } - - if !pow.HasMinPower { - fmt.Print("Below minimum power threshold, no blocks will be won") - } else { - - winRatio := new(corebig.Rat).SetFrac( - types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, - pow.TotalPower.QualityAdjPower.Int, - ) - - if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { - - // if the corresponding poisson distribution isn't infinitely small then - // throw it into the mix as well, accounting for multi-wins - winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) - winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) - if winRationWithPoisson != nil { - winRatio = winRationWithPoisson - winRatioFloat = winRationWithPoissonFloat - } - - weekly, _ := new(corebig.Rat).Mul( - winRatio, - new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), - ).Float64() - - avgDuration, _ := new(corebig.Rat).Mul( - new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), - new(corebig.Rat).Inv(winRatio), - ).Float64() - - fmt.Print("Projected average block win rate: ") - color.Blue( - "%.02f/week (every %s)", - weekly, - (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), - ) - - // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples - // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t - // t == how many dice-rolls (epochs) before win - // p == winRate == ( minerPower / netPower ) - // c == target probability of win ( 99.9% in this case ) - fmt.Print("Projected block win with ") - color.Green( - "99.9%% probability every %s", - (time.Second * time.Duration( - builtin.EpochDurationSeconds*math.Log(1-0.999)/ - math.Log(1-winRatioFloat), - )).Truncate(time.Second).String(), - ) - fmt.Println("(projections DO NOT account for future network and miner growth)") - } - } - - fmt.Println() - - spendable := big.Zero() - - // NOTE: there's no need to unlock anything here. Funds only - // vest on deadline boundaries, and they're unlocked by cron. - lockedFunds, err := mas.LockedFunds() - if err != nil { - return xerrors.Errorf("getting locked funds: %w", err) - } - availBalance, err := mas.AvailableBalance(mact.Balance) - if err != nil { - return xerrors.Errorf("getting available balance: %w", err) - } - spendable = big.Add(spendable, availBalance) - - fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) - fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) - fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) - fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) - colorTokenAmount(" Available: %s\n", availBalance) - - mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting market balance: %w", err) - } - spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) - - fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) - fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) - colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) - - wb, err := fullapi.WalletBalance(ctx, mi.Worker) - if err != nil { - return xerrors.Errorf("getting worker balance: %w", err) - } - spendable = big.Add(spendable, wb) - color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) - if len(mi.ControlAddresses) > 0 { - cbsum := big.Zero() - for _, ca := range mi.ControlAddresses { - b, err := fullapi.WalletBalance(ctx, ca) - if err != nil { - return xerrors.Errorf("getting control address balance: %w", err) - } - cbsum = big.Add(cbsum, b) - } - spendable = big.Add(spendable, cbsum) - - fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) - } - colorTokenAmount("Total Spendable: %s\n", spendable) - - fmt.Println() - - if !cctx.Bool("hide-sectors-info") { - fmt.Println("Sectors:") - err = sectorsInfo(ctx, nodeApi) - if err != nil { - return err - } - } - - // TODO: grab actr state / info - // * Sealed sectors (count / bytes) - // * Power } if subsystems.Has(api.MarketsSubsystem) { - deals, err := nodeApi.MarketListIncompleteDeals(ctx) + err := handleMarketsInfo(ctx, nodeApi) if err != nil { return err } - - type dealStat struct { - count, verifCount int - bytes, verifBytes uint64 - } - dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { - ds.count++ - ds.bytes += uint64(deal.Proposal.PieceSize) - if deal.Proposal.VerifiedDeal { - ds.verifCount++ - ds.verifBytes += uint64(deal.Proposal.PieceSize) - } - } - - showDealStates := map[storagemarket.StorageDealStatus]struct{}{ - storagemarket.StorageDealActive: {}, - storagemarket.StorageDealTransferring: {}, - storagemarket.StorageDealStaged: {}, - storagemarket.StorageDealAwaitingPreCommit: {}, - storagemarket.StorageDealSealing: {}, - storagemarket.StorageDealPublish: {}, - storagemarket.StorageDealCheckForAcceptance: {}, - storagemarket.StorageDealPublishing: {}, - } - - var total dealStat - perState := map[storagemarket.StorageDealStatus]*dealStat{} - for _, deal := range deals { - if _, ok := showDealStates[deal.State]; !ok { - continue - } - if perState[deal.State] == nil { - perState[deal.State] = new(dealStat) - } - - dsAdd(&total, deal) - dsAdd(perState[deal.State], deal) - } - - type wstr struct { - str string - status storagemarket.StorageDealStatus - } - sorted := make([]wstr, 0, len(perState)) - for status, stat := range perState { - st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") - sorted = append(sorted, wstr{ - str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), - status: status, - }, - ) - } - sort.Slice(sorted, func(i, j int) bool { - if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { - return sorted[i].status == storagemarket.StorageDealActive - } - return sorted[i].status > sorted[j].status - }) - - fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) - - tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - for _, e := range sorted { - _, _ = tw.Write([]byte(e.str)) - } - - _ = tw.Flush() - fmt.Println() - - retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) - if err != nil { - return xerrors.Errorf("getting retrieval deal list: %w", err) - } - - var retrComplete dealStat - for _, retrieval := range retrievals { - if retrieval.Status == retrievalmarket.DealStatusCompleted { - retrComplete.count++ - retrComplete.bytes += retrieval.TotalSent - } - } - - fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) - - fmt.Println() } return nil } +func handleMiningInfo(cctx *cli.Context, ctx context.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return err + } + + mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } + + // Sector size + mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) + fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) + + pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + fmt.Printf("Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) + + fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) + secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + proving := secCounts.Active + secCounts.Faulty + nfaults := secCounts.Faulty + fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) + if nfaults == 0 { + fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) + } else { + var faultyPercentage float64 + if secCounts.Live != 0 { + faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) + } + fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", + types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), + types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), + faultyPercentage) + } + + if !pow.HasMinPower { + fmt.Print("Below minimum power threshold, no blocks will be won") + } else { + + winRatio := new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, + ) + + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + + // if the corresponding poisson distribution isn't infinitely small then + // throw it into the mix as well, accounting for multi-wins + winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) + winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) + if winRationWithPoisson != nil { + winRatio = winRationWithPoisson + winRatioFloat = winRationWithPoissonFloat + } + + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Print("Projected average block win rate: ") + color.Blue( + "%.02f/week (every %s)", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) + + // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t + // t == how many dice-rolls (epochs) before win + // p == winRate == ( minerPower / netPower ) + // c == target probability of win ( 99.9% in this case ) + fmt.Print("Projected block win with ") + color.Green( + "99.9%% probability every %s", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Println("(projections DO NOT account for future network and miner growth)") + } + } + + fmt.Println() + + spendable := big.Zero() + + // NOTE: there's no need to unlock anything here. Funds only + // vest on deadline boundaries, and they're unlocked by cron. + lockedFunds, err := mas.LockedFunds() + if err != nil { + return xerrors.Errorf("getting locked funds: %w", err) + } + availBalance, err := mas.AvailableBalance(mact.Balance) + if err != nil { + return xerrors.Errorf("getting available balance: %w", err) + } + spendable = big.Add(spendable, availBalance) + + fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) + fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) + fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) + fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) + colorTokenAmount(" Available: %s\n", availBalance) + + mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting market balance: %w", err) + } + spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) + + fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) + fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) + colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) + + wb, err := fullapi.WalletBalance(ctx, mi.Worker) + if err != nil { + return xerrors.Errorf("getting worker balance: %w", err) + } + spendable = big.Add(spendable, wb) + color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) + if len(mi.ControlAddresses) > 0 { + cbsum := big.Zero() + for _, ca := range mi.ControlAddresses { + b, err := fullapi.WalletBalance(ctx, ca) + if err != nil { + return xerrors.Errorf("getting control address balance: %w", err) + } + cbsum = big.Add(cbsum, b) + } + spendable = big.Add(spendable, cbsum) + + fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) + } + colorTokenAmount("Total Spendable: %s\n", spendable) + + fmt.Println() + + if !cctx.Bool("hide-sectors-info") { + fmt.Println("Sectors:") + err = sectorsInfo(ctx, nodeApi) + if err != nil { + return err + } + } + + // TODO: grab actr state / info + // * Sealed sectors (count / bytes) + // * Power + + return nil +} + +func handleMarketsInfo(ctx context.Context, nodeApi api.StorageMiner) error { + deals, err := nodeApi.MarketListIncompleteDeals(ctx) + if err != nil { + return err + } + + type dealStat struct { + count, verifCount int + bytes, verifBytes uint64 + } + dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { + ds.count++ + ds.bytes += uint64(deal.Proposal.PieceSize) + if deal.Proposal.VerifiedDeal { + ds.verifCount++ + ds.verifBytes += uint64(deal.Proposal.PieceSize) + } + } + + showDealStates := map[storagemarket.StorageDealStatus]struct{}{ + storagemarket.StorageDealActive: {}, + storagemarket.StorageDealTransferring: {}, + storagemarket.StorageDealStaged: {}, + storagemarket.StorageDealAwaitingPreCommit: {}, + storagemarket.StorageDealSealing: {}, + storagemarket.StorageDealPublish: {}, + storagemarket.StorageDealCheckForAcceptance: {}, + storagemarket.StorageDealPublishing: {}, + } + + var total dealStat + perState := map[storagemarket.StorageDealStatus]*dealStat{} + for _, deal := range deals { + if _, ok := showDealStates[deal.State]; !ok { + continue + } + if perState[deal.State] == nil { + perState[deal.State] = new(dealStat) + } + + dsAdd(&total, deal) + dsAdd(perState[deal.State], deal) + } + + type wstr struct { + str string + status storagemarket.StorageDealStatus + } + sorted := make([]wstr, 0, len(perState)) + for status, stat := range perState { + st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") + sorted = append(sorted, wstr{ + str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), + status: status, + }, + ) + } + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { + return sorted[i].status == storagemarket.StorageDealActive + } + return sorted[i].status > sorted[j].status + }) + + fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) + + tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + for _, e := range sorted { + _, _ = tw.Write([]byte(e.str)) + } + + _ = tw.Flush() + fmt.Println() + + retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) + if err != nil { + return xerrors.Errorf("getting retrieval deal list: %w", err) + } + + var retrComplete dealStat + for _, retrieval := range retrievals { + if retrieval.Status == retrievalmarket.DealStatusCompleted { + retrComplete.count++ + retrComplete.bytes += retrieval.TotalSent + } + } + + fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) + + fmt.Println() + + return nil +} + type stateMeta struct { i int col color.Attribute From 6969202a03466e7f46d280c05ae5bf0ca2ed0468 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 17:47:01 +0300 Subject: [PATCH 90/98] make linter happy --- cmd/lotus-miner/info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 2a3627959..92f25667c 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -104,7 +104,7 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() if subsystems.Has(api.SectorStorageSubsystem) { - err := handleMiningInfo(cctx, ctx, fullapi, nodeApi) + err := handleMiningInfo(ctx, cctx, fullapi, nodeApi) if err != nil { return err } @@ -120,7 +120,7 @@ func infoCmdAct(cctx *cli.Context) error { return nil } -func handleMiningInfo(cctx *cli.Context, ctx context.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { +func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { maddr, err := getActorAddress(ctx, cctx) if err != nil { return err From f8afb699cddb455075c4f90c0512d92723bfcd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 28 Jul 2021 17:29:17 +0200 Subject: [PATCH 91/98] make: Allow setting Go compiler with GOCC --- Makefile | 62 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 77fd38b9e..77040d1b6 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,8 @@ MODULES:= CLEAN:= BINS:= +GOCC?=go + ldflags=-X=github.com/filecoin-project/lotus/build.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) ifneq ($(strip $(LDFLAGS)),) ldflags+=-extldflags=$(LDFLAGS) @@ -85,32 +87,32 @@ interopnet: build-devnets lotus: $(BUILD_DEPS) rm -f lotus - go build $(GOFLAGS) -o lotus ./cmd/lotus + $(GOCC) build $(GOFLAGS) -o lotus ./cmd/lotus .PHONY: lotus BINS+=lotus lotus-miner: $(BUILD_DEPS) rm -f lotus-miner - go build $(GOFLAGS) -o lotus-miner ./cmd/lotus-miner + $(GOCC) build $(GOFLAGS) -o lotus-miner ./cmd/lotus-miner .PHONY: lotus-miner BINS+=lotus-miner lotus-worker: $(BUILD_DEPS) rm -f lotus-worker - go build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker + $(GOCC) build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker .PHONY: lotus-worker BINS+=lotus-worker lotus-shed: $(BUILD_DEPS) rm -f lotus-shed - go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed + $(GOCC) build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed .PHONY: lotus-shed BINS+=lotus-shed lotus-gateway: $(BUILD_DEPS) rm -f lotus-gateway - go build $(GOFLAGS) -o lotus-gateway ./cmd/lotus-gateway + $(GOCC) build $(GOFLAGS) -o lotus-gateway ./cmd/lotus-gateway .PHONY: lotus-gateway BINS+=lotus-gateway @@ -138,19 +140,19 @@ install-app: lotus-seed: $(BUILD_DEPS) rm -f lotus-seed - go build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed + $(GOCC) build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed .PHONY: lotus-seed BINS+=lotus-seed benchmarks: - go run github.com/whyrusleeping/bencher ./... > bench.json + $(GOCC) run github.com/whyrusleeping/bencher ./... > bench.json @echo Submitting results @curl -X POST 'http://benchmark.kittyhawk.wtf/benchmark' -d '@bench.json' -u "${benchmark_http_cred}" .PHONY: benchmarks lotus-pond: 2k - go build -o lotus-pond ./lotuspond + $(GOCC) build -o lotus-pond ./lotuspond .PHONY: lotus-pond BINS+=lotus-pond @@ -163,7 +165,7 @@ lotus-pond-app: lotus-pond-front lotus-pond lotus-townhall: rm -f lotus-townhall - go build -o lotus-townhall ./cmd/lotus-townhall + $(GOCC) build -o lotus-townhall ./cmd/lotus-townhall .PHONY: lotus-townhall BINS+=lotus-townhall @@ -176,61 +178,61 @@ lotus-townhall-app: lotus-touch lotus-townhall-front lotus-fountain: rm -f lotus-fountain - go build -o lotus-fountain ./cmd/lotus-fountain + $(GOCC) build -o lotus-fountain ./cmd/lotus-fountain .PHONY: lotus-fountain BINS+=lotus-fountain lotus-chainwatch: rm -f lotus-chainwatch - go build $(GOFLAGS) -o lotus-chainwatch ./cmd/lotus-chainwatch + $(GOCC) build $(GOFLAGS) -o lotus-chainwatch ./cmd/lotus-chainwatch .PHONY: lotus-chainwatch BINS+=lotus-chainwatch lotus-bench: rm -f lotus-bench - go build -o lotus-bench ./cmd/lotus-bench + $(GOCC) build -o lotus-bench ./cmd/lotus-bench .PHONY: lotus-bench BINS+=lotus-bench lotus-stats: rm -f lotus-stats - go build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats + $(GOCC) build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats .PHONY: lotus-stats BINS+=lotus-stats lotus-pcr: rm -f lotus-pcr - go build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr + $(GOCC) build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr .PHONY: lotus-pcr BINS+=lotus-pcr lotus-health: rm -f lotus-health - go build -o lotus-health ./cmd/lotus-health + $(GOCC) build -o lotus-health ./cmd/lotus-health .PHONY: lotus-health BINS+=lotus-health lotus-wallet: rm -f lotus-wallet - go build -o lotus-wallet ./cmd/lotus-wallet + $(GOCC) build -o lotus-wallet ./cmd/lotus-wallet .PHONY: lotus-wallet BINS+=lotus-wallet lotus-keygen: rm -f lotus-keygen - go build -o lotus-keygen ./cmd/lotus-keygen + $(GOCC) build -o lotus-keygen ./cmd/lotus-keygen .PHONY: lotus-keygen BINS+=lotus-keygen testground: - go build -tags testground -o /dev/null ./cmd/lotus + $(GOCC) build -tags testground -o /dev/null ./cmd/lotus .PHONY: testground BINS+=testground tvx: rm -f tvx - go build -o tvx ./cmd/tvx + $(GOCC) build -o tvx ./cmd/tvx .PHONY: tvx BINS+=tvx @@ -239,7 +241,7 @@ install-chainwatch: lotus-chainwatch lotus-sim: $(BUILD_DEPS) rm -f lotus-sim - go build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim + $(GOCC) build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim .PHONY: lotus-sim BINS+=lotus-sim @@ -319,25 +321,25 @@ dist-clean: .PHONY: dist-clean type-gen: api-gen - go run ./gen/main.go - go generate -x ./... + $(GOCC) run ./gen/main.go + $(GOCC) generate -x ./... goimports -w api/ method-gen: api-gen - (cd ./lotuspond/front/src/chain && go run ./methodgen.go) + (cd ./lotuspond/front/src/chain && $(GOCC) run ./methodgen.go) actors-gen: - go run ./chain/actors/agen - go fmt ./... + $(GOCC) run ./chain/actors/agen + $(GOCC) fmt ./... api-gen: - go run ./gen/api + $(GOCC) run ./gen/api goimports -w api goimports -w api .PHONY: api-gen cfgdoc-gen: - go run ./node/config/cfgdocgen > ./node/config/doc_gen.go + $(GOCC) run ./node/config/cfgdocgen > ./node/config/doc_gen.go appimage: lotus rm -rf appimage-builder-cache || true @@ -351,9 +353,9 @@ appimage: lotus docsgen: docsgen-md docsgen-openrpc docsgen-md-bin: api-gen actors-gen - go build $(GOFLAGS) -o docgen-md ./api/docgen/cmd + $(GOCC) build $(GOFLAGS) -o docgen-md ./api/docgen/cmd docsgen-openrpc-bin: api-gen actors-gen - go build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd + $(GOCC) build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker @@ -393,4 +395,4 @@ print-%: @echo $*=$($*) circleci: - go generate -x ./.circleci \ No newline at end of file + $(GOCC) generate -x ./.circleci \ No newline at end of file From 389f71251c770030a59e22400eb97202eaa9c835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 28 Jul 2021 17:50:29 +0200 Subject: [PATCH 92/98] Remove townhall --- Makefile | 13 -- chain/metrics/consensus.go | 129 ----------------- cmd/lotus-townhall/main.go | 134 ------------------ cmd/lotus-townhall/townhall/.gitignore | 23 --- cmd/lotus-townhall/townhall/package.json | 31 ---- cmd/lotus-townhall/townhall/public/index.html | 13 -- cmd/lotus-townhall/townhall/public/robots.txt | 2 - cmd/lotus-townhall/townhall/src/App.css | 1 - cmd/lotus-townhall/townhall/src/App.js | 87 ------------ cmd/lotus-townhall/townhall/src/App.test.js | 9 -- cmd/lotus-townhall/townhall/src/index.css | 6 - cmd/lotus-townhall/townhall/src/index.js | 6 - node/builder_chain.go | 5 - node/config/doc_gen.go | 20 --- node/config/types.go | 7 - 15 files changed, 486 deletions(-) delete mode 100644 chain/metrics/consensus.go delete mode 100644 cmd/lotus-townhall/main.go delete mode 100644 cmd/lotus-townhall/townhall/.gitignore delete mode 100644 cmd/lotus-townhall/townhall/package.json delete mode 100644 cmd/lotus-townhall/townhall/public/index.html delete mode 100644 cmd/lotus-townhall/townhall/public/robots.txt delete mode 100644 cmd/lotus-townhall/townhall/src/App.css delete mode 100644 cmd/lotus-townhall/townhall/src/App.js delete mode 100644 cmd/lotus-townhall/townhall/src/App.test.js delete mode 100644 cmd/lotus-townhall/townhall/src/index.css delete mode 100644 cmd/lotus-townhall/townhall/src/index.js diff --git a/Makefile b/Makefile index 77040d1b6..4aac5f21a 100644 --- a/Makefile +++ b/Makefile @@ -163,19 +163,6 @@ lotus-pond-front: lotus-pond-app: lotus-pond-front lotus-pond .PHONY: lotus-pond-app -lotus-townhall: - rm -f lotus-townhall - $(GOCC) build -o lotus-townhall ./cmd/lotus-townhall -.PHONY: lotus-townhall -BINS+=lotus-townhall - -lotus-townhall-front: - (cd ./cmd/lotus-townhall/townhall && npm i && npm run build) -.PHONY: lotus-townhall-front - -lotus-townhall-app: lotus-touch lotus-townhall-front -.PHONY: lotus-townhall-app - lotus-fountain: rm -f lotus-fountain $(GOCC) build -o lotus-fountain ./cmd/lotus-fountain diff --git a/chain/metrics/consensus.go b/chain/metrics/consensus.go deleted file mode 100644 index c3c4a10d1..000000000 --- a/chain/metrics/consensus.go +++ /dev/null @@ -1,129 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "go.uber.org/fx" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl/full" - "github.com/filecoin-project/lotus/node/modules/helpers" -) - -var log = logging.Logger("metrics") - -const baseTopic = "/fil/headnotifs/" - -type Update struct { - Type string -} - -func SendHeadNotifs(nickname string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - ctx := helpers.LifecycleCtx(mctx, lc) - - lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - gen, err := chain.Chain.GetGenesis() - if err != nil { - return err - } - - topic := baseTopic + gen.Cid().String() - - go func() { - if err := sendHeadNotifs(ctx, ps, topic, chain, nickname); err != nil { - log.Error("consensus metrics error", err) - return - } - }() - go func() { - sub, err := ps.Subscribe(topic) //nolint - if err != nil { - return - } - defer sub.Cancel() - - for { - if _, err := sub.Next(ctx); err != nil { - return - } - } - - }() - return nil - }, - }) - - return nil - } -} - -type message struct { - // TipSet - Cids []cid.Cid - Blocks []*types.BlockHeader - Height abi.ChainEpoch - Weight types.BigInt - Time uint64 - Nonce uint64 - - // Meta - - NodeName string -} - -func sendHeadNotifs(ctx context.Context, ps *pubsub.PubSub, topic string, chain full.ChainAPI, nickname string) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - notifs, err := chain.ChainNotify(ctx) - if err != nil { - return err - } - - // using unix nano time makes very sure we pick a nonce higher than previous restart - nonce := uint64(build.Clock.Now().UnixNano()) - - for { - select { - case notif := <-notifs: - n := notif[len(notif)-1] - - w, err := chain.ChainTipSetWeight(ctx, n.Val.Key()) - if err != nil { - return err - } - - m := message{ - Cids: n.Val.Cids(), - Blocks: n.Val.Blocks(), - Height: n.Val.Height(), - Weight: w, - NodeName: nickname, - Time: uint64(build.Clock.Now().UnixNano() / 1000_000), - Nonce: nonce, - } - - b, err := json.Marshal(m) - if err != nil { - return err - } - - //nolint - if err := ps.Publish(topic, b); err != nil { - return err - } - case <-ctx.Done(): - return nil - } - - nonce++ - } -} diff --git a/cmd/lotus-townhall/main.go b/cmd/lotus-townhall/main.go deleted file mode 100644 index 1e0460dee..000000000 --- a/cmd/lotus-townhall/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - rice "github.com/GeertJohan/go.rice" - "github.com/gorilla/websocket" - "github.com/ipld/go-car" - "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p-core/peer" - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/build" -) - -var topic = "/fil/headnotifs/" - -func init() { - genBytes := build.MaybeGenesis() - if len(genBytes) == 0 { - topic = "" - return - } - - bs := blockstore.NewMemory() - - c, err := car.LoadCar(bs, bytes.NewReader(genBytes)) - if err != nil { - panic(err) - } - if len(c.Roots) != 1 { - panic("expected genesis file to have one root") - } - - fmt.Printf("Genesis CID: %s\n", c.Roots[0]) - topic = topic + c.Roots[0].String() -} - -var upgrader = websocket.Upgrader{ - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -func main() { - if topic == "" { - fmt.Println("FATAL: No genesis found") - return - } - - ctx := context.Background() - - host, err := libp2p.New( - ctx, - libp2p.Defaults, - ) - if err != nil { - panic(err) - } - ps, err := pubsub.NewGossipSub(ctx, host) - if err != nil { - panic(err) - } - - pi, err := build.BuiltinBootstrap() - if err != nil { - panic(err) - } - - if err := host.Connect(ctx, pi[0]); err != nil { - panic(err) - } - - http.HandleFunc("/sub", handler(ps)) - http.Handle("/", http.FileServer(rice.MustFindBox("townhall/build").HTTPBox())) - - fmt.Println("listening on http://localhost:2975") - - if err := http.ListenAndServe("0.0.0.0:2975", nil); err != nil { - panic(err) - } -} - -type update struct { - From peer.ID - Update json.RawMessage - Time uint64 -} - -func handler(ps *pubsub.PubSub) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - if r.Header.Get("Sec-WebSocket-Protocol") != "" { - w.Header().Set("Sec-WebSocket-Protocol", r.Header.Get("Sec-WebSocket-Protocol")) - } - - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - sub, err := ps.Subscribe(topic) //nolint - if err != nil { - return - } - defer sub.Cancel() //nolint:errcheck - - fmt.Println("new conn") - - for { - msg, err := sub.Next(r.Context()) - if err != nil { - return - } - - //fmt.Println(msg) - - if err := conn.WriteJSON(update{ - From: peer.ID(msg.From), - Update: msg.Data, - Time: uint64(time.Now().UnixNano() / 1000_000), - }); err != nil { - return - } - } - } -} diff --git a/cmd/lotus-townhall/townhall/.gitignore b/cmd/lotus-townhall/townhall/.gitignore deleted file mode 100644 index 4d29575de..000000000 --- a/cmd/lotus-townhall/townhall/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/cmd/lotus-townhall/townhall/package.json b/cmd/lotus-townhall/townhall/package.json deleted file mode 100644 index 5a8167622..000000000 --- a/cmd/lotus-townhall/townhall/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "townhall", - "version": "0.1.0", - "private": true, - "dependencies": { - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-scripts": "3.2.0" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/cmd/lotus-townhall/townhall/public/index.html b/cmd/lotus-townhall/townhall/public/index.html deleted file mode 100644 index 38af10597..000000000 --- a/cmd/lotus-townhall/townhall/public/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Lotus TownHall - - - -
- - diff --git a/cmd/lotus-townhall/townhall/public/robots.txt b/cmd/lotus-townhall/townhall/public/robots.txt deleted file mode 100644 index 01b0f9a10..000000000 --- a/cmd/lotus-townhall/townhall/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/cmd/lotus-townhall/townhall/src/App.css b/cmd/lotus-townhall/townhall/src/App.css deleted file mode 100644 index 8b1378917..000000000 --- a/cmd/lotus-townhall/townhall/src/App.css +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cmd/lotus-townhall/townhall/src/App.js b/cmd/lotus-townhall/townhall/src/App.js deleted file mode 100644 index 2f216f5da..000000000 --- a/cmd/lotus-townhall/townhall/src/App.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import './App.css'; - -function colForH(besth, height) { - const diff = besth - height - if(diff === 0) return '#6f6' - if(diff === 1) return '#df4' - if(diff < 4) return '#ff0' - if(diff < 10) return '#f60' - return '#f00' -} - -function colLag(lag) { - if(lag < 100) return '#6f6' - if(lag < 400) return '#df4' - if(lag < 1000) return '#ff0' - if(lag < 4000) return '#f60' - return '#f00' -} - -function lagCol(lag, good) { - return - {lag} - ms - -} - -class App extends React.Component { - constructor(props) { - super(props); - - let ws = new WebSocket("ws://" + window.location.host + "/sub") - //let ws = new WebSocket("ws://127.0.0.1:2975/sub") - - ws.onmessage = (ev) => { - console.log(ev) - let update = JSON.parse(ev.data) - - update.Update.Weight = Number(update.Update.Weight) - - let wdiff = update.Update.Weight - (this.state[update.From] || {Weight: update.Update.Weight}).Weight - wdiff = {wdiff} - - let utDiff = update.Time - (this.state[update.From] || {utime: update.Time}).utime - utDiff = {utDiff}ms - - this.setState( prev => ({ - ...prev, [update.From]: {...update.Update, utime: update.Time, wdiff: wdiff, utDiff: utDiff}, - })) - } - - ws.onclose = () => { - this.setState({disconnected: true}) - } - - this.state = {} - } - - render() { - if(this.state.disconnected) { - return Error: disconnected - } - - let besth = Object.keys(this.state).map(k => this.state[k]).reduce((p, n) => p > n.Height ? p : n.Height, -1) - let bestw = Object.keys(this.state).map(k => this.state[k]).reduce((p, n) => p > n.Weight ? p : n.Weight, -1) - - return - - {Object.keys(this.state).map(k => [k, this.state[k]]).map(([k, v]) => { - let mnrs = v.Blocks.map(b => ) - let l = [ - , - , - , - , - , - ...mnrs, - ] - - l = {l} - return l - }) - } -
PeerIDNicknameLagWeight(best, prev)HeightBlocks
 m:{b.Miner}({lagCol(v.Time ? v.Time - (b.Timestamp*1000) : v.utime - (b.Timestamp*1000), v.Time)}){k}{v.NodeName}{v.Time ? lagCol(v.utime - v.Time, true) : ""}(Δ{v.utDiff}){v.Weight}({bestw - v.Weight}, {v.wdiff}){v.Height}({besth - v.Height})
- } -} -export default App; diff --git a/cmd/lotus-townhall/townhall/src/App.test.js b/cmd/lotus-townhall/townhall/src/App.test.js deleted file mode 100644 index a754b201b..000000000 --- a/cmd/lotus-townhall/townhall/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cmd/lotus-townhall/townhall/src/index.css b/cmd/lotus-townhall/townhall/src/index.css deleted file mode 100644 index fb0d9d10e..000000000 --- a/cmd/lotus-townhall/townhall/src/index.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - margin: 0; - font-family: monospace; - background: #1f1f1f; - color: #f0f0f0; -} diff --git a/cmd/lotus-townhall/townhall/src/index.js b/cmd/lotus-townhall/townhall/src/index.js deleted file mode 100644 index 395b74997..000000000 --- a/cmd/lotus-townhall/townhall/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; - -ReactDOM.render(, document.getElementById('root')); diff --git a/node/builder_chain.go b/node/builder_chain.go index 1447a4df7..4d9294972 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -17,7 +17,6 @@ import ( "github.com/filecoin-project/lotus/chain/market" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/messagesigner" - "github.com/filecoin-project/lotus/chain/metrics" "github.com/filecoin-project/lotus/chain/stmgr" rpcstmgr "github.com/filecoin-project/lotus/chain/stmgr/rpc" "github.com/filecoin-project/lotus/chain/store" @@ -174,10 +173,6 @@ func ConfigFullNode(c interface{}) Option { ), Override(new(dtypes.Graphsync), modules.Graphsync(cfg.Client.SimultaneousTransfers)), - If(cfg.Metrics.HeadNotifs, - Override(HeadMetricsKey, metrics.SendHeadNotifs(cfg.Metrics.Nickname)), - ), - If(cfg.Wallet.RemoteBackend != "", Override(new(*remotewallet.RemoteWallet), remotewallet.SetupRemoteWallet(cfg.Wallet.RemoteBackend)), ), diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 5d4a91d5f..430abebbb 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -245,12 +245,6 @@ see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-f Comment: ``, }, - { - Name: "Metrics", - Type: "Metrics", - - Comment: ``, - }, { Name: "Wallet", Type: "Wallet", @@ -324,20 +318,6 @@ Format: multiaddress`, Comment: ``, }, }, - "Metrics": []DocField{ - { - Name: "Nickname", - Type: "string", - - Comment: ``, - }, - { - Name: "HeadNotifs", - Type: "bool", - - Comment: ``, - }, - }, "MinerAddressConfig": []DocField{ { Name: "PreCommitControl", diff --git a/node/config/types.go b/node/config/types.go index fe42aa27e..12f7653fb 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -23,7 +23,6 @@ type Common struct { type FullNode struct { Common Client Client - Metrics Metrics Wallet Wallet Fees FeeConfig Chainstore Chainstore @@ -298,12 +297,6 @@ type Splitstore struct { } // // Full Node - -type Metrics struct { - Nickname string - HeadNotifs bool -} - type Client struct { UseIpfs bool IpfsOnlineMode bool From 25052fe48f6e155e2cf05b57572e5102f0be8360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 28 Jul 2021 17:53:33 +0200 Subject: [PATCH 93/98] Remove chainwatch (now filecoin-project/sentinel) --- Makefile | 27 +- cmd/lotus-chainwatch/dot.go | 131 --- cmd/lotus-chainwatch/main.go | 54 - .../processor/common_actors.go | 299 ----- cmd/lotus-chainwatch/processor/market.go | 316 ----- cmd/lotus-chainwatch/processor/messages.go | 318 ----- cmd/lotus-chainwatch/processor/miner.go | 1035 ----------------- cmd/lotus-chainwatch/processor/mpool.go | 100 -- cmd/lotus-chainwatch/processor/power.go | 190 --- cmd/lotus-chainwatch/processor/processor.go | 420 ------- cmd/lotus-chainwatch/processor/reward.go | 234 ---- cmd/lotus-chainwatch/run.go | 107 -- .../refresh_top_miners_by_base_reward.go | 78 -- cmd/lotus-chainwatch/scheduler/scheduler.go | 60 - cmd/lotus-chainwatch/syncer/blockssub.go | 27 - cmd/lotus-chainwatch/syncer/sync.go | 527 --------- cmd/lotus-chainwatch/util/api.go | 34 - cmd/lotus-chainwatch/util/contextStore.go | 51 - scripts/lotus-chainwatch.service | 15 - 19 files changed, 2 insertions(+), 4021 deletions(-) delete mode 100644 cmd/lotus-chainwatch/dot.go delete mode 100644 cmd/lotus-chainwatch/main.go delete mode 100644 cmd/lotus-chainwatch/processor/common_actors.go delete mode 100644 cmd/lotus-chainwatch/processor/market.go delete mode 100644 cmd/lotus-chainwatch/processor/messages.go delete mode 100644 cmd/lotus-chainwatch/processor/miner.go delete mode 100644 cmd/lotus-chainwatch/processor/mpool.go delete mode 100644 cmd/lotus-chainwatch/processor/power.go delete mode 100644 cmd/lotus-chainwatch/processor/processor.go delete mode 100644 cmd/lotus-chainwatch/processor/reward.go delete mode 100644 cmd/lotus-chainwatch/run.go delete mode 100644 cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go delete mode 100644 cmd/lotus-chainwatch/scheduler/scheduler.go delete mode 100644 cmd/lotus-chainwatch/syncer/blockssub.go delete mode 100644 cmd/lotus-chainwatch/syncer/sync.go delete mode 100644 cmd/lotus-chainwatch/util/api.go delete mode 100644 cmd/lotus-chainwatch/util/contextStore.go delete mode 100644 scripts/lotus-chainwatch.service diff --git a/Makefile b/Makefile index 4aac5f21a..dfe4a65c7 100644 --- a/Makefile +++ b/Makefile @@ -169,12 +169,6 @@ lotus-fountain: .PHONY: lotus-fountain BINS+=lotus-fountain -lotus-chainwatch: - rm -f lotus-chainwatch - $(GOCC) build $(GOFLAGS) -o lotus-chainwatch ./cmd/lotus-chainwatch -.PHONY: lotus-chainwatch -BINS+=lotus-chainwatch - lotus-bench: rm -f lotus-bench $(GOCC) build -o lotus-bench ./cmd/lotus-bench @@ -223,9 +217,6 @@ tvx: .PHONY: tvx BINS+=tvx -install-chainwatch: lotus-chainwatch - install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch - lotus-sim: $(BUILD_DEPS) rm -f lotus-sim $(GOCC) build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim @@ -250,21 +241,13 @@ install-miner-service: install-miner install-daemon-service @echo @echo "lotus-miner service installed. Don't forget to run 'sudo systemctl start lotus-miner' to start it and 'sudo systemctl enable lotus-miner' for it to be enabled on startup." -install-chainwatch-service: install-chainwatch install-daemon-service - mkdir -p /etc/systemd/system - mkdir -p /var/log/lotus - install -C -m 0644 ./scripts/lotus-chainwatch.service /etc/systemd/system/lotus-chainwatch.service - systemctl daemon-reload - @echo - @echo "chainwatch service installed. Don't forget to run 'sudo systemctl start lotus-chainwatch' to start it and 'sudo systemctl enable lotus-chainwatch' for it to be enabled on startup." - install-main-services: install-miner-service -install-all-services: install-main-services install-chainwatch-service +install-all-services: install-main-services install-services: install-main-services -clean-daemon-service: clean-miner-service clean-chainwatch-service +clean-daemon-service: clean-miner-service -systemctl stop lotus-daemon -systemctl disable lotus-daemon rm -f /etc/systemd/system/lotus-daemon.service @@ -276,12 +259,6 @@ clean-miner-service: rm -f /etc/systemd/system/lotus-miner.service systemctl daemon-reload -clean-chainwatch-service: - -systemctl stop lotus-chainwatch - -systemctl disable lotus-chainwatch - rm -f /etc/systemd/system/lotus-chainwatch.service - systemctl daemon-reload - clean-main-services: clean-daemon-service clean-all-services: clean-main-services diff --git a/cmd/lotus-chainwatch/dot.go b/cmd/lotus-chainwatch/dot.go deleted file mode 100644 index 3149d65f5..000000000 --- a/cmd/lotus-chainwatch/dot.go +++ /dev/null @@ -1,131 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - "hash/crc32" - "strconv" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" -) - -var dotCmd = &cli.Command{ - Name: "dot", - Usage: "generate dot graphs", - ArgsUsage: " ", - Action: func(cctx *cli.Context) error { - ll := cctx.String("log-level") - if err := logging.SetLogLevel("*", ll); err != nil { - return err - } - - db, err := sql.Open("postgres", cctx.String("db")) - if err != nil { - return err - } - defer func() { - if err := db.Close(); err != nil { - log.Errorw("Failed to close database", "error", err) - } - }() - - if err := db.Ping(); err != nil { - return xerrors.Errorf("Database failed to respond to ping (is it online?): %w", err) - } - - minH, err := strconv.ParseInt(cctx.Args().Get(0), 10, 32) - if err != nil { - return err - } - tosee, err := strconv.ParseInt(cctx.Args().Get(1), 10, 32) - if err != nil { - return err - } - maxH := minH + tosee - - res, err := db.Query(`select block, parent, b.miner, b.height, p.height from block_parents - inner join blocks b on block_parents.block = b.cid - inner join blocks p on block_parents.parent = p.cid -where b.height > $1 and b.height < $2`, minH, maxH) - - if err != nil { - return err - } - - fmt.Println("digraph D {") - - hl, err := syncedBlocks(db) - if err != nil { - log.Fatal(err) - } - - for res.Next() { - var block, parent, miner string - var height, ph uint64 - if err := res.Scan(&block, &parent, &miner, &height, &ph); err != nil { - return err - } - - bc, err := cid.Parse(block) - if err != nil { - return err - } - - _, has := hl[bc] - - col := crc32.Checksum([]byte(miner), crc32.MakeTable(crc32.Castagnoli))&0xc0c0c0c0 + 0x30303030 - - hasstr := "" - if !has { - //col = 0xffffffff - hasstr = " UNSYNCED" - } - - nulls := height - ph - 1 - for i := uint64(0); i < nulls; i++ { - name := block + "NP" + fmt.Sprint(i) - - fmt.Printf("%s [label = \"NULL:%d\", fillcolor = \"#ffddff\", style=filled, forcelabels=true]\n%s -> %s\n", - name, height-nulls+i, name, parent) - - parent = name - } - - fmt.Printf("%s [label = \"%s:%d%s\", fillcolor = \"#%06x\", style=filled, forcelabels=true]\n%s -> %s\n", block, miner, height, hasstr, col, block, parent) - } - if res.Err() != nil { - return res.Err() - } - - fmt.Println("}") - - return nil - }, -} - -func syncedBlocks(db *sql.DB) (map[cid.Cid]struct{}, error) { - // timestamp is used to return a configurable amount of rows based on when they were last added. - rws, err := db.Query(`select cid FROM blocks_synced`) - if err != nil { - return nil, xerrors.Errorf("Failed to query blocks_synced: %w", err) - } - out := map[cid.Cid]struct{}{} - - for rws.Next() { - var c string - if err := rws.Scan(&c); err != nil { - return nil, xerrors.Errorf("Failed to scan blocks_synced: %w", err) - } - - ci, err := cid.Parse(c) - if err != nil { - return nil, xerrors.Errorf("Failed to parse blocks_synced: %w", err) - } - - out[ci] = struct{}{} - } - return out, nil -} diff --git a/cmd/lotus-chainwatch/main.go b/cmd/lotus-chainwatch/main.go deleted file mode 100644 index 5cb0f3507..000000000 --- a/cmd/lotus-chainwatch/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "os" - - "github.com/filecoin-project/lotus/build" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" -) - -var log = logging.Logger("chainwatch") - -func main() { - if err := logging.SetLogLevel("*", "info"); err != nil { - log.Fatal(err) - } - log.Info("Starting chainwatch", " v", build.UserVersion()) - - app := &cli.App{ - Name: "lotus-chainwatch", - Usage: "Devnet token distribution utility", - Version: build.UserVersion(), - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo", - EnvVars: []string{"LOTUS_PATH"}, - Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME - }, - &cli.StringFlag{ - Name: "api", - EnvVars: []string{"FULLNODE_API_INFO"}, - Value: "", - }, - &cli.StringFlag{ - Name: "db", - EnvVars: []string{"LOTUS_DB"}, - Value: "", - }, - &cli.StringFlag{ - Name: "log-level", - EnvVars: []string{"GOLOG_LOG_LEVEL"}, - Value: "info", - }, - }, - Commands: []*cli.Command{ - dotCmd, - runCmd, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/lotus-chainwatch/processor/common_actors.go b/cmd/lotus-chainwatch/processor/common_actors.go deleted file mode 100644 index 0f2c0d2ea..000000000 --- a/cmd/lotus-chainwatch/processor/common_actors.go +++ /dev/null @@ -1,299 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-cid" - - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors/builtin" - _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -func (p *Processor) setupCommonActors() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists id_address_map -( - id text not null, - address text not null, - constraint id_address_map_pk - primary key (id, address) -); - -create unique index if not exists id_address_map_id_uindex - on id_address_map (id); - -create unique index if not exists id_address_map_address_uindex - on id_address_map (address); - -create table if not exists actors - ( - id text not null - constraint id_address_map_actors_id_fk - references id_address_map (id), - code text not null, - head text not null, - nonce int not null, - balance text not null, - stateroot text - ); - -create index if not exists actors_id_index - on actors (id); - -create index if not exists id_address_map_address_index - on id_address_map (address); - -create index if not exists id_address_map_id_index - on id_address_map (id); - -create or replace function actor_tips(epoch bigint) - returns table (id text, - code text, - head text, - nonce int, - balance text, - stateroot text, - height bigint, - parentstateroot text) as -$body$ - select distinct on (id) * from actors - inner join state_heights sh on sh.parentstateroot = stateroot - where height < $1 - order by id, height desc; -$body$ language sql; - -create table if not exists actor_states -( - head text not null, - code text not null, - state json not null -); - -create unique index if not exists actor_states_head_code_uindex - on actor_states (head, code); - -create index if not exists actor_states_head_index - on actor_states (head); - -create index if not exists actor_states_code_head_index - on actor_states (head, code); - -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleCommonActorsChanges(ctx context.Context, actors map[cid.Cid]ActorTips) error { - if err := p.storeActorAddresses(ctx, actors); err != nil { - return err - } - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeActorHeads(actors); err != nil { - return err - } - return nil - }) - - grp.Go(func() error { - if err := p.storeActorStates(actors); err != nil { - return err - } - return nil - }) - - return grp.Wait() -} - -type UpdateAddresses struct { - Old state.AddressPair - New state.AddressPair -} - -func (p Processor) storeActorAddresses(ctx context.Context, actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor Addresses", "duration", time.Since(start).String()) - }() - - addressToID := map[address.Address]address.Address{} - // HACK until genesis storage is figured out: - addressToID[builtin2.SystemActorAddr] = builtin2.SystemActorAddr - addressToID[builtin2.InitActorAddr] = builtin2.InitActorAddr - addressToID[builtin2.RewardActorAddr] = builtin2.RewardActorAddr - addressToID[builtin2.CronActorAddr] = builtin2.CronActorAddr - addressToID[builtin2.StoragePowerActorAddr] = builtin2.StoragePowerActorAddr - addressToID[builtin2.StorageMarketActorAddr] = builtin2.StorageMarketActorAddr - addressToID[builtin2.VerifiedRegistryActorAddr] = builtin2.VerifiedRegistryActorAddr - addressToID[builtin2.BurntFundsActorAddr] = builtin2.BurntFundsActorAddr - initActor, err := p.node.StateGetActor(ctx, builtin2.InitActorAddr, types.EmptyTSK) - if err != nil { - return err - } - - initActorState, err := _init.Load(cw_util.NewAPIIpldStore(ctx, p.node), initActor) - if err != nil { - return err - } - // gross.. - if err := initActorState.ForEachActor(func(id abi.ActorID, addr address.Address) error { - idAddr, err := address.NewIDAddress(uint64(id)) - if err != nil { - return err - } - addressToID[addr] = idAddr - return nil - }); err != nil { - return err - } - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table iam (like id_address_map excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy iam (id, address) from STDIN `) - if err != nil { - return err - } - - for a, i := range addressToID { - if i == address.Undef { - continue - } - if _, err := stmt.Exec( - i.String(), - a.String(), - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - // HACK until chain watch can handle reorgs we need to update this table when ID -> PubKey mappings change - if _, err := tx.Exec(`insert into id_address_map select * from iam on conflict (id) do update set address = EXCLUDED.address`); err != nil { - log.Warnw("Failed to update id_address_map table, this is a known issue") - return nil - } - - return tx.Commit() -} - -func (p *Processor) storeActorHeads(actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor Heads", "duration", time.Since(start).String()) - }() - // Basic - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create temp table a_tmp (like actors excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy a_tmp (id, code, head, nonce, balance, stateroot) from stdin `) - if err != nil { - return err - } - - for code, actTips := range actors { - actorName := code.String() - if builtin.IsBuiltinActor(code) { - actorName = builtin.ActorNameByCode(code) - } - for _, actorInfo := range actTips { - for _, a := range actorInfo { - if _, err := stmt.Exec(a.addr.String(), actorName, a.act.Head.String(), a.act.Nonce, a.act.Balance.String(), a.stateroot.String()); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into actors select * from a_tmp on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeActorStates(actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor States", "duration", time.Since(start).String()) - }() - // States - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create temp table as_tmp (like actor_states excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy as_tmp (head, code, state) from stdin `) - if err != nil { - return err - } - - for code, actTips := range actors { - actorName := code.String() - if builtin.IsBuiltinActor(code) { - actorName = builtin.ActorNameByCode(code) - } - for _, actorInfo := range actTips { - for _, a := range actorInfo { - if _, err := stmt.Exec(a.act.Head.String(), actorName, a.state); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into actor_states select * from as_tmp on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/market.go b/cmd/lotus-chainwatch/processor/market.go deleted file mode 100644 index 17aa1c37b..000000000 --- a/cmd/lotus-chainwatch/processor/market.go +++ /dev/null @@ -1,316 +0,0 @@ -package processor - -import ( - "context" - "strconv" - "time" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/events/state" -) - -func (p *Processor) setupMarket() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists market_deal_proposals -( - deal_id bigint not null, - - state_root text not null, - - piece_cid text not null, - padded_piece_size bigint not null, - unpadded_piece_size bigint not null, - is_verified bool not null, - - client_id text not null, - provider_id text not null, - - start_epoch bigint not null, - end_epoch bigint not null, - slashed_epoch bigint, - storage_price_per_epoch text not null, - - provider_collateral text not null, - client_collateral text not null, - - constraint market_deal_proposal_pk - primary key (deal_id) -); - -create table if not exists market_deal_states -( - deal_id bigint not null, - - sector_start_epoch bigint not null, - last_update_epoch bigint not null, - slash_epoch bigint not null, - - state_root text not null, - - unique (deal_id, sector_start_epoch, last_update_epoch, slash_epoch), - - constraint market_deal_states_pk - primary key (deal_id, state_root) - -); - -create table if not exists minerid_dealid_sectorid -( - deal_id bigint not null - constraint sectors_sector_ids_id_fk - references market_deal_proposals(deal_id), - - sector_id bigint not null, - miner_id text not null, - foreign key (sector_id, miner_id) references sector_precommit_info(sector_id, miner_id), - - constraint miner_sector_deal_ids_pk - primary key (miner_id, sector_id, deal_id) -); - -`); err != nil { - return err - } - - return tx.Commit() -} - -type marketActorInfo struct { - common actorInfo -} - -func (p *Processor) HandleMarketChanges(ctx context.Context, marketTips ActorTips) error { - marketChanges, err := p.processMarket(ctx, marketTips) - if err != nil { - log.Fatalw("Failed to process market actors", "error", err) - } - - if err := p.persistMarket(ctx, marketChanges); err != nil { - log.Fatalw("Failed to persist market actors", "error", err) - } - - if err := p.updateMarket(ctx, marketChanges); err != nil { - log.Fatalw("Failed to update market actors", "error", err) - } - return nil -} - -func (p *Processor) processMarket(ctx context.Context, marketTips ActorTips) ([]marketActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Market", "duration", time.Since(start).String()) - }() - - var out []marketActorInfo - for _, markets := range marketTips { - for _, mt := range markets { - // NB: here is where we can extract the market state when we need it. - out = append(out, marketActorInfo{common: mt}) - } - } - return out, nil -} - -func (p *Processor) persistMarket(ctx context.Context, info []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Market", "duration", time.Since(start).String()) - }() - - grp, ctx := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeMarketActorDealProposals(ctx, info); err != nil { - return xerrors.Errorf("Failed to store marker deal proposals: %w", err) - } - return nil - }) - - grp.Go(func() error { - if err := p.storeMarketActorDealStates(info); err != nil { - return xerrors.Errorf("Failed to store marker deal states: %w", err) - } - return nil - }) - - return grp.Wait() - -} - -func (p *Processor) updateMarket(ctx context.Context, info []marketActorInfo) error { - if err := p.updateMarketActorDealProposals(ctx, info); err != nil { - return xerrors.Errorf("Failed to update market info: %w", err) - } - return nil -} - -func (p *Processor) storeMarketActorDealStates(marketTips []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Market Deal States", "duration", time.Since(start).String()) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(`create temp table mds (like market_deal_states excluding constraints) on commit drop;`); err != nil { - return err - } - stmt, err := tx.Prepare(`copy mds (deal_id, sector_start_epoch, last_update_epoch, slash_epoch, state_root) from STDIN`) - if err != nil { - return err - } - for _, mt := range marketTips { - dealStates, err := p.node.StateMarketDeals(context.TODO(), mt.common.tsKey) - if err != nil { - return err - } - - for dealID, ds := range dealStates { - id, err := strconv.ParseUint(dealID, 10, 64) - if err != nil { - return err - } - - if _, err := stmt.Exec( - id, - ds.State.SectorStartEpoch, - ds.State.LastUpdatedEpoch, - ds.State.SlashEpoch, - mt.common.stateroot.String(), - ); err != nil { - return err - } - - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into market_deal_states select * from mds on conflict do nothing`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) storeMarketActorDealProposals(ctx context.Context, marketTips []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Market Deal Proposals", "duration", time.Since(start).String()) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mdp (like market_deal_proposals excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mdp (deal_id, state_root, piece_cid, padded_piece_size, unpadded_piece_size, is_verified, client_id, provider_id, start_epoch, end_epoch, slashed_epoch, storage_price_per_epoch, provider_collateral, client_collateral) from STDIN`) - if err != nil { - return err - } - - // insert in sorted order (lowest height -> highest height) since dealid is pk of table. - for _, mt := range marketTips { - dealStates, err := p.node.StateMarketDeals(ctx, mt.common.tsKey) - if err != nil { - return err - } - - for dealID, ds := range dealStates { - id, err := strconv.ParseUint(dealID, 10, 64) - if err != nil { - return err - } - - if _, err := stmt.Exec( - id, - mt.common.stateroot.String(), - ds.Proposal.PieceCID.String(), - ds.Proposal.PieceSize, - ds.Proposal.PieceSize.Unpadded(), - ds.Proposal.VerifiedDeal, - ds.Proposal.Client.String(), - ds.Proposal.Provider.String(), - ds.Proposal.StartEpoch, - ds.Proposal.EndEpoch, - nil, // slashed_epoch - ds.Proposal.StoragePricePerEpoch.String(), - ds.Proposal.ProviderCollateral.String(), - ds.Proposal.ClientCollateral.String(), - ); err != nil { - return err - } - - } - } - if err := stmt.Close(); err != nil { - return err - } - if _, err := tx.Exec(`insert into market_deal_proposals select * from mdp on conflict do nothing`); err != nil { - return err - } - - return tx.Commit() - -} - -func (p *Processor) updateMarketActorDealProposals(ctx context.Context, marketTip []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Updated Market Deal Proposals", "duration", time.Since(start).String()) - }() - pred := state.NewStatePredicates(p.node) - - tx, err := p.db.Begin() - if err != nil { - return err - } - - stmt, err := tx.Prepare(`update market_deal_proposals set slashed_epoch=$1 where deal_id=$2`) - if err != nil { - return err - } - - for _, mt := range marketTip { - stateDiff := pred.OnStorageMarketActorChanged(pred.OnDealStateChanged(pred.OnDealStateAmtChanged())) - - changed, val, err := stateDiff(ctx, mt.common.parentTsKey, mt.common.tsKey) - if err != nil { - log.Warnw("error getting market deal state diff", "error", err) - } - if !changed { - continue - } - changes, ok := val.(*market.DealStateChanges) - if !ok { - return xerrors.Errorf("Unknown type returned by Deal State AMT predicate: %T", val) - } - - for _, modified := range changes.Modified { - if modified.From.SlashEpoch != modified.To.SlashEpoch { - if _, err := stmt.Exec(modified.To.SlashEpoch, modified.ID); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/messages.go b/cmd/lotus-chainwatch/processor/messages.go deleted file mode 100644 index 333477c6a..000000000 --- a/cmd/lotus-chainwatch/processor/messages.go +++ /dev/null @@ -1,318 +0,0 @@ -package processor - -import ( - "context" - "sync" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/parmap" -) - -func (p *Processor) setupMessages() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists messages -( - cid text not null - constraint messages_pk - primary key, - "from" text not null, - "to" text not null, - size_bytes bigint not null, - nonce bigint not null, - value text not null, - gas_fee_cap text not null, - gas_premium text not null, - gas_limit bigint not null, - method bigint, - params bytea -); - -create unique index if not exists messages_cid_uindex - on messages (cid); - -create index if not exists messages_from_index - on messages ("from"); - -create index if not exists messages_to_index - on messages ("to"); - -create table if not exists block_messages -( - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid), - message text not null, - constraint block_messages_pk - primary key (block, message) -); - -create table if not exists mpool_messages -( - msg text not null - constraint mpool_messages_pk - primary key - constraint mpool_messages_messages_cid_fk - references messages, - add_ts int not null -); - -create unique index if not exists mpool_messages_msg_uindex - on mpool_messages (msg); - -create table if not exists receipts -( - msg text not null, - state text not null, - idx int not null, - exit int not null, - gas_used bigint not null, - return bytea, - constraint receipts_pk - primary key (msg, state) -); - -create index if not exists receipts_msg_state_index - on receipts (msg, state); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleMessageChanges(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) error { - if err := p.persistMessagesAndReceipts(ctx, blocks); err != nil { - return err - } - return nil -} - -func (p *Processor) persistMessagesAndReceipts(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) error { - messages, inclusions := p.fetchMessages(ctx, blocks) - receipts := p.fetchParentReceipts(ctx, blocks) - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - return p.storeMessages(messages) - }) - - grp.Go(func() error { - return p.storeMsgInclusions(inclusions) - }) - - grp.Go(func() error { - return p.storeReceipts(receipts) - }) - - return grp.Wait() -} - -func (p *Processor) storeReceipts(recs map[mrec]*types.MessageReceipt) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table recs (like receipts excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy recs (msg, state, idx, exit, gas_used, return) from stdin `) - if err != nil { - return err - } - - for c, m := range recs { - if _, err := stmt.Exec( - c.msg.String(), - c.state.String(), - c.idx, - m.ExitCode, - m.GasUsed, - m.Return, - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into receipts select * from recs on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeMsgInclusions(incls map[cid.Cid][]cid.Cid) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table mi (like block_messages excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (block, message) from STDIN `) - if err != nil { - return err - } - - for b, msgs := range incls { - for _, msg := range msgs { - if _, err := stmt.Exec( - b.String(), - msg.String(), - ); err != nil { - return err - } - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_messages select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeMessages(msgs map[cid.Cid]*types.Message) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table msgs (like messages excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy msgs (cid, "from", "to", size_bytes, nonce, "value", gas_premium, gas_fee_cap, gas_limit, method, params) from stdin `) - if err != nil { - return err - } - - for c, m := range msgs { - var msgBytes int - if b, err := m.Serialize(); err == nil { - msgBytes = len(b) - } - - if _, err := stmt.Exec( - c.String(), - m.From.String(), - m.To.String(), - msgBytes, - m.Nonce, - m.Value.String(), - m.GasPremium.String(), - m.GasFeeCap.String(), - m.GasLimit, - m.Method, - m.Params, - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into messages select * from msgs on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) fetchMessages(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) (map[cid.Cid]*types.Message, map[cid.Cid][]cid.Cid) { - var lk sync.Mutex - messages := map[cid.Cid]*types.Message{} - inclusions := map[cid.Cid][]cid.Cid{} // block -> msgs - - parmap.Par(50, parmap.MapArr(blocks), func(header *types.BlockHeader) { - msgs, err := p.node.ChainGetBlockMessages(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetBlockMessages", "header_cid", header.Cid()) - return - } - - vmm := make([]*types.Message, 0, len(msgs.Cids)) - for _, m := range msgs.BlsMessages { - vmm = append(vmm, m) - } - - for _, m := range msgs.SecpkMessages { - vmm = append(vmm, &m.Message) - } - - lk.Lock() - for _, message := range vmm { - messages[message.Cid()] = message - inclusions[header.Cid()] = append(inclusions[header.Cid()], message.Cid()) - } - lk.Unlock() - }) - - return messages, inclusions -} - -type mrec struct { - msg cid.Cid - state cid.Cid - idx int -} - -func (p *Processor) fetchParentReceipts(ctx context.Context, toSync map[cid.Cid]*types.BlockHeader) map[mrec]*types.MessageReceipt { - var lk sync.Mutex - out := map[mrec]*types.MessageReceipt{} - - parmap.Par(50, parmap.MapArr(toSync), func(header *types.BlockHeader) { - recs, err := p.node.ChainGetParentReceipts(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetParentReceipts", "header_cid", header.Cid()) - return - } - msgs, err := p.node.ChainGetParentMessages(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetParentMessages", "header_cid", header.Cid()) - return - } - - lk.Lock() - for i, r := range recs { - out[mrec{ - msg: msgs[i].Cid, - state: header.ParentStateRoot, - idx: i, - }] = r - } - lk.Unlock() - }) - - return out -} diff --git a/cmd/lotus-chainwatch/processor/miner.go b/cmd/lotus-chainwatch/processor/miner.go deleted file mode 100644 index f3514df88..000000000 --- a/cmd/lotus-chainwatch/processor/miner.go +++ /dev/null @@ -1,1035 +0,0 @@ -package processor - -import ( - "context" - "strings" - "time" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - "github.com/ipfs/go-cid" - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -func (p *Processor) setupMiners() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` - -create table if not exists miner_info -( - miner_id text not null, - owner_addr text not null, - worker_addr text not null, - peer_id text, - sector_size text not null, - - constraint miner_info_pk - primary key (miner_id) -); - -create table if not exists sector_precommit_info -( - miner_id text not null, - sector_id bigint not null, - sealed_cid text not null, - state_root text not null, - - seal_rand_epoch bigint not null, - expiration_epoch bigint not null, - - precommit_deposit text not null, - precommit_epoch bigint not null, - deal_weight text not null, - verified_deal_weight text not null, - - - is_replace_capacity bool not null, - replace_sector_deadline bigint, - replace_sector_partition bigint, - replace_sector_number bigint, - - unique (miner_id, sector_id), - - constraint sector_precommit_info_pk - primary key (miner_id, sector_id, sealed_cid) - -); - -create table if not exists sector_info -( - miner_id text not null, - sector_id bigint not null, - sealed_cid text not null, - state_root text not null, - - activation_epoch bigint not null, - expiration_epoch bigint not null, - - deal_weight text not null, - verified_deal_weight text not null, - - initial_pledge text not null, - expected_day_reward text not null, - expected_storage_pledge text not null, - - constraint sector_info_pk - primary key (miner_id, sector_id, sealed_cid) -); - -/* -* captures miner-specific power state for any given stateroot -*/ -create table if not exists miner_power -( - miner_id text not null, - state_root text not null, - raw_bytes_power text not null, - quality_adjusted_power text not null, - constraint miner_power_pk - primary key (miner_id, state_root) -); - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'miner_sector_event_type') THEN - CREATE TYPE miner_sector_event_type AS ENUM - ( - 'PRECOMMIT_ADDED', 'PRECOMMIT_EXPIRED', 'COMMIT_CAPACITY_ADDED', 'SECTOR_ADDED', - 'SECTOR_EXTENDED', 'SECTOR_EXPIRED', 'SECTOR_FAULTED', 'SECTOR_RECOVERING', 'SECTOR_RECOVERED', 'SECTOR_TERMINATED' - ); - END IF; -END$$; - -create table if not exists miner_sector_events -( - miner_id text not null, - sector_id bigint not null, - state_root text not null, - event miner_sector_event_type not null, - - constraint miner_sector_events_pk - primary key (sector_id, event, miner_id, state_root) -); - -`); err != nil { - return err - } - - return tx.Commit() -} - -type SectorLifecycleEvent string - -const ( - PreCommitAdded = "PRECOMMIT_ADDED" - PreCommitExpired = "PRECOMMIT_EXPIRED" - - CommitCapacityAdded = "COMMIT_CAPACITY_ADDED" - - SectorAdded = "SECTOR_ADDED" - SectorExpired = "SECTOR_EXPIRED" - SectorExtended = "SECTOR_EXTENDED" - SectorFaulted = "SECTOR_FAULTED" - SectorRecovering = "SECTOR_RECOVERING" - SectorRecovered = "SECTOR_RECOVERED" - SectorTerminated = "SECTOR_TERMINATED" -) - -type MinerSectorsEvent struct { - MinerID address.Address - SectorIDs []uint64 - StateRoot cid.Cid - Event SectorLifecycleEvent -} - -type SectorDealEvent struct { - MinerID address.Address - SectorID uint64 - DealIDs []abi.DealID -} - -type PartitionStatus struct { - Terminated bitfield.BitField - Expired bitfield.BitField - Faulted bitfield.BitField - InRecovery bitfield.BitField - Recovered bitfield.BitField -} - -type minerActorInfo struct { - common actorInfo - - state miner.State - - // tracked by power actor - rawPower big.Int - qalPower big.Int -} - -func (p *Processor) HandleMinerChanges(ctx context.Context, minerTips ActorTips) error { - minerChanges, err := p.processMiners(ctx, minerTips) - if err != nil { - log.Fatalw("Failed to process miner actors", "error", err) - } - - if err := p.persistMiners(ctx, minerChanges); err != nil { - log.Fatalw("Failed to persist miner actors", "error", err) - } - - return nil -} - -func (p *Processor) processMiners(ctx context.Context, minerTips map[types.TipSetKey][]actorInfo) ([]minerActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Miners", "duration", time.Since(start).String()) - }() - - stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(p.node)) - - var out []minerActorInfo - // TODO add parallel calls if this becomes slow - for tipset, miners := range minerTips { - // get the power actors claims map - powerState, err := getPowerActorState(ctx, p.node, tipset) - if err != nil { - return nil, err - } - - // Get miner raw and quality power - for _, act := range miners { - var mi minerActorInfo - mi.common = act - - // get miner claim from power actors claim map and store if found, else the miner had no claim at - // this tipset - claim, found, err := powerState.MinerPower(act.addr) - if err != nil { - return nil, err - } - if found { - mi.qalPower = claim.QualityAdjPower - mi.rawPower = claim.RawBytePower - } - - // Get the miner state - mas, err := miner.Load(stor, &act.act) - if err != nil { - log.Warnw("failed to find miner actor state", "address", act.addr, "error", err) - continue - } - mi.state = mas - out = append(out, mi) - } - } - return out, nil -} - -func (p *Processor) persistMiners(ctx context.Context, miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Miners", "duration", time.Since(start).String()) - }() - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeMinersPower(miners); err != nil { - return err - } - return nil - }) - - grp.Go(func() error { - if err := p.storeMinersActorInfoState(ctx, miners); err != nil { - return err - } - return nil - }) - - // 8 is arbitrary, idk what a good value here is. - preCommitEvents := make(chan *MinerSectorsEvent, 8) - sectorEvents := make(chan *MinerSectorsEvent, 8) - partitionEvents := make(chan *MinerSectorsEvent, 8) - dealEvents := make(chan *SectorDealEvent, 8) - - grp.Go(func() error { - return p.storePreCommitDealInfo(dealEvents) - }) - - grp.Go(func() error { - return p.storeMinerSectorEvents(ctx, sectorEvents, preCommitEvents, partitionEvents) - }) - - grp.Go(func() error { - defer func() { - close(preCommitEvents) - close(dealEvents) - }() - return p.storeMinerPreCommitInfo(ctx, miners, preCommitEvents, dealEvents) - }) - - grp.Go(func() error { - defer close(sectorEvents) - return p.storeMinerSectorInfo(ctx, miners, sectorEvents) - }) - - grp.Go(func() error { - defer close(partitionEvents) - return p.getMinerPartitionsDifferences(ctx, miners, partitionEvents) - }) - - return grp.Wait() -} - -func (p *Processor) storeMinerPreCommitInfo(ctx context.Context, miners []minerActorInfo, sectorEvents chan<- *MinerSectorsEvent, sectorDeals chan<- *SectorDealEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table spi (like sector_precommit_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_precommit_info: %w", err) - } - - stmt, err := tx.Prepare(`copy spi (miner_id, sector_id, sealed_cid, state_root, seal_rand_epoch, expiration_epoch, precommit_deposit, precommit_epoch, deal_weight, verified_deal_weight, is_replace_capacity, replace_sector_deadline, replace_sector_partition, replace_sector_number) from STDIN`) - - if err != nil { - return xerrors.Errorf("Failed to prepare miner precommit info statement: %w", err) - } - - grp, _ := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - changes, err := p.getMinerPreCommitChanges(ctx, m) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - if changes == nil { - return nil - } - - preCommitAdded := make([]uint64, len(changes.Added)) - for i, added := range changes.Added { - if len(added.Info.DealIDs) > 0 { - sectorDeals <- &SectorDealEvent{ - MinerID: m.common.addr, - SectorID: uint64(added.Info.SectorNumber), - DealIDs: added.Info.DealIDs, - } - } - if added.Info.ReplaceCapacity { - if _, err := stmt.Exec( - m.common.addr.String(), - added.Info.SectorNumber, - added.Info.SealedCID.String(), - m.common.stateroot.String(), - added.Info.SealRandEpoch, - added.Info.Expiration, - added.PreCommitDeposit.String(), - added.PreCommitEpoch, - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.Info.ReplaceCapacity, - added.Info.ReplaceSectorDeadline, - added.Info.ReplaceSectorPartition, - added.Info.ReplaceSectorNumber, - ); err != nil { - return err - } - } else { - if _, err := stmt.Exec( - m.common.addr.String(), - added.Info.SectorNumber, - added.Info.SealedCID.String(), - m.common.stateroot.String(), - added.Info.SealRandEpoch, - added.Info.Expiration, - added.PreCommitDeposit.String(), - added.PreCommitEpoch, - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.Info.ReplaceCapacity, - nil, // replace deadline - nil, // replace partition - nil, // replace sector - ); err != nil { - return err - } - - } - preCommitAdded[i] = uint64(added.Info.SectorNumber) - } - if len(preCommitAdded) > 0 { - sectorEvents <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: preCommitAdded, - Event: PreCommitAdded, - } - } - var preCommitExpired []uint64 - for _, removed := range changes.Removed { - // TODO: we can optimize this to not load the AMT every time, if necessary. - si, err := m.state.GetSector(removed.Info.SectorNumber) - if err != nil { - return err - } - if si == nil { - preCommitExpired = append(preCommitExpired, uint64(removed.Info.SectorNumber)) - } - } - if len(preCommitExpired) > 0 { - sectorEvents <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: preCommitExpired, - Event: PreCommitExpired, - } - } - return nil - }) - } - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector precommit info statement: %w", err) - } - - if _, err := tx.Exec(`insert into sector_precommit_info select * from spi on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector precommit info table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector precommit info: %w", err) - } - return nil -} - -func (p *Processor) storeMinerSectorInfo(ctx context.Context, miners []minerActorInfo, events chan<- *MinerSectorsEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table si (like sector_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_: %w", err) - } - - stmt, err := tx.Prepare(`copy si (miner_id, sector_id, sealed_cid, state_root, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, initial_pledge, expected_day_reward, expected_storage_pledge) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare miner sector info statement: %w", err) - } - - grp, _ := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - changes, err := p.getMinerSectorChanges(ctx, m) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - if changes == nil { - return nil - } - var sectorsAdded []uint64 - var ccAdded []uint64 - var extended []uint64 - for _, added := range changes.Added { - // add the sector to the table - if _, err := stmt.Exec( - m.common.addr.String(), - added.SectorNumber, - added.SealedCID.String(), - m.common.stateroot.String(), - added.Activation.String(), - added.Expiration.String(), - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.InitialPledge.String(), - added.ExpectedDayReward.String(), - added.ExpectedStoragePledge.String(), - ); err != nil { - log.Errorw("writing miner sector changes statement", "error", err.Error()) - } - if len(added.DealIDs) == 0 { - ccAdded = append(ccAdded, uint64(added.SectorNumber)) - } else { - sectorsAdded = append(sectorsAdded, uint64(added.SectorNumber)) - } - } - - for _, mod := range changes.Extended { - extended = append(extended, uint64(mod.To.SectorNumber)) - } - - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: ccAdded, - Event: CommitCapacityAdded, - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: sectorsAdded, - Event: SectorAdded, - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: extended, - Event: SectorExtended, - } - return nil - }) - } - - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector info statement: %w", err) - } - - if _, err := tx.Exec(`insert into sector_info select * from si on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector info table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector info: %w", err) - } - return nil - -} - -func (p *Processor) getMinerPartitionsDifferences(ctx context.Context, miners []minerActorInfo, events chan<- *MinerSectorsEvent) error { - grp, ctx := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - if err := p.diffMinerPartitions(ctx, m, events); err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - return nil - }) - } - return grp.Wait() -} - -func (p *Processor) storeMinerSectorEvents(ctx context.Context, sectorEvents, preCommitEvents, partitionEvents <-chan *MinerSectorsEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mse (like miner_sector_events excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_: %w", err) - } - - stmt, err := tx.Prepare(`copy mse (miner_id, sector_id, event, state_root) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare miner sector info statement: %w", err) - } - - grp, ctx := errgroup.WithContext(ctx) - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range sectorEvents { - mse := mse - innerGrp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range preCommitEvents { - mse := mse - innerGrp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range partitionEvents { - mse := mse - grp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector event statement: %w", err) - } - - if _, err := tx.Exec(`insert into miner_sector_events select * from mse on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector event table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector events: %w", err) - } - return nil -} - -func (p *Processor) getMinerStateAt(ctx context.Context, maddr address.Address, tskey types.TipSetKey) (miner.State, error) { - prevActor, err := p.node.StateGetActor(ctx, maddr, tskey) - if err != nil { - return nil, err - } - return miner.Load(store.ActorStore(ctx, blockstore.NewAPIBlockstore(p.node)), prevActor) -} - -func (p *Processor) getMinerPreCommitChanges(ctx context.Context, m minerActorInfo) (*miner.PreCommitChanges, error) { - pred := state.NewStatePredicates(p.node) - changed, val, err := pred.OnMinerActorChange(m.common.addr, pred.OnMinerPreCommitChange())(ctx, m.common.parentTsKey, m.common.tsKey) - if err != nil { - return nil, xerrors.Errorf("Failed to diff miner precommit amt: %w", err) - } - if !changed { - return nil, nil - } - out := val.(*miner.PreCommitChanges) - return out, nil -} - -func (p *Processor) getMinerSectorChanges(ctx context.Context, m minerActorInfo) (*miner.SectorChanges, error) { - pred := state.NewStatePredicates(p.node) - changed, val, err := pred.OnMinerActorChange(m.common.addr, pred.OnMinerSectorChange())(ctx, m.common.parentTsKey, m.common.tsKey) - if err != nil { - return nil, xerrors.Errorf("Failed to diff miner sectors amt: %w", err) - } - if !changed { - return nil, nil - } - out := val.(*miner.SectorChanges) - return out, nil -} - -func (p *Processor) diffMinerPartitions(ctx context.Context, m minerActorInfo, events chan<- *MinerSectorsEvent) error { - prevMiner, err := p.getMinerStateAt(ctx, m.common.addr, m.common.parentTsKey) - if err != nil { - return err - } - curMiner := m.state - dc, err := prevMiner.DeadlinesChanged(curMiner) - if err != nil { - return err - } - if !dc { - return nil - } - panic("TODO") - - // FIXME: This code doesn't work. - // 1. We need to diff all deadlines, not just the "current" deadline. - // 2. We need to handle the case where we _add_ a partition. (i.e., - // where len(newPartitions) != len(oldPartitions). - /* - - // NOTE: If we change the number of deadlines in an upgrade, this will - // break. - - // load the old deadline - prevDls, err := prevMiner.LoadDeadlines(p.ctxStore) - if err != nil { - return err - } - var prevDl miner.Deadline - if err := p.ctxStore.Get(ctx, prevDls.Due[dlIdx], &prevDl); err != nil { - return err - } - - prevPartitions, err := prevDl.PartitionsArray(p.ctxStore) - if err != nil { - return err - } - - // load the new deadline - curDls, err := curMiner.LoadDeadlines(p.ctxStore) - if err != nil { - return err - } - - var curDl miner.Deadline - if err := p.ctxStore.Get(ctx, curDls.Due[dlIdx], &curDl); err != nil { - return err - } - - curPartitions, err := curDl.PartitionsArray(p.ctxStore) - if err != nil { - return err - } - - // TODO this can be optimized by inspecting the miner state for partitions that have changed and only inspecting those. - var prevPart miner.Partition - if err := prevPartitions.ForEach(&prevPart, func(i int64) error { - var curPart miner.Partition - if found, err := curPartitions.Get(uint64(i), &curPart); err != nil { - return err - } else if !found { - log.Fatal("I don't know what this means, are partitions ever removed?") - } - partitionDiff, err := p.diffPartition(prevPart, curPart) - if err != nil { - return err - } - - recovered, err := partitionDiff.Recovered.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: recovered, - Event: SectorRecovered, - } - inRecovery, err := partitionDiff.InRecovery.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: inRecovery, - Event: SectorRecovering, - } - faulted, err := partitionDiff.Faulted.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: faulted, - Event: SectorFaulted, - } - terminated, err := partitionDiff.Terminated.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: terminated, - Event: SectorTerminated, - } - expired, err := partitionDiff.Expired.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: expired, - Event: SectorExpired, - } - - return nil - }); err != nil { - return err - } - - return nil - */ -} - -func (p *Processor) diffPartition(prevPart, curPart miner.Partition) (*PartitionStatus, error) { - prevLiveSectors, err := prevPart.LiveSectors() - if err != nil { - return nil, err - } - curLiveSectors, err := curPart.LiveSectors() - if err != nil { - return nil, err - } - - removedSectors, err := bitfield.SubtractBitField(prevLiveSectors, curLiveSectors) - if err != nil { - return nil, err - } - - prevRecoveries, err := prevPart.RecoveringSectors() - if err != nil { - return nil, err - } - - curRecoveries, err := curPart.RecoveringSectors() - if err != nil { - return nil, err - } - - newRecoveries, err := bitfield.SubtractBitField(curRecoveries, prevRecoveries) - if err != nil { - return nil, err - } - - prevFaults, err := prevPart.FaultySectors() - if err != nil { - return nil, err - } - - curFaults, err := curPart.FaultySectors() - if err != nil { - return nil, err - } - - newFaults, err := bitfield.SubtractBitField(curFaults, prevFaults) - if err != nil { - return nil, err - } - - // all current good sectors - curActiveSectors, err := curPart.ActiveSectors() - if err != nil { - return nil, err - } - - // sectors that were previously fault and are now currently active are considered recovered. - recovered, err := bitfield.IntersectBitField(prevFaults, curActiveSectors) - if err != nil { - return nil, err - } - - // TODO: distinguish between "terminated" and "expired" sectors. The - // previous code here never had a chance of working in the first place, - // so I'm not going to try to replicate it right now. - // - // How? If the sector expires before it should (according to sector - // info) and it wasn't replaced by a pre-commit deleted in this change - // set, it was "early terminated". - - return &PartitionStatus{ - Terminated: bitfield.New(), - Expired: removedSectors, - Faulted: newFaults, - InRecovery: newRecoveries, - Recovered: recovered, - }, nil -} - -func (p *Processor) storeMinersActorInfoState(ctx context.Context, miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Miners Actor State", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mi (like miner_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (miner_id, owner_addr, worker_addr, peer_id, sector_size) from STDIN`) - if err != nil { - return err - } - for _, m := range miners { - mi, err := p.node.StateMinerInfo(ctx, m.common.addr, m.common.tsKey) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - continue - } else { - return err - } - } - var pid string - if mi.PeerId != nil { - pid = mi.PeerId.String() - } - if _, err := stmt.Exec( - m.common.addr.String(), - mi.Owner.String(), - mi.Worker.String(), - pid, - mi.SectorSize.ShortString(), - ); err != nil { - log.Errorw("failed to store miner state", "state", m.state, "info", m.state.Info, "error", err) - return xerrors.Errorf("failed to store miner state: %w", err) - } - - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into miner_info select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storePreCommitDealInfo(dealEvents <-chan *SectorDealEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mds (like minerid_dealid_sectorid excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for minerid_dealid_sectorid: %w", err) - } - - stmt, err := tx.Prepare(`copy mds (deal_id, miner_id, sector_id) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare minerid_dealid_sectorid statement: %w", err) - } - - for sde := range dealEvents { - for _, did := range sde.DealIDs { - if _, err := stmt.Exec( - uint64(did), - sde.MinerID.String(), - sde.SectorID, - ); err != nil { - return err - } - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close miner sector deals statement: %w", err) - } - - if _, err := tx.Exec(`insert into minerid_dealid_sectorid select * from mds on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into miner deal sector table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit miner deal sector table: %w", err) - } - return nil - -} - -func (p *Processor) storeMinersPower(miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Miners Power", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin miner_power tx: %w", err) - } - - if _, err := tx.Exec(`create temp table mp (like miner_power excluding constraints) on commit drop`); err != nil { - return xerrors.Errorf("prep miner_power temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mp (miner_id, state_root, raw_bytes_power, quality_adjusted_power) from STDIN`) - if err != nil { - return xerrors.Errorf("prepare tmp miner_power: %w", err) - } - - for _, m := range miners { - if _, err := stmt.Exec( - m.common.addr.String(), - m.common.stateroot.String(), - m.rawPower.String(), - m.qalPower.String(), - ); err != nil { - log.Errorw("failed to store miner power", "miner", m.common.addr, "stateroot", m.common.stateroot, "error", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared miner_power: %w", err) - } - - if _, err := tx.Exec(`insert into miner_power select * from mp on conflict do nothing`); err != nil { - return xerrors.Errorf("insert miner_power from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit miner_power tx: %w", err) - } - - return nil - -} - -// load the power actor state clam as an adt.Map at the tipset `ts`. -func getPowerActorState(ctx context.Context, api v0api.FullNode, ts types.TipSetKey) (power.State, error) { - powerActor, err := api.StateGetActor(ctx, power.Address, ts) - if err != nil { - return nil, err - } - return power.Load(cw_util.NewAPIIpldStore(ctx, api), powerActor) -} diff --git a/cmd/lotus-chainwatch/processor/mpool.go b/cmd/lotus-chainwatch/processor/mpool.go deleted file mode 100644 index 0a6445d78..000000000 --- a/cmd/lotus-chainwatch/processor/mpool.go +++ /dev/null @@ -1,100 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" -) - -func (p *Processor) subMpool(ctx context.Context) { - sub, err := p.node.MpoolSub(ctx) - if err != nil { - return - } - - for { - var updates []api.MpoolUpdate - - select { - case update := <-sub: - updates = append(updates, update) - case <-ctx.Done(): - return - } - - loop: - for { - select { - case update := <-sub: - updates = append(updates, update) - case <-time.After(10 * time.Millisecond): - break loop - } - } - - msgs := map[cid.Cid]*types.Message{} - for _, v := range updates { - if v.Type != api.MpoolAdd { - continue - } - - msgs[v.Message.Message.Cid()] = &v.Message.Message - } - - err := p.storeMessages(msgs) - if err != nil { - log.Error(err) - } - - if err := p.storeMpoolInclusions(updates); err != nil { - log.Error(err) - } - } -} - -func (p *Processor) storeMpoolInclusions(msgs []api.MpoolUpdate) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` - create temp table mi (like mpool_messages excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (msg, add_ts) from stdin `) - if err != nil { - return err - } - - for _, msg := range msgs { - if msg.Type != api.MpoolAdd { - continue - } - - if _, err := stmt.Exec( - msg.Message.Message.Cid().String(), - time.Now().Unix(), - ); err != nil { - return err - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into mpool_messages select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/power.go b/cmd/lotus-chainwatch/processor/power.go deleted file mode 100644 index 726a46706..000000000 --- a/cmd/lotus-chainwatch/processor/power.go +++ /dev/null @@ -1,190 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/chain/actors/builtin" -) - -type powerActorInfo struct { - common actorInfo - - totalRawBytes big.Int - totalRawBytesCommitted big.Int - totalQualityAdjustedBytes big.Int - totalQualityAdjustedBytesCommitted big.Int - totalPledgeCollateral big.Int - - qaPowerSmoothed builtin.FilterEstimate - - minerCount int64 - minerCountAboveMinimumPower int64 -} - -func (p *Processor) setupPower() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists chain_power -( - state_root text not null - constraint power_smoothing_estimates_pk - primary key, - - total_raw_bytes_power text not null, - total_raw_bytes_committed text not null, - total_qa_bytes_power text not null, - total_qa_bytes_committed text not null, - total_pledge_collateral text not null, - - qa_smoothed_position_estimate text not null, - qa_smoothed_velocity_estimate text not null, - - miner_count int not null, - minimum_consensus_miner_count int not null -); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandlePowerChanges(ctx context.Context, powerTips ActorTips) error { - powerChanges, err := p.processPowerActors(ctx, powerTips) - if err != nil { - return xerrors.Errorf("Failed to process power actors: %w", err) - } - - if err := p.persistPowerActors(ctx, powerChanges); err != nil { - return err - } - - return nil -} - -func (p *Processor) processPowerActors(ctx context.Context, powerTips ActorTips) ([]powerActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Power Actors", "duration", time.Since(start).String()) - }() - - var out []powerActorInfo - for tipset, powerStates := range powerTips { - for _, act := range powerStates { - var pw powerActorInfo - pw.common = act - - powerActorState, err := getPowerActorState(ctx, p.node, tipset) - if err != nil { - return nil, xerrors.Errorf("get power state (@ %s): %w", pw.common.stateroot.String(), err) - } - - totalPower, err := powerActorState.TotalPower() - if err != nil { - return nil, xerrors.Errorf("failed to compute total power: %w", err) - } - - totalCommitted, err := powerActorState.TotalCommitted() - if err != nil { - return nil, xerrors.Errorf("failed to compute total committed: %w", err) - } - - totalLocked, err := powerActorState.TotalLocked() - if err != nil { - return nil, xerrors.Errorf("failed to compute total locked: %w", err) - } - - powerSmoothed, err := powerActorState.TotalPowerSmoothed() - if err != nil { - return nil, xerrors.Errorf("failed to determine smoothed power: %w", err) - } - - // NOTE: this doesn't set new* fields. Previously, we - // filled these using ThisEpoch* fields from the actor - // state, but these fields are effectively internal - // state and don't represent "new" power, as was - // assumed. - - participatingMiners, totalMiners, err := powerActorState.MinerCounts() - if err != nil { - return nil, xerrors.Errorf("failed to count miners: %w", err) - } - - pw.totalRawBytes = totalPower.RawBytePower - pw.totalQualityAdjustedBytes = totalPower.QualityAdjPower - pw.totalRawBytesCommitted = totalCommitted.RawBytePower - pw.totalQualityAdjustedBytesCommitted = totalCommitted.QualityAdjPower - pw.totalPledgeCollateral = totalLocked - pw.qaPowerSmoothed = powerSmoothed - pw.minerCountAboveMinimumPower = int64(participatingMiners) - pw.minerCount = int64(totalMiners) - } - } - - return out, nil -} - -func (p *Processor) persistPowerActors(ctx context.Context, powerStates []powerActorInfo) error { - // NB: use errgroup when there is more than a single store operation - return p.storePowerSmoothingEstimates(powerStates) -} - -func (p *Processor) storePowerSmoothingEstimates(powerStates []powerActorInfo) error { - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin chain_power tx: %w", err) - } - - if _, err := tx.Exec(`create temp table cp (like chain_power) on commit drop`); err != nil { - return xerrors.Errorf("prep chain_power: %w", err) - } - - stmt, err := tx.Prepare(`copy cp (state_root, total_raw_bytes_power, total_raw_bytes_committed, total_qa_bytes_power, total_qa_bytes_committed, total_pledge_collateral, qa_smoothed_position_estimate, qa_smoothed_velocity_estimate, miner_count, minimum_consensus_miner_count) from stdin;`) - if err != nil { - return xerrors.Errorf("prepare tmp chain_power: %w", err) - } - - for _, ps := range powerStates { - if _, err := stmt.Exec( - ps.common.stateroot.String(), - - ps.totalRawBytes.String(), - ps.totalRawBytesCommitted.String(), - ps.totalQualityAdjustedBytes.String(), - ps.totalQualityAdjustedBytesCommitted.String(), - ps.totalPledgeCollateral.String(), - - ps.qaPowerSmoothed.PositionEstimate.String(), - ps.qaPowerSmoothed.VelocityEstimate.String(), - - ps.minerCount, - ps.minerCountAboveMinimumPower, - ); err != nil { - return xerrors.Errorf("failed to store smoothing estimate: %w", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared chain_power: %w", err) - } - - if _, err := tx.Exec(`insert into chain_power select * from cp on conflict do nothing`); err != nil { - return xerrors.Errorf("insert chain_power from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit chain_power tx: %w", err) - } - - return nil - -} diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go deleted file mode 100644 index af5935d47..000000000 --- a/cmd/lotus-chainwatch/processor/processor.go +++ /dev/null @@ -1,420 +0,0 @@ -package processor - -import ( - "context" - "database/sql" - "encoding/json" - "math" - "sync" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/go-state-types/abi" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" - "github.com/filecoin-project/lotus/lib/parmap" -) - -var log = logging.Logger("processor") - -type Processor struct { - db *sql.DB - - node v0api.FullNode - ctxStore *cw_util.APIIpldStore - - genesisTs *types.TipSet - - // number of blocks processed at a time - batch int -} - -type ActorTips map[types.TipSetKey][]actorInfo - -type actorInfo struct { - act types.Actor - - stateroot cid.Cid - height abi.ChainEpoch // so that we can walk the actor changes in chronological order. - - tsKey types.TipSetKey - parentTsKey types.TipSetKey - - addr address.Address - state string -} - -func NewProcessor(ctx context.Context, db *sql.DB, node v0api.FullNode, batch int) *Processor { - ctxStore := cw_util.NewAPIIpldStore(ctx, node) - return &Processor{ - db: db, - ctxStore: ctxStore, - node: node, - batch: batch, - } -} - -func (p *Processor) setupSchemas() error { - // maintain order, subsequent calls create tables with foreign keys. - if err := p.setupMiners(); err != nil { - return err - } - - if err := p.setupMarket(); err != nil { - return err - } - - if err := p.setupRewards(); err != nil { - return err - } - - if err := p.setupMessages(); err != nil { - return err - } - - if err := p.setupCommonActors(); err != nil { - return err - } - - if err := p.setupPower(); err != nil { - return err - } - - return nil -} - -func (p *Processor) Start(ctx context.Context) { - log.Debug("Starting Processor") - - if err := p.setupSchemas(); err != nil { - log.Fatalw("Failed to setup processor", "error", err) - } - - var err error - p.genesisTs, err = p.node.ChainGetGenesis(ctx) - if err != nil { - log.Fatalw("Failed to get genesis state from lotus", "error", err.Error()) - } - - go p.subMpool(ctx) - - // main processor loop - go func() { - for { - select { - case <-ctx.Done(): - log.Info("Stopping Processor...") - return - default: - loopStart := time.Now() - toProcess, err := p.unprocessedBlocks(ctx, p.batch) - if err != nil { - log.Fatalw("Failed to get unprocessed blocks", "error", err) - } - - if len(toProcess) == 0 { - log.Info("No unprocessed blocks. Wait then try again...") - time.Sleep(time.Second * 30) - continue - } - - // TODO special case genesis state handling here to avoid all the special cases that will be needed for it else where - // before doing "normal" processing. - - actorChanges, nullRounds, err := p.collectActorChanges(ctx, toProcess) - if err != nil { - log.Fatalw("Failed to collect actor changes", "error", err) - } - log.Infow("Collected Actor Changes", - "MarketChanges", len(actorChanges[builtin2.StorageMarketActorCodeID]), - "MinerChanges", len(actorChanges[builtin2.StorageMinerActorCodeID]), - "RewardChanges", len(actorChanges[builtin2.RewardActorCodeID]), - "AccountChanges", len(actorChanges[builtin2.AccountActorCodeID]), - "nullRounds", len(nullRounds)) - - grp := sync.WaitGroup{} - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMarketChanges(ctx, actorChanges[builtin2.StorageMarketActorCodeID]); err != nil { - log.Errorf("Failed to handle market changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMinerChanges(ctx, actorChanges[builtin2.StorageMinerActorCodeID]); err != nil { - log.Errorf("Failed to handle miner changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleRewardChanges(ctx, actorChanges[builtin2.RewardActorCodeID], nullRounds); err != nil { - log.Errorf("Failed to handle reward changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandlePowerChanges(ctx, actorChanges[builtin2.StoragePowerActorCodeID]); err != nil { - log.Errorf("Failed to handle power actor changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMessageChanges(ctx, toProcess); err != nil { - log.Errorf("Failed to handle message changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleCommonActorsChanges(ctx, actorChanges); err != nil { - log.Errorf("Failed to handle common actor changes: %v", err) - return - } - }() - - grp.Wait() - - if err := p.markBlocksProcessed(ctx, toProcess); err != nil { - log.Fatalw("Failed to mark blocks as processed", "error", err) - } - - if err := p.refreshViews(); err != nil { - log.Errorw("Failed to refresh views", "error", err) - } - log.Infow("Processed Batch Complete", "duration", time.Since(loopStart).String()) - } - } - }() - -} - -func (p *Processor) refreshViews() error { - if _, err := p.db.Exec(`refresh materialized view state_heights`); err != nil { - return err - } - - return nil -} - -func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.Cid]*types.BlockHeader) (map[cid.Cid]ActorTips, []types.TipSetKey, error) { - start := time.Now() - defer func() { - log.Debugw("Collected Actor Changes", "duration", time.Since(start).String()) - }() - // ActorCode - > tipset->[]actorInfo - out := map[cid.Cid]ActorTips{} - var outMu sync.Mutex - - // map of addresses to changed actors - var changes map[string]types.Actor - actorsSeen := map[cid.Cid]struct{}{} - - var nullRounds []types.TipSetKey - var nullBlkMu sync.Mutex - - // collect all actor state that has changes between block headers - paDone := 0 - parmap.Par(50, parmap.MapArr(toProcess), func(bh *types.BlockHeader) { - paDone++ - if paDone%100 == 0 { - log.Debugw("Collecting actor changes", "done", paDone, "percent", (paDone*100)/len(toProcess)) - } - - pts, err := p.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) - if err != nil { - log.Error(err) - return - } - - if pts.ParentState().Equals(bh.ParentStateRoot) { - nullBlkMu.Lock() - nullRounds = append(nullRounds, pts.Key()) - nullBlkMu.Unlock() - } - - // collect all actors that had state changes between the blockheader parent-state and its grandparent-state. - // TODO: changes will contain deleted actors, this causes needless processing further down the pipeline, consider - // a separate strategy for deleted actors - changes, err = p.node.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot) - if err != nil { - log.Error(err) - log.Debugw("StateChangedActors", "grandparent_state", pts.ParentState(), "parent_state", bh.ParentStateRoot) - return - } - - // record the state of all actors that have changed - for a, act := range changes { - act := act - a := a - - // ignore actors that were deleted. - has, err := p.node.ChainHasObj(ctx, act.Head) - if err != nil { - log.Error(err) - log.Debugw("ChanHasObj", "actor_head", act.Head) - return - } - if !has { - continue - } - - addr, err := address.NewFromString(a) - if err != nil { - log.Error(err) - log.Debugw("NewFromString", "address_string", a) - return - } - - ast, err := p.node.StateReadState(ctx, addr, pts.Key()) - if err != nil { - log.Error(err) - log.Debugw("StateReadState", "address_string", a, "parent_tipset_key", pts.Key()) - return - } - - // TODO look here for an empty state, maybe thats a sign the actor was deleted? - - state, err := json.Marshal(ast.State) - if err != nil { - log.Error(err) - return - } - - outMu.Lock() - if _, ok := actorsSeen[act.Head]; !ok { - _, ok := out[act.Code] - if !ok { - out[act.Code] = map[types.TipSetKey][]actorInfo{} - } - out[act.Code][pts.Key()] = append(out[act.Code][pts.Key()], actorInfo{ - act: act, - stateroot: bh.ParentStateRoot, - height: bh.Height, - tsKey: pts.Key(), - parentTsKey: pts.Parents(), - addr: addr, - state: string(state), - }) - } - actorsSeen[act.Head] = struct{}{} - outMu.Unlock() - } - }) - return out, nullRounds, nil -} - -func (p *Processor) unprocessedBlocks(ctx context.Context, batch int) (map[cid.Cid]*types.BlockHeader, error) { - start := time.Now() - defer func() { - log.Debugw("Gathered Blocks to process", "duration", time.Since(start).String()) - }() - rows, err := p.db.Query(` -with toProcess as ( - select b.cid, b.height, rank() over (order by height) as rnk - from blocks_synced bs - left join blocks b on bs.cid = b.cid - where bs.processed_at is null and b.height > 0 -) -select cid -from toProcess -where rnk <= $1 -`, batch) - if err != nil { - return nil, xerrors.Errorf("Failed to query for unprocessed blocks: %w", err) - } - out := map[cid.Cid]*types.BlockHeader{} - - minBlock := abi.ChainEpoch(math.MaxInt64) - maxBlock := abi.ChainEpoch(0) - // TODO consider parallel execution here for getting the blocks from the api as is done in fetchMessages() - for rows.Next() { - if rows.Err() != nil { - return nil, err - } - var c string - if err := rows.Scan(&c); err != nil { - log.Errorf("Failed to scan unprocessed blocks: %s", err.Error()) - continue - } - ci, err := cid.Parse(c) - if err != nil { - log.Errorf("Failed to parse unprocessed blocks: %s", err.Error()) - continue - } - bh, err := p.node.ChainGetBlock(ctx, ci) - if err != nil { - // this is a pretty serious issue. - log.Errorf("Failed to get block header %s: %s", ci.String(), err.Error()) - continue - } - out[ci] = bh - if bh.Height < minBlock { - minBlock = bh.Height - } - if bh.Height > maxBlock { - maxBlock = bh.Height - } - } - if minBlock <= maxBlock { - log.Infow("Gathered Blocks to Process", "start", minBlock, "end", maxBlock) - } - return out, rows.Close() -} - -func (p *Processor) markBlocksProcessed(ctx context.Context, processed map[cid.Cid]*types.BlockHeader) error { - start := time.Now() - processedHeight := abi.ChainEpoch(0) - defer func() { - log.Debugw("Marked blocks as Processed", "duration", time.Since(start).String()) - log.Infow("Processed Blocks", "height", processedHeight) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - - processedAt := time.Now().Unix() - stmt, err := tx.Prepare(`update blocks_synced set processed_at=$1 where cid=$2`) - if err != nil { - return err - } - - for c, bh := range processed { - if bh.Height > processedHeight { - processedHeight = bh.Height - } - if _, err := stmt.Exec(processedAt, c.String()); err != nil { - return err - } - } - - if err := stmt.Close(); err != nil { - return err - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/reward.go b/cmd/lotus-chainwatch/processor/reward.go deleted file mode 100644 index 72a329c87..000000000 --- a/cmd/lotus-chainwatch/processor/reward.go +++ /dev/null @@ -1,234 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - "github.com/filecoin-project/lotus/chain/types" - - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -type rewardActorInfo struct { - common actorInfo - - cumSumBaselinePower big.Int - cumSumRealizedPower big.Int - - effectiveNetworkTime abi.ChainEpoch - effectiveBaselinePower big.Int - - // NOTE: These variables are wrong. Talk to @ZX about fixing. These _do - // not_ represent "new" anything. - newBaselinePower big.Int - newBaseReward big.Int - newSmoothingEstimate builtin.FilterEstimate - - totalMinedReward big.Int -} - -func (rw *rewardActorInfo) set(s reward.State) (err error) { - rw.cumSumBaselinePower, err = s.CumsumBaseline() - if err != nil { - return xerrors.Errorf("getting cumsum baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.cumSumRealizedPower, err = s.CumsumRealized() - if err != nil { - return xerrors.Errorf("getting cumsum realized power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.effectiveNetworkTime, err = s.EffectiveNetworkTime() - if err != nil { - return xerrors.Errorf("getting effective network time (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.effectiveBaselinePower, err = s.EffectiveBaselinePower() - if err != nil { - return xerrors.Errorf("getting effective baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.totalMinedReward, err = s.TotalStoragePowerReward() - if err != nil { - return xerrors.Errorf("getting total mined (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newBaselinePower, err = s.ThisEpochBaselinePower() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newBaseReward, err = s.ThisEpochReward() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newSmoothingEstimate, err = s.ThisEpochRewardSmoothed() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - return nil -} - -func (p *Processor) setupRewards() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -/* captures chain-specific power state for any given stateroot */ -create table if not exists chain_reward -( - state_root text not null - constraint chain_reward_pk - primary key, - cum_sum_baseline text not null, - cum_sum_realized text not null, - effective_network_time int not null, - effective_baseline_power text not null, - - new_baseline_power text not null, - new_reward numeric not null, - new_reward_smoothed_position_estimate text not null, - new_reward_smoothed_velocity_estimate text not null, - - total_mined_reward text not null -); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleRewardChanges(ctx context.Context, rewardTips ActorTips, nullRounds []types.TipSetKey) error { - rewardChanges, err := p.processRewardActors(ctx, rewardTips, nullRounds) - if err != nil { - return xerrors.Errorf("Failed to process reward actors: %w", err) - } - - if err := p.persistRewardActors(ctx, rewardChanges); err != nil { - return err - } - - return nil -} - -func (p *Processor) processRewardActors(ctx context.Context, rewardTips ActorTips, nullRounds []types.TipSetKey) ([]rewardActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Reward Actors", "duration", time.Since(start).String()) - }() - - var out []rewardActorInfo - for tipset, rewards := range rewardTips { - for _, act := range rewards { - var rw rewardActorInfo - rw.common = act - - // get reward actor states at each tipset once for all updates - rewardActor, err := p.node.StateGetActor(ctx, reward.Address, tipset) - if err != nil { - return nil, xerrors.Errorf("get reward state (@ %s): %w", rw.common.stateroot.String(), err) - } - - rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, p.node), rewardActor) - if err != nil { - return nil, xerrors.Errorf("read state obj (@ %s): %w", rw.common.stateroot.String(), err) - } - if err := rw.set(rewardActorState); err != nil { - return nil, err - } - - out = append(out, rw) - } - } - for _, tsKey := range nullRounds { - var rw rewardActorInfo - tipset, err := p.node.ChainGetTipSet(ctx, tsKey) - if err != nil { - return nil, err - } - rw.common.tsKey = tipset.Key() - rw.common.height = tipset.Height() - rw.common.stateroot = tipset.ParentState() - rw.common.parentTsKey = tipset.Parents() - // get reward actor states at each tipset once for all updates - rewardActor, err := p.node.StateGetActor(ctx, reward.Address, tsKey) - if err != nil { - return nil, err - } - - rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, p.node), rewardActor) - if err != nil { - return nil, xerrors.Errorf("read state obj (@ %s): %w", rw.common.stateroot.String(), err) - } - - if err := rw.set(rewardActorState); err != nil { - return nil, err - } - out = append(out, rw) - } - - return out, nil -} - -func (p *Processor) persistRewardActors(ctx context.Context, rewards []rewardActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Reward Actors", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin chain_reward tx: %w", err) - } - - if _, err := tx.Exec(`create temp table cr (like chain_reward excluding constraints) on commit drop`); err != nil { - return xerrors.Errorf("prep chain_reward temp: %w", err) - } - - stmt, err := tx.Prepare(`copy cr ( state_root, cum_sum_baseline, cum_sum_realized, effective_network_time, effective_baseline_power, new_baseline_power, new_reward, new_reward_smoothed_position_estimate, new_reward_smoothed_velocity_estimate, total_mined_reward) from STDIN`) - if err != nil { - return xerrors.Errorf("prepare tmp chain_reward: %w", err) - } - - for _, rewardState := range rewards { - if _, err := stmt.Exec( - rewardState.common.stateroot.String(), - rewardState.cumSumBaselinePower.String(), - rewardState.cumSumRealizedPower.String(), - uint64(rewardState.effectiveNetworkTime), - rewardState.effectiveBaselinePower.String(), - rewardState.newBaselinePower.String(), - rewardState.newBaseReward.String(), - rewardState.newSmoothingEstimate.PositionEstimate.String(), - rewardState.newSmoothingEstimate.VelocityEstimate.String(), - rewardState.totalMinedReward.String(), - ); err != nil { - log.Errorw("failed to store chain power", "state_root", rewardState.common.stateroot, "error", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared chain_reward: %w", err) - } - - if _, err := tx.Exec(`insert into chain_reward select * from cr on conflict do nothing`); err != nil { - return xerrors.Errorf("insert chain_reward from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit chain_reward tx: %w", err) - } - - return nil -} diff --git a/cmd/lotus-chainwatch/run.go b/cmd/lotus-chainwatch/run.go deleted file mode 100644 index 6e47a100d..000000000 --- a/cmd/lotus-chainwatch/run.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - "net/http" - _ "net/http/pprof" - "os" - "strings" - - "github.com/filecoin-project/lotus/api/v0api" - - _ "github.com/lib/pq" - - "github.com/filecoin-project/go-jsonrpc" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/processor" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/scheduler" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/syncer" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -var runCmd = &cli.Command{ - Name: "run", - Usage: "Start lotus chainwatch", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "max-batch", - Value: 50, - }, - }, - Action: func(cctx *cli.Context) error { - go func() { - http.ListenAndServe(":6060", nil) //nolint:errcheck - }() - ll := cctx.String("log-level") - if err := logging.SetLogLevel("*", ll); err != nil { - return err - } - if err := logging.SetLogLevel("rpc", "error"); err != nil { - return err - } - - var api v0api.FullNode - var closer jsonrpc.ClientCloser - var err error - if tokenMaddr := cctx.String("api"); tokenMaddr != "" { - toks := strings.Split(tokenMaddr, ":") - if len(toks) != 2 { - return fmt.Errorf("invalid api tokens, expected :, got: %s", tokenMaddr) - } - - api, closer, err = util.GetFullNodeAPIUsingCredentials(cctx.Context, toks[1], toks[0]) - if err != nil { - return err - } - } else { - api, closer, err = lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - } - defer closer() - ctx := lcli.ReqContext(cctx) - - v, err := api.Version(ctx) - if err != nil { - return err - } - - log.Infof("Remote version: %s", v.Version) - - maxBatch := cctx.Int("max-batch") - - db, err := sql.Open("postgres", cctx.String("db")) - if err != nil { - return err - } - defer func() { - if err := db.Close(); err != nil { - log.Errorw("Failed to close database", "error", err) - } - }() - - if err := db.Ping(); err != nil { - return xerrors.Errorf("Database failed to respond to ping (is it online?): %w", err) - } - db.SetMaxOpenConns(1350) - - sync := syncer.NewSyncer(db, api, 1400) - sync.Start(ctx) - - proc := processor.NewProcessor(ctx, db, api, maxBatch) - proc.Start(ctx) - - sched := scheduler.PrepareScheduler(db) - sched.Start(ctx) - - <-ctx.Done() - os.Exit(0) - return nil - }, -} diff --git a/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go b/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go deleted file mode 100644 index 145e84229..000000000 --- a/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go +++ /dev/null @@ -1,78 +0,0 @@ -package scheduler - -import ( - "context" - "database/sql" - - "golang.org/x/xerrors" -) - -func setupTopMinerByBaseRewardSchema(ctx context.Context, db *sql.DB) error { - select { - case <-ctx.Done(): - return nil - default: - } - - tx, err := db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create materialized view if not exists top_miners_by_base_reward as - with total_rewards_by_miner as ( - select - b.miner, - sum(cr.new_reward * b.win_count) as total_reward - from blocks b - inner join chain_reward cr on b.parentstateroot = cr.state_root - group by 1 - ) select - rank() over (order by total_reward desc), - miner, - total_reward - from total_rewards_by_miner - group by 2, 3; - - create index if not exists top_miners_by_base_reward_miner_index - on top_miners_by_base_reward (miner); - - create materialized view if not exists top_miners_by_base_reward_max_height as - select - b."timestamp"as current_timestamp, - max(b.height) as current_height - from blocks b - join chain_reward cr on b.parentstateroot = cr.state_root - where cr.new_reward is not null - group by 1 - order by 1 desc - limit 1; - `); err != nil { - return xerrors.Errorf("create top_miners_by_base_reward views: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("committing top_miners_by_base_reward views; %w", err) - } - return nil -} - -func refreshTopMinerByBaseReward(ctx context.Context, db *sql.DB) error { - select { - case <-ctx.Done(): - return nil - default: - } - - _, err := db.Exec("refresh materialized view top_miners_by_base_reward;") - if err != nil { - return xerrors.Errorf("refresh top_miners_by_base_reward: %w", err) - } - - _, err = db.Exec("refresh materialized view top_miners_by_base_reward_max_height;") - if err != nil { - return xerrors.Errorf("refresh top_miners_by_base_reward_max_height: %w", err) - } - - return nil -} diff --git a/cmd/lotus-chainwatch/scheduler/scheduler.go b/cmd/lotus-chainwatch/scheduler/scheduler.go deleted file mode 100644 index 6782bc16d..000000000 --- a/cmd/lotus-chainwatch/scheduler/scheduler.go +++ /dev/null @@ -1,60 +0,0 @@ -package scheduler - -import ( - "context" - "database/sql" - "time" - - logging "github.com/ipfs/go-log/v2" - - "golang.org/x/xerrors" -) - -var log = logging.Logger("scheduler") - -// Scheduler manages the execution of jobs triggered -// by tickers. Not externally configurable at runtime. -type Scheduler struct { - db *sql.DB -} - -// PrepareScheduler returns a ready-to-run Scheduler -func PrepareScheduler(db *sql.DB) *Scheduler { - return &Scheduler{db} -} - -func (s *Scheduler) setupSchema(ctx context.Context) error { - if err := setupTopMinerByBaseRewardSchema(ctx, s.db); err != nil { - return xerrors.Errorf("setup top miners by reward schema: %w", err) - } - return nil -} - -// Start the scheduler jobs at the defined intervals -func (s *Scheduler) Start(ctx context.Context) { - log.Debug("Starting Scheduler") - - if err := s.setupSchema(ctx); err != nil { - log.Fatalw("applying scheduling schema", "error", err) - } - - go func() { - // run once on start after schema has initialized - time.Sleep(1 * time.Minute) - if err := refreshTopMinerByBaseReward(ctx, s.db); err != nil { - log.Errorw("failed to refresh top miner", "error", err) - } - refreshTopMinerCh := time.NewTicker(30 * time.Second) - defer refreshTopMinerCh.Stop() - for { - select { - case <-refreshTopMinerCh.C: - if err := refreshTopMinerByBaseReward(ctx, s.db); err != nil { - log.Errorw("failed to refresh top miner", "error", err) - } - case <-ctx.Done(): - return - } - } - }() -} diff --git a/cmd/lotus-chainwatch/syncer/blockssub.go b/cmd/lotus-chainwatch/syncer/blockssub.go deleted file mode 100644 index ea9c079e8..000000000 --- a/cmd/lotus-chainwatch/syncer/blockssub.go +++ /dev/null @@ -1,27 +0,0 @@ -package syncer - -import ( - "context" - "time" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" -) - -func (s *Syncer) subBlocks(ctx context.Context) { - sub, err := s.node.SyncIncomingBlocks(ctx) - if err != nil { - log.Errorf("opening incoming block channel: %+v", err) - return - } - - log.Infow("Capturing incoming blocks") - for bh := range sub { - err := s.storeHeaders(map[cid.Cid]*types.BlockHeader{ - bh.Cid(): bh, - }, false, time.Now()) - if err != nil { - log.Errorf("storing incoming block header: %+v", err) - } - } -} diff --git a/cmd/lotus-chainwatch/syncer/sync.go b/cmd/lotus-chainwatch/syncer/sync.go deleted file mode 100644 index b5e9c73d6..000000000 --- a/cmd/lotus-chainwatch/syncer/sync.go +++ /dev/null @@ -1,527 +0,0 @@ -package syncer - -import ( - "container/list" - "context" - "database/sql" - "fmt" - "sync" - "time" - - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" -) - -var log = logging.Logger("syncer") - -type Syncer struct { - db *sql.DB - - lookbackLimit uint64 - - headerLk sync.Mutex - node v0api.FullNode -} - -func NewSyncer(db *sql.DB, node v0api.FullNode, lookbackLimit uint64) *Syncer { - return &Syncer{ - db: db, - node: node, - lookbackLimit: lookbackLimit, - } -} - -func (s *Syncer) setupSchemas() error { - tx, err := s.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -/* tracks circulating fil available on the network at each tipset */ -create table if not exists chain_economics -( - parent_state_root text not null - constraint chain_economics_pk primary key, - circulating_fil text not null, - vested_fil text not null, - mined_fil text not null, - burnt_fil text not null, - locked_fil text not null -); - -create table if not exists block_cids -( - cid text not null - constraint block_cids_pk - primary key -); - -create unique index if not exists block_cids_cid_uindex - on block_cids (cid); - -create table if not exists blocks_synced -( - cid text not null - constraint blocks_synced_pk - primary key - constraint blocks_block_cids_cid_fk - references block_cids (cid), - synced_at int not null, - processed_at int -); - -create unique index if not exists blocks_synced_cid_uindex - on blocks_synced (cid,processed_at); - -create table if not exists block_parents -( - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid), - parent text not null -); - -create unique index if not exists block_parents_block_parent_uindex - on block_parents (block, parent); - -create table if not exists drand_entries -( - round bigint not null - constraint drand_entries_pk - primary key, - data bytea not null -); -create unique index if not exists drand_entries_round_uindex - on drand_entries (round); - -create table if not exists block_drand_entries -( - round bigint not null - constraint block_drand_entries_drand_entries_round_fk - references drand_entries (round), - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid) -); -create unique index if not exists block_drand_entries_round_uindex - on block_drand_entries (round, block); - -create table if not exists blocks -( - cid text not null - constraint blocks_pk - primary key - constraint blocks_block_cids_cid_fk - references block_cids (cid), - parentWeight numeric not null, - parentStateRoot text not null, - height bigint not null, - miner text not null, - timestamp bigint not null, - ticket bytea not null, - election_proof bytea, - win_count bigint, - parent_base_fee text not null, - forksig bigint not null -); - -create unique index if not exists block_cid_uindex - on blocks (cid,height); - -create materialized view if not exists state_heights - as select min(b.height) height, b.parentstateroot - from blocks b group by b.parentstateroot; - -create index if not exists state_heights_height_index - on state_heights (height); - -create index if not exists state_heights_parentstateroot_index - on state_heights (parentstateroot); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (s *Syncer) Start(ctx context.Context) { - if err := logging.SetLogLevel("syncer", "info"); err != nil { - log.Fatal(err) - } - log.Debug("Starting Syncer") - - if err := s.setupSchemas(); err != nil { - log.Fatal(err) - } - - // capture all reported blocks - go s.subBlocks(ctx) - - // we need to ensure that on a restart we don't reprocess the whole flarping chain - var sinceEpoch uint64 - blkCID, height, err := s.mostRecentlySyncedBlockHeight() - if err != nil { - log.Fatalw("failed to find most recently synced block", "error", err) - } else { - if height > 0 { - log.Infow("Found starting point for syncing", "blockCID", blkCID.String(), "height", height) - sinceEpoch = uint64(height) - } - } - - // continue to keep the block headers table up to date. - notifs, err := s.node.ChainNotify(ctx) - if err != nil { - log.Fatal(err) - } - - go func() { - for notif := range notifs { - for _, change := range notif { - switch change.Type { - case store.HCCurrent: - // This case is important for capturing the initial state of a node - // which might be on a dead network with no new blocks being produced. - // It also allows a fresh Chainwatch instance to start walking the - // chain without waiting for a new block to come along. - fallthrough - case store.HCApply: - unsynced, err := s.unsyncedBlocks(ctx, change.Val, sinceEpoch) - if err != nil { - log.Errorw("failed to gather unsynced blocks", "error", err) - } - - if err := s.storeCirculatingSupply(ctx, change.Val); err != nil { - log.Errorw("failed to store circulating supply", "error", err) - } - - if len(unsynced) == 0 { - continue - } - - if err := s.storeHeaders(unsynced, true, time.Now()); err != nil { - // so this is pretty bad, need some kind of retry.. - // for now just log an error and the blocks will be attempted again on next notifi - log.Errorw("failed to store unsynced blocks", "error", err) - } - - sinceEpoch = uint64(change.Val.Height()) - case store.HCRevert: - log.Debug("revert todo") - } - } - } - }() -} - -func (s *Syncer) unsyncedBlocks(ctx context.Context, head *types.TipSet, since uint64) (map[cid.Cid]*types.BlockHeader, error) { - hasList, err := s.syncedBlocks(since, s.lookbackLimit) - if err != nil { - return nil, err - } - - // build a list of blocks that we have not synced. - toVisit := list.New() - for _, header := range head.Blocks() { - toVisit.PushBack(header) - } - - toSync := map[cid.Cid]*types.BlockHeader{} - - for toVisit.Len() > 0 { - bh := toVisit.Remove(toVisit.Back()).(*types.BlockHeader) - _, has := hasList[bh.Cid()] - if _, seen := toSync[bh.Cid()]; seen || has { - continue - } - - toSync[bh.Cid()] = bh - if len(toSync)%500 == 10 { - log.Debugw("To visit", "toVisit", toVisit.Len(), "toSync", len(toSync), "current_height", bh.Height) - } - - if bh.Height == 0 { - continue - } - - pts, err := s.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) - if err != nil { - log.Error(err) - continue - } - - for _, header := range pts.Blocks() { - toVisit.PushBack(header) - } - } - log.Debugw("Gathered unsynced blocks", "count", len(toSync)) - return toSync, nil -} - -func (s *Syncer) syncedBlocks(since, limit uint64) (map[cid.Cid]struct{}, error) { - rws, err := s.db.Query(`select bs.cid FROM blocks_synced bs left join blocks b on b.cid = bs.cid where b.height <= $1 and bs.processed_at is not null limit $2`, since, limit) - if err != nil { - return nil, xerrors.Errorf("Failed to query blocks_synced: %w", err) - } - out := map[cid.Cid]struct{}{} - - for rws.Next() { - var c string - if err := rws.Scan(&c); err != nil { - return nil, xerrors.Errorf("Failed to scan blocks_synced: %w", err) - } - - ci, err := cid.Parse(c) - if err != nil { - return nil, xerrors.Errorf("Failed to parse blocks_synced: %w", err) - } - - out[ci] = struct{}{} - } - return out, nil -} - -func (s *Syncer) mostRecentlySyncedBlockHeight() (cid.Cid, int64, error) { - rw := s.db.QueryRow(` -select blocks_synced.cid, b.height -from blocks_synced -left join blocks b on blocks_synced.cid = b.cid -where processed_at is not null -order by height desc -limit 1 -`) - - var c string - var h int64 - if err := rw.Scan(&c, &h); err != nil { - if err == sql.ErrNoRows { - return cid.Undef, 0, nil - } - return cid.Undef, -1, err - } - - ci, err := cid.Parse(c) - if err != nil { - return cid.Undef, -1, err - } - - return ci, h, nil -} - -func (s *Syncer) storeCirculatingSupply(ctx context.Context, tipset *types.TipSet) error { - supply, err := s.node.StateVMCirculatingSupplyInternal(ctx, tipset.Key()) - if err != nil { - return err - } - - ceInsert := `insert into chain_economics (parent_state_root, circulating_fil, vested_fil, mined_fil, burnt_fil, locked_fil) ` + - `values ('%s', '%s', '%s', '%s', '%s', '%s') on conflict on constraint chain_economics_pk do ` + - `update set (circulating_fil, vested_fil, mined_fil, burnt_fil, locked_fil) = ('%[2]s', '%[3]s', '%[4]s', '%[5]s', '%[6]s') ` + - `where chain_economics.parent_state_root = '%[1]s';` - - if _, err := s.db.Exec(fmt.Sprintf(ceInsert, - tipset.ParentState().String(), - supply.FilCirculating.String(), - supply.FilVested.String(), - supply.FilMined.String(), - supply.FilBurnt.String(), - supply.FilLocked.String(), - )); err != nil { - return xerrors.Errorf("insert circulating supply for tipset (%s): %w", tipset.Key().String(), err) - } - - return nil -} - -func (s *Syncer) storeHeaders(bhs map[cid.Cid]*types.BlockHeader, sync bool, timestamp time.Time) error { - s.headerLk.Lock() - defer s.headerLk.Unlock() - if len(bhs) == 0 { - return nil - } - log.Debugw("Storing Headers", "count", len(bhs)) - - tx, err := s.db.Begin() - if err != nil { - return xerrors.Errorf("begin: %w", err) - } - - if _, err := tx.Exec(` - -create temp table bc (like block_cids excluding constraints) on commit drop; -create temp table de (like drand_entries excluding constraints) on commit drop; -create temp table bde (like block_drand_entries excluding constraints) on commit drop; -create temp table tbp (like block_parents excluding constraints) on commit drop; -create temp table bs (like blocks_synced excluding constraints) on commit drop; -create temp table b (like blocks excluding constraints) on commit drop; - - -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - { - stmt, err := tx.Prepare(`copy bc (cid) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - if _, err := stmt.Exec(bh.Cid().String()); err != nil { - log.Error(err) - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_cids select * from bc on conflict do nothing `); err != nil { - return xerrors.Errorf("drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy de (round, data) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, ent := range bh.BeaconEntries { - if _, err := stmt.Exec(ent.Round, ent.Data); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into drand_entries select * from de on conflict do nothing `); err != nil { - return xerrors.Errorf("drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy bde (round, block) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, ent := range bh.BeaconEntries { - if _, err := stmt.Exec(ent.Round, bh.Cid().String()); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_drand_entries select * from bde on conflict do nothing `); err != nil { - return xerrors.Errorf("block drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy tbp (block, parent) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, parent := range bh.Parents { - if _, err := stmt.Exec(bh.Cid().String(), parent.String()); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_parents select * from tbp on conflict do nothing `); err != nil { - return xerrors.Errorf("parent put: %w", err) - } - } - - if sync { - - stmt, err := tx.Prepare(`copy bs (cid, synced_at) from stdin `) - if err != nil { - return err - } - - for _, bh := range bhs { - if _, err := stmt.Exec(bh.Cid().String(), timestamp.Unix()); err != nil { - log.Error(err) - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into blocks_synced select * from bs on conflict do nothing `); err != nil { - return xerrors.Errorf("syncd put: %w", err) - } - } - - stmt2, err := tx.Prepare(`copy b (cid, parentWeight, parentStateRoot, height, miner, "timestamp", ticket, election_proof, win_count, parent_base_fee, forksig) from stdin`) - if err != nil { - return err - } - - for _, bh := range bhs { - var eproof, winCount interface{} - if bh.ElectionProof != nil { - eproof = bh.ElectionProof.VRFProof - winCount = bh.ElectionProof.WinCount - } - - if bh.Ticket == nil { - log.Warnf("got a block with nil ticket") - - bh.Ticket = &types.Ticket{ - VRFProof: []byte{}, - } - } - - if _, err := stmt2.Exec( - bh.Cid().String(), - bh.ParentWeight.String(), - bh.ParentStateRoot.String(), - bh.Height, - bh.Miner.String(), - bh.Timestamp, - bh.Ticket.VRFProof, - eproof, - winCount, - bh.ParentBaseFee.String(), - bh.ForkSignaling); err != nil { - log.Error(err) - } - } - - if err := stmt2.Close(); err != nil { - return xerrors.Errorf("s2 close: %w", err) - } - - if _, err := tx.Exec(`insert into blocks select * from b on conflict do nothing `); err != nil { - return xerrors.Errorf("blk put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/util/api.go b/cmd/lotus-chainwatch/util/api.go deleted file mode 100644 index f8f22cbbf..000000000 --- a/cmd/lotus-chainwatch/util/api.go +++ /dev/null @@ -1,34 +0,0 @@ -package util - -import ( - "context" - "net/http" - - "github.com/filecoin-project/go-jsonrpc" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v0api" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" -) - -func GetFullNodeAPIUsingCredentials(ctx context.Context, listenAddr, token string) (v0api.FullNode, jsonrpc.ClientCloser, error) { - parsedAddr, err := ma.NewMultiaddr(listenAddr) - if err != nil { - return nil, nil, err - } - - _, addr, err := manet.DialArgs(parsedAddr) - if err != nil { - return nil, nil, err - } - - return client.NewFullNodeRPCV0(ctx, apiURI(addr), apiHeaders(token)) -} -func apiURI(addr string) string { - return "ws://" + addr + "/rpc/v0" -} -func apiHeaders(token string) http.Header { - headers := http.Header{} - headers.Add("Authorization", "Bearer "+token) - return headers -} diff --git a/cmd/lotus-chainwatch/util/contextStore.go b/cmd/lotus-chainwatch/util/contextStore.go deleted file mode 100644 index c93f87f9b..000000000 --- a/cmd/lotus-chainwatch/util/contextStore.go +++ /dev/null @@ -1,51 +0,0 @@ -package util - -import ( - "bytes" - "context" - "fmt" - - "github.com/ipfs/go-cid" - cbg "github.com/whyrusleeping/cbor-gen" - - "github.com/filecoin-project/lotus/api/v0api" -) - -// TODO extract this to a common location in lotus and reuse the code - -// APIIpldStore is required for AMT and HAMT access. -type APIIpldStore struct { - ctx context.Context - api v0api.FullNode -} - -func NewAPIIpldStore(ctx context.Context, api v0api.FullNode) *APIIpldStore { - return &APIIpldStore{ - ctx: ctx, - api: api, - } -} - -func (ht *APIIpldStore) Context() context.Context { - return ht.ctx -} - -func (ht *APIIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error { - raw, err := ht.api.ChainReadObj(ctx, c) - if err != nil { - return err - } - - cu, ok := out.(cbg.CBORUnmarshaler) - if ok { - if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil { - return err - } - return nil - } - return fmt.Errorf("Object does not implement CBORUnmarshaler: %T", out) -} - -func (ht *APIIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { - return cid.Undef, fmt.Errorf("Put is not implemented on APIIpldStore") -} diff --git a/scripts/lotus-chainwatch.service b/scripts/lotus-chainwatch.service deleted file mode 100644 index e121cb1d1..000000000 --- a/scripts/lotus-chainwatch.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Chainwatch -After=lotus-daemon.service -Requires=lotus-daemon.service - -[Service] -Environment=GOLOG_FILE="/var/log/lotus/chainwatch.log" -Environment=GOLOG_LOG_FMT="json" -Environment=LOTUS_DB="" -Environment=LOTUS_PATH="%h/.lotus" -EnvironmentFile=-/etc/lotus/chainwatch.env -ExecStart=/usr/local/bin/lotus-chainwatch run - -[Install] -WantedBy=multi-user.target From 3b617f4528eaa8fe221cd0bc59ddea718d270144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 28 Jul 2021 17:59:37 +0200 Subject: [PATCH 94/98] mod tidy --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 5f968f6e0..a094cd6c3 100644 --- a/go.mod +++ b/go.mod @@ -100,7 +100,6 @@ require ( github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 github.com/kelseyhightower/envconfig v1.4.0 - github.com/lib/pq v1.7.0 github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-eventbus v0.2.1 github.com/libp2p/go-libp2p v0.14.2 diff --git a/go.sum b/go.sum index b22f3dc15..9757c15f3 100644 --- a/go.sum +++ b/go.sum @@ -835,8 +835,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= From 07f40b94882e1233d29b9c7868a5755c6fbec688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 28 Jul 2021 19:51:45 +0100 Subject: [PATCH 95/98] fix docs and nits. --- api/api_storage.go | 2 + api/api_subsystems.go | 63 ------------------ api/docgen/docgen.go | 10 ++- api/miner_subsystems.go | 79 +++++++++++++++++++++++ build/openrpc/miner.json.gz | Bin 9540 -> 9596 bytes cmd/lotus-miner/info.go | 4 +- documentation/en/api-v0-methods-miner.md | 12 +++- node/builder_miner.go | 2 +- node/impl/storminer.go | 10 +-- node/modules/storageminer.go | 11 ++-- 10 files changed, 113 insertions(+), 80 deletions(-) delete mode 100644 api/api_subsystems.go create mode 100644 api/miner_subsystems.go diff --git a/api/api_storage.go b/api/api_storage.go index d52032650..c39114929 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -166,6 +166,8 @@ type StorageMiner interface { MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write MarketPublishPendingDeals(ctx context.Context) error //perm:admin + // RuntimeSubsystems returns the subsystems that are enabled + // in this instance. RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin diff --git a/api/api_subsystems.go b/api/api_subsystems.go deleted file mode 100644 index 1894bbdd8..000000000 --- a/api/api_subsystems.go +++ /dev/null @@ -1,63 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" -) - -type MinerSubsystems []MinerSubsystem - -func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { - for _, v := range ms { - if v == entry { - return true - } - - } - return false -} - -type MinerSubsystem int - -const ( - MarketsSubsystem MinerSubsystem = iota - MiningSubsystem - SealingSubsystem - SectorStorageSubsystem -) - -func (ms MinerSubsystem) String() string { - return MinerSubsystemToString[ms] -} - -var MinerSubsystemToString = map[MinerSubsystem]string{ - MarketsSubsystem: "Markets", - MiningSubsystem: "Mining", - SealingSubsystem: "Sealing", - SectorStorageSubsystem: "SectorStorage", -} - -var MinerSubsystemToID = map[string]MinerSubsystem{ - "Markets": MarketsSubsystem, - "Mining": MiningSubsystem, - "Sealing": SealingSubsystem, - "SectorStorage": SectorStorageSubsystem, -} - -func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { - buffer := bytes.NewBufferString(`"`) - buffer.WriteString(MinerSubsystemToString[ms]) - buffer.WriteString(`"`) - return buffer.Bytes(), nil -} - -func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { - var j string - err := json.Unmarshal(b, &j) - if err != nil { - return err - } - // TODO: handle zero value - *ms = MinerSubsystemToID[j] - return nil -} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 1e712a0ae..f9addc940 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -16,10 +16,10 @@ import ( "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" - metrics "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" - protocol "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/multiformats/go-multiaddr" @@ -265,6 +265,12 @@ func init() { addExample(api.CheckStatusCode(0)) addExample(map[string]interface{}{"abc": 123}) + addExample(api.MinerSubsystems{ + api.SubsystemMining, + api.SubsystemSealing, + api.SubsystemSectorStorage, + api.SubsystemMarkets, + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/miner_subsystems.go b/api/miner_subsystems.go new file mode 100644 index 000000000..a77de7e3c --- /dev/null +++ b/api/miner_subsystems.go @@ -0,0 +1,79 @@ +package api + +import ( + "encoding/json" +) + +// MinerSubsystem represents a miner subsystem. Int and string values are not +// guaranteed to be stable over time is not +// guaranteed to be stable over time. +type MinerSubsystem int + +const ( + // SubsystemUnknown is a placeholder for the zero value. It should never + // be used. + SubsystemUnknown MinerSubsystem = iota + // SubsystemMarkets signifies the storage and retrieval + // deal-making subsystem. + SubsystemMarkets + // SubsystemMining signifies the mining subsystem. + SubsystemMining + // SubsystemSealing signifies the sealing subsystem. + SubsystemSealing + // SubsystemSectorStorage signifies the sector storage subsystem. + SubsystemSectorStorage +) + +var MinerSubsystemToString = map[MinerSubsystem]string{ + SubsystemUnknown: "Unknown", + SubsystemMarkets: "Markets", + SubsystemMining: "Mining", + SubsystemSealing: "Sealing", + SubsystemSectorStorage: "SectorStorage", +} + +var MinerSubsystemToID = map[string]MinerSubsystem{ + "Unknown": SubsystemUnknown, + "Markets": SubsystemMarkets, + "Mining": SubsystemMining, + "Sealing": SubsystemSealing, + "SectorStorage": SubsystemSectorStorage, +} + +func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { + return json.Marshal(MinerSubsystemToString[ms]) +} + +func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + s, ok := MinerSubsystemToID[j] + if !ok { + *ms = SubsystemUnknown + } else { + *ms = s + } + return nil +} + +type MinerSubsystems []MinerSubsystem + +func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { + for _, v := range ms { + if v == entry { + return true + } + } + return false +} + +func (ms MinerSubsystem) String() string { + s, ok := MinerSubsystemToString[ms] + if !ok { + return MinerSubsystemToString[SubsystemUnknown] + } + return s +} diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 69d471dbd5673f06dc006a4d21f0a193b36bbb30..57a56d94fffd25d9f55069cc5127c44ce67d0b69 100644 GIT binary patch literal 9596 zcmV-?C4<@@iwFP!00000|LlEhbK5r7@L$33{qQ6m+0o6i?3sRWhJ$ksz8cOJ5qE1Er^euLcsMb7 z1}00#IQ?o+Ol@SG8kY{;dEokL2F|W7jGl3aTn{^h_ZSQghX)qAGah@!2GO-+dB*9- zuLgk|*7Ys6k?CM^cxF<^J+mwqd7jZTEaaIk-ckYPufP6UIiJXKM@;a-13%5FTgStEY5do0Nk8`t7kN9IvM=MW zZVJE5x@O-ehW5-g+CT;m-NVh+Mr<5o*&hr?W8-s=b?0b6`^t5;$fcOISVG%FJ%c`M z5o_)&{)0?ve12s3{db35WErQ_-64j|)ihzyDt8n>O-sI49Hm{aGy&Y^_hvD_gX0-ciVLOXg8|YEw zzJgo)YU?`p5C2{va=3E--{t72KQSI3d-+s5bI1~}YSXR&Bd6d*6J7cht#hb2(XKKL zhj_O_$@y!Xep^h%UyUml&792!Q?|UWV;iSrXWKnP`tSz18%!WYspsbi+E=@UjlFgH z4ff!|M!$a{$bAcKdjZYcfbRWwe>m=K`sHI0TWw>HzNV4=YG8^s{EY^02CVrQK7g(Z zAJ}i%yqW_pU7D{!yM^P}2$ERK^g_v+Wbq-3{QAU6Z%H~tu44?qw$iA{Sn9vz(=kEWx^^m8;hzvcJMoWrS{IcIBR-d?%R z9h0<`STg}Z$(Rd#c#T$=;fJg%XHKtN$65Bkb_hOx_5@h}6JI0td<2+ye+gb};Pzwm z{lzEz>`@n!TpK8XRt_`gfG@?_>1ANkX47L+X_vp{OWPWgRA-H77Q@V_5+cx&RLtD^ z+zS-)#{!LG4G#8C+F3Bv<)4Jq%!Kg+>}EfwQvcSi^&EAR!=HGBVwT>gIZ zVgB#eXR}{FT>bm?+5FAFug-3M0f^jT*C87wE$^U<`KHH16i^3v&IW-kr0X7l#NuOp zB5W}BEQaKlH6p--w#`ONOcQ$?157xMj3Id6PoyCQRl+>;+k!i2?~oB=Hy=iIvG&`} zwk5)qKa6djLcwa=-#K$n26E;;N6_|W4)L&s+~1c=8xwSmsEg4ZwE4?QEME*(e(J%t z4|#@LFa4I7qv3fJA!=@f}Qk_ zGzj(sxkE1B`!q|ma<~l;t{iY1#H*Y_N5D@*zyn3dS>h}Gvla4{L2ZC|WnkMNT?P6b z;XV!F4i!;A{5xE1*ta}~6jmqG2!_gWXkzPdhAnt8^SaU*yXe(p^96~z;7ZhL*EFE`)EUaTo#mQS=+Y7k=CkaTi%oQe z+^}c4Q3LytldvS%To$i2r;wtOWOxZZ`eAD^$ysKOA_}(b%Gl6bYq!tNaQ~0GCu2oq z6PKrg;js1EcFbG5miowmQt6V=iY-ti8GyN3#fMs6rO5DZNxwwKY+AV#nz=f0G<#$M zXAHzWW6Fsl{)AKH9MP6esf!@1Hl8A4J%6iN1iO(9sKx|Z&pY{c;le-MZs#~Jk|RqC z&A1Em;Wo=6#XiH@43a{=V~_n|k7-`4NSqQHJ$Wwx+!TXoAHdh(5IOoCi|h}Joa9BO zWJsZT1)_7zU!6T#RUb0cJJ#46);R9x#lFM{q3LLXLeLGdNSUC5>TIH%LdPom!zzQk zsGAruG#j^20JtGG(GOp!&Lj%-I~Lg=78&Nn^~@1%(9={&D}y z>$mgaFWwvH-OauG{_^hE|D(&w5m3*xipdWr`^JhOEWx7h5Iys5YsfnRKz@AwlV43} z!&@HC@rs0I^_h2@cc>l*E|+8k+hvZ=Q`ok0XkIYi%qkbR*Qu1lTJkt7Y%(PJMif00 zfA$`eSlrQ|KN|Lo_wYXZOq5p&iqG|o8@xdtg_|vFG3XC^#+&;sW=Q!z=jgw`ImYRD z_}DYp9H3aauZF0dH0&8X*7(%;7qR<)&R?%h@{cw8-=BXr9!r+m^ru>)M}VqMd`@yT zw0b1gRI^95Kra3vOUG`BFQbE4>?`XuPf=Jo#H7Jnal$F_fdy$*Djm`@N*DLajm7$& zwZyR^#v>G);lWdFbrFZ8&>L>h#Fvb^$|4TBS*?*mx*hc)nRZrZ$Q$WZTNWXiuODcj z6h07-SBQ$##1(wlAVT>H+(Da(^~}QyV!8-=h|BzP5Wmzh$!5k4kcPIPyJ@P!bLeli2-{~5;S18dnR&JBR6)L{`AXN=C?4);f zLMmg67o)S-MzOpdbsqo9lKEUZtICL7Ix8tmKd+|>!m=G|`bvu5;70!CS{+xF__Xsh z_xX{hyHtCw7_>YC&dyv*sdoSKm!<lY-8hOaQ6P9^7Z1Q%V?-7L-r0@#TwY))i1CLanEBkc@QfDAN z4+ANn#7aUixRkR>-)poiN&P`QZtJvj4ZrZ1-x56>hn7bx@F;|$Sm7l|#4(h+T&Apr z8MB!6s1}uVpZK24i(*kNyB)1~9GM$3PIcbaNx31_>-_^**CRy&Kx8pCkbtZLrn0Tm zC;Nd&W}P^F>RH)3X~@2jT{vPzbNgvZglph0vAcEpv}=I}vY_n6hu=aL^o-fgbxTKV z_|~b3a3$+hcczzU1fe`3579F2DLpK7&U~tV0=W%ZCosupF2(5}viZbIez9x#rQ*iA?x|Ec zo64cko(Il_!sZQ!aP65TG|o+pxu<2&^dc22I&$@LtZTw3N#lF_vmUm{1o8cWCBKq|5-(E-I9lw$+!M?CcOC^xY# zT&HFfXcn4Oj~0YLOTaV}2QA0162Un7q(VME#L5@=77zAhQJ8N!rL zNH>99h8Cn0ZMM_{)B&z|-(|CR+3a05`;!R6X>Ok4ylyu8T$RnvMIKv6@9;HVT@cFU zTtsy4d%N?s=wv10sJcoj{5WTAamb*uW%?zS~K3D&%rB zJyFzY55;0bsqXW_RClgaWC`6mil8{xy@EzaA>4Z1w=SD~-?P~}k#jjZ>8n!bF~MZ@ zntlCBawhDqsG}ilm>ayEBbs}p5&~+bYDOHdc1mT$XO@*Jskb`fWQMKqtb~Wbg};75 zc(ta+Y4PF5KDjqf28yI=ZsBDFPW3~*^x)M_ zf=h-aS3iinMz(%*QHfmbJy8imMRs#27o#|{T1p`VQ%)^}-Lw=#*t%KO5XZe!4nMMT zP&0$7mDaX5Cfu6Q60tC$rws4u!bA5qG~#5WNM06F@)CP=hAl?))nX#DU}{|7n+4YR z)xA=DyK8&*`C8(AwP z?2c-T7Xu(?I=H}xfnk%&cQHsgJQoL7j_s0|%31CPZMFbzF{3#^kw-CEl?sZIF=8BL z=~->;rH;+**E<;W`~98~WFeoFG?t^H358~xKjZaXln)^%Jru|}K(t9@C0qDXV_B^M^p#(&pwBggMk)Eda2&h(p+q=lGh#eGh2mq0PLOwk{D}nl)6{6_eDR$w(a|MCMRHXjL~R5; zB(gP8Hk_!8MoBUPh%Qy8RxXaZ1$yE5M8Y128 zNjm+dp})q8q^-1&&T>f={WYwlzph-zSvH}%hE1!kVW+w})%6@z7iXDHYjs*{Keg78 zBFiz7&sQ#LX5$qBs;IkAQ`@fyRC8)9F}qHab(-uMYBJRA!#a(&?;35QNFA1l>bhI8 z2v8-BCM3}eTV6xztifGbVrrc_>(tq^Q)dzPXi`ad9^6>zr+K6BN)lc)c)5Ja_%b|6 zk)=*=cY0exZy)E@F7Qil*NhO1L1{mhBQ{twdO3-G7sJshq>JN=-TQ(X#K0nf{|8t~W4*`5gSkWM-7ZB`#F) zjL*-<`}90HZqQd`I{RyTws73+1rIvR76+hfE9IC(ad365p%iMZmx=C-pw0;DG`x^E z14SmT&y#a=jjVGFS9N4g2t*}!#GSY=FcS!jFTXzKDfu+iMhezTP*uyJ3dSq*3sz&FD1%k&LtirwG>AATU)%q6VRNya&(ke^}j*Ss`6SvoJl3o(kQDWA`y8EkS)@i^2{ zcDQ`f#m5_GmIe5w1AsaJ65wnR0Uif-2ria@IQ}nB++B@`vs2{t>8|z;PQu~@n|T1F-#p#+@aX~ z9TyP^_|+u;iCnzI$YMhWD>1V%BGfoFmi@tSG&Xw1mFwJLi`i+>vrD)@cG}O=Gv*Y! zR9wP)%5Tf<8E=S{eBg_*;0{OsrMRflA8F>;Hl)a9Sn(GEsnlct){eQaZux0S;rNd| z<5z^X(eVixnYZ@Nt5y;=14VT` z2;Vpg&lGR`8d7t8jdFe78Q{4WhNwoUV0I_xquME_k;9t}vM{8`egGaKmd9_>VDG_3 z9O4L*JFtl1YUQF8qZfWBf@pg9iCp_`bY?&Syo@?SkNgj1plNM?xq7)ISDNHBtq_@6 zMjrD6I+@me3$cwX@eO#>Eu`!dUM2b;{*-rY78Atpy;XVr9UHw__hr&WDHBM0b~G?n zBvi+F>;z2XVL}m^Em_vx~Gwp3gl1&vM2Z2mdxvRWz3V^*0N_jd!$Gr6(8;$qHpfE$fQh2 zL;HN^LZ39Ma<2lSoR2Bqpu?a^F&isj=P-0)fC~Jqo=qNStMFVQDo)*z{}wR(xK#lI z=N)yp^S(CsCT5=bp6R#kc;P1f@PCz^anF>kCWeeEOOJzTih)9a?Q%^V z?*dkdFbesDvxSI90R(-q9}awfw9h4<$QOtyzZZ|YuYrZ2?STbavL8%YvOT{S9D{Yt z^t_qrSdm~ONaQ_qZ{IrZhwaLRR<&4N8KAtgDbZehk=ziJEZF*?C{k2g4@pT+yEQsK zsY<@#Xi^BH${-Wd%nl7WvI2E*0W^Q@zqeaBB4R5QMxG}JcTKav?} z3729gP$Xmu{6JjXbUvWY2NZGeaG(t7W*_bvZJfJ0cC`S|m_04PHDgDIsO5!H#s+oy zfBe)4;gDIn&L#{?0~Zun4&oY`B6oucqRA_lE`Ec{t^1E>`b=-M59*%P3tmn*B^@wk8%Tu z!o5)q9t>eL#(*KjX1FhoU=iy@Lx+3K#MU&}bb=RbTS!(+-BAa?Xea3q-yyWbu18fw6=63!DSzz%1AowS8Js$T0!OXFc30?^28zvSuxv+#_v$H8S z+1rCHG;fg=j>1^?C3n6-@Se3=LvMYUlqkNpI^c5ucfnDQ>MSG7h@*uR)t#TO~&SPo8;O?{)h zJgWvc%lVUI0BxJ?NLQin$ZM3Fev=!m(k2>Jtp>wUZ!jEp1*?j9;UqUCiqpjfnR8D> zU7iTws5X}$KZiaOit2DnFwm25O>9=`9VzFan73yOU5d?}4c*Yo)6jM?v4|CK6-e7_ z6-mBh`De-Uqke8kl6Q@kwOF0?m#e=G+#*A32fdq?d+KfUTyk z>(=7XttTPNf-R0f{GNGxm>aC*F|Dwx#oYpeu78+UrSi+e`Jb399Sx2b!xYw>;$^KW z@Oe0k{HopW5EUL{fsL~S5UlWBSZoR`9>B%<59I32t?u;g%GyQ5peWo?qPkNgiB5R+ z6UqkQ#bvlVXXb1+=Rw^k_TxJEA#Nr-HU35H{-5*LYm@wAjsExNpAAv1#S7ZqVDnap z#eec*Oa9|jZKF97)l%}>gkh~b#ydhO@!%R!mkZ?auxH#jK_Za9zL(IWZ(KK90P4~S zA&C2D_~fQNLvLSobam|JilUM$#XE2%~o&bH!d`D>*Om%4&(DbEnnM@r0+FHJB zf1(_lWSujrqQ*cu?b(!Lnom}OOs1a7NtJ!uNsMyCq`kRsO4Zp)2oP#tLCll&8550y zwE@TiUqK3Pr42-gyL#u~{{omS9kzF9%55&0g4BzUZKGA))Hiic2g81WQ=kFX-8Zfo z=Y}tPb+66Jg2}rJBD$83yV?8#(IQ1Ps3!dSfuajyp?h90T2)$l?C>1hS!w^&De+#gMwD5ae(T>V{E1RpD0UU9MVUEhR#)Wv``7z5S?mR^oW zN4d!t-iNJKFXr2aP|Bm9IHxaE{2YE$IzK@6Nv_-c&K>e)BLPC&zhl@M3_DS!X$TfP zv#d+UgxNLGV%a1NweinBS{`dA;nZf%3|dcc37jhqWHiV+akSCVb`9D-R%JQ(1VAY? zE2cNZA>?gpkr#|6j*_=Su=I8m+lO!Yv91Qy^D&yD`2gX3g%n`!pGI2fzOhr|?4p#| zh#JR}<=|vGT^yk0sDCiFj?uwlgv^5zI2j%<#|wA_r$*2CaD5pbU5SGkg8$p)6_5J&qbC?*mI-6cG>zK1T12KKX68Vn5$u2j+ z3A&d)!!0y1ec%V)XLk_WaKSJ?=^1b5$=}z=L+%~J<%BOL|L+HnKPY=J8v)ijmkp8B zX`R4Z>;7pr7oCRD3TV-fr`NK(0<(rrnyNB#1gF179zC;d$E>54LLkLg?+Ovmu()~H zifj3zZG`Vm9mU#TLAt)-8|fDxKg$$c&Y?#Nx%@Ja8PMg@`P zE8DRo8MKD{XgoSPIUY?%lW8lS$1Le;TH$_)h$ani8ClCJb5=8v>f~nqc*e_~MaiG2 zlElO}I7g-pT_pIqj%rB&D%kj3oTu~&e=y3^K4U-pwrEq7K%=3(QF?0WBf8d=246Bg z>7=y}s+`QMrxvuYvR51)mb1X0EPJ#;jq@Gp|Lj@Uw~LSd(e?!6zACHH7fi0RZ9sB| zP#PB78qzu(x+zf|pUaaP_MMFGWb~8D=uSXhj>iL44yX_ZZ!xjz3&;pc@#(TJBXca- zQPz(V7`HL^hJWN)#(xr_spUXPk5I*pl31B1HH$k*rXMNx+$C9IfY^8EZF){dvzp^` zJWP_N-rSRO#6{#8pD!vi^m8k(b~_rDF!xEJr~$bH3ou;0eM< zp3lxhCVVv9+TFST-nh=cXOT8m<%x*}uUYrCl#%JTtp$a}nrmZ2(3aYB*0Iwp} za?7hVQddTbs-n1zs;!Rj(2^UFYp9*u0m_Ydj zL7Ub}8i>QuJBsyxi7ujhv4D&Z(~zJ{Vg4jZZO_T|Q7E%gA4#`{8--MO5}FBZ?-`OB zX(e@qs@_z9YZp6TGj_gqC7w=XUyjGq{0uKYucWf5X8mD=#v`->*Pd4i(`58es?nB-Ex(}l5E&Gdwd4x5X3*gu#I7S_S|2p%0Q z%>MKMF8fEzfjOL@BP%L*T>$Y0dbdnkAZWk3>%U?=#Y=y`V)n=Nj{E^JHBS57SLIUU z)EM@M{e!{aU@*MtpPUYdr~TtUjh^v~<57Rv*N#VxR?hXKK_t$!(iJd8hh~dWZ{Yof z063WRj14BSU+K7^==(eyb2aNniv>G`)SzE${drCoM99WDg|v=-QU&H`CT|(m^U{xL{UKpyH$`7pOQ@ zpXOBDSAz$rCgitJ5jup0_9gGBG!kg zb(KnhyevL8L{=I+>wlrflyUmI$_7omF0&;3)9Eb8JHMrVFmHm^wEVYmQq$}pNVX}x m94AUm1VWmA$}_g-s?^4P(KGIl>tToR9)rQ*@W4WM#$(UeAi8!e&p7?~ z)gW-gy1vCWG963~&rIsLXO`t6&og?4g*?;6TPnc(_19l3=M!1(hzVYJ;HNotT)0B- zF+uK}x;vA;1aSZ_wtZkagra+TI1_)q1TUa?dFVIl0pz-l`x1QpL_Sfx1m58Vp~xlB zPGAFm{uvN#i)Npwi|EcJ;3tBx9~ z@e}#$ufL3*;q5jX=sp;yhS4*&(1jZ@$(_rVZ1Hfgbv(?M#(&M0^mET}k+-uc`!fFO zrtr(GYxaF&XwO`u4P@}pJ=|<<#Ktj}{lRcFHa_=Qca8?MuUuz~T#8wXCA2-%Gw8z> zvF6U=KgguU=SPO$e|OkLmT^km9b(9AU1UPap736el4m^w-HRnVcTA5VlbpjlOxL>w zaM8x02-uk18a-p@+QzA|rgZC_zIwIdO%6?G^J?kS+rid#7+$YzM!T;TwzGJ(fgVNf zE4an4wytyk@b48Ohb!m*U5<|W6XWr*mru1bhb-}`Hth;9atcl~(WPI}I){oA?JCo7 zh<7WLoWI8Dx5ZTa)wpue%-L)(Wy|Y2wsA^!w%s$N4{wmW!30v2dVY?eeYIQI*juOH zU=J>A^!pcr+_%uS7tp*7=-z+#hvVL+Up^MG)i(C%YZ}?F2Bv7k-)Qh=z?zTY1L(T& zf&G@vt2yA(rTH4PTR4u5Ac?h1FO;ll_H#CdZ}#Ii1p0B$q~zc@M@r5S|3Cg4RTq&F z42gW~f$adKH8OYs7x*yhS12^2rg?fl0kgcwOw5DfU0Y^`i@qH^e=RRHRT4{@okRMy zVhVvtN_LwCax-9l5ob=HVxG0coAAp$K)#mueG zy+9#TC@L!;x`nn2`Jr1<>U90vACG*p7K?Fa)=d zGOoW$5Xa$9G1Jr^aPMrq(TPHhIGgQ`qCoOg&>}GaDkbvq^J0f>ii!VmFUZp28iajs zCit!)v78MNmS5C5rfzcdMGS!)bs>RTPE91|8EA=CHH2*4S{JT!#2YzFJ$cERBWor^ za?qTAd_uv6g$Tt<9Pb)%80#8w`nQvpS(TQh%4&;VMPFwWbj1nEq_Knra!5|YAZdu= zqLH*HjyHY%Kt<;bvM@(xfeCaU-VoEV7=4`@3+SQAIJSMylkHXM=$VT9qrO7iy9iSB z8k)DeZ8dQp1E3Q3z7Z~Dwp3)tfXGF53Avm^NWCjh3~KTp9w3f|z(PE8h=p80*9dr9 zWHPB`0k-^rE#e`#S))%x9EAX%Fg!rlkn%44vm9*LQZbHpcZ6WF0$(s)v-h9Kg?thfXE$o9kOB4@(#L~Z+bjL0d;`qY!KK&y6yo;EI!sJ z!Uki{Vn}{jBLYll+ib+dG_l7qz=Y$-7=rixL>f|1CCoFwEx3dB4jD0a^I=pMYrpMm zTOwTf!`S926s)%Woiq1jAZPA#1Z{8T5D#0({e8K#F+taex)|L-o4>5Y^2K1~rygwk zkY~6R!X6J#p^+X~&v(eDWNX$bdZtWCHL3)>9L#)!6cQ7c2eBiJV)*h&9L zgJ3_9JLK}cPqRcThuZ+*$^o}Qyviwb1pG7vJWzz3CBD)>TOnT=)CP!G2DS~-RiNJy z?$Z$NP!R>hzr)3beamx5VRbT%V5l62CbkY|*y2Y+g-*m2%({NWEQ(KZ#N$u7_|RW^ zq>_X+!xnHEZFz`L4?y4{dwC$*EHGJ#H77KPwn7%bA+$qqK>_x_(s4J)0*eO#!9PVn zcn?4@b1Y@9xu7)5`y=v)f#&1!(>dt zSK}4;)Gr@~i@%|X;OL8IttSs7IGWJK$h$?G?SGv6|892=&Sv|c#b${fmZR|!k9H4} z-42b1cVpwRsy8wB6sYrUehVoyQiMBk_PoR%tuB99Z@D`B`1w6_ZxLn93e_S5lY7n_ z!76pY9LKR0ofpSUa_*OS+46y(>uenl+9jiSW;RBImXF>Q{br7BLyBCDVKWfk5bMp> zG1qzrl$UUUY)MQ>&$z_W${{b3Mnc{hL_NzAGQ{goMq-HqF~Fej19fDcKhrM_y4GSGFC)3 zad|2j4qLBn$Go*`sgDdOl`aXb*aAh80hp^*e5mDBiVW|T^h;#Srj<*fnX3~=vqvUy z#z5RNrkp6^PdG)+5pC&|x(Kpr<0&H6^S7Esup8NcYD}Q@ypwMiF8ssoc8>ERIkL3S zjJq%&ZnG>>>@%#*ASvWK_ShfxnC8Wb#3`ZCllKC^O)-e}0elS(k)z+S$o{a%NnT`1 zh7_7tAUenV)!Cy}^&vyOV~xFGjpKe^>`ROgnvNzY1leSl6Fl||tAMGv-GUTSzs+IL_~M)g zOl=Wz5Bc9mMJiG?Y+ZSfGo9k9DoUhG{)q7en?4BUALdqq6~03xdVX=vnZu!L@vPI+b!*OCE=XO@>6@h@xlW z&)#Dai#rdaIDVptWpogWePx~IDGDoxm^64RPBEd3wu~^@; zmN-_#c!XjzJb0?DF5-|BdczHx_>xgqS;Rp%t2I(cx1&BJ)6VJ)c_Y1Q%OWK6^#cu* z!Uy8<3Q>`oxPlKGL?~Z@J7_bpo_TmdOcy~9ahYFEf@i>M1;DC?*NdKw&$_GxO)OAF zDYqvg;zV(PFL=bywn5OYoLD3S)JS%b3~)2s;|#K0o>7-))a4mHtz??^6>$@SM=!BQ z<36Zrhg=F+s<`oZquCk~f^w6Q6wq^n$R$IR3Mg04sHKQ<3BrsJ6$993MIP6%574(LdBOKq^g02o%F6w zNM&sCVssYUD3-UQ&f{NMGM`IlRT;5MXC;N{=k-)UShhn=UrF&B+{nLNtK+H?pLU++ zK0oqwmuk-ygO+E&*_mr8)$V`((sZB*Q;Nyj%3&5M81=GWQ^4BDfz9IEWx{ruu#%o# zCTy1pTi^S-XPRWFh~kT+>IE?!Cbnpe&^;wIm3B#I|gk zVIT#RSV;&5mvUC=dySSQsXvIvZJl)uOWQ6W^10Q7o!ux1$x0BXdK>sm|LvDL15gy?+4fdZb7Ih%Cki5|CBERJL{c zWIqtetP`hCJu6!$4cRxc3rDPIZa+jC@LR}&o-y0GZs~{( z-#RrBu4J9+&h!$kp!sm+I@B>8g`u2h>0ZGH+kw?CJo^TbEcL8Ne&JlntrnMk6^~`g zYgrH6&inNQVEAh$v51Sa{+r8pf#HlKLOFLn*TRNPqCJ(VhF zQ#lmc^T4@K*u3Eou06Aa#<{6cSBS7rU)KhTgOOW5u@>S&(IQJH4l>8aNx`jP&!li~ z#ga*3nOW)Vd!2YsUwSiBCET=LAs0Iq->4g>M!(-59Ps~c`u$V>-=9XRt#LZ(hyRp} z_N=8pzEdw@=j7^~T+hMDrDe_}8C`4fC357ev2@%Gq#~;l9Z6IrbMHS!n}Hk7?Cf%YWn>ymMsAx!y% zbQ9QRXhBNRW=lOl9pH-hT{e4{&E93RKZ!7$=H@BR>t?ghRoU!ZOL<_b>~V&me8%E2#RyvD`$2JRJ)6A~IhUi8zAAMd6HHdG z+1IZmXTt7^IvT=;xxw2xqPa&ZA)sceX2kJor&LCKW?89{daEN&X4nePN_ZGt`0E#h zS8HmV79W1>lY8@Iph&9b7LNB0(b>hh^sq{Ya??Nx$~-Vy-tsQganYgQDUYJl12jU{ z60BR-kX@~ZZ&>R=DtfYnm=!?MHd94_@sg zxMWyz^@GT3Wa~#4mB`iJ6O}MjWH*O$F^V&*r4&Li<EIB!mSxC5epM~%J7~pJalhEBThz&m3aG{eI5~vXDj(dw-0ivL9bmIQonL}UOZKTiF64hb}vcOj*qfO{gs`{x{m7xRy`XZCKFV;M-h&NjMIKB zgF0EVy6hnW2d8yMiZWxn3qq)&TaOia1VR_QMU=-6R#mLLe<`h6Nr&=bno*zu#!phR zztfPNhAh&MoviL;wJ)nr6lo5=}%nUy*YlMA&=h4*5d5hA@voXCplnBRcMvTX&P<$-W3Gz;mKan7Rni>tAFTT?yI=W=2NUjQmsEwe9 zM7Ac%h7*<1C<#dI?+a1`HlEuL!`Ss zNvFRw^w(ICw3QapSuUxfzlN3c*Oluy%O+IUuxZsb>{M5$x}Kxz;w;l?txjw0r`9@B zWI0Ci`N~DjY`h{s6?GSCYWo#|YEF$MX4h%5PLn-DO@_LCSf|nUU87ADslyUcU3V)M z0ji|Yge00_%WFuTHMlEFOs!LAojQAV>MY_OO)3e`gBvUTG;b7MNy3W;FPBdlUxp_s zvefDAPH$`I?c?0q1%B!6nh}CADDB5`#0G0dFDJ3@VmLa5{45l5C&N1#?#uA0Do1`t zDBhs?Zs9$6oOLS6@H7;80VmNXY?N2SDFZR5kW&h5saj4l%v-6UmO?VK%-Q$fn-}PL zQvRT?$T((JIwq^Lh2v%~TF@c-H~?LnCC4<0gR5(mq)=AVCl#3-V+dp?h5u*orr<4{Z4!}3WN zA2Xa;7U0GI0d)W*z}X@KJPzy-Tr2@`{9m59LmClhw^{>l3rz%|i+G3LCxS4)LK*^s zzW6=B7i`;Lfq>~EMuR=zxPY#)2TW+&{#)BIZvnIAZLo1%^oazcnUKW#9yTU1q@TRS zyD^93RxYNF(`OShroQ7j!63SKM~GYcXV?nvHVZCcI2wx_8R@TD#vkQ>iL&=hW*4h0glNsQ?gtUHknx4pxeF>rs(Kf51|(W`sxp!* z3nNZ-S{I*4IiT#QN#+e#Kd|=rSE9tV@ch^7rtR`c8XdosSfW;C^rbk6?Lg@4RZISu;>n*L3iWqwq}e z#;+kY*Vic5_nZNqdvS+qgbHSNaz3h^avC|j*&qu;itGpAA!2#_Mho^HY{X%TFu4PZ z7_L?>S}}UzcN2)Fho6Wz%Jcv;pa5P*ouNnmhceK#_5}v&<&s=2lGC(;Z@i2=<_Bdm zt@{>Y8(HET@TOZx*(bb8^gsM5@AxVvh~Imw^7=bAdb94!q>EA}koN3oV5~@Nj`P?R zA{JZtx>CxAqmom)cbiB+=1Ql^mz>CPFP2FGQm!veAh5AVfwKf#|BWx30&zqgwm)am z1z-Z`8UcdtX(XirIn;pc$-O=$^SWIb^JKTC>>1A9_58;U@j?pUTd-XUbL+MYdT;0Q|!v8`klq$H6qkKq0_(xh9Tx0joq9 zh5W(Ug03MIj&1wl!1qV{T=I#0ftd39>A3qESP0r4SfC~Q!IUN2^LxQDSjSAyo0*Ok zi7tXf-b45Ht>b>!u3TtUio0CyG$$6ob@v{G>rO$$jC;ac*=Kj}Y`zYU=Q03`}Fza2#kuY?(x}kS*f~1la-5wvh!K z5}K%(fH#{heE^ON{(zXCb1vjbN8Tqw9Y9^YS|K;gOhVALA3DSJ$6~{y+yJ6*Z&ZT^ zLl})QU#Cp-t;a)SbH4QeM;04v{`WL!iNDZ z%SE1tEPz93-1UaJoxedASUdm-{)t?V$Gt!>b1YG|x_FxOm zTV#c!FxGv^oo^7lXRX%ITOTGRitnusxZFQp`o8)6I5%`DB<1?&IcZSMoadyWwt1c- znSquVC_c{#JVgkN8s(heSY zN42^9_&M~MP*jIof`OidYhtrf??^cZ#k@UR=u&L%Z0Lqwo`$xIiAAhf*DYoZT52#g1DV6ja=29(+Q+?>nv8NR zGb^oZrjv7iGVS;KvF!7Ub6(~yo`F7|9vx5mJtHU|!hb(f^Y$<|Sjl5rVONX01q5CHFt1AGmxuE|F^qJjMbWX9*x!;k&Td6j(ffi}N4I)tg)0>D!gHi-jhFWcp-MOZW0mb1U&pcqhkSBuH?}|gY&y#xry!m7DWWO6 z03p>&@+COoS2K8i#l({5U?|JXGjXs%9)+8&-pp@YXy(?dSDHEzTj>?zLN%z<7Z+a* zew4%hvcLqo4{wO+aKHGev49?$jALj=Y3Z@QvxoLIB$l%w$n$z&djx_I zhr&QGufc1uq^l6G8qF|f=(T%l!5FYEu=H{~I?7GH z@IGv;#5sMT;^*+2()j_hPjc<$ckYlc8wn8F{vE^CVAzQ&O+&EYnPpu% zCd{sh7Rx4KsEvR2(ehX`38yx5X3%EZw_NBx7bb&L)cBV-<&z{&7;X_ocH3Cmq+fjeEK_hfhaM^9^2Hfi@#yH}cr+bNrmc7$v!ttOh5IEUnl!*=WG$=ASKY#swDxaVB>Rfp3*1$!6;ArjQ#N2qD@f(jfVC{>8YiU=vr4Ae982rlh!_{ zax$}?TF}1AUU7I>&H{h3?9m1_&Ud8$vu9o3ECLMcQU$@(N8L)I{|q)9uHJGph6tH#l)&FAR{Qnr^~*K%&}xgSwBi( z+{WA+{*h-H|4D?VmIEa{LKQbkVr8DxEbb(kex%rQmt=(jV&9#&={XtAYL3tGFiDzz zZ+@mL7Ylr4eLAO7EwwvRrDx|5R|$p7`WNm+UV;~uju}|990l3T`GP-#CkP*TK06PY z@X>Hakiu76NY_RIi>k^I>|#*!kL(csh}NIUY~*Grat~lFFi*_1E!GClXmSHhrvF&YmxsAy#3t=i~ju z`=!$fMqT>RK8esh*{q-BX9V524|VtJK8Uur>@OCMqvOo>(UA*4@m zcl-EA3HmW9-y%@unTp(_z9L)Z568Vl?#v;CF+dfGn}ATc;6(yT-OYO%CAC$U9F`*6 z7FUoG$3lQQY%b!B1HSO49=N`mHCP&;E0Xc_yf9EC9|&UjhqI`50YlVXeO$KNVe9b2 zhwyAy$k?!Fo+%N5FE~3Z7^BqgDlw_tCLV&~j>>oWSA1WI8!I7!Rf= z2h-&e9-J)EvcH`6CkUZfQAj`JT2&r$(C_;(F2?bsKOHP!|6np$SO?=HcyzEZ`_lus z>>n)$=5T_Jtf<^|0mK{V-7;x`p#AEu|BCSxFa7}L*ij$&TpyE`0np1IK z4IZGHkl#W{^o)xYaa?qbJZI;cF}VGAPNe{wP&_t>lX z7q~S}2gB)fe9|9Gr_(-v9J$MegYofrI+~29J!5wD!81-L>h`7QN>c@wmz<-d)Snq~( Date: Wed, 28 Jul 2021 14:46:13 -0700 Subject: [PATCH 96/98] feat(deps): update to branches with improved logging update sub repos with improved logging around data transfer processing --- go.mod | 8 ++++---- go.sum | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index b45284c8f..11d24b0f6 100644 --- a/go.mod +++ b/go.mod @@ -33,16 +33,16 @@ require ( github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 - github.com/filecoin-project/go-data-transfer v1.7.0 + github.com/filecoin-project/go-data-transfer v1.7.1 github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 - github.com/filecoin-project/go-fil-markets v1.6.0 + github.com/filecoin-project/go-fil-markets v1.6.1 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-20210723183308-812a16dc01b1 github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498 github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 - github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe + github.com/filecoin-project/go-statemachine v1.0.0 github.com/filecoin-project/go-statestore v0.1.1 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/specs-actors v0.9.14 @@ -78,7 +78,7 @@ require ( github.com/ipfs/go-ds-pebble v0.0.2-0.20200921225637-ce220f8ac459 github.com/ipfs/go-filestore v1.0.0 github.com/ipfs/go-fs-lock v0.0.6 - github.com/ipfs/go-graphsync v0.6.5 + github.com/ipfs/go-graphsync v0.6.6 github.com/ipfs/go-ipfs-blockstore v1.0.3 github.com/ipfs/go-ipfs-chunker v0.0.5 github.com/ipfs/go-ipfs-ds-help v1.0.0 diff --git a/go.sum b/go.sum index 88a31e57f..ca71b27d2 100644 --- a/go.sum +++ b/go.sum @@ -276,8 +276,9 @@ github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7/ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= -github.com/filecoin-project/go-data-transfer v1.7.0 h1:mFRn+UuTdPROmhplLSekzd4rAs9ug8ubtSY4nw9wYkU= github.com/filecoin-project/go-data-transfer v1.7.0/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= +github.com/filecoin-project/go-data-transfer v1.7.1 h1:Co4bTenvCc3WnOhQWyXRt59FLZvxwH8UeF0ZCOc1ik0= +github.com/filecoin-project/go-data-transfer v1.7.1/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= @@ -287,8 +288,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+ github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= -github.com/filecoin-project/go-fil-markets v1.6.0 h1:+1usyX7rXz6Ey6hbHd/Fhx616ZvGCI94rW7wneMcptU= -github.com/filecoin-project/go-fil-markets v1.6.0/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= +github.com/filecoin-project/go-fil-markets v1.6.1 h1:8xdFyWrELfOzwcGa229bLu/olD+1l4sEWFIsZR7oz5U= +github.com/filecoin-project/go-fil-markets v1.6.1/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= 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= @@ -313,8 +314,9 @@ github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psS github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 h1:veGrNABg/9I7prngrowkhwbvW5d5JN55MNKmbsr5FqA= github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124/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-statemachine v1.0.0 h1:b8FpFewPSklyAIUqH0oHt4nvKf03bU7asop1bJpjAtQ= +github.com/filecoin-project/go-statemachine v1.0.0/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-statestore v0.1.1 h1:ufMFq00VqnT2CAuDpcGnwLnCX1I/c3OROw/kXVNSTZk= github.com/filecoin-project/go-statestore v0.1.1/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= @@ -640,8 +642,8 @@ github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CE github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= github.com/ipfs/go-graphsync v0.6.4/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= -github.com/ipfs/go-graphsync v0.6.5 h1:YAJl6Yit23PQcaawzb1rPK9PSnbbq2jjMRPpRpJ0Y5U= -github.com/ipfs/go-graphsync v0.6.5/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.6 h1:In7jjzvSXlrAUz4OjN41lxYf/dzkf1bVeVxLpwKMRo8= +github.com/ipfs/go-graphsync v0.6.6/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= From d3742048f9cff5565f34f9725c7fbd27ca078da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 28 Jul 2021 23:46:21 +0100 Subject: [PATCH 97/98] address nits. --- node/builder_miner.go | 2 +- node/modules/storageminer.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node/builder_miner.go b/node/builder_miner.go index acb9d3d43..3be055de7 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,7 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(new(api.MinerSubsystems), modules.PopulateEnabledMinerSubsystems(cfg.Subsystems)), + Override(new(api.MinerSubsystems), modules.ExtractEnabledMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 26792ca19..5497eab58 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1008,7 +1008,7 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } -func PopulateEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { +func ExtractEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { if cfg.EnableMining { res = append(res, api.SubsystemMining) } @@ -1021,5 +1021,5 @@ func PopulateEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.Mi if cfg.EnableMarkets { res = append(res, api.SubsystemMarkets) } - return + return res } From 256d12773be0adc008a0aa4642c77d141126cecc Mon Sep 17 00:00:00 2001 From: frrist Date: Wed, 28 Jul 2021 17:50:22 -0700 Subject: [PATCH 98/98] polish(errors): better state tree errors --- chain/state/statetree.go | 2 +- chain/stmgr/stmgr.go | 6 +++--- chain/store/messages.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 8705aeff8..8140cd4db 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -273,7 +273,7 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { } if err != nil { log.Errorf("failed to load state tree: %s", err) - return nil, xerrors.Errorf("failed to load state tree: %w", err) + return nil, xerrors.Errorf("failed to load state tree %s: %w", c, err) } s := &StateTree{ diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index b76dd0faf..1748c341e 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -221,7 +221,7 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad // First try to resolve the actor in the parent state, so we don't have to compute anything. tree, err := state.LoadStateTree(cst, ts.ParentState()) if err != nil { - return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + return address.Undef, xerrors.Errorf("failed to load parent state tree at tipset %s: %w", ts.Parents(), err) } resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) @@ -232,12 +232,12 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad // If that fails, compute the tip-set and try again. st, _, err := sm.TipSetState(ctx, ts) if err != nil { - return address.Undef, xerrors.Errorf("resolve address failed to get tipset state: %w", err) + return address.Undef, xerrors.Errorf("resolve address failed to get tipset %s state: %w", ts, err) } tree, err = state.LoadStateTree(cst, st) if err != nil { - return address.Undef, xerrors.Errorf("failed to load state tree") + return address.Undef, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) } return vm.ResolveToKeyAddr(tree, cst, addr) diff --git a/chain/store/messages.go b/chain/store/messages.go index 50cf0e6a2..9f5160559 100644 --- a/chain/store/messages.go +++ b/chain/store/messages.go @@ -110,7 +110,7 @@ func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, err 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") + return nil, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) } selectMsg := func(m *types.Message) (bool, error) {