From 2fec0c2440f2d6e6ceba5c0fae6702d9e1153f35 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 5 Jan 2021 17:53:27 +0800 Subject: [PATCH 001/122] add produced blocks info --- chain/actors/builtin/reward/reward.go | 2 +- cmd/lotus-miner/info.go | 61 +++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go index ebec85517..25ab3ef6b 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -123,7 +123,7 @@ type State interface { cbor.Marshaler ThisEpochBaselinePower() (abi.StoragePower, error) - ThisEpochReward() (abi.StoragePower, error) + ThisEpochReward() (abi.TokenAmount, error) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) EffectiveBaselinePower() (abi.StoragePower, error) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index f37952057..9d1fa0300 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -18,18 +18,20 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-address" "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/specs-actors/actors/builtin" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -45,6 +47,10 @@ var infoCmd = &cli.Command{ Name: "hide-sectors-info", Usage: "hide sectors info", }, + &cli.IntFlag{ + Name: "blocks", + Usage: "Log of produced newest blocks and rewards(Miner Fee excluded)", + }, }, Action: infoCmdAct, } @@ -141,6 +147,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full } tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) if err != nil { return err @@ -148,6 +155,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full // Sector size mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { return err } @@ -178,6 +186,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full ), ) secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) + if err != nil { return err } @@ -275,6 +284,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full colorTokenAmount(" Available: %s\n", availBalance) mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) + if err != nil { return xerrors.Errorf("getting market balance: %w", err) } @@ -285,6 +295,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full 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) } @@ -315,6 +326,13 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full } } + if cctx.IsSet("blocks") { + fmt.Println("Produced newest blocks:") + err = producedBlocks(ctx, cctx.Int("blocks"), head, maddr, fullapi) + if err != nil { + return err + } + } // TODO: grab actr state / info // * Sealed sectors (count / bytes) // * Power @@ -484,8 +502,8 @@ func init() { } } -func sectorsInfo(ctx context.Context, napi api.StorageMiner) error { - summary, err := napi.SectorsSummary(ctx) +func sectorsInfo(ctx context.Context, mapi api.StorageMiner) error { + summary, err := mapi.SectorsSummary(ctx) if err != nil { return err } @@ -523,3 +541,40 @@ func colorTokenAmount(format string, amount abi.TokenAmount) { color.Red(format, types.FIL(amount).Short()) } } + +func producedBlocks(ctx context.Context, count int, head *types.TipSet, maddr address.Address, napi v0api.FullNode) error { + var err error + ts := head + fmt.Printf(" Epoch | Block ID | Reward\n") + for count > 0 { + tsk := ts.Key() + bhs := ts.Blocks() + for _, bh := range bhs { + if bh.Miner == maddr { + rewardActor, err := napi.StateGetActor(ctx, reward.Address, tsk) + if err != nil { + return err + } + + rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, napi), rewardActor) + if err != nil { + return err + } + blockReward, err := rewardActorState.ThisEpochReward() + if err != nil { + return err + } + fmt.Printf("%8d | %s | %s\n", ts.Height(), bh.Cid(), + types.BigDiv(types.BigMul(types.NewInt(uint64(bh.ElectionProof.WinCount)), + blockReward), types.NewInt(uint64(builtin.ExpectedLeadersPerEpoch)))) + count-- + } + } + tsk = ts.Parents() + ts, err = napi.ChainGetTipSet(ctx, tsk) + if err != nil { + return err + } + } + return nil +} From 2744bb4e5a66e3788af887569222064b178d6777 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 4 Aug 2021 15:49:35 +0800 Subject: [PATCH 002/122] update --- cmd/lotus-miner/info.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 9d1fa0300..87abd474a 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -17,8 +17,8 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -34,6 +34,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/tools/stats" ) var infoCmd = &cli.Command{ @@ -328,7 +329,7 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.Full if cctx.IsSet("blocks") { fmt.Println("Produced newest blocks:") - err = producedBlocks(ctx, cctx.Int("blocks"), head, maddr, fullapi) + err = producedBlocks(ctx, cctx.Int("blocks"), maddr, fullapi) if err != nil { return err } @@ -542,8 +543,12 @@ func colorTokenAmount(format string, amount abi.TokenAmount) { } } -func producedBlocks(ctx context.Context, count int, head *types.TipSet, maddr address.Address, napi v0api.FullNode) error { +func producedBlocks(ctx context.Context, count int, maddr address.Address, napi v0api.FullNode) error { var err error + head, err := napi.ChainHead(ctx) + if err != nil { + return err + } ts := head fmt.Printf(" Epoch | Block ID | Reward\n") for count > 0 { @@ -556,7 +561,7 @@ func producedBlocks(ctx context.Context, count int, head *types.TipSet, maddr ad return err } - rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, napi), rewardActor) + rewardActorState, err := reward.Load(stats.NewApiIpldStore(ctx, napi), rewardActor) if err != nil { return err } From 0b8a211e70151c3eaeedcf3298f09e68d6a00adf Mon Sep 17 00:00:00 2001 From: lanzafame Date: Wed, 11 Aug 2021 13:29:52 +1000 Subject: [PATCH 003/122] fix: init restore adds empty storage.json --- cmd/lotus-miner/init_restore.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/lotus-miner/init_restore.go b/cmd/lotus-miner/init_restore.go index 393b44dd2..0974a7c5d 100644 --- a/cmd/lotus-miner/init_restore.go +++ b/cmd/lotus-miner/init_restore.go @@ -223,6 +223,12 @@ func restore(ctx context.Context, cctx *cli.Context, targetPath string, strConfi } } else { log.Warn("--storage-config NOT SET. NO SECTOR PATHS WILL BE CONFIGURED") + // setting empty config to allow miner to be started + if err := lr.SetStorage(func(sc *stores.StorageConfig) { + sc.StoragePaths = append(sc.StoragePaths, stores.LocalPath{}) + }); err != nil { + return xerrors.Errorf("set storage config: %w", err) + } } log.Info("Restoring metadata backup") From 90e60f7a98703302a01bcc64c07fdb498962a79e Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 16 Aug 2021 12:56:27 -0400 Subject: [PATCH 004/122] Shed: Create a verifreg command for when VRK isn't a multisig --- cmd/lotus-shed/verifreg.go | 70 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 7640e636a..03be5f916 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -28,7 +28,8 @@ var verifRegCmd = &cli.Command{ Usage: "Interact with the verified registry actor", Flags: []cli.Flag{}, Subcommands: []*cli.Command{ - verifRegAddVerifierCmd, + verifRegAddVerifierFromMsigCmd, + verifRegAddVerifierFromAccountCmd, verifRegVerifyClientCmd, verifRegListVerifiersCmd, verifRegListClientsCmd, @@ -37,7 +38,7 @@ var verifRegCmd = &cli.Command{ }, } -var verifRegAddVerifierCmd = &cli.Command{ +var verifRegAddVerifierFromMsigCmd = &cli.Command{ Name: "add-verifier", Usage: "make a given account a verifier", ArgsUsage: " ", @@ -110,6 +111,71 @@ var verifRegAddVerifierCmd = &cli.Command{ }, } +var verifRegAddVerifierFromAccountCmd = &cli.Command{ + Name: "add-verifier-from-account", + Usage: "make a given account a verifier", + ArgsUsage: " ", + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 3 { + return fmt.Errorf("must specify three arguments: sender, verifier, and allowance") + } + + sender, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + verifier, err := address.NewFromString(cctx.Args().Get(1)) + if err != nil { + return err + } + + allowance, err := types.BigFromString(cctx.Args().Get(2)) + if err != nil { + return err + } + + // TODO: ActorUpgrade: Abstract + params, err := actors.SerializeParams(&verifreg2.AddVerifierParams{Address: verifier, Allowance: allowance}) + if err != nil { + return err + } + + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := lcli.ReqContext(cctx) + + msg := &types.Message{ + To: verifreg.Address, + From: sender, + Method: verifreg.Methods.AddVerifier, + Params: params, + } + + smsg, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return err + } + + fmt.Printf("message sent, now waiting on cid: %s\n", smsg.Cid()) + + mwait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) + if err != nil { + return err + } + + if mwait.Receipt.ExitCode != 0 { + return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode) + } + + return nil + + }, +} + var verifRegVerifyClientCmd = &cli.Command{ Name: "verify-client", Usage: "make a given account a verified client", From 2dde022a00d7b725415e5cee07d8861ebe4d1000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 20 Aug 2021 15:53:24 +0100 Subject: [PATCH 005/122] itests: support larger sector sizes; add large deal test. --- .circleci/config.yml | 5 +++++ itests/api_test.go | 6 ++--- itests/deals_512mb_test.go | 44 +++++++++++++++++++++++++++++++++++++ itests/kit/ensemble.go | 32 +++++++++++++++++---------- itests/kit/ensemble_opts.go | 5 +++++ itests/kit/init.go | 4 ++-- itests/kit/node_opts.go | 19 +++++++++------- itests/kit/sectors.go | 16 ++++++++++++++ 8 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 itests/deals_512mb_test.go create mode 100644 itests/kit/sectors.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d24e25b1..d570e303c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -810,6 +810,11 @@ workflows: suite: itest-deadlines target: "./itests/deadlines_test.go" + - test: + name: test-itest-deals_512mb + suite: itest-deals_512mb + target: "./itests/deals_512mb_test.go" + - test: name: test-itest-deals_concurrent suite: itest-deals_concurrent diff --git a/itests/api_test.go b/itests/api_test.go index 6c25ef181..01e006fed 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -7,14 +7,14 @@ import ( "time" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/stretchr/testify/require" + lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" - "github.com/stretchr/testify/require" ) func TestAPI(t *testing.T) { @@ -186,7 +186,7 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { var newMiner kit.TestMiner ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey), - kit.ProofType(abi.RegisteredSealProof_StackedDrg2KiBV1_1), + kit.SectorSize(2<<10), kit.WithAllSubsystems(), ).Start().InterconnectAll() diff --git a/itests/deals_512mb_test.go b/itests/deals_512mb_test.go new file mode 100644 index 000000000..eac598ab9 --- /dev/null +++ b/itests/deals_512mb_test.go @@ -0,0 +1,44 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestStorageDealMissingBlock(t *testing.T) { + ctx := context.Background() + + // enable 512MiB proofs so we can conduct larger transfers. + kit.EnableLargeSectors() + kit.QuietMiningLogs() + + client, miner, ens := kit.EnsembleMinimal(t, + kit.MockProofs(), + kit.SectorSize(512<<20), // 512MiB sectors. + ) + ens.InterconnectAll().BeginMining(50 * time.Millisecond) + + dh := kit.NewDealHarness(t, client, miner, miner) + + client.WaitTillChain(ctx, kit.HeightAtLeast(5)) + + res, _ := client.CreateImportFile(ctx, 0, 64<<20) // 64MiB file. + list, err := client.ClientListImports(ctx) + require.NoError(t, err) + require.Len(t, list, 1) + require.Equal(t, res.Root, *list[0].Root) + + dp := dh.DefaultStartDealParams() + dp.Data.Root = res.Root + dp.FastRetrieval = true + dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. + deal := dh.StartDeal(ctx, dp) + + dh.WaitDealSealed(ctx, deal, false, false, nil) +} diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 672efd6be..f876fa6f8 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -17,6 +17,14 @@ import ( "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/go-storedcounter" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + "github.com/ipfs/go-datastore" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/build" @@ -43,13 +51,6 @@ import ( testing2 "github.com/filecoin-project/lotus/node/modules/testing" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/mockstorage" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" - "github.com/ipfs/go-datastore" - libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" ) func init() { @@ -216,11 +217,11 @@ func (n *Ensemble) Miner(minerNode *TestMiner, full *TestFullNode, opts ...NodeO genm *genesis.Miner ) - // Default 2KiB sector for the network version - proofType, err := miner.SealProofTypeFromSectorSize(2<<10, n.genesis.version) + // Will use 2KiB sectors by default (default value of sectorSize). + proofType, err := miner.SealProofTypeFromSectorSize(options.sectorSize, n.genesis.version) require.NoError(n.t, err) - // create the preseal commitment. + // Create the preseal commitment. if n.options.mockProofs { genm, k, err = mockstorage.PreSeal(proofType, actorAddr, sectors) } else { @@ -363,10 +364,19 @@ func (n *Ensemble) Start() *Ensemble { if m.options.mainMiner == nil { // this is a miner created after genesis, so it won't have a preseal. // we need to create it on chain. + + // we get the proof type for the requested sector size, for + // the current network version. + nv, err := m.FullNode.FullNode.StateNetworkVersion(ctx, types.EmptyTSK) + require.NoError(n.t, err) + + proofType, err := miner.SealProofTypeFromSectorSize(m.options.sectorSize, nv) + require.NoError(n.t, err) + params, aerr := actors.SerializeParams(&power2.CreateMinerParams{ Owner: m.OwnerKey.Address, Worker: m.OwnerKey.Address, - SealProofType: m.options.proofType, + SealProofType: proofType, Peer: abi.PeerID(m.Libp2p.PeerID), }) require.NoError(n.t, aerr) diff --git a/itests/kit/ensemble_opts.go b/itests/kit/ensemble_opts.go index 9db64d3bc..327da076e 100644 --- a/itests/kit/ensemble_opts.go +++ b/itests/kit/ensemble_opts.go @@ -4,8 +4,10 @@ import ( "time" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/wallet" ) @@ -37,6 +39,9 @@ var DefaultEnsembleOpts = ensembleOpts{ func MockProofs() EnsembleOpt { return func(opts *ensembleOpts) error { opts.mockProofs = true + // since we're using mock proofs, we don't need to download + // proof parameters + build.DisableBuiltinAssets = true return nil } } diff --git a/itests/kit/init.go b/itests/kit/init.go index dc8463cb4..c455cf8c6 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -6,16 +6,16 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + logging "github.com/ipfs/go-log/v2" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/policy" - logging "github.com/ipfs/go-log/v2" ) func init() { _ = logging.SetLogLevel("*", "INFO") policy.SetProviderCollateralSupplyTarget(big.Zero(), big.NewInt(1)) - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 87707aa16..f56c2fb13 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -3,6 +3,7 @@ package kit import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" @@ -30,14 +31,14 @@ type nodeOpts struct { mainMiner *TestMiner disableLibp2p bool optBuilders []OptBuilder - proofType abi.RegisteredSealProof + sectorSize abi.SectorSize } // DefaultNodeOpts are the default options that will be applied to test nodes. var DefaultNodeOpts = nodeOpts{ - balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), - sectors: DefaultPresealsPerBootstrapMiner, - proofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, // default _concrete_ proof type for non-genesis miners (notice the _1) for new actors versions. + balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), + sectors: DefaultPresealsPerBootstrapMiner, + sectorSize: abi.SectorSize(2 << 10), // 2KiB. } // OptBuilder is used to create an option after some other node is already @@ -135,11 +136,13 @@ func ConstructorOpts(extra ...node.Option) NodeOpt { } } -// ProofType sets the proof type for this node. If you're using new actor -// versions, this should be a _1 proof type. -func ProofType(proofType abi.RegisteredSealProof) NodeOpt { +// SectorSize sets the sector size for this miner. Start() will populate the +// corresponding proof type depending on the network version (genesis network +// version if the Ensemble is unstarted, or the current network version +// if started). +func SectorSize(sectorSize abi.SectorSize) NodeOpt { return func(opts *nodeOpts) error { - opts.proofType = proofType + opts.sectorSize = sectorSize return nil } } diff --git a/itests/kit/sectors.go b/itests/kit/sectors.go new file mode 100644 index 000000000..5f82055c0 --- /dev/null +++ b/itests/kit/sectors.go @@ -0,0 +1,16 @@ +package kit + +import ( + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +// EnableLargeSectors enables 512MiB sectors. This is useful in combination with +// mock proofs, for testing larger transfers. +func EnableLargeSectors() { + policy.SetSupportedProofTypes( + abi.RegisteredSealProof_StackedDrg2KiBV1, + abi.RegisteredSealProof_StackedDrg512MiBV1, // <== + ) +} From 0030e208a0a7920c7831e1d49d41843046667074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 20 Aug 2021 15:59:19 +0100 Subject: [PATCH 006/122] avoid reordering imports. --- itests/kit/ensemble.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index f876fa6f8..9e5333794 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -17,13 +17,6 @@ import ( "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/go-storedcounter" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" - "github.com/ipfs/go-datastore" - libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" @@ -51,6 +44,13 @@ import ( testing2 "github.com/filecoin-project/lotus/node/modules/testing" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/mockstorage" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + "github.com/ipfs/go-datastore" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" ) func init() { From 342134f8535cea4f9f2978463520e1ce48b2ed67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 20 Aug 2021 18:07:25 +0100 Subject: [PATCH 007/122] EnableLargeSectors: restore previous supported proof types. --- itests/deals_512mb_test.go | 2 +- itests/kit/sectors.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/itests/deals_512mb_test.go b/itests/deals_512mb_test.go index eac598ab9..766d83835 100644 --- a/itests/deals_512mb_test.go +++ b/itests/deals_512mb_test.go @@ -15,7 +15,7 @@ func TestStorageDealMissingBlock(t *testing.T) { ctx := context.Background() // enable 512MiB proofs so we can conduct larger transfers. - kit.EnableLargeSectors() + kit.EnableLargeSectors(t) kit.QuietMiningLogs() client, miner, ens := kit.EnsembleMinimal(t, diff --git a/itests/kit/sectors.go b/itests/kit/sectors.go index 5f82055c0..d9c52b9ca 100644 --- a/itests/kit/sectors.go +++ b/itests/kit/sectors.go @@ -1,6 +1,8 @@ package kit import ( + "testing" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -8,9 +10,14 @@ import ( // EnableLargeSectors enables 512MiB sectors. This is useful in combination with // mock proofs, for testing larger transfers. -func EnableLargeSectors() { +func EnableLargeSectors(t *testing.T) { policy.SetSupportedProofTypes( abi.RegisteredSealProof_StackedDrg2KiBV1, - abi.RegisteredSealProof_StackedDrg512MiBV1, // <== + abi.RegisteredSealProof_StackedDrg512MiBV1, // <== here ) + t.Cleanup(func() { // reset when done. + policy.SetSupportedProofTypes( + abi.RegisteredSealProof_StackedDrg2KiBV1, + ) + }) } From 2a4ad207a5e273eb0d3a11780e60b7758325f260 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 20 Aug 2021 14:59:01 -0700 Subject: [PATCH 008/122] test: disable flaky TestSimultaneousTransferLimit See https://github.com/filecoin-project/lotus/issues/7152 for details. --- itests/deals_concurrent_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go index 3fd554c62..ff8bab257 100644 --- a/itests/deals_concurrent_test.go +++ b/itests/deals_concurrent_test.go @@ -114,6 +114,8 @@ func TestDealCyclesConcurrent(t *testing.T) { } func TestSimultanenousTransferLimit(t *testing.T) { + t.Skip("skipping as flaky #7152") + if testing.Short() { t.Skip("skipping test in short mode") } From 927ef041f8d86c8dd86798f35988278b3cfa1efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 19 Aug 2021 15:53:59 +0200 Subject: [PATCH 009/122] miner: Command to list expired sectors --- chain/actors/builtin/miner/actor.go.template | 11 ++ chain/actors/builtin/miner/miner.go | 11 ++ cmd/lotus-miner/sectors.go | 104 +++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index edc298ca9..7ffe9f146 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -141,10 +141,21 @@ type Deadline interface { } type Partition interface { + // AllSectors returns all sector numbers in this partition, including faulty, unproven, and terminated sectors AllSectors() (bitfield.BitField, error) + + // Subset of sectors detected/declared faulty and not yet recovered (excl. from PoSt). + // Faults ∩ Terminated = ∅ FaultySectors() (bitfield.BitField, error) + + // Subset of faulty sectors expected to recover on next PoSt + // Recoveries ∩ Terminated = ∅ RecoveringSectors() (bitfield.BitField, error) + + // Live sectors are those that are not terminated (but may be faulty). LiveSectors() (bitfield.BitField, error) + + // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. ActiveSectors() (bitfield.BitField, error) } diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 43e3a0828..4621fa48b 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -200,10 +200,21 @@ type Deadline interface { } type Partition interface { + // AllSectors returns all sector numbers in this partition, including faulty, unproven, and terminated sectors AllSectors() (bitfield.BitField, error) + + // Subset of sectors detected/declared faulty and not yet recovered (excl. from PoSt). + // Faults ∩ Terminated = ∅ FaultySectors() (bitfield.BitField, error) + + // Subset of faulty sectors expected to recover on next PoSt + // Recoveries ∩ Terminated = ∅ RecoveringSectors() (bitfield.BitField, error) + + // Live sectors are those that are not terminated (but may be faulty). LiveSectors() (bitfield.BitField, error) + + // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. ActiveSectors() (bitfield.BitField, error) } diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 39f9f53b9..aaae3d73f 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -44,6 +44,7 @@ var sectorsCmd = &cli.Command{ sectorsUpdateCmd, sectorsPledgeCmd, sectorsCheckExpireCmd, + sectorsExpiredCmd, sectorsRenewCmd, sectorsExtendCmd, sectorsTerminateCmd, @@ -1515,6 +1516,109 @@ var sectorsUpdateCmd = &cli.Command{ }, } +var sectorsExpiredCmd = &cli.Command{ + Name: "expired", + Usage: "Get or cleanup expired sectors", + ArgsUsage: "", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer nCloser() + ctx := lcli.ReqContext(cctx) + + maddr, err := api.ActorAddress(ctx) + if err != nil { + return xerrors.Errorf("getting actor address: %w", err) + } + + // toCheck is a working bitfield which will only contain terminated sectors + toCheck := bitfield.New() + { + sectors, err := api.SectorsList(ctx) + if err != nil { + return xerrors.Errorf("getting sector list: %w", err) + } + + for _, sector := range sectors { + toCheck.Set(uint64(sector)) + } + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) + } + + nv, err := fullApi.StateNetworkVersion(ctx, head.Key()) + if err != nil { + return xerrors.Errorf("getting network version: %w", err) + } + + lbEpoch := head.Height() - policy.GetWinningPoStSectorSetLookback(nv) + if lbEpoch < 0 { + return xerrors.Errorf("too early to terminate sectors") + } + + lbts, err := fullApi.ChainGetTipSetByHeight(ctx, lbEpoch, head.Key()) + if err != nil { + return xerrors.Errorf("getting lookback tipset: %w", err) + } + + mact, err := fullApi.StateGetActor(ctx, maddr, lbts.Key()) + 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 + } + + alloc, err := mas.GetAllocatedSectors() + if err != nil { + return xerrors.Errorf("getting allocated sectors: %w", err) + } + + // only allocated sectors can be expired, + toCheck, err = bitfield.IntersectBitField(toCheck, *alloc) + + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + live, err := part.LiveSectors() + if err != nil { + return err + } + + toCheck, err = bitfield.SubtractBitField(toCheck, live) + return err + }) + }); err != nil { + return err + } + + // toCheck now only contains sectors which either failed to precommit or are expired/terminated + err = toCheck.ForEach(func(u uint64) error { + fmt.Println(u) + return nil + }) + if err != nil { + return err + } + + return nil + }, +} + var sectorsBatching = &cli.Command{ Name: "batching", Usage: "manage batch sector operations", From d1759a43353d04955de341ef99eb7db937638864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 19 Aug 2021 16:22:29 +0200 Subject: [PATCH 010/122] Show more info in sectors expired cmd --- cmd/lotus-miner/sectors.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index aaae3d73f..16d022997 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1520,9 +1520,14 @@ var sectorsExpiredCmd = &cli.Command{ Name: "expired", Usage: "Get or cleanup expired sectors", ArgsUsage: "", - Flags: []cli.Flag{}, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "show-removed", + Usage: "show removed sectors", + }, + }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1535,7 +1540,7 @@ var sectorsExpiredCmd = &cli.Command{ defer nCloser() ctx := lcli.ReqContext(cctx) - maddr, err := api.ActorAddress(ctx) + maddr, err := nodeApi.ActorAddress(ctx) if err != nil { return xerrors.Errorf("getting actor address: %w", err) } @@ -1543,7 +1548,7 @@ var sectorsExpiredCmd = &cli.Command{ // toCheck is a working bitfield which will only contain terminated sectors toCheck := bitfield.New() { - sectors, err := api.SectorsList(ctx) + sectors, err := nodeApi.SectorsList(ctx) if err != nil { return xerrors.Errorf("getting sector list: %w", err) } @@ -1607,8 +1612,23 @@ var sectorsExpiredCmd = &cli.Command{ } // toCheck now only contains sectors which either failed to precommit or are expired/terminated + fmt.Printf("Sector\tState\tExpiration\n") + err = toCheck.ForEach(func(u uint64) error { - fmt.Println(u) + s := abi.SectorNumber(u) + + st, err := nodeApi.SectorsStatus(ctx, s, true) + if err != nil { + fmt.Printf("%d:\tError getting status: %s\n", u, err) + return nil + } + + if !cctx.Bool("show-removed") && st.State == api.SectorState(sealing.Removed) { + return nil + } + + fmt.Printf("%d:\t%s\t%s\n", s, st.State, lcli.EpochTime(head.Height(), st.Expiration)) + return nil }) if err != nil { From ccf884468980697773712181045b8e25c5399fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 19 Aug 2021 17:05:34 +0200 Subject: [PATCH 011/122] lotus-miner sectors expired --remove-expired --- cmd/lotus-miner/sectors.go | 86 ++++++++++++++++++++++++----- documentation/en/cli-lotus-miner.md | 17 ++++++ 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 16d022997..215995b9a 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1517,14 +1517,27 @@ var sectorsUpdateCmd = &cli.Command{ } var sectorsExpiredCmd = &cli.Command{ - Name: "expired", - Usage: "Get or cleanup expired sectors", - ArgsUsage: "", - Flags: []cli.Flag{ + Name: "expired", + Usage: "Get or cleanup expired sectors", + Flags: []cli.Flag{ &cli.BoolFlag{ Name: "show-removed", Usage: "show removed sectors", }, + &cli.BoolFlag{ + Name: "remove-expired", + Usage: "remove expired sectors", + }, + + &cli.Int64Flag{ + Name: "confirm-remove-count", + Hidden: true, + }, + &cli.Int64Flag{ + Name: "expired-epoch", + Usage: "epoch at which to check sector expirations", + DefaultText: "WinningPoSt lookback epoch", + }, }, Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -1563,14 +1576,21 @@ var sectorsExpiredCmd = &cli.Command{ return xerrors.Errorf("getting chain head: %w", err) } - nv, err := fullApi.StateNetworkVersion(ctx, head.Key()) - if err != nil { - return xerrors.Errorf("getting network version: %w", err) + lbEpoch := abi.ChainEpoch(cctx.Int64("expired-epoch")) + if !cctx.IsSet("expired-epoch") { + nv, err := fullApi.StateNetworkVersion(ctx, head.Key()) + if err != nil { + return xerrors.Errorf("getting network version: %w", err) + } + + lbEpoch = head.Height() - policy.GetWinningPoStSectorSetLookback(nv) + if lbEpoch < 0 { + return xerrors.Errorf("too early to terminate sectors") + } } - lbEpoch := head.Height() - policy.GetWinningPoStSectorSetLookback(nv) - if lbEpoch < 0 { - return xerrors.Errorf("too early to terminate sectors") + if cctx.IsSet("confirm-remove-count") && !cctx.IsSet("expired-epoch") { + return xerrors.Errorf("--expired-epoch must be specified with --confirm-remove-count") } lbts, err := fullApi.ChainGetTipSetByHeight(ctx, lbEpoch, head.Key()) @@ -1611,9 +1631,15 @@ var sectorsExpiredCmd = &cli.Command{ return err } + if cctx.Bool("remove-expired") { + color.Red("Removing sectors:\n") + } + // toCheck now only contains sectors which either failed to precommit or are expired/terminated fmt.Printf("Sector\tState\tExpiration\n") + var toRemove []abi.SectorNumber + err = toCheck.ForEach(func(u uint64) error { s := abi.SectorNumber(u) @@ -1623,11 +1649,17 @@ var sectorsExpiredCmd = &cli.Command{ return nil } - if !cctx.Bool("show-removed") && st.State == api.SectorState(sealing.Removed) { - return nil + rmMsg := "" + + if st.State == api.SectorState(sealing.Removed) { + if cctx.IsSet("confirm-remove-count") || !cctx.Bool("show-removed") { + return nil + } + } else { // not removed + toRemove = append(toRemove, s) } - fmt.Printf("%d:\t%s\t%s\n", s, st.State, lcli.EpochTime(head.Height(), st.Expiration)) + fmt.Printf("%d%s\t%s\t%s\n", s, rmMsg, st.State, lcli.EpochTime(head.Height(), st.Expiration)) return nil }) @@ -1635,6 +1667,34 @@ var sectorsExpiredCmd = &cli.Command{ return err } + if cctx.Bool("remove-expired") { + if !cctx.IsSet("confirm-remove-count") { + fmt.Println() + fmt.Println(color.YellowString("All"), color.GreenString("%d", len(toRemove)), color.YellowString("sectors listed above will be removed\n")) + fmt.Println(color.YellowString("To confirm removal of the above sectors, including\n all related sealed and unsealed data, run:\n")) + fmt.Println(color.RedString("lotus-miner sectors expired --remove-expired --confirm-remove-count=%d --expired-epoch=%d\n", len(toRemove), lbts.Height())) + fmt.Println(color.YellowString("WARNING: This operation is irreversible")) + return nil + } + + fmt.Println() + + if int64(len(toRemove)) != cctx.Int64("confirm-remove-count") { + return xerrors.Errorf("value of confirm-remove-count doesn't match the number of sectors which can be removed (%d)", len(toRemove)) + } + + for _, number := range toRemove { + fmt.Printf("Removing sector\t%s:\t", color.YellowString("%d", number)) + + err := nodeApi.SectorRemove(ctx, number) + if err != nil { + color.Red("ERROR: %s\n", err.Error()) + } else { + color.Green("OK\n") + } + } + } + return nil }, } diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 80178c8a7..372f9deac 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -1472,6 +1472,7 @@ COMMANDS: update-state ADVANCED: manually update the state of a sector, this may aid in error recovery pledge store random data in a sector check-expire Inspect expiring sectors + expired Get or cleanup expired sectors renew Renew expiring sectors while not exceeding each sector's max life extend Extend sector expiration terminate Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector) @@ -1577,6 +1578,22 @@ OPTIONS: ``` +### lotus-miner sectors expired +``` +NAME: + lotus-miner sectors expired - Get or cleanup expired sectors + +USAGE: + lotus-miner sectors expired [command options] [arguments...] + +OPTIONS: + --show-removed show removed sectors (default: false) + --remove-expired remove expired sectors (default: false) + --expired-epoch value epoch at which to check sector expirations (default: WinningPoSt lookback epoch) + --help, -h show help (default: false) + +``` + ### lotus-miner sectors renew ``` NAME: From a9bf24695d6ac5a1496614bd95e8c33502209d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 19 Aug 2021 17:10:34 +0200 Subject: [PATCH 012/122] Fix lint --- cmd/lotus-miner/sectors.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 215995b9a..a64a7632d 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1616,6 +1616,9 @@ var sectorsExpiredCmd = &cli.Command{ // only allocated sectors can be expired, toCheck, err = bitfield.IntersectBitField(toCheck, *alloc) + if err != nil { + return xerrors.Errorf("intersecting bitfields: %w", err) + } if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { From 95d4483593010d56fab8be12b1d99ebbb2798ab5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 23 Aug 2021 15:18:01 -0700 Subject: [PATCH 013/122] chore: remove unecessary replace --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index f73511574..be7d911e1 100644 --- a/go.mod +++ b/go.mod @@ -162,8 +162,6 @@ require ( replace github.com/libp2p/go-libp2p-yamux => github.com/libp2p/go-libp2p-yamux v0.5.1 -replace github.com/filecoin-project/lotus => ./ - replace github.com/filecoin-project/filecoin-ffi => ./extern/filecoin-ffi replace github.com/filecoin-project/test-vectors => ./extern/test-vectors From e1a056db4631fdae3f2ce5e70302772045c14aee Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Tue, 24 Aug 2021 10:42:00 +0530 Subject: [PATCH 014/122] ignore nil throttler --- node/impl/storminer.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 657e8cfdf..ff016259a 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -725,11 +725,13 @@ func (sm *StorageMinerAPI) DagstoreInitializeAll(ctx context.Context, params api go func() { for i, k := range toInitialize { - select { - case <-throttle: - // acquired a throttle token, proceed. - case <-ctx.Done(): - return + if throttle != nil { + select { + case <-throttle: + // acquired a throttle token, proceed. + case <-ctx.Done(): + return + } } go func(k string, i int) { From 5a23c2bb90173e81425bf0d0a52e444e224bceb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 24 Aug 2021 11:28:58 +0200 Subject: [PATCH 015/122] sectors expired: Address review --- cmd/lotus-miner/sectors.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index a64a7632d..194b1e554 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1553,24 +1553,6 @@ var sectorsExpiredCmd = &cli.Command{ defer nCloser() ctx := lcli.ReqContext(cctx) - maddr, err := nodeApi.ActorAddress(ctx) - if err != nil { - return xerrors.Errorf("getting actor address: %w", err) - } - - // toCheck is a working bitfield which will only contain terminated sectors - toCheck := bitfield.New() - { - sectors, err := nodeApi.SectorsList(ctx) - if err != nil { - return xerrors.Errorf("getting sector list: %w", err) - } - - for _, sector := range sectors { - toCheck.Set(uint64(sector)) - } - } - head, err := fullApi.ChainHead(ctx) if err != nil { return xerrors.Errorf("getting chain head: %w", err) @@ -1598,6 +1580,24 @@ var sectorsExpiredCmd = &cli.Command{ return xerrors.Errorf("getting lookback tipset: %w", err) } + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return xerrors.Errorf("getting actor address: %w", err) + } + + // toCheck is a working bitfield which will only contain terminated sectors + toCheck := bitfield.New() + { + sectors, err := nodeApi.SectorsList(ctx) + if err != nil { + return xerrors.Errorf("getting sector list: %w", err) + } + + for _, sector := range sectors { + toCheck.Set(uint64(sector)) + } + } + mact, err := fullApi.StateGetActor(ctx, maddr, lbts.Key()) if err != nil { return err From d29509b23364533470a20f4e6db3949c085c9059 Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Tue, 24 Aug 2021 11:52:45 -0400 Subject: [PATCH 016/122] turn off patch --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index a70061aaa..d004bcb9e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,5 +1,6 @@ coverage: status: + patch: off project: tools-and-tests: target: auto From 16f8a35e76801868fc18cd6b7bd5c947a5fb0ae4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 24 Aug 2021 11:05:05 -0700 Subject: [PATCH 017/122] fix: idiomatic go 1. No need to slice a slice. 2. Idiomatic filter (https://github.com/golang/go/wiki/SliceTricks). --- markets/storageadapter/dealpublisher.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index 7b1ca1793..99f5980ee 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -257,7 +257,7 @@ func (p *DealPublisher) publishAllDeals() { // Filter out any deals that have been cancelled p.filterCancelledDeals() - deals := p.pending[:] + deals := p.pending p.pending = nil // Send the publish message @@ -384,12 +384,12 @@ func pieceCids(deals []market2.ClientDealProposal) string { // filter out deals that have been cancelled func (p *DealPublisher) filterCancelledDeals() { - i := 0 + filtered := p.pending[:0] for _, pd := range p.pending { - if pd.ctx.Err() == nil { - p.pending[i] = pd - i++ + if pd.ctx.Err() != nil { + continue } + filtered = append(filtered, pd) } - p.pending = p.pending[:i] + p.pending = filtered } From add699d2386714c85073d5cb1d7d82eaa5a4847c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 24 Aug 2021 11:05:51 -0700 Subject: [PATCH 018/122] fix: make sure we start the publish timer before recording the time Otherwise, our nice and deterministic test may keep repeatedly setting the time to _right_ before the timer fires. --- markets/storageadapter/dealpublisher.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index 99f5980ee..f458e7a4f 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -228,11 +228,15 @@ func (p *DealPublisher) waitForMoreDeals() { // Set a timeout to wait for more deals to arrive log.Infof("waiting publish deals queue period of %s before publishing", p.publishPeriod) ctx, cancel := context.WithCancel(p.ctx) + + // Create the timer _before_ taking the current time so publishPeriod+timeout is always >= + // the actual timer timeout. + timer := build.Clock.Timer(p.publishPeriod) + p.publishPeriodStart = build.Clock.Now() p.cancelWaitForMoreDeals = cancel go func() { - timer := build.Clock.Timer(p.publishPeriod) select { case <-ctx.Done(): timer.Stop() From cbf69f6ded936577a344a1409fe5c63009452669 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 24 Aug 2021 11:35:56 -0700 Subject: [PATCH 019/122] fix: make TestTimedCacheBlockstoreSimple pass reliably --- blockstore/timed.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blockstore/timed.go b/blockstore/timed.go index 80e6c8a08..b21dceeb8 100644 --- a/blockstore/timed.go +++ b/blockstore/timed.go @@ -47,8 +47,12 @@ func (t *TimedCacheBlockstore) Start(_ context.Context) error { return fmt.Errorf("already started") } t.closeCh = make(chan struct{}) + + // Create this timer before starting the goroutine. Otherwise, creating the timer will race + // with addint time to the mock clock, and we could add time _first_, then stall waiting for + // a timer that'll never fire. + ticker := t.clock.Ticker(t.interval) go func() { - ticker := t.clock.Ticker(t.interval) defer ticker.Stop() for { select { From 1105c24e8cef2819fbbad475284781ff8350e27d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 24 Aug 2021 15:37:52 -0700 Subject: [PATCH 020/122] test: disable flaky TestBatchDealInput See #4611. --- itests/batch_deal_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go index 01622486a..93d338e53 100644 --- a/itests/batch_deal_test.go +++ b/itests/batch_deal_test.go @@ -18,6 +18,7 @@ import ( ) func TestBatchDealInput(t *testing.T) { + t.Skip("this test is disabled as it's flaky: #4611") kit.QuietMiningLogs() var ( From b3966ab4eb003f597aa38af0450ee6308e7560ac Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 24 Aug 2021 20:39:28 -0400 Subject: [PATCH 021/122] Fix typo in comment --- blockstore/timed.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockstore/timed.go b/blockstore/timed.go index b21dceeb8..b279943b6 100644 --- a/blockstore/timed.go +++ b/blockstore/timed.go @@ -49,7 +49,7 @@ func (t *TimedCacheBlockstore) Start(_ context.Context) error { t.closeCh = make(chan struct{}) // Create this timer before starting the goroutine. Otherwise, creating the timer will race - // with addint time to the mock clock, and we could add time _first_, then stall waiting for + // with adding time to the mock clock, and we could add time _first_, then stall waiting for // a timer that'll never fire. ticker := t.clock.Ticker(t.interval) go func() { From 0eacb9b9c80f2d8486004a11ea0c8f5bae3cb085 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Tue, 17 Aug 2021 15:07:32 +0300 Subject: [PATCH 022/122] call string.Repeat always with positive int --- cmd/lotus-miner/sealing.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index 75f02845c..e4ee7e14d 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -101,17 +101,27 @@ var sealingWorkersCmd = &cli.Command{ ramBarsRes := int(stat.Info.Resources.MemReserved * barCols / stat.Info.Resources.MemPhysical) ramBarsUsed := int(stat.MemUsedMin * barCols / stat.Info.Resources.MemPhysical) - ramBar := color.YellowString(strings.Repeat("|", ramBarsRes)) + - color.GreenString(strings.Repeat("|", ramBarsUsed)) + - strings.Repeat(" ", int(barCols)-ramBarsUsed-ramBarsRes) + ramRepeatSpace := int(barCols) - (ramBarsUsed + ramBarsRes) + var ramBar string + if ramRepeatSpace < 0 { + ramRepeatSpace = 0 + ramBar = color.RedString(strings.Repeat("|", ramBarsRes)) + + color.GreenString(strings.Repeat("|", ramBarsUsed)) + + strings.Repeat(" ", ramRepeatSpace) + } else { + ramBar = color.YellowString(strings.Repeat("|", ramBarsRes)) + + color.GreenString(strings.Repeat("|", ramBarsUsed)) + + strings.Repeat(" ", ramRepeatSpace) + } vmem := stat.Info.Resources.MemPhysical + stat.Info.Resources.MemSwap vmemBarsRes := int(stat.Info.Resources.MemReserved * barCols / vmem) vmemBarsUsed := int(stat.MemUsedMax * barCols / vmem) + vmemRepeatSpace := int(barCols) - (vmemBarsUsed + vmemBarsRes) vmemBar := color.YellowString(strings.Repeat("|", vmemBarsRes)) + color.GreenString(strings.Repeat("|", vmemBarsUsed)) + - strings.Repeat(" ", int(barCols)-vmemBarsUsed-vmemBarsRes) + strings.Repeat(" ", vmemRepeatSpace) fmt.Printf("\tRAM: [%s] %d%% %s/%s\n", ramBar, (stat.Info.Resources.MemReserved+stat.MemUsedMin)*100/stat.Info.Resources.MemPhysical, From 548865e8dd50b732eb1ca6239623079374cbe056 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 25 Aug 2021 11:48:03 +0300 Subject: [PATCH 023/122] simplify assignments to vmemBar and ramBar --- cmd/lotus-miner/sealing.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index e4ee7e14d..a7e0a8de8 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -102,24 +102,30 @@ var sealingWorkersCmd = &cli.Command{ ramBarsRes := int(stat.Info.Resources.MemReserved * barCols / stat.Info.Resources.MemPhysical) ramBarsUsed := int(stat.MemUsedMin * barCols / stat.Info.Resources.MemPhysical) ramRepeatSpace := int(barCols) - (ramBarsUsed + ramBarsRes) - var ramBar string + + colorFunc := color.YellowString if ramRepeatSpace < 0 { ramRepeatSpace = 0 - ramBar = color.RedString(strings.Repeat("|", ramBarsRes)) + - color.GreenString(strings.Repeat("|", ramBarsUsed)) + - strings.Repeat(" ", ramRepeatSpace) - } else { - ramBar = color.YellowString(strings.Repeat("|", ramBarsRes)) + - color.GreenString(strings.Repeat("|", ramBarsUsed)) + - strings.Repeat(" ", ramRepeatSpace) + colorFunc = color.RedString } + ramBar := colorFunc(strings.Repeat("|", ramBarsRes)) + + color.GreenString(strings.Repeat("|", ramBarsUsed)) + + strings.Repeat(" ", ramRepeatSpace) + vmem := stat.Info.Resources.MemPhysical + stat.Info.Resources.MemSwap vmemBarsRes := int(stat.Info.Resources.MemReserved * barCols / vmem) vmemBarsUsed := int(stat.MemUsedMax * barCols / vmem) vmemRepeatSpace := int(barCols) - (vmemBarsUsed + vmemBarsRes) - vmemBar := color.YellowString(strings.Repeat("|", vmemBarsRes)) + + + colorFunc = color.YellowString + if vmemRepeatSpace < 0 { + vmemRepeatSpace = 0 + colorFunc = color.RedString + } + + vmemBar := colorFunc(strings.Repeat("|", vmemBarsRes)) + color.GreenString(strings.Repeat("|", vmemBarsUsed)) + strings.Repeat(" ", vmemRepeatSpace) From 928ac2191b5af51009bb7d085eb6a640c52bd500 Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Wed, 25 Aug 2021 16:19:02 +0530 Subject: [PATCH 024/122] fix throttling bug --- node/impl/storminer.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/impl/storminer.go b/node/impl/storminer.go index ff016259a..4e970343e 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -748,7 +748,10 @@ func (sm *StorageMinerAPI) DagstoreInitializeAll(ctx context.Context, params api } err := sm.DagstoreInitializeShard(ctx, k) - throttle <- struct{}{} + + if throttle != nil { + throttle <- struct{}{} + } r.Event = "end" if err == nil { From de79bf57e5e79979fa8576f6176b25be90d38456 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 25 Aug 2021 11:15:24 -0400 Subject: [PATCH 025/122] Bump version to v1.11.13-dev --- build/openrpc/full.json.gz | Bin 25412 -> 25412 bytes build/openrpc/miner.json.gz | Bin 10375 -> 10375 bytes build/openrpc/worker.json.gz | Bin 2711 -> 2711 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 ba509bb515071e0da2a539c72be00e22e9efe12a..6cae0a3833531540a2c695e2b3711a49f2043f14 100644 GIT binary patch delta 23 fcmX?djPb}Z#tB`Fl^eS^CUJzo0W<*TCfS+SeEv0BS`D<^TWy delta 21 ccmZno0YVo?i8M?Q4t-0BU;(yZ`_I diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index bedd7b1207bf296e7118c6aa8cd0b78a06222edd..f943040b66ce2b3ac991a57b3a7ad84780bc2035 100644 GIT binary patch delta 21 ccmbO(I$d-^BjenSO}tzjEXGTsBdZt~08ds1V*mgE delta 21 ccmbO(I$d-^BjekRO}tzj3h&&iBdZt~09YRfOaK4? diff --git a/build/version.go b/build/version.go index 5fe979cc2..b6931cddc 100644 --- a/build/version.go +++ b/build/version.go @@ -40,7 +40,7 @@ func buildType() string { } // BuildVersion is the local build version -const BuildVersion = "1.11.2-dev" +const BuildVersion = "1.11.3-dev" 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 372f9deac..d93efa903 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.2-dev + 1.11.3-dev COMMANDS: init Initialize a lotus miner repo diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index a06ad0fff..0da936e17 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.2-dev + 1.11.3-dev COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index b8cf6230a..855745348 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.2-dev + 1.11.3-dev COMMANDS: daemon Start a lotus daemon process From bdd1c3680274bb22b96737d8b92a50054ceab6a0 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 15 Jul 2021 13:10:11 -0400 Subject: [PATCH 026/122] Incoming: improve a log message --- chain/sub/incoming.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 115c33261..ac0c15b3b 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -308,7 +308,7 @@ func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub return pubsub.ValidationReject } - log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain") + log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain: %s", blk.Header.Cid()) return pubsub.ValidationIgnore } From 15667db6a924c2a7537bf1ffd47943bcac66a191 Mon Sep 17 00:00:00 2001 From: Adrian Lanzafame Date: Thu, 26 Aug 2021 18:48:28 +1000 Subject: [PATCH 027/122] journal: make current log file have a fixed named (#7112) --- journal/fs.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/journal/fs.go b/journal/fs.go index 57774d3aa..a41583c93 100644 --- a/journal/fs.go +++ b/journal/fs.go @@ -108,14 +108,28 @@ func (f *fsJournal) rollJournalFile() error { if f.fi != nil { _ = f.fi.Close() } + current := filepath.Join(f.dir, "lotus-journal.ndjson") + rolled := filepath.Join(f.dir, fmt.Sprintf( + "lotus-journal-%s.ndjson", + build.Clock.Now().Format(RFC3339nocolon), + )) - nfi, err := os.Create(filepath.Join(f.dir, fmt.Sprintf("lotus-journal-%s.ndjson", build.Clock.Now().Format(RFC3339nocolon)))) + // check if journal file exists + if fi, err := os.Stat(current); err == nil && !fi.IsDir() { + err := os.Rename(current, rolled) + if err != nil { + return xerrors.Errorf("failed to roll journal file: %w", err) + } + } + + nfi, err := os.Create(current) if err != nil { - return xerrors.Errorf("failed to open journal file: %w", err) + return xerrors.Errorf("failed to create journal file: %w", err) } f.fi = nfi f.fSize = 0 + return nil } From 81b1dd12f8418a51abd145b30e1b4661d057209d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 17 Aug 2021 14:39:36 +0200 Subject: [PATCH 028/122] Simple alert system; FD limit alerts --- journal/alerting/alerts.go | 129 +++++++++++++++++++++++++++++++++++ lib/ulimit/ulimit.go | 4 +- lib/ulimit/ulimit_freebsd.go | 2 +- lib/ulimit/ulimit_unix.go | 2 +- node/builder.go | 7 ++ node/builder_miner.go | 2 + node/modules/alerts.go | 43 ++++++++++++ 7 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 journal/alerting/alerts.go create mode 100644 node/modules/alerts.go diff --git a/journal/alerting/alerts.go b/journal/alerting/alerts.go new file mode 100644 index 000000000..21e9a79b8 --- /dev/null +++ b/journal/alerting/alerts.go @@ -0,0 +1,129 @@ +package alerting + +import ( + "encoding/json" + "sync" + "time" + + "github.com/filecoin-project/lotus/journal" + logging "github.com/ipfs/go-log/v2" +) + +var log = logging.Logger("alerting") + +type Alerting struct { + j journal.Journal + + lk sync.Mutex + alerts map[AlertType]Alert +} + +type AlertType struct { + System, Subsystem string +} + +type AlertEvent struct { + Message json.RawMessage + Time time.Time +} + +type Alert struct { + Type AlertType + Active bool + + LastActive *AlertEvent // NOTE: pointer for nullability, don't mutate the referenced object! + LastResolved *AlertEvent + + journalType journal.EventType +} + +func NewAlertingSystem(j journal.Journal) *Alerting { + return &Alerting{ + j: j, + + alerts: map[AlertType]Alert{}, + } +} + +func (a *Alerting) AddAlertType(system, subsystem string) AlertType { + a.lk.Lock() + defer a.lk.Unlock() + + at := AlertType{ + System: system, + Subsystem: subsystem, + } + + if _, exists := a.alerts[at]; exists { + return at + } + + et := a.j.RegisterEventType(system, subsystem) + + a.alerts[at] = Alert{ + Type: at, + Active: false, + journalType: et, + } + + return at +} + +func (a *Alerting) update(at AlertType, message interface{}, upd func(Alert, json.RawMessage) Alert) { + a.lk.Lock() + defer a.lk.Unlock() + + alert, ok := a.alerts[at] + if !ok { + log.Errorw("unknown alert", "type", at, "message", message) + } + + rawMsg, err := json.Marshal(message) + if err != nil { + log.Errorw("marshaling alert message failed", "type", at, "error", err) + rawMsg, err = json.Marshal(&struct { + AlertError string + }{ + AlertError: err.Error(), + }) + log.Errorw("marshaling marshaling error failed", "type", at, "error", err) + } + + a.alerts[at] = upd(alert, rawMsg) +} + +func (a *Alerting) Raise(at AlertType, message interface{}) { + log.Errorw("alert raised", "type", at, "message", message) + + a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert { + alert.Active = true + alert.LastActive = &AlertEvent{ + Message: rawMsg, + Time: time.Now(), + } + + a.j.RecordEvent(alert.journalType, func() interface{} { + return alert.LastActive + }) + + return alert + }) +} + +func (a *Alerting) Resolve(at AlertType, message interface{}) { + log.Errorw("alert resolved", "type", at, "message", message) + + a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert { + alert.Active = false + alert.LastResolved = &AlertEvent{ + Message: rawMsg, + Time: time.Now(), + } + + a.j.RecordEvent(alert.journalType, func() interface{} { + return alert.LastResolved + }) + + return alert + }) +} diff --git a/lib/ulimit/ulimit.go b/lib/ulimit/ulimit.go index 16bd4c9c1..7e80fd223 100644 --- a/lib/ulimit/ulimit.go +++ b/lib/ulimit/ulimit.go @@ -17,7 +17,7 @@ var ( supportsFDManagement = false // getlimit returns the soft and hard limits of file descriptors counts - getLimit func() (uint64, uint64, error) + GetLimit func() (uint64, uint64, error) // set limit sets the soft and hard limits of file descriptors counts setLimit func(uint64, uint64) error ) @@ -61,7 +61,7 @@ func ManageFdLimit() (changed bool, newLimit uint64, err error) { targetLimit = userLimit } - soft, hard, err := getLimit() + soft, hard, err := GetLimit() if err != nil { return false, 0, err } diff --git a/lib/ulimit/ulimit_freebsd.go b/lib/ulimit/ulimit_freebsd.go index 7e50436f3..aeea77d9d 100644 --- a/lib/ulimit/ulimit_freebsd.go +++ b/lib/ulimit/ulimit_freebsd.go @@ -11,7 +11,7 @@ import ( func init() { supportsFDManagement = true - getLimit = freebsdGetLimit + GetLimit = freebsdGetLimit setLimit = freebsdSetLimit } diff --git a/lib/ulimit/ulimit_unix.go b/lib/ulimit/ulimit_unix.go index a351236dc..e015b2b32 100644 --- a/lib/ulimit/ulimit_unix.go +++ b/lib/ulimit/ulimit_unix.go @@ -8,7 +8,7 @@ import ( func init() { supportsFDManagement = true - getLimit = unixGetLimit + GetLimit = unixGetLimit setLimit = unixSetLimit } diff --git a/node/builder.go b/node/builder.go index f04678bc8..58095da8f 100644 --- a/node/builder.go +++ b/node/builder.go @@ -32,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/journal/alerting" "github.com/filecoin-project/lotus/lib/peermgr" _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" @@ -82,6 +83,9 @@ const ( // System processes. InitMemoryWatchdog + // health checks + CheckFDLimit + // libp2p PstoreAddSelfKeysKey StartListeningKey @@ -146,6 +150,9 @@ func defaults() []Option { // global system journal. Override(new(journal.DisabledEvents), journal.EnvDisabledEvents), Override(new(journal.Journal), modules.OpenFilesystemJournal), + Override(new(*alerting.Alerting), alerting.NewAlertingSystem), + + Override(CheckFDLimit, modules.CheckFdLimit(16<<10)), Override(new(system.MemoryConstraints), modules.MemoryConstraints), Override(InitMemoryWatchdog, modules.MemoryWatchdog), diff --git a/node/builder_miner.go b/node/builder_miner.go index fd69de678..4c9d2492a 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -74,6 +74,8 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), + Override(CheckFDLimit, modules.CheckFdLimit(100_000)), // recommend at least 100k FD limit to miners + Override(new(api.MinerSubsystems), modules.ExtractEnabledMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), diff --git a/node/modules/alerts.go b/node/modules/alerts.go new file mode 100644 index 000000000..89261e231 --- /dev/null +++ b/node/modules/alerts.go @@ -0,0 +1,43 @@ +package modules + +import ( + "github.com/filecoin-project/lotus/journal/alerting" + "github.com/filecoin-project/lotus/lib/ulimit" +) + +func CheckFdLimit(min uint64) func(al *alerting.Alerting) { + return func(al *alerting.Alerting) { + if ulimit.GetLimit == nil { + return + } + + alert := al.AddAlertType("process", "fd-limit") + + soft, _, err := ulimit.GetLimit() + if err != nil { + al.Raise(alert, map[string]string{ + "message": "failed to get FD limit", + "error": err.Error(), + }) + } + + if soft < min { + al.Raise(alert, map[string]interface{}{ + "message": "soft FD limit is low", + "soft_limit": soft, + "recommended_min": min, + }) + } + } +} + +// TODO: More things: +// * Space in repo dirs (taking into account mounts) +// * Miner +// * Faulted partitions +// * Low balances +// * Market provider +// * Reachability +// * on-chain config +// * Low memory (maybe) +// * Network / sync issues From 53668211445d54399b06132bbc9dde32330ff5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 17 Aug 2021 14:51:54 +0200 Subject: [PATCH 029/122] api for getting alert states --- api/api_common.go | 5 +++++ api/proxy_gen.go | 14 ++++++++++++++ journal/alerting/alerts.go | 20 ++++++++++++++++++++ node/impl/common/common.go | 7 +++++++ 4 files changed, 46 insertions(+) diff --git a/api/api_common.go b/api/api_common.go index 629299db3..1a83dc25e 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -5,6 +5,7 @@ import ( "fmt" apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/journal/alerting" "github.com/google/uuid" @@ -33,6 +34,10 @@ type Common interface { LogList(context.Context) ([]string, error) //perm:write LogSetLevel(context.Context, string, string) error //perm:write + // LogAlerts returns list of all, active and inactive alerts tracked by the + // node + LogAlerts(ctx context.Context) ([]alerting.Alert, error) + // MethodGroup: Common // Version provides information about API provider diff --git a/api/proxy_gen.go b/api/proxy_gen.go index fd521a2b5..db3e668a2 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + "github.com/filecoin-project/lotus/journal/alerting" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" @@ -63,6 +64,8 @@ type CommonStruct struct { Discover func(p0 context.Context) (apitypes.OpenRPCDocument, error) `perm:"read"` + LogAlerts func(p0 context.Context) ([]alerting.Alert, error) `` + LogList func(p0 context.Context) ([]string, error) `perm:"write"` LogSetLevel func(p0 context.Context, p1 string, p2 string) error `perm:"write"` @@ -946,6 +949,17 @@ func (s *CommonStub) Discover(p0 context.Context) (apitypes.OpenRPCDocument, err return *new(apitypes.OpenRPCDocument), ErrNotSupported } +func (s *CommonStruct) LogAlerts(p0 context.Context) ([]alerting.Alert, error) { + if s.Internal.LogAlerts == nil { + return *new([]alerting.Alert), ErrNotSupported + } + return s.Internal.LogAlerts(p0) +} + +func (s *CommonStub) LogAlerts(p0 context.Context) ([]alerting.Alert, error) { + return *new([]alerting.Alert), ErrNotSupported +} + func (s *CommonStruct) LogList(p0 context.Context) ([]string, error) { if s.Internal.LogList == nil { return *new([]string), ErrNotSupported diff --git a/journal/alerting/alerts.go b/journal/alerting/alerts.go index 21e9a79b8..fb4bcdc4f 100644 --- a/journal/alerting/alerts.go +++ b/journal/alerting/alerts.go @@ -2,6 +2,7 @@ package alerting import ( "encoding/json" + "sort" "sync" "time" @@ -127,3 +128,22 @@ func (a *Alerting) Resolve(at AlertType, message interface{}) { return alert }) } + +func (a *Alerting) GetAlerts() []Alert { + a.lk.Lock() + defer a.lk.Unlock() + + out := make([]Alert, 0, len(a.alerts)) + for _, alert := range a.alerts { + out = append(out, alert) + } + sort.Slice(out, func(i, j int) bool { + if out[i].Type.System != out[j].Type.System { + return out[i].Type.System < out[j].Type.System + } + + return out[i].Type.Subsystem < out[j].Type.Subsystem + }) + + return out +} diff --git a/node/impl/common/common.go b/node/impl/common/common.go index a681e4a4a..182b74989 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -10,9 +10,11 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/filecoin-project/lotus/api" apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/journal/alerting" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -21,6 +23,7 @@ var session = uuid.New() type CommonAPI struct { fx.In + Alerting *alerting.Alerting APISecret *dtypes.APIAlg ShutdownChan dtypes.ShutdownChan } @@ -72,6 +75,10 @@ func (a *CommonAPI) LogSetLevel(ctx context.Context, subsystem, level string) er return logging.SetLogLevel(subsystem, level) } +func (a *CommonAPI) LogAlerts(ctx context.Context) ([]alerting.Alert, error) { + return a.Alerting.GetAlerts(), nil +} + func (a *CommonAPI) Shutdown(ctx context.Context) error { a.ShutdownChan <- struct{}{} return nil From b094e0913d7ff91db6958d9a09f5d0d0969cb587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 17 Aug 2021 14:55:35 +0200 Subject: [PATCH 030/122] untangle fsjournal dependencies --- cmd/lotus-miner/init.go | 3 ++- cmd/lotus/daemon.go | 3 ++- journal/{ => fsjournal}/fs.go | 22 +++++++++++++--------- node/modules/services.go | 3 ++- 4 files changed, 19 insertions(+), 12 deletions(-) rename journal/{ => fsjournal}/fs.go (81%) diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index 1cce52a41..c3e1c2770 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -51,6 +51,7 @@ import ( sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/genesis" "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/journal/fsjournal" storageminer "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node/modules" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -479,7 +480,7 @@ func storageMinerInit(ctx context.Context, cctx *cli.Context, api v1api.FullNode return err } - j, err := journal.OpenFSJournal(lr, journal.EnvDisabledEvents()) + j, err := fsjournal.OpenFSJournal(lr, journal.EnvDisabledEvents()) if err != nil { return fmt.Errorf("failed to open filesystem journal: %w", err) } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 486ac8ed7..31fc738e5 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -37,6 +37,7 @@ import ( lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/journal/fsjournal" "github.com/filecoin-project/lotus/lib/peermgr" "github.com/filecoin-project/lotus/lib/ulimit" "github.com/filecoin-project/lotus/metrics" @@ -476,7 +477,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return err } - j, err := journal.OpenFSJournal(lr, journal.EnvDisabledEvents()) + j, err := fsjournal.OpenFSJournal(lr, journal.EnvDisabledEvents()) if err != nil { return xerrors.Errorf("failed to open journal: %w", err) } diff --git a/journal/fs.go b/journal/fsjournal/fs.go similarity index 81% rename from journal/fs.go rename to journal/fsjournal/fs.go index a41583c93..71aaa95a5 100644 --- a/journal/fs.go +++ b/journal/fsjournal/fs.go @@ -1,4 +1,4 @@ -package journal +package fsjournal import ( "encoding/json" @@ -6,17 +6,21 @@ import ( "os" "path/filepath" + logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/node/repo" ) +var log = logging.Logger("fsjournal") + const RFC3339nocolon = "2006-01-02T150405Z0700" // fsJournal is a basic journal backed by files on a filesystem. type fsJournal struct { - EventTypeRegistry + journal.EventTypeRegistry dir string sizeLimit int64 @@ -24,7 +28,7 @@ type fsJournal struct { fi *os.File fSize int64 - incoming chan *Event + incoming chan *journal.Event closing chan struct{} closed chan struct{} @@ -32,17 +36,17 @@ type fsJournal struct { // OpenFSJournal constructs a rolling filesystem journal, with a default // per-file size limit of 1GiB. -func OpenFSJournal(lr repo.LockedRepo, disabled DisabledEvents) (Journal, error) { +func OpenFSJournal(lr repo.LockedRepo, disabled journal.DisabledEvents) (journal.Journal, error) { dir := filepath.Join(lr.Path(), "journal") if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to mk directory %s for file journal: %w", dir, err) } f := &fsJournal{ - EventTypeRegistry: NewEventTypeRegistry(disabled), + EventTypeRegistry: journal.NewEventTypeRegistry(disabled), dir: dir, sizeLimit: 1 << 30, - incoming: make(chan *Event, 32), + incoming: make(chan *journal.Event, 32), closing: make(chan struct{}), closed: make(chan struct{}), } @@ -56,7 +60,7 @@ func OpenFSJournal(lr repo.LockedRepo, disabled DisabledEvents) (Journal, error) return f, nil } -func (f *fsJournal) RecordEvent(evtType EventType, supplier func() interface{}) { +func (f *fsJournal) RecordEvent(evtType journal.EventType, supplier func() interface{}) { defer func() { if r := recover(); r != nil { log.Warnf("recovered from panic while recording journal event; type=%s, err=%v", evtType, r) @@ -67,7 +71,7 @@ func (f *fsJournal) RecordEvent(evtType EventType, supplier func() interface{}) return } - je := &Event{ + je := &journal.Event{ EventType: evtType, Timestamp: build.Clock.Now(), Data: supplier(), @@ -85,7 +89,7 @@ func (f *fsJournal) Close() error { return nil } -func (f *fsJournal) putEvent(evt *Event) error { +func (f *fsJournal) putEvent(evt *journal.Event) error { b, err := json.Marshal(evt) if err != nil { return err diff --git a/node/modules/services.go b/node/modules/services.go index 011b89163..ebcacb247 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -30,6 +30,7 @@ import ( "github.com/filecoin-project/lotus/chain/sub" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/journal/fsjournal" "github.com/filecoin-project/lotus/lib/peermgr" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/hello" @@ -237,7 +238,7 @@ func RandomSchedule(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.Sche } func OpenFilesystemJournal(lr repo.LockedRepo, lc fx.Lifecycle, disabled journal.DisabledEvents) (journal.Journal, error) { - jrnl, err := journal.OpenFSJournal(lr, disabled) + jrnl, err := fsjournal.OpenFSJournal(lr, disabled) if err != nil { return nil, err } From e2ba650a8cc7d4cd025d57ec34fade4ba9e1f739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 17 Aug 2021 15:15:36 +0200 Subject: [PATCH 031/122] cli for checking alerts --- api/api_common.go | 2 +- api/mocks/mock_full.go | 16 +++++++ api/proxy_gen.go | 2 +- api/v0api/v0mocks/mock_full.go | 16 +++++++ cli/log.go | 51 +++++++++++++++++++++ documentation/en/api-v0-methods-miner.md | 10 ++++ documentation/en/api-v0-methods.md | 10 ++++ documentation/en/api-v1-unstable-methods.md | 10 ++++ documentation/en/cli-lotus-miner.md | 15 ++++++ documentation/en/cli-lotus.md | 15 ++++++ 10 files changed, 145 insertions(+), 2 deletions(-) diff --git a/api/api_common.go b/api/api_common.go index 1a83dc25e..48d167b59 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -36,7 +36,7 @@ type Common interface { // LogAlerts returns list of all, active and inactive alerts tracked by the // node - LogAlerts(ctx context.Context) ([]alerting.Alert, error) + LogAlerts(ctx context.Context) ([]alerting.Alert, error) //perm:admin // MethodGroup: Common diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index a6e0e9e91..7e3818401 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -24,6 +24,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" miner "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" + alerting "github.com/filecoin-project/lotus/journal/alerting" marketevents "github.com/filecoin-project/lotus/markets/loggers" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" imports "github.com/filecoin-project/lotus/node/repo/imports" @@ -995,6 +996,21 @@ func (mr *MockFullNodeMockRecorder) ID(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockFullNode)(nil).ID), arg0) } +// LogAlerts mocks base method. +func (m *MockFullNode) LogAlerts(arg0 context.Context) ([]alerting.Alert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogAlerts", arg0) + ret0, _ := ret[0].([]alerting.Alert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogAlerts indicates an expected call of LogAlerts. +func (mr *MockFullNodeMockRecorder) LogAlerts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogAlerts", reflect.TypeOf((*MockFullNode)(nil).LogAlerts), arg0) +} + // LogList mocks base method. func (m *MockFullNode) LogList(arg0 context.Context) ([]string, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index db3e668a2..f2dc7c560 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -64,7 +64,7 @@ type CommonStruct struct { Discover func(p0 context.Context) (apitypes.OpenRPCDocument, error) `perm:"read"` - LogAlerts func(p0 context.Context) ([]alerting.Alert, error) `` + LogAlerts func(p0 context.Context) ([]alerting.Alert, error) `perm:"admin"` LogList func(p0 context.Context) ([]string, error) `perm:"write"` diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index ae717e1ec..97ca4e7fe 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -23,6 +23,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" miner "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" + alerting "github.com/filecoin-project/lotus/journal/alerting" marketevents "github.com/filecoin-project/lotus/markets/loggers" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" imports "github.com/filecoin-project/lotus/node/repo/imports" @@ -950,6 +951,21 @@ func (mr *MockFullNodeMockRecorder) ID(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockFullNode)(nil).ID), arg0) } +// LogAlerts mocks base method. +func (m *MockFullNode) LogAlerts(arg0 context.Context) ([]alerting.Alert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogAlerts", arg0) + ret0, _ := ret[0].([]alerting.Alert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogAlerts indicates an expected call of LogAlerts. +func (mr *MockFullNodeMockRecorder) LogAlerts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogAlerts", reflect.TypeOf((*MockFullNode)(nil).LogAlerts), arg0) +} + // LogList mocks base method. func (m *MockFullNode) LogList(arg0 context.Context) ([]string, error) { m.ctrl.T.Helper() diff --git a/cli/log.go b/cli/log.go index 4ab6aa748..7b223aa17 100644 --- a/cli/log.go +++ b/cli/log.go @@ -2,7 +2,9 @@ package cli import ( "fmt" + "time" + "github.com/fatih/color" "github.com/urfave/cli/v2" "golang.org/x/xerrors" ) @@ -13,6 +15,7 @@ var LogCmd = &cli.Command{ Subcommands: []*cli.Command{ LogList, LogSetLevel, + LogAlerts, }, } @@ -100,3 +103,51 @@ var LogSetLevel = &cli.Command{ return nil }, } + +var LogAlerts = &cli.Command{ + Name: "alerts", + Usage: "Get alert states", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Usage: "get all (active and inactive) alerts", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + alerts, err := api.LogAlerts(ctx) + if err != nil { + return xerrors.Errorf("getting alerts: %w", err) + } + + all := cctx.Bool("all") + + for _, alert := range alerts { + if !all && !alert.Active { + continue + } + + active := color.RedString("active ") + if !alert.Active { + active = color.GreenString("inactive") + } + + fmt.Printf("%s %s:%s\n", active, alert.Type.System, alert.Type.Subsystem) + if alert.LastResolved != nil { + fmt.Printf(" last resolved at %s; reason: %s\n", alert.LastResolved.Time.Truncate(time.Millisecond), alert.LastResolved.Message) + } + if alert.LastActive != nil { + fmt.Printf(" %s %s; reason: %s\n", color.YellowString("last raised at"), alert.LastActive.Time.Truncate(time.Millisecond), alert.LastActive.Message) + } + } + + return nil + }, +} diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index b02ac6c0d..1c6891329 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -44,6 +44,7 @@ * [I](#I) * [ID](#ID) * [Log](#Log) + * [LogAlerts](#LogAlerts) * [LogList](#LogList) * [LogSetLevel](#LogSetLevel) * [Market](#Market) @@ -664,6 +665,15 @@ Response: `"12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf"` ## Log +### LogAlerts + + +Perms: admin + +Inputs: `null` + +Response: `null` + ### LogList diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 6f030e979..f5907f494 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -71,6 +71,7 @@ * [I](#I) * [ID](#ID) * [Log](#Log) + * [LogAlerts](#LogAlerts) * [LogList](#LogList) * [LogSetLevel](#LogSetLevel) * [Market](#Market) @@ -1816,6 +1817,15 @@ Response: `"12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf"` ## Log +### LogAlerts + + +Perms: admin + +Inputs: `null` + +Response: `null` + ### LogList diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 72a9becba..e77e0c7bf 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -74,6 +74,7 @@ * [I](#I) * [ID](#ID) * [Log](#Log) + * [LogAlerts](#LogAlerts) * [LogList](#LogList) * [LogSetLevel](#LogSetLevel) * [Market](#Market) @@ -1880,6 +1881,15 @@ Response: `"12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf"` ## Log +### LogAlerts + + +Perms: admin + +Inputs: `null` + +Response: `null` + ### LogList diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index d93efa903..f69c8b144 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -506,6 +506,7 @@ USAGE: COMMANDS: list List log systems set-level Set log level + alerts Get alert states help, h Shows a list of commands or help for one command OPTIONS: @@ -561,6 +562,20 @@ OPTIONS: ``` +### lotus-miner log alerts +``` +NAME: + lotus-miner log alerts - Get alert states + +USAGE: + lotus-miner log alerts [command options] [arguments...] + +OPTIONS: + --all get all (active and inactive) alerts (default: false) + --help, -h show help (default: false) + +``` + ## lotus-miner wait-api ``` NAME: diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 855745348..0b354777e 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2366,6 +2366,7 @@ USAGE: COMMANDS: list List log systems set-level Set log level + alerts Get alert states help, h Shows a list of commands or help for one command OPTIONS: @@ -2421,6 +2422,20 @@ OPTIONS: ``` +### lotus log alerts +``` +NAME: + lotus log alerts - Get alert states + +USAGE: + lotus log alerts [command options] [arguments...] + +OPTIONS: + --all get all (active and inactive) alerts (default: false) + --help, -h show help (default: false) + +``` + ## lotus wait-api ``` NAME: From d6e309aa0133e0934eb16ca6000fabab060759cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 17 Aug 2021 15:29:38 +0200 Subject: [PATCH 032/122] Show alert count in lotus-miner info --- cmd/lotus-miner/info.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index f37952057..4c3d25e7b 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -32,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/journal/alerting" ) var infoCmd = &cli.Command{ @@ -116,6 +117,21 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() + alerts, err := minerApi.LogAlerts(ctx) + if err != nil { + return xerrors.Errorf("getting alerts: %w", err) + } + + activeAlerts := make([]alerting.Alert, 0) + for _, alert := range alerts { + if alert.Active { + activeAlerts = append(activeAlerts, alert) + } + } + if len(activeAlerts) > 0 { + fmt.Printf("%s (check %s)\n", color.RedString("⚠ %d Active alerts", len(activeAlerts)), color.YellowString("lotus-miner log alerts")) + } + err = handleMiningInfo(ctx, cctx, fullapi, minerApi) if err != nil { return err From 2d79b34a1df4e26a577151afdebcdb835203a7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 18 Aug 2021 13:50:08 +0200 Subject: [PATCH 033/122] fix lint --- journal/types.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/journal/types.go b/journal/types.go index 3e240a9f1..7381a4dc9 100644 --- a/journal/types.go +++ b/journal/types.go @@ -4,12 +4,8 @@ import ( "fmt" "strings" "time" - - logging "github.com/ipfs/go-log/v2" ) -var log = logging.Logger("journal") - var ( // DefaultDisabledEvents lists the journal events disabled by // default, usually because they are considered noisy. From 502f2ba2ef55871e70373ffa21e828652cc35e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 18 Aug 2021 14:41:13 +0200 Subject: [PATCH 034/122] alerting: Add some docs --- journal/alerting/alerts.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/journal/alerting/alerts.go b/journal/alerting/alerts.go index fb4bcdc4f..f0dd3126b 100644 --- a/journal/alerting/alerts.go +++ b/journal/alerting/alerts.go @@ -11,7 +11,10 @@ import ( ) var log = logging.Logger("alerting") - +// Alerting provides simple stateful alert system. Consumers can register alerts, +// which can be raised and resolved. +// +// When an alert is raised or resolved, a related journal entry is recorded. type Alerting struct { j journal.Journal @@ -19,10 +22,12 @@ type Alerting struct { alerts map[AlertType]Alert } +// AlertType is a unique alert identifier type AlertType struct { System, Subsystem string } +// AlertEvent contains information about alert state transition type AlertEvent struct { Message json.RawMessage Time time.Time @@ -93,6 +98,7 @@ func (a *Alerting) update(at AlertType, message interface{}, upd func(Alert, jso a.alerts[at] = upd(alert, rawMsg) } +// Raise marks the alert condition as active and records related event in the journal func (a *Alerting) Raise(at AlertType, message interface{}) { log.Errorw("alert raised", "type", at, "message", message) @@ -111,6 +117,7 @@ func (a *Alerting) Raise(at AlertType, message interface{}) { }) } +// Resolve marks the alert condition as resolved and records related event in the journal func (a *Alerting) Resolve(at AlertType, message interface{}) { log.Errorw("alert resolved", "type", at, "message", message) @@ -122,13 +129,14 @@ func (a *Alerting) Resolve(at AlertType, message interface{}) { } a.j.RecordEvent(alert.journalType, func() interface{} { - return alert.LastResolved + return alert.LastResolved // todo log something more useful }) return alert }) } +// GetAlerts returns all registered (active and inactive) alerts func (a *Alerting) GetAlerts() []Alert { a.lk.Lock() defer a.lk.Unlock() From 88df721dcc2f6e9ddcc320bd1e746f3151630744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 18 Aug 2021 14:46:06 +0200 Subject: [PATCH 035/122] alerting: Add Type to AlertEvent --- journal/alerting/alerts.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/journal/alerting/alerts.go b/journal/alerting/alerts.go index f0dd3126b..b5df2b8ba 100644 --- a/journal/alerting/alerts.go +++ b/journal/alerting/alerts.go @@ -11,6 +11,7 @@ import ( ) var log = logging.Logger("alerting") + // Alerting provides simple stateful alert system. Consumers can register alerts, // which can be raised and resolved. // @@ -29,6 +30,7 @@ type AlertType struct { // AlertEvent contains information about alert state transition type AlertEvent struct { + Type string // either 'raised' or 'resolved' Message json.RawMessage Time time.Time } @@ -105,6 +107,7 @@ func (a *Alerting) Raise(at AlertType, message interface{}) { a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert { alert.Active = true alert.LastActive = &AlertEvent{ + Type: "raised", Message: rawMsg, Time: time.Now(), } @@ -124,12 +127,13 @@ func (a *Alerting) Resolve(at AlertType, message interface{}) { a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert { alert.Active = false alert.LastResolved = &AlertEvent{ + Type: "resolved", Message: rawMsg, Time: time.Now(), } a.j.RecordEvent(alert.journalType, func() interface{} { - return alert.LastResolved // todo log something more useful + return alert.LastResolved }) return alert From 667836fbe5cae4dc51a45b0eae40e8e9b7f486d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 18 Aug 2021 14:59:59 +0200 Subject: [PATCH 036/122] alerting: Add a test --- journal/alerting/alerts_test.go | 61 +++++++++++++++++++++++++++ journal/mockjournal/journal.go | 75 +++++++++++++++++++++++++++++++++ journal/types.go | 2 + 3 files changed, 138 insertions(+) create mode 100644 journal/alerting/alerts_test.go create mode 100644 journal/mockjournal/journal.go diff --git a/journal/alerting/alerts_test.go b/journal/alerting/alerts_test.go new file mode 100644 index 000000000..46ab4bbbf --- /dev/null +++ b/journal/alerting/alerts_test.go @@ -0,0 +1,61 @@ +package alerting + +import ( + "encoding/json" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/journal/mockjournal" +) + +func TestAlerting(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + j := mockjournal.NewMockJournal(mockCtrl) + + a := NewAlertingSystem(j) + + j.EXPECT().RegisterEventType("s1", "b1").Return(journal.EventType{System: "s1", Event: "b1"}) + al1 := a.AddAlertType("s1", "b1") + + j.EXPECT().RegisterEventType("s2", "b2").Return(journal.EventType{System: "s2", Event: "b2"}) + al2 := a.AddAlertType("s2", "b2") + + l := a.GetAlerts() + require.Len(t, l, 2) + require.Equal(t, al1, l[0].Type) + require.Equal(t, al2, l[1].Type) + + for _, alert := range l { + require.False(t, alert.Active) + require.Nil(t, alert.LastActive) + require.Nil(t, alert.LastResolved) + } + + j.EXPECT().RecordEvent(a.alerts[al1].journalType, gomock.Any()) + a.Raise(al1, "test") + + for _, alert := range l { // check for no magic mutations + require.False(t, alert.Active) + require.Nil(t, alert.LastActive) + require.Nil(t, alert.LastResolved) + } + + l = a.GetAlerts() + require.Len(t, l, 2) + require.Equal(t, al1, l[0].Type) + require.Equal(t, al2, l[1].Type) + + require.True(t, l[0].Active) + require.NotNil(t, l[0].LastActive) + require.Equal(t, "raised", l[0].LastActive.Type) + require.Equal(t, json.RawMessage(`"test"`), l[0].LastActive.Message) + require.Nil(t, l[0].LastResolved) + + require.False(t, l[1].Active) + require.Nil(t, l[1].LastActive) + require.Nil(t, l[1].LastResolved) +} diff --git a/journal/mockjournal/journal.go b/journal/mockjournal/journal.go new file mode 100644 index 000000000..3668923e5 --- /dev/null +++ b/journal/mockjournal/journal.go @@ -0,0 +1,75 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/journal (interfaces: Journal) + +// Package mockjournal is a generated GoMock package. +package mockjournal + +import ( + reflect "reflect" + + journal "github.com/filecoin-project/lotus/journal" + gomock "github.com/golang/mock/gomock" +) + +// MockJournal is a mock of Journal interface. +type MockJournal struct { + ctrl *gomock.Controller + recorder *MockJournalMockRecorder +} + +// MockJournalMockRecorder is the mock recorder for MockJournal. +type MockJournalMockRecorder struct { + mock *MockJournal +} + +// NewMockJournal creates a new mock instance. +func NewMockJournal(ctrl *gomock.Controller) *MockJournal { + mock := &MockJournal{ctrl: ctrl} + mock.recorder = &MockJournalMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockJournal) EXPECT() *MockJournalMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockJournal) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockJournalMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockJournal)(nil).Close)) +} + +// RecordEvent mocks base method. +func (m *MockJournal) RecordEvent(arg0 journal.EventType, arg1 func() interface{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordEvent", arg0, arg1) +} + +// RecordEvent indicates an expected call of RecordEvent. +func (mr *MockJournalMockRecorder) RecordEvent(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordEvent", reflect.TypeOf((*MockJournal)(nil).RecordEvent), arg0, arg1) +} + +// RegisterEventType mocks base method. +func (m *MockJournal) RegisterEventType(arg0, arg1 string) journal.EventType { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterEventType", arg0, arg1) + ret0, _ := ret[0].(journal.EventType) + return ret0 +} + +// RegisterEventType indicates an expected call of RegisterEventType. +func (mr *MockJournalMockRecorder) RegisterEventType(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterEventType", reflect.TypeOf((*MockJournal)(nil).RegisterEventType), arg0, arg1) +} diff --git a/journal/types.go b/journal/types.go index 7381a4dc9..af21607bf 100644 --- a/journal/types.go +++ b/journal/types.go @@ -65,6 +65,8 @@ func (et EventType) Enabled() bool { return et.safe && et.enabled } +//go:generate go run github.com/golang/mock/mockgen -destination=mockjournal/journal.go -package=mockjournal . Journal + // Journal represents an audit trail of system actions. // // Every entry is tagged with a timestamp, a system name, and an event name. From 1ba427f638d684a7872cf889905a3c5f8cfe4cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 26 Aug 2021 15:59:54 +0200 Subject: [PATCH 037/122] alerting: Address review --- build/limits.go | 6 ++++++ build/openrpc/full.json.gz | Bin 25412 -> 25415 bytes build/openrpc/miner.json.gz | Bin 10375 -> 10374 bytes build/openrpc/worker.json.gz | Bin 2711 -> 2709 bytes lib/ulimit/ulimit.go | 18 ++++++++++++++---- lib/ulimit/ulimit_freebsd.go | 2 +- lib/ulimit/ulimit_test.go | 4 +++- lib/ulimit/ulimit_unix.go | 2 +- node/builder.go | 3 ++- node/builder_miner.go | 3 ++- node/modules/alerts.go | 7 ++++--- 11 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 build/limits.go diff --git a/build/limits.go b/build/limits.go new file mode 100644 index 000000000..93d56577c --- /dev/null +++ b/build/limits.go @@ -0,0 +1,6 @@ +package build + +var ( + DefaultFDLimit uint64 = 16 << 10 + MinerFDLimit uint64 = 100_000 +) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 6cae0a3833531540a2c695e2b3711a49f2043f14..4892e090148247a49af2cd354ba5a370f055ae76 100644 GIT binary patch delta 22250 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 delta 9407 zcmV;wBtYASQHN2G90k{zesJWGA~$~%tKS`T53G)b$&6Yj-z<)~i>wptg7P&3SC?aO zdUEXP>(z>S9692X2k|feD?=BrR=w9Z*p|BGyHeR&Z0{k6s(a;y{Xt&u=x zl`Xhw5v_SA>KQoIQ126XSg`-6J7 z>6Z^hY_*C#`j$ran}s=A$~Pr=Ghoe!@B#EZxDmf~<7y7Lc51!_?WUBv2$ERK^hD{J zW|y-ue6x$+5a@>&CM6FJbEM=1$$#U&QGF2^!O+OZ4p{X;S|fjhS8$5E(RYPHGisWr z_Y*Lyi_FA01m0WA%y80I{im; zI2!IB9}bU(2S;C`!TBwpH*+2y>6vr9K=$pWNAHBBt;L!N2u8-7;_eliV}T!XF6o3{ zdX&yOU^M`rzc7CV)?eZ)B%Tj}5bv+St0i20j@Dl-;TOg|Omc0Y1X?-FoCCfRXQ!t@ z3|kCO3}v_ct)JSRF-d*ah(<-}ko*q^(3bcD4?zdG)V{R@f-A@c*Iy=xqx)0LH1#`taW>x3u|bW{ z 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 R{|f*B|NjItq+3~f008}TT_*ql diff --git a/lib/ulimit/ulimit.go b/lib/ulimit/ulimit.go index 7e80fd223..764cd6874 100644 --- a/lib/ulimit/ulimit.go +++ b/lib/ulimit/ulimit.go @@ -3,11 +3,14 @@ package ulimit // from go-ipfs import ( + "errors" "fmt" "os" "strconv" "syscall" + "github.com/filecoin-project/lotus/build" + logging "github.com/ipfs/go-log/v2" ) @@ -17,7 +20,7 @@ var ( supportsFDManagement = false // getlimit returns the soft and hard limits of file descriptors counts - GetLimit func() (uint64, uint64, error) + getLimit func() (uint64, uint64, error) // set limit sets the soft and hard limits of file descriptors counts setLimit func(uint64, uint64) error ) @@ -25,8 +28,15 @@ var ( // minimum file descriptor limit before we complain const minFds = 2048 -// default max file descriptor limit. -const maxFds = 16 << 10 +var ErrUnsupported = errors.New("unsupported") + +func GetLimit() (uint64, uint64, error) { + if getLimit == nil { + return 0, 0, ErrUnsupported + } + + return getLimit() +} // userMaxFDs returns the value of LOTUS_FD_MAX func userMaxFDs() uint64 { @@ -55,7 +65,7 @@ func ManageFdLimit() (changed bool, newLimit uint64, err error) { return false, 0, nil } - targetLimit := uint64(maxFds) + targetLimit := build.DefaultFDLimit userLimit := userMaxFDs() if userLimit > 0 { targetLimit = userLimit diff --git a/lib/ulimit/ulimit_freebsd.go b/lib/ulimit/ulimit_freebsd.go index aeea77d9d..7e50436f3 100644 --- a/lib/ulimit/ulimit_freebsd.go +++ b/lib/ulimit/ulimit_freebsd.go @@ -11,7 +11,7 @@ import ( func init() { supportsFDManagement = true - GetLimit = freebsdGetLimit + getLimit = freebsdGetLimit setLimit = freebsdSetLimit } diff --git a/lib/ulimit/ulimit_test.go b/lib/ulimit/ulimit_test.go index c99b7fb57..ad27a163a 100644 --- a/lib/ulimit/ulimit_test.go +++ b/lib/ulimit/ulimit_test.go @@ -8,6 +8,8 @@ import ( "strings" "syscall" "testing" + + "github.com/filecoin-project/lotus/build" ) func TestManageFdLimit(t *testing.T) { @@ -16,7 +18,7 @@ func TestManageFdLimit(t *testing.T) { t.Errorf("Cannot manage file descriptors") } - if maxFds != uint64(16<<10) { + if build.DefaultFDLimit != uint64(16<<10) { t.Errorf("Maximum file descriptors default value changed") } } diff --git a/lib/ulimit/ulimit_unix.go b/lib/ulimit/ulimit_unix.go index e015b2b32..a351236dc 100644 --- a/lib/ulimit/ulimit_unix.go +++ b/lib/ulimit/ulimit_unix.go @@ -8,7 +8,7 @@ import ( func init() { supportsFDManagement = true - GetLimit = unixGetLimit + getLimit = unixGetLimit setLimit = unixSetLimit } diff --git a/node/builder.go b/node/builder.go index 58095da8f..887f9046f 100644 --- a/node/builder.go +++ b/node/builder.go @@ -28,6 +28,7 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/stores" @@ -152,7 +153,7 @@ func defaults() []Option { Override(new(journal.Journal), modules.OpenFilesystemJournal), Override(new(*alerting.Alerting), alerting.NewAlertingSystem), - Override(CheckFDLimit, modules.CheckFdLimit(16<<10)), + Override(CheckFDLimit, modules.CheckFdLimit(build.DefaultFDLimit)), Override(new(system.MemoryConstraints), modules.MemoryConstraints), Override(InitMemoryWatchdog, modules.MemoryWatchdog), diff --git a/node/builder_miner.go b/node/builder_miner.go index 4c9d2492a..c8ee5d48c 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -15,6 +15,7 @@ import ( storage2 "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen/slashfilter" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" @@ -74,7 +75,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(CheckFDLimit, modules.CheckFdLimit(100_000)), // recommend at least 100k FD limit to miners + Override(CheckFDLimit, modules.CheckFdLimit(build.MinerFDLimit)), // recommend at least 100k FD limit to miners Override(new(api.MinerSubsystems), modules.ExtractEnabledMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), diff --git a/node/modules/alerts.go b/node/modules/alerts.go index 89261e231..75be08354 100644 --- a/node/modules/alerts.go +++ b/node/modules/alerts.go @@ -7,13 +7,14 @@ import ( func CheckFdLimit(min uint64) func(al *alerting.Alerting) { return func(al *alerting.Alerting) { - if ulimit.GetLimit == nil { + soft, _, err := ulimit.GetLimit() + + if err == ulimit.ErrUnsupported { + log.Warn("FD limit monitoring not available") return } alert := al.AddAlertType("process", "fd-limit") - - soft, _, err := ulimit.GetLimit() if err != nil { al.Raise(alert, map[string]string{ "message": "failed to get FD limit", From b9015f8f29373d310fd50e66b7fbc739ae4cf2fa Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 26 Aug 2021 12:20:54 -0700 Subject: [PATCH 038/122] fix: disable broken testground integration test --- .github/workflows/testground-on-push.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testground-on-push.yml b/.github/workflows/testground-on-push.yml index 2a3c8af1d..4b5a6e6cf 100644 --- a/.github/workflows/testground-on-push.yml +++ b/.github/workflows/testground-on-push.yml @@ -10,10 +10,11 @@ jobs: strategy: matrix: include: - - backend_addr: ci.testground.ipfs.team - backend_proto: https - plan_directory: testplans/lotus-soup - composition_file: testplans/lotus-soup/_compositions/baseline-k8s-3-1.toml + # Currently broken. + #- backend_addr: ci.testground.ipfs.team + # backend_proto: https + # plan_directory: testplans/lotus-soup + # composition_file: testplans/lotus-soup/_compositions/baseline-k8s-3-1.toml - backend_addr: ci.testground.ipfs.team backend_proto: https plan_directory: testplans/lotus-soup From 1cecf3c9cb71aaa378ad5f85cb270917e9b53de5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 26 Aug 2021 12:40:12 -0700 Subject: [PATCH 039/122] ci: exclude cruft from code coverage 1. Exclude generated code. 2. Exclude generated actors, we mostly test against the latest version. --- .codecov.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index d004bcb9e..68a5ecc5a 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,3 +1,18 @@ +ignore: + # Auto generated + - "^.*_gen.go$" + - "^.*/mock_full.go$" + # Old actors. + - "^chain/actors/builtin/[^/]*/(message|state|v)[0-4]\\.go$" # We test the latest version only. + # Tests + - "api/test/**" + - "conformance/**" + # Generators + - "gen/**" + - "chain/actors/agen/**" + # Non-critical utilities + - "api/docgen/**" + - "api/docgen-openrpc/**" coverage: status: patch: off @@ -36,7 +51,7 @@ coverage: informational: false paths: - "chain" - node: + node: target: auto threshold: 0.5% informational: false @@ -55,4 +70,4 @@ coverage: informational: true paths: - "cli" - - "cmd" \ No newline at end of file + - "cmd" From b42171dc04d97adb70016bcd9ba2f94baf8d24e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 26 Aug 2021 16:22:43 +0200 Subject: [PATCH 040/122] sealing: Check piece CIDs after AddPiece --- extern/storage-sealing/input.go | 13 +++++++++++++ extern/storage-sealing/states_sealing.go | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 1a0b7bf1e..218616aea 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -9,6 +9,7 @@ import ( "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-commp-utils/zerocomm" "github.com/filecoin-project/go-padreader" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-statemachine" @@ -187,6 +188,8 @@ func (m *Sealing) handleAddPiece(ctx statemachine.Context, sector SectorInfo) er offset += padLength.Unpadded() for _, p := range pads { + expectCid := zerocomm.ZeroPieceCommitment(p.Unpadded()) + ppi, err := m.sealer.AddPiece(sectorstorage.WithPriority(ctx.Context(), DealSectorPriority), m.minerSector(sector.SectorType, sector.SectorNumber), pieceSizes, @@ -197,6 +200,11 @@ func (m *Sealing) handleAddPiece(ctx statemachine.Context, sector SectorInfo) er deal.accepted(sector.SectorNumber, offset, err) return ctx.Send(SectorAddPieceFailed{err}) } + if !ppi.PieceCID.Equals(expectCid) { + err = xerrors.Errorf("got unexpected padding piece CID: expected:%s, got:%s", expectCid, ppi.PieceCID) + deal.accepted(sector.SectorNumber, offset, err) + return ctx.Send(SectorAddPieceFailed{err}) + } pieceSizes = append(pieceSizes, p.Unpadded()) res.NewPieces = append(res.NewPieces, Piece{ @@ -214,6 +222,11 @@ func (m *Sealing) handleAddPiece(ctx statemachine.Context, sector SectorInfo) er deal.accepted(sector.SectorNumber, offset, err) return ctx.Send(SectorAddPieceFailed{err}) } + if !ppi.PieceCID.Equals(deal.deal.DealProposal.PieceCID) { + err = xerrors.Errorf("got unexpected piece CID: expected:%s, got:%s", deal.deal.DealProposal.PieceCID, ppi.PieceCID) + deal.accepted(sector.SectorNumber, offset, err) + return ctx.Send(SectorAddPieceFailed{err}) + } log.Infow("deal added to a sector", "deal", deal.deal.DealID, "sector", sector.SectorNumber, "piece", ppi.PieceCID) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index ed704d9dc..0df000eda 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -7,6 +7,7 @@ import ( "github.com/ipfs/go-cid" "golang.org/x/xerrors" + "github.com/filecoin-project/go-commp-utils/zerocomm" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" @@ -88,10 +89,15 @@ func (m *Sealing) padSector(ctx context.Context, sectorID storage.SectorRef, exi out := make([]abi.PieceInfo, len(sizes)) for i, size := range sizes { + expectCid := zerocomm.ZeroPieceCommitment(size) + ppi, err := m.sealer.AddPiece(ctx, sectorID, existingPieceSizes, size, NewNullReader(size)) if err != nil { return nil, xerrors.Errorf("add piece: %w", err) } + if !expectCid.Equals(expectCid) { + return nil, xerrors.Errorf("got unexpected padding piece CID: expected:%s, got:%s", expectCid, ppi.PieceCID) + } existingPieceSizes = append(existingPieceSizes, size) From 737ee90e69da1ab70436f7db685511ab42010e4e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 26 Aug 2021 14:27:28 -0700 Subject: [PATCH 041/122] Revert "Merge pull request #7187 from filecoin-project/test/disable-broken-testground" This reverts commit a9826dce27a37718c990d8d7f5c081c5b64f60d0, reversing changes made to 8bcd917c12d6a741942c467f04f0184e510714a1. --- .github/workflows/testground-on-push.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/testground-on-push.yml b/.github/workflows/testground-on-push.yml index 4b5a6e6cf..2a3c8af1d 100644 --- a/.github/workflows/testground-on-push.yml +++ b/.github/workflows/testground-on-push.yml @@ -10,11 +10,10 @@ jobs: strategy: matrix: include: - # Currently broken. - #- backend_addr: ci.testground.ipfs.team - # backend_proto: https - # plan_directory: testplans/lotus-soup - # composition_file: testplans/lotus-soup/_compositions/baseline-k8s-3-1.toml + - backend_addr: ci.testground.ipfs.team + backend_proto: https + plan_directory: testplans/lotus-soup + composition_file: testplans/lotus-soup/_compositions/baseline-k8s-3-1.toml - backend_addr: ci.testground.ipfs.team backend_proto: https plan_directory: testplans/lotus-soup From 0e8abc197328636120752eca434b3adf5eed54e8 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Thu, 26 Aug 2021 23:55:46 +0000 Subject: [PATCH 042/122] Insert miner and network power data as gibibytes to avoid int64 overflows. --- cmd/lotus-stats/README.md | 17 +- cmd/lotus-stats/chain.dashboard.json | 340 +++++++++++++++++++++++---- cmd/lotus-stats/env.stats | 6 +- cmd/lotus-stats/setup.bash | 8 +- tools/stats/metrics.go | 11 +- 5 files changed, 317 insertions(+), 65 deletions(-) diff --git a/cmd/lotus-stats/README.md b/cmd/lotus-stats/README.md index 04220aa3b..4311e1aa7 100644 --- a/cmd/lotus-stats/README.md +++ b/cmd/lotus-stats/README.md @@ -7,22 +7,24 @@ Influx configuration can be configured through env variables. ``` -INFLUX_ADDR="http://localhost:8086" -INFLUX_USER="" -INFLUX_PASS="" +LOTUS_STATS_INFLUX_ADDR="http://localhost:8086" +LOTUS_STATS_INFLUX_USER="" +LOTUS_STATS_INFLUX_PASS="" ``` ## Usage -lotus-stats will be default look in `~/.lotus` to connect to a running daemon and resume collecting stats from last record block height. +lotus-stats will look in `~/.lotus` to connect to a running daemon and resume collecting stats from last record block height. For other usage see `./lotus-stats --help` ``` go build -o lotus-stats *.go -. env.stats && ./lotus-stats +. env.stats && ./lotus-stats run ``` +For large networks there is an additional query in the `Top Miner Power` table, which can be toggled on to only show miners larger +than 1 PiB. This is a good option to enable to reduce the number of miners listed when viewing mainnet stats. ## Development @@ -37,3 +39,8 @@ docker-compose up -d ``` The default username and password for grafana are both `admin`. + +## Updating the dashboard + +After importing the provided dashboard in `chain.dashboard.json`, you may make changes to the dashboard. To export +the dashboard to be commited back to the project, make sure the option "sharing externally" is toggled on. diff --git a/cmd/lotus-stats/chain.dashboard.json b/cmd/lotus-stats/chain.dashboard.json index 8083c96b1..f8f545978 100644 --- a/cmd/lotus-stats/chain.dashboard.json +++ b/cmd/lotus-stats/chain.dashboard.json @@ -30,6 +30,12 @@ "id": "table-old", "name": "Table (old)", "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" } ], "annotations": { @@ -47,11 +53,217 @@ }, "editable": true, "gnetId": null, - "graphTooltip": 0, + "graphTooltip": 1, "id": null, - "iteration": 1604018016916, + "iteration": 1630020824868, "links": [], "panels": [ + { + "datasource": "$network", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 0, + "y": 0 + }, + "id": 56, + "options": { + "content": "
\n \n
\n", + "mode": "html" + }, + "pluginVersion": "7.3.0", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "transparent": true, + "type": "text" + }, + { + "datasource": "$network", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 16, + "x": 5, + "y": 0 + }, + "id": 58, + "options": { + "content": "
\n The stats dashboard is undergoing some maintance. Please check back later.\n
", + "mode": "html" + }, + "pluginVersion": "7.3.0", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "transparent": true, + "type": "text" + }, + { + "datasource": "$network", + "fieldConfig": { + "defaults": { + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 21, + "y": 0 + }, + "id": 54, + "options": { + "content": "
\n
\n \n
\n
", + "mode": "html" + }, + "pluginVersion": "7.3.0", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "transparent": true, + "type": "text" + }, { "aliasColors": {}, "bars": true, @@ -72,7 +284,7 @@ "h": 9, "w": 24, "x": 0, - "y": 0 + "y": 3 }, "hiddenSeries": false, "hideTimeOverride": false, @@ -186,7 +398,7 @@ ], "hide": false, "orderByTime": "ASC", - "policy": "defult", + "policy": "default", "query": "SELECT TRIPLE_EXPONENTIAL_MOVING_AVERAGE(sum(\"value\"), 40) FROM \"chain.election\" WHERE $timeFilter -$blockInterval*40 AND time < now() - $blockInterval*3 GROUP BY time($blockInterval) fill(0)", "rawQuery": true, "refId": "B", @@ -271,7 +483,7 @@ "h": 4, "w": 8, "x": 0, - "y": 9 + "y": 12 }, "hiddenSeries": false, "id": 22, @@ -396,7 +608,7 @@ "h": 4, "w": 4, "x": 8, - "y": 9 + "y": 12 }, "id": 12, "interval": null, @@ -493,7 +705,7 @@ }, "overrides": [] }, - "format": "bytes", + "format": "gbytes", "gauge": { "maxValue": 100, "minValue": 0, @@ -505,7 +717,7 @@ "h": 4, "w": 4, "x": 12, - "y": 9 + "y": 12 }, "id": 42, "interval": "", @@ -626,7 +838,7 @@ "h": 4, "w": 8, "x": 16, - "y": 9 + "y": 12 }, "id": 6, "interval": null, @@ -741,7 +953,7 @@ "h": 3, "w": 4, "x": 0, - "y": 13 + "y": 16 }, "id": 4, "interval": null, @@ -844,7 +1056,7 @@ "h": 3, "w": 4, "x": 4, - "y": 13 + "y": 16 }, "id": 14, "interval": null, @@ -951,7 +1163,7 @@ "h": 3, "w": 4, "x": 8, - "y": 13 + "y": 16 }, "id": 32, "interval": null, @@ -1071,7 +1283,7 @@ "h": 3, "w": 4, "x": 12, - "y": 13 + "y": 16 }, "id": 20, "interval": null, @@ -1191,7 +1403,7 @@ "h": 3, "w": 4, "x": 16, - "y": 13 + "y": 16 }, "id": 8, "interval": null, @@ -1311,7 +1523,7 @@ "h": 3, "w": 4, "x": 20, - "y": 13 + "y": 16 }, "id": 10, "interval": null, @@ -1435,7 +1647,7 @@ "h": 3, "w": 4, "x": 0, - "y": 16 + "y": 19 }, "id": 16, "interval": "", @@ -1536,7 +1748,7 @@ "h": 3, "w": 16, "x": 4, - "y": 16 + "y": 19 }, "hiddenSeries": false, "id": 2, @@ -1706,7 +1918,7 @@ "h": 3, "w": 4, "x": 20, - "y": 16 + "y": 19 }, "id": 30, "interval": null, @@ -1795,7 +2007,7 @@ "h": 21, "w": 4, "x": 0, - "y": 19 + "y": 22 }, "id": 28, "pageSize": null, @@ -1827,7 +2039,7 @@ "pattern": "power", "thresholds": [], "type": "number", - "unit": "bytes" + "unit": "gbytes" }, { "alias": "", @@ -1894,7 +2106,7 @@ "h": 8, "w": 12, "x": 4, - "y": 19 + "y": 22 }, "hiddenSeries": false, "id": 40, @@ -1979,6 +2191,44 @@ ], "slimit": "", "tags": [] + }, + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": true, + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"value\") FROM \"chain.miner_power\" WHERE $timeFilter AND value > 1125899906842624 GROUP BY time($__interval), \"miner\" fill(null)", + "rawQuery": true, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] } ], "thresholds": [], @@ -2002,7 +2252,7 @@ "yaxes": [ { "decimals": 2, - "format": "bytes", + "format": "gbytes", "label": "Power", "logBase": 1, "max": "100", @@ -2037,7 +2287,7 @@ "h": 21, "w": 8, "x": 16, - "y": 19 + "y": 22 }, "id": 18, "pageSize": null, @@ -2142,7 +2392,7 @@ "h": 7, "w": 12, "x": 4, - "y": 27 + "y": 30 }, "hiddenSeries": false, "id": 50, @@ -2182,7 +2432,7 @@ }, { "params": [ - "null" + "previous" ], "type": "fill" } @@ -2221,7 +2471,7 @@ }, { "params": [ - "null" + "previous" ], "type": "fill" } @@ -2260,7 +2510,7 @@ }, { "params": [ - "null" + "previous" ], "type": "fill" } @@ -2361,7 +2611,7 @@ "h": 6, "w": 12, "x": 4, - "y": 34 + "y": 37 }, "hiddenSeries": false, "id": 44, @@ -2491,7 +2741,7 @@ "h": 9, "w": 12, "x": 0, - "y": 40 + "y": 43 }, "hiddenSeries": false, "id": 34, @@ -2632,7 +2882,7 @@ "h": 9, "w": 12, "x": 12, - "y": 40 + "y": 43 }, "hiddenSeries": false, "id": 36, @@ -2785,7 +3035,7 @@ "h": 8, "w": 12, "x": 0, - "y": 49 + "y": 52 }, "hiddenSeries": false, "id": 48, @@ -2915,7 +3165,7 @@ "h": 8, "w": 12, "x": 12, - "y": 49 + "y": 52 }, "hiddenSeries": false, "id": 46, @@ -3046,7 +3296,7 @@ "h": 8, "w": 12, "x": 0, - "y": 57 + "y": 60 }, "hiddenSeries": false, "id": 51, @@ -3218,7 +3468,7 @@ "h": 8, "w": 12, "x": 12, - "y": 57 + "y": 60 }, "hiddenSeries": false, "id": 52, @@ -3417,8 +3667,8 @@ { "current": { "selected": false, - "text": "filecoin-ntwk-testnet", - "value": "filecoin-ntwk-testnet" + "text": "ntwk-localstats", + "value": "ntwk-localstats" }, "error": null, "hide": 0, @@ -3430,7 +3680,7 @@ "query": "influxdb", "queryValue": "", "refresh": 1, - "regex": "/^filecoin-ntwk-/", + "regex": "/^ntwk-/", "skipUrlSync": false, "type": "datasource" }, @@ -3466,23 +3716,13 @@ "to": "now" }, "timepicker": { + "hidden": true, "refresh_intervals": [ - "5s", - "10s", - "25s", - "30s", - "45s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" + "30s" ] }, "timezone": "", "title": "Filecoin Chain Stats", "uid": "z6FtI92Zz", - "version": 4 + "version": 2 } diff --git a/cmd/lotus-stats/env.stats b/cmd/lotus-stats/env.stats index ad5ec1619..bc4e44ca9 100644 --- a/cmd/lotus-stats/env.stats +++ b/cmd/lotus-stats/env.stats @@ -1,3 +1,3 @@ -export INFLUX_ADDR="http://localhost:18086" -export INFLUX_USER="" -export INFLUX_PASS="" +export LOTUS_STATS_INFLUX_ADDR="http://localhost:18086" +export LOTUS_STATS_INFLUX_USER="" +export LOTUS_STATS_INFLUX_PASS="" diff --git a/cmd/lotus-stats/setup.bash b/cmd/lotus-stats/setup.bash index 6510c2fc6..5aacad472 100755 --- a/cmd/lotus-stats/setup.bash +++ b/cmd/lotus-stats/setup.bash @@ -1,10 +1,10 @@ #!/usr/bin/env bash -GRAFANA_HOST="http://localhost:13000" +GRAFANA_HOST="localhost:13000" -curl -s -XPOST http://admin:admin@$GRAFANA_HOST/api/datasources -H 'Content-Type: text/json' --data-binary @- > /dev/null << EOF +curl -XPOST http://admin:admin@$GRAFANA_HOST/api/datasources -H 'Content-Type: text/json' --data-binary @- > /dev/null << EOF { - "name":"filecoin-ntwk-localstats", + "name":"ntwk-localstats", "type":"influxdb", "database":"lotus", "url": "http://influxdb:8086", @@ -13,7 +13,7 @@ curl -s -XPOST http://admin:admin@$GRAFANA_HOST/api/datasources -H 'Content-Type } EOF -curl -s -XPOST http://admin:admin@$GRAFANA_HOST/api/dashboards/import -H 'Content-Type: text/json' --data-binary @- << EOF | jq -r "\"http://$GRAFANA_HOST\" + .importedUrl" +curl -XPOST http://admin:admin@$GRAFANA_HOST/api/dashboards/import -H 'Content-Type: text/json' --data-binary @- << EOF | jq -r "\"http://$GRAFANA_HOST\" + .importedUrl" { "dashboard": $(cat ./chain.dashboard.json), "overwrite": true, diff --git a/tools/stats/metrics.go b/tools/stats/metrics.go index 7764c4bca..ca3f26336 100644 --- a/tools/stats/metrics.go +++ b/tools/stats/metrics.go @@ -267,7 +267,11 @@ func RecordTipsetStatePoints(ctx context.Context, api v0api.FullNode, pl *PointL return err } - p = NewPoint("chain.power", totalPower.TotalPower.QualityAdjPower.Int64()) + // We divide the power into gibibytes because 2^63 bytes is 8 exbibytes which is smaller than the Filecoin Mainnet. + // Dividing by a gibibyte gives us more room to work with. This will allow the dashboard to report network and miner + // sizes up to 8192 yobibytes. + gibi := types.NewInt(1024 * 1024 * 1024) + p = NewPoint("chain.power", types.BigDiv(totalPower.TotalPower.QualityAdjPower, gibi).Int64()) pl.AddPoint(p) powerActor, err := api.StateGetActor(ctx, power.Address, tipset.Key()) @@ -281,11 +285,12 @@ func RecordTipsetStatePoints(ctx context.Context, api v0api.FullNode, pl *PointL } return powerActorState.ForEachClaim(func(addr address.Address, claim power.Claim) error { - if claim.QualityAdjPower.Int64() == 0 { + // BigCmp returns 0 if values are equal + if types.BigCmp(claim.QualityAdjPower, types.NewInt(0)) == 0 { return nil } - p = NewPoint("chain.miner_power", claim.QualityAdjPower.Int64()) + p = NewPoint("chain.miner_power", types.BigDiv(claim.QualityAdjPower, gibi).Int64()) p.AddTag("miner", addr.String()) pl.AddPoint(p) From c7e7f890266d6f46a6e1dda58cfd5e42e39e68d2 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 26 Jan 2021 15:45:48 +0100 Subject: [PATCH 043/122] refactor: OnDealExpiredOrSlashed get deal by proposal instead of deal ID --- markets/storageadapter/client.go | 107 ++--------------- markets/storageadapter/ondealexpired.go | 152 ++++++++++++++++++++++++ markets/storageadapter/provider.go | 98 ++------------- 3 files changed, 171 insertions(+), 186 deletions(-) create mode 100644 markets/storageadapter/ondealexpired.go diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go index 80ead2be3..9b375cf08 100644 --- a/markets/storageadapter/client.go +++ b/markets/storageadapter/client.go @@ -18,7 +18,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" - + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" @@ -38,10 +38,10 @@ import ( type ClientNodeAdapter struct { *clientApi - fundmgr *market.FundManager - ev *events.Events - dsMatcher *dealStateMatcher - scMgr *SectorCommittedManager + fundmgr *market.FundManager + ev *events.Events + scMgr *SectorCommittedManager + deMgr *DealExpiryManager } type clientApi struct { @@ -58,11 +58,12 @@ func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi ful a := &ClientNodeAdapter{ clientApi: capi, - fundmgr: fundmgr, - ev: ev, - dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))), + fundmgr: fundmgr, + ev: ev, } a.scMgr = NewSectorCommittedManager(ev, a, &apiWrapper{api: capi}) + dsMatcher := newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))) + a.deMgr = NewDealExpiryManager(ev, capi, capi, dsMatcher) return a } @@ -249,94 +250,8 @@ func (c *ClientNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider return c.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, marketactor.DealProposal(proposal), *publishCid, cb) } -// TODO: Replace dealID parameter with DealProposal -func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - head, err := c.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("client: failed to get chain head: %w", err) - } - - sd, err := c.StateMarketStorageDeal(ctx, dealID, head.Key()) - if err != nil { - return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) - } - - // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { - if ts == nil { - // keep listening for events - return false, true, nil - } - - // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts.Height() { - onDealExpired(nil) - return true, false, nil - } - - // If there is no deal assume it's already been slashed - if sd.State.SectorStartEpoch < 0 { - onDealSlashed(ts.Height(), nil) - return true, false, nil - } - - // No events have occurred yet, so return - // done: false, more: true (keep listening for events) - return false, true, nil - } - - // Called when there was a match against the state change we're looking for - // and the chain has advanced to the confidence height - stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { - // Check if the deal has already expired - if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { - onDealExpired(nil) - return false, nil - } - - // Timeout waiting for state change - if states == nil { - log.Error("timed out waiting for deal expiry") - return false, nil - } - - changedDeals, ok := states.(state.ChangedDeals) - if !ok { - panic("Expected state.ChangedDeals") - } - - deal, ok := changedDeals[dealID] - if !ok { - // No change to deal - return true, nil - } - - // Deal was slashed - if deal.To == nil { - onDealSlashed(ts2.Height(), nil) - return false, nil - } - - return true, nil - } - - // Called when there was a chain reorg and the state change was reverted - revert := func(ctx context.Context, ts *types.TipSet) error { - // TODO: Is it ok to just ignore this? - log.Warn("deal state reverted; TODO: actually handle this!") - return nil - } - - // Watch for state changes to the deal - match := c.dsMatcher.matcher(ctx, dealID) - - // Wait until after the end epoch for the deal and then timeout - timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 - if err := c.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { - return xerrors.Errorf("failed to set up state changed handler: %w", err) - } - - return nil +func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { + return c.deMgr.OnDealExpiredOrSlashed(ctx, publishCid, proposal, onDealExpired, onDealSlashed) } func (c *ClientNodeAdapter) SignProposal(ctx context.Context, signer address.Address, proposal market2.DealProposal) (*market2.ClientDealProposal, error) { diff --git a/markets/storageadapter/ondealexpired.go b/markets/storageadapter/ondealexpired.go new file mode 100644 index 000000000..2d13c04b5 --- /dev/null +++ b/markets/storageadapter/ondealexpired.go @@ -0,0 +1,152 @@ +package storageadapter + +import ( + "context" + + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/events" + "github.com/filecoin-project/lotus/chain/events/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" +) + +type demEventsAPI interface { + Called(check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error + StateChanged(check events.CheckFunc, scHnd events.StateChangeHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.StateMatchFunc) error +} + +type demChainAPI interface { + ChainHead(context.Context) (*types.TipSet, error) +} + +type DealExpiryManagerAPI interface { + demEventsAPI + demChainAPI + GetCurrentDealInfo(ctx context.Context, tok sealing.TipSetToken, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, error) +} + +type dealExpiryManagerAdapter struct { + demEventsAPI + demChainAPI + *sealing.CurrentDealInfoManager +} + +type DealExpiryManager struct { + demAPI DealExpiryManagerAPI + dsMatcher *dealStateMatcher +} + +func NewDealExpiryManager(ev demEventsAPI, ch demChainAPI, tskAPI sealing.CurrentDealInfoTskAPI, dsMatcher *dealStateMatcher) *DealExpiryManager { + dim := &sealing.CurrentDealInfoManager{ + CDAPI: &sealing.CurrentDealInfoAPIAdapter{CurrentDealInfoTskAPI: tskAPI}, + } + + adapter := &dealExpiryManagerAdapter{ + demEventsAPI: ev, + demChainAPI: ch, + CurrentDealInfoManager: dim, + } + return newDealExpiryManager(adapter, dsMatcher) +} + +func newDealExpiryManager(demAPI DealExpiryManagerAPI, dsMatcher *dealStateMatcher) *DealExpiryManager { + return &DealExpiryManager{demAPI: demAPI, dsMatcher: dsMatcher} +} + +func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { + head, err := mgr.demAPI.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("client: failed to get chain head: %w", err) + } + + prop := market.DealProposal(proposal) + res, err := mgr.demAPI.GetCurrentDealInfo(ctx, head.Key().Bytes(), &prop, publishCid) + if err != nil { + return xerrors.Errorf("awaiting deal expired: getting deal info: %w", err) + } + + // Called immediately to check if the deal has already expired or been slashed + checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + if ts == nil { + // keep listening for events + return false, true, nil + } + + // Check if the deal has already expired + if res.MarketDeal.Proposal.EndEpoch <= ts.Height() { + onDealExpired(nil) + return true, false, nil + } + + // If there is no deal assume it's already been slashed + if res.MarketDeal.State.SectorStartEpoch < 0 { + onDealSlashed(ts.Height(), nil) + return true, false, nil + } + + // No events have occurred yet, so return + // done: false, more: true (keep listening for events) + return false, true, nil + } + + // Called when there was a match against the state change we're looking for + // and the chain has advanced to the confidence height + stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { + // Check if the deal has already expired + if ts2 == nil || res.MarketDeal.Proposal.EndEpoch <= ts2.Height() { + onDealExpired(nil) + return false, nil + } + + // Timeout waiting for state change + if states == nil { + log.Error("timed out waiting for deal expiry") + return false, nil + } + + changedDeals, ok := states.(state.ChangedDeals) + if !ok { + panic("Expected state.ChangedDeals") + } + + deal, ok := changedDeals[res.DealID] + if !ok { + // No change to deal + return true, nil + } + + // Deal was slashed + if deal.To == nil { + onDealSlashed(ts2.Height(), nil) + return false, nil + } + + return true, nil + } + + // Called when there was a chain reorg and the state change was reverted + revert := func(ctx context.Context, ts *types.TipSet) error { + // TODO: Is it ok to just ignore this? + log.Warn("deal state reverted; TODO: actually handle this!") + return nil + } + + // Watch for state changes to the deal + match := mgr.dsMatcher.matcher(ctx, res.DealID) + + // Wait until after the end epoch for the deal and then timeout + timeout := (res.MarketDeal.Proposal.EndEpoch - head.Height()) + 1 + if err := mgr.demAPI.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { + return xerrors.Errorf("failed to set up state changed handler: %w", err) + } + + return nil +} diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go index 23a3c32a8..d3d442623 100644 --- a/markets/storageadapter/provider.go +++ b/markets/storageadapter/provider.go @@ -18,6 +18,8 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/chain/events/state" + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/filecoin-project/lotus/api" @@ -26,7 +28,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/events/state" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/lib/sigs" @@ -51,8 +52,8 @@ type ProviderNodeAdapter struct { addBalanceSpec *api.MessageSendSpec maxDealCollateralMultiplier uint64 - dsMatcher *dealStateMatcher scMgr *SectorCommittedManager + deMgr *DealExpiryManager } func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { @@ -66,7 +67,6 @@ func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConf secb: secb, ev: ev, dealPublisher: dealPublisher, - dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))), } if fc != nil { na.addBalanceSpec = &api.MessageSendSpec{MaxFee: abi.TokenAmount(fc.MaxMarketBalanceAddFee)} @@ -77,6 +77,9 @@ func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConf } na.scMgr = NewSectorCommittedManager(ev, na, &apiWrapper{api: full}) + dsMatcher := newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))) + na.deMgr = NewDealExpiryManager(ev, full, full, dsMatcher) + return na } } @@ -328,93 +331,8 @@ func (n *ProviderNodeAdapter) GetDataCap(ctx context.Context, addr address.Addre return sp, err } -func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - head, err := n.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("client: failed to get chain head: %w", err) - } - - sd, err := n.StateMarketStorageDeal(ctx, dealID, head.Key()) - if err != nil { - return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) - } - - // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { - if ts == nil { - // keep listening for events - return false, true, nil - } - - // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts.Height() { - onDealExpired(nil) - return true, false, nil - } - - // If there is no deal assume it's already been slashed - if sd.State.SectorStartEpoch < 0 { - onDealSlashed(ts.Height(), nil) - return true, false, nil - } - - // No events have occurred yet, so return - // done: false, more: true (keep listening for events) - return false, true, nil - } - - // Called when there was a match against the state change we're looking for - // and the chain has advanced to the confidence height - stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { - // Check if the deal has already expired - if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { - onDealExpired(nil) - return false, nil - } - - // Timeout waiting for state change - if states == nil { - log.Error("timed out waiting for deal expiry") - return false, nil - } - - changedDeals, ok := states.(state.ChangedDeals) - if !ok { - panic("Expected state.ChangedDeals") - } - - deal, ok := changedDeals[dealID] - if !ok { - // No change to deal - return true, nil - } - - // Deal was slashed - if deal.To == nil { - onDealSlashed(ts2.Height(), nil) - return false, nil - } - - return true, nil - } - - // Called when there was a chain reorg and the state change was reverted - revert := func(ctx context.Context, ts *types.TipSet) error { - // TODO: Is it ok to just ignore this? - log.Warn("deal state reverted; TODO: actually handle this!") - return nil - } - - // Watch for state changes to the deal - match := n.dsMatcher.matcher(ctx, dealID) - - // Wait until after the end epoch for the deal and then timeout - timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 - if err := n.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { - return xerrors.Errorf("failed to set up state changed handler: %w", err) - } - - return nil +func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { + return n.deMgr.OnDealExpiredOrSlashed(ctx, publishCid, proposal, onDealExpired, onDealSlashed) } var _ storagemarket.StorageProviderNode = &ProviderNodeAdapter{} From aa8ac05d6be671aa7172bd7c0570896a5199e767 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 4 Feb 2021 11:58:08 +0100 Subject: [PATCH 044/122] fix: replace panic with err in OnDealExpiredOrSlashed --- markets/storageadapter/ondealexpired.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/markets/storageadapter/ondealexpired.go b/markets/storageadapter/ondealexpired.go index 2d13c04b5..e57b39634 100644 --- a/markets/storageadapter/ondealexpired.go +++ b/markets/storageadapter/ondealexpired.go @@ -114,7 +114,7 @@ func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publis changedDeals, ok := states.(state.ChangedDeals) if !ok { - panic("Expected state.ChangedDeals") + return false, xerrors.Errorf("OnDealExpireOrSlashed stateChanged: Expected state.ChangedDeals") } deal, ok := changedDeals[res.DealID] From 77a19774cfef35982a7d223f3794123ab0473e30 Mon Sep 17 00:00:00 2001 From: dirkmc Date: Thu, 26 Aug 2021 17:36:06 +0200 Subject: [PATCH 045/122] fix events API timeout handling for nil blocks (#7184) --- .circleci/config.yml | 5 + chain/events/events_called.go | 14 +-- chain/events/events_test.go | 153 +++++++++++++----------- itests/deals_expiry_test.go | 140 ++++++++++++++++++++++ itests/kit/deals.go | 4 + markets/storageadapter/ondealexpired.go | 4 +- node/modules/storageminer.go | 2 +- 7 files changed, 239 insertions(+), 83 deletions(-) create mode 100644 itests/deals_expiry_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index d570e303c..3f9a5f676 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -820,6 +820,11 @@ workflows: suite: itest-deals_concurrent target: "./itests/deals_concurrent_test.go" + - test: + name: test-itest-deals_expiry + suite: itest-deals_expiry + target: "./itests/deals_expiry_test.go" + - test: name: test-itest-deals_offline suite: itest-deals_offline diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 1f0b80169..e783f7800 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -157,7 +157,7 @@ func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error { // Apply any queued events and timeouts that were targeted at the // current chain height e.applyWithConfidence(ts, at) - e.applyTimeouts(ts) + e.applyTimeouts(at) } // Update the latest known tipset @@ -273,8 +273,8 @@ func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) } // Apply any timeouts that expire at this height -func (e *hcEvents) applyTimeouts(ts *types.TipSet) { - triggers, ok := e.timeouts[ts.Height()] +func (e *hcEvents) applyTimeouts(at abi.ChainEpoch) { + triggers, ok := e.timeouts[at] if !ok { return // nothing to do } @@ -288,14 +288,14 @@ func (e *hcEvents) applyTimeouts(ts *types.TipSet) { continue } - timeoutTs, err := e.tsc.get(ts.Height() - abi.ChainEpoch(trigger.confidence)) + timeoutTs, err := e.tsc.get(at - abi.ChainEpoch(trigger.confidence)) if err != nil { - log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height()) + log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", at-abi.ChainEpoch(trigger.confidence), at) } - more, err := trigger.handle(nil, nil, timeoutTs, ts.Height()) + more, err := trigger.handle(nil, nil, timeoutTs, at) if err != nil { - log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err) + log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), at, err) continue // don't revert failed calls } diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 04f938055..6f73dfe58 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -1293,81 +1293,88 @@ func TestStateChangedRevert(t *testing.T) { } func TestStateChangedTimeout(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + timeoutHeight := abi.ChainEpoch(20) + confidence := 3 - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, + testCases := []struct { + name string + checkFn CheckFunc + nilBlocks []int + expectTimeout bool + }{{ + // Verify that the state changed timeout is called at the expected height + name: "state changed timeout", + checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + expectTimeout: true, + }, { + // Verify that the state changed timeout is called even if the timeout + // falls on nil block + name: "state changed timeout falls on nil block", + checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + nilBlocks: []int{20, 21, 22, 23}, + expectTimeout: true, + }, { + // Verify that the state changed timeout is not called if the check + // function reports that it's complete + name: "no timeout callback if check func reports done", + checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { + return true, true, nil + }, + expectTimeout: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + fcs := &fakeCS{ + t: t, + h: 1, + + msgs: map[cid.Cid]fakeMsg{}, + blkMsgs: map[cid.Cid]cid.Cid{}, + tsc: newTSCache(2*build.ForkLengthThreshold, nil), + callNumber: map[string]int{}, + } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + + events := NewEvents(context.Background(), fcs) + + // Track whether the callback was called + called := false + + // Set up state change tracking that will timeout at the given height + err := events.StateChanged( + tc.checkFn, + func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + // Expect the callback to be called at the timeout height with nil data + called = true + require.Nil(t, data) + require.Equal(t, timeoutHeight, newTs.Height()) + require.Equal(t, timeoutHeight+abi.ChainEpoch(confidence), curH) + return false, nil + }, func(_ context.Context, ts *types.TipSet) error { + t.Fatal("revert on timeout") + return nil + }, confidence, timeoutHeight, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + return false, nil, nil + }) + + require.NoError(t, err) + + // Advance to timeout height + fcs.advance(0, int(timeoutHeight)+1, nil) + require.False(t, called) + + // Advance past timeout height + fcs.advance(0, 5, nil, tc.nilBlocks...) + require.Equal(t, tc.expectTimeout, called) + called = false + }) } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) - - called := false - - err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { - return false, true, nil - }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { - called = true - require.Nil(t, data) - require.Equal(t, abi.ChainEpoch(20), newTs.Height()) - require.Equal(t, abi.ChainEpoch(23), curH) - return false, nil - }, func(_ context.Context, ts *types.TipSet) error { - t.Fatal("revert on timeout") - return nil - }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { - return false, nil, nil - }) - - require.NoError(t, err) - - fcs.advance(0, 21, nil) - require.False(t, called) - - fcs.advance(0, 5, nil) - require.True(t, called) - called = false - - // with check func reporting done - - fcs = &fakeCS{ - t: t, - h: 1, - - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - callNumber: map[string]int{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events = NewEvents(context.Background(), fcs) - - err = events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { - return true, true, nil - }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { - called = true - require.Nil(t, data) - require.Equal(t, abi.ChainEpoch(20), newTs.Height()) - require.Equal(t, abi.ChainEpoch(23), curH) - return false, nil - }, func(_ context.Context, ts *types.TipSet) error { - t.Fatal("revert on timeout") - return nil - }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { - return false, nil, nil - }) - require.NoError(t, err) - - fcs.advance(0, 21, nil) - require.False(t, called) - - fcs.advance(0, 5, nil) - require.False(t, called) } func TestCalledMultiplePerEpoch(t *testing.T) { diff --git a/itests/deals_expiry_test.go b/itests/deals_expiry_test.go new file mode 100644 index 000000000..b8b3c4b5a --- /dev/null +++ b/itests/deals_expiry_test.go @@ -0,0 +1,140 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + market3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/market" + market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" + + "github.com/filecoin-project/lotus/itests/kit" +) + +// Test that the deal state eventually moves to "Expired" on both client and miner +func TestDealExpiry(t *testing.T) { + kit.QuietMiningLogs() + + resetMinDealDuration(t) + + ctx := context.Background() + + var ( + client kit.TestFullNode + miner1 kit.TestMiner + ) + + ens := kit.NewEnsemble(t, kit.MockProofs()) + ens.FullNode(&client) + ens.Miner(&miner1, &client, kit.WithAllSubsystems()) + bm := ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) + + dh := kit.NewDealHarness(t, &client, &miner1, &miner1) + + client.WaitTillChain(ctx, kit.HeightAtLeast(5)) + + // Make a deal with a short duration + dealProposalCid, _, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ + Rseed: 0, + FastRet: true, + // Needs to be far enough in the future to ensure the deal has been sealed + StartEpoch: 3000, + // Short deal duration + MinBlocksDuration: 50, + }) + + // Inject null blocks each time the chain advances by a block so as to + // get to deal expiration faster + go func() { + ch, _ := client.ChainNotify(ctx) + for range ch { + bm[0].InjectNulls(10) + } + }() + + clientExpired := false + minerExpired := false + for { + ts, err := client.ChainHead(ctx) + require.NoError(t, err) + + t.Logf("Chain height: %d", ts.Height()) + + // Get the miner deal from the proposal CID + minerDeal := getMinerDeal(ctx, t, miner1, *dealProposalCid) + + t.Logf("Miner deal:") + t.Logf(" %s -> %s", minerDeal.Proposal.Client, minerDeal.Proposal.Provider) + t.Logf(" StartEpoch: %d", minerDeal.Proposal.StartEpoch) + t.Logf(" EndEpoch: %d", minerDeal.Proposal.EndEpoch) + t.Logf(" State: %s", storagemarket.DealStates[minerDeal.State]) + //spew.Dump(d) + + // Get the client deal + clientDeals, err := client.ClientListDeals(ctx) + require.NoError(t, err) + + t.Logf("Client deal state: %s\n", storagemarket.DealStates[clientDeals[0].State]) + + // Expect the deal to eventually expire on the client and the miner + if clientDeals[0].State == storagemarket.StorageDealExpired { + t.Logf("Client deal expired") + clientExpired = true + } + if minerDeal.State == storagemarket.StorageDealExpired { + t.Logf("Miner deal expired") + minerExpired = true + } + if clientExpired && minerExpired { + t.Logf("PASS: Client and miner deal expired") + return + } + + if ts.Height() > 5000 { + t.Fatalf("Reached height %d without client and miner deals expiring", ts.Height()) + } + + time.Sleep(2 * time.Second) + } +} + +func getMinerDeal(ctx context.Context, t *testing.T, miner1 kit.TestMiner, dealProposalCid cid.Cid) storagemarket.MinerDeal { + minerDeals, err := miner1.MarketListIncompleteDeals(ctx) + require.NoError(t, err) + require.Greater(t, len(minerDeals), 0) + + for _, d := range minerDeals { + if d.ProposalCid == dealProposalCid { + return d + } + } + t.Fatalf("miner deal with proposal CID %s not found", dealProposalCid) + return storagemarket.MinerDeal{} +} + +// reset minimum deal duration to 0, so we can make very short-lived deals. +// NOTE: this will need updating with every new specs-actors version. +func resetMinDealDuration(t *testing.T) { + m2 := market2.DealMinDuration + m3 := market3.DealMinDuration + m4 := market4.DealMinDuration + m5 := market5.DealMinDuration + + market2.DealMinDuration = 0 + market3.DealMinDuration = 0 + market4.DealMinDuration = 0 + market5.DealMinDuration = 0 + + t.Cleanup(func() { + market2.DealMinDuration = m2 + market3.DealMinDuration = m3 + market4.DealMinDuration = m4 + market5.DealMinDuration = m5 + }) +} diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 7d78d8c02..804c18165 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -36,6 +36,7 @@ type MakeFullDealParams struct { Rseed int FastRet bool StartEpoch abi.ChainEpoch + MinBlocksDuration uint64 UseCARFileForStorageDeal bool // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon @@ -97,6 +98,9 @@ func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealPa dp.Data.Root = res.Root dp.DealStartEpoch = params.StartEpoch dp.FastRetrieval = params.FastRet + if params.MinBlocksDuration > 0 { + dp.MinBlocksDuration = params.MinBlocksDuration + } deal = dh.StartDeal(ctx, dp) // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this diff --git a/markets/storageadapter/ondealexpired.go b/markets/storageadapter/ondealexpired.go index e57b39634..a6c6efd82 100644 --- a/markets/storageadapter/ondealexpired.go +++ b/markets/storageadapter/ondealexpired.go @@ -101,7 +101,7 @@ func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publis // and the chain has advanced to the confidence height stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { // Check if the deal has already expired - if ts2 == nil || res.MarketDeal.Proposal.EndEpoch <= ts2.Height() { + if res.MarketDeal.Proposal.EndEpoch <= h { onDealExpired(nil) return false, nil } @@ -143,7 +143,7 @@ func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publis match := mgr.dsMatcher.matcher(ctx, res.DealID) // Wait until after the end epoch for the deal and then timeout - timeout := (res.MarketDeal.Proposal.EndEpoch - head.Height()) + 1 + timeout := res.MarketDeal.Proposal.EndEpoch + 1 if err := mgr.demAPI.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { return xerrors.Errorf("failed to set up state changed handler: %w", err) } diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 075eed99d..13e751be4 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -525,7 +525,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside earliest := abi.ChainEpoch(sealEpochs) + ht if deal.Proposal.StartEpoch < earliest { log.Warnw("proposed deal would start before sealing can be completed; rejecting storage deal proposal from client", "piece_cid", deal.Proposal.PieceCID, "client", deal.Client.String(), "seal_duration", sealDuration, "earliest", earliest, "curepoch", ht) - return false, fmt.Sprintf("cannot seal a sector before %s", deal.Proposal.StartEpoch), nil + return false, fmt.Sprintf("proposed deal start epoch %s too early, cannot seal a sector before %s", deal.Proposal.StartEpoch, earliest), nil } sd, err := startDelay() From ac51b8eabcdfc75336c0bf47d73e9b6ace875652 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Fri, 27 Aug 2021 09:22:44 +0200 Subject: [PATCH 046/122] feat: go-fil-markets v1.9.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index be7d911e1..62a96e02c 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/filecoin-project/go-data-transfer v1.7.6 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.8.1 + github.com/filecoin-project/go-fil-markets v1.9.0 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 diff --git a/go.sum b/go.sum index f16a81a9f..507ca4d2d 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,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.8.1 h1:nNJB5EIp5c6yo/z51DloVaL7T24SslCoxSDOXwNQr9k= -github.com/filecoin-project/go-fil-markets v1.8.1/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= +github.com/filecoin-project/go-fil-markets v1.9.0 h1:atoORQmjN1SEjB4RKj3uvPCqL9Jcs2RZ1GHKefstkxw= +github.com/filecoin-project/go-fil-markets v1.9.0/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= 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= From 07dcb074f843291db6046d7d206d83659b119241 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Fri, 27 Aug 2021 12:28:47 +0200 Subject: [PATCH 047/122] fix: catch deal slashed because sector was terminated --- .circleci/config.yml | 5 + itests/deals_slash_test.go | 166 ++++++++++++++++++++++++ markets/storageadapter/ondealexpired.go | 4 +- 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 itests/deals_slash_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f9a5f676..dfaa3fb09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -850,6 +850,11 @@ workflows: suite: itest-deals_publish target: "./itests/deals_publish_test.go" + - test: + name: test-itest-deals_slash + suite: itest-deals_slash + target: "./itests/deals_slash_test.go" + - test: name: test-itest-deals suite: itest-deals diff --git a/itests/deals_slash_test.go b/itests/deals_slash_test.go new file mode 100644 index 000000000..929b69754 --- /dev/null +++ b/itests/deals_slash_test.go @@ -0,0 +1,166 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "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/modules/dtypes" +) + +// Test that when a miner terminates a sector containing a deal, the deal state +// eventually moves to "Slashed" on both client and miner +func TestDealSlashing(t *testing.T) { + kit.QuietMiningLogs() + _ = logging.SetLogLevel("sectors", "debug") + + ctx := context.Background() + + var ( + client kit.TestFullNode + miner1 kit.TestMiner + ) + + // Set up sealing config so that there is no batching of terminate actions + var sealingCfgFn dtypes.GetSealingConfigFunc = func() (sealiface.Config, error) { + return sealiface.Config{ + MaxWaitDealsSectors: 2, + MaxSealingSectors: 0, + MaxSealingSectorsForDeals: 0, + WaitDealsDelay: time.Second, + AlwaysKeepUnsealedCopy: true, + + BatchPreCommits: true, + MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, + PreCommitBatchWait: time.Second, + PreCommitBatchSlack: time.Second, + + AggregateCommits: true, + MinCommitBatch: 1, + MaxCommitBatch: 1, + CommitBatchWait: time.Second, + CommitBatchSlack: time.Second, + + AggregateAboveBaseFee: types.BigMul(types.PicoFil, types.NewInt(150)), // 0.15 nFIL + + TerminateBatchMin: 1, + TerminateBatchMax: 1, + TerminateBatchWait: time.Second, + }, nil + } + fn := func() dtypes.GetSealingConfigFunc { return sealingCfgFn } + sealingCfg := kit.ConstructorOpts(node.Override(new(dtypes.GetSealingConfigFunc), fn)) + + // Set up a client and miner + ens := kit.NewEnsemble(t, kit.MockProofs()) + ens.FullNode(&client) + ens.Miner(&miner1, &client, kit.WithAllSubsystems(), sealingCfg) + ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) + + dh := kit.NewDealHarness(t, &client, &miner1, &miner1) + + client.WaitTillChain(ctx, kit.HeightAtLeast(5)) + + // Make a storage deal + dealProposalCid, _, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ + Rseed: 0, + FastRet: true, + }) + + // Get the miner deal from the proposal CID + minerDeal := getDealByProposalCid(ctx, t, miner1, *dealProposalCid) + + // Terminate the sector containing the deal + t.Logf("Terminating sector %d containing deal %s", minerDeal.SectorNumber, dealProposalCid) + err := miner1.SectorTerminate(ctx, minerDeal.SectorNumber) + require.NoError(t, err) + + clientExpired := false + minerExpired := false + for { + ts, err := client.ChainHead(ctx) + require.NoError(t, err) + + t.Logf("Chain height: %d", ts.Height()) + + // Get the miner deal from the proposal CID + minerDeal := getDealByProposalCid(ctx, t, miner1, *dealProposalCid) + // Get the miner state from the piece CID + mktDeal := getMarketDeal(ctx, t, miner1, minerDeal.Proposal.PieceCID) + + t.Logf("Miner deal:") + t.Logf(" %s -> %s", minerDeal.Proposal.Client, minerDeal.Proposal.Provider) + t.Logf(" StartEpoch: %d", minerDeal.Proposal.StartEpoch) + t.Logf(" EndEpoch: %d", minerDeal.Proposal.EndEpoch) + t.Logf(" SlashEpoch: %d", mktDeal.State.SlashEpoch) + t.Logf(" LastUpdatedEpoch: %d", mktDeal.State.LastUpdatedEpoch) + t.Logf(" State: %s", storagemarket.DealStates[minerDeal.State]) + //spew.Dump(d) + + // Get the client deal + clientDeals, err := client.ClientListDeals(ctx) + require.NoError(t, err) + + t.Logf("Client deal state: %s\n", storagemarket.DealStates[clientDeals[0].State]) + + // Expect the deal to eventually be slashed on the client and the miner + if clientDeals[0].State == storagemarket.StorageDealSlashed { + t.Logf("Client deal slashed") + clientExpired = true + } + if minerDeal.State == storagemarket.StorageDealSlashed { + t.Logf("Miner deal slashed") + minerExpired = true + } + if clientExpired && minerExpired { + t.Logf("PASS: Client and miner deal slashed") + return + } + + if ts.Height() > 4000 { + t.Fatalf("Reached height %d without client and miner deals being slashed", ts.Height()) + } + + time.Sleep(2 * time.Second) + } +} + +func getMarketDeal(ctx context.Context, t *testing.T, miner1 kit.TestMiner, pieceCid cid.Cid) api.MarketDeal { + mktDeals, err := miner1.MarketListDeals(ctx) + require.NoError(t, err) + require.Greater(t, len(mktDeals), 0) + + for _, d := range mktDeals { + if d.Proposal.PieceCID == pieceCid { + return d + } + } + t.Fatalf("miner deal with piece CID %s not found", pieceCid) + return api.MarketDeal{} +} + +func getDealByProposalCid(ctx context.Context, t *testing.T, miner1 kit.TestMiner, dealProposalCid cid.Cid) storagemarket.MinerDeal { + minerDeals, err := miner1.MarketListIncompleteDeals(ctx) + require.NoError(t, err) + require.Greater(t, len(minerDeals), 0) + + for _, d := range minerDeals { + if d.ProposalCid == dealProposalCid { + return d + } + } + t.Fatalf("miner deal with proposal CID %s not found", dealProposalCid) + return storagemarket.MinerDeal{} +} diff --git a/markets/storageadapter/ondealexpired.go b/markets/storageadapter/ondealexpired.go index a6c6efd82..6767c6d34 100644 --- a/markets/storageadapter/ondealexpired.go +++ b/markets/storageadapter/ondealexpired.go @@ -108,7 +108,7 @@ func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publis // Timeout waiting for state change if states == nil { - log.Error("timed out waiting for deal expiry") + log.Errorf("timed out waiting for deal expiry for deal with piece CID %s", proposal.PieceCID) return false, nil } @@ -124,7 +124,7 @@ func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publis } // Deal was slashed - if deal.To == nil { + if deal.To == nil || deal.To.SlashEpoch > 0 { onDealSlashed(ts2.Height(), nil) return false, nil } From 22d75f484376b7a37d9cf6005dbf10da8f6e8d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 27 Aug 2021 13:36:40 +0200 Subject: [PATCH 048/122] Tweak miner info --blocks output --- chain/actors/builtin/reward/reward.go | 2 +- cmd/lotus-miner/info.go | 26 +++++++++++++++++++++++--- documentation/en/cli-lotus-miner.md | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go index 25ab3ef6b..ebec85517 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -123,7 +123,7 @@ type State interface { cbor.Marshaler ThisEpochBaselinePower() (abi.StoragePower, error) - ThisEpochReward() (abi.TokenAmount, error) + ThisEpochReward() (abi.StoragePower, error) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) EffectiveBaselinePower() (abi.StoragePower, error) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 14af8a27d..7be9e1fe1 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -12,6 +12,7 @@ import ( "time" "github.com/fatih/color" + "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" "golang.org/x/xerrors" @@ -567,13 +568,23 @@ func producedBlocks(ctx context.Context, count int, maddr address.Address, napi tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(napi), blockstore.NewMemory()) + tty := isatty.IsTerminal(os.Stderr.Fd()) + ts := head fmt.Printf(" Epoch | Block ID | Reward\n") for count > 0 { tsk := ts.Key() bhs := ts.Blocks() for _, bh := range bhs { + if ctx.Err() != nil { + return ctx.Err() + } + if bh.Miner == maddr { + if tty { + _, _ = fmt.Fprint(os.Stderr, "\r\x1b[0K") + } + rewardActor, err := napi.StateGetActor(ctx, reward.Address, tsk) if err != nil { return err @@ -587,10 +598,14 @@ func producedBlocks(ctx context.Context, count int, maddr address.Address, napi if err != nil { return err } - fmt.Printf("%8d | %s | %s\n", ts.Height(), bh.Cid(), - types.BigDiv(types.BigMul(types.NewInt(uint64(bh.ElectionProof.WinCount)), - blockReward), types.NewInt(uint64(builtin.ExpectedLeadersPerEpoch)))) + + minerReward := types.BigDiv(types.BigMul(types.NewInt(uint64(bh.ElectionProof.WinCount)), + blockReward), types.NewInt(uint64(builtin.ExpectedLeadersPerEpoch))) + + fmt.Printf("%8d | %s | %s\n", ts.Height(), bh.Cid(), types.FIL(minerReward)) count-- + } else if tty && bh.Height%120 == 0 { + _, _ = fmt.Fprintf(os.Stderr, "\r\x1b[0KChecking epoch %s", lcli.EpochTime(head.Height(), bh.Height)) } } tsk = ts.Parents() @@ -599,5 +614,10 @@ func producedBlocks(ctx context.Context, count int, maddr address.Address, napi return err } } + + if tty { + _, _ = fmt.Fprint(os.Stderr, "\r\x1b[0K") + } + return nil } diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index f69c8b144..c96b3140e 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -430,6 +430,7 @@ COMMANDS: OPTIONS: --hide-sectors-info hide sectors info (default: false) + --blocks value Log of produced newest blocks and rewards(Miner Fee excluded) (default: 0) --help, -h show help (default: false) --version, -v print the version (default: false) From 17b7dcef6fe15a08e40ce4142a601483017cf92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 27 Aug 2021 19:14:29 +0200 Subject: [PATCH 049/122] config for disabling NAT port mapping --- node/builder.go | 3 ++- node/config/doc_gen.go | 9 +++++++++ node/config/types.go | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/node/builder.go b/node/builder.go index 887f9046f..5810910a7 100644 --- a/node/builder.go +++ b/node/builder.go @@ -195,7 +195,6 @@ var LibP2P = Options( Override(new(routing.Routing), lp2p.Routing), // Services - Override(NatPortMapKey, lp2p.NatPortMap), Override(BandwidthReporterKey, lp2p.BandwidthCounter), Override(AutoNATSvcKey, lp2p.AutoNATService), @@ -277,6 +276,8 @@ func ConfigCommon(cfg *config.Common, enableLibp2pNode bool) Option { Override(AddrsFactoryKey, lp2p.AddrsFactory( cfg.Libp2p.AnnounceAddresses, cfg.Libp2p.NoAnnounceAddresses)), + + If(!cfg.Libp2p.DisableNatPortMap, Override(NatPortMapKey, lp2p.NatPortMap)), ), Override(new(dtypes.MetadataDS), modules.Datastore(cfg.Backup.DisableMetadataLog)), ) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 9efe4e03c..2228393e4 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -348,6 +348,15 @@ Format: multiaddress`, Comment: ``, }, + { + Name: "DisableNatPortMap", + Type: "bool", + + Comment: `When not disabled (default), lotus asks NAT devices (e.g., routers), to +open up an external port and forward it to the port lotus is running on. +When this works (i.e., when your router supports NAT port forwarding), +it makes the local lotus node accessible from the public internet`, + }, { Name: "ConnMgrLow", Type: "uint", diff --git a/node/config/types.go b/node/config/types.go index 1576169e6..499912d40 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -292,6 +292,12 @@ type Libp2p struct { BootstrapPeers []string ProtectedPeers []string + // When not disabled (default), lotus asks NAT devices (e.g., routers), to + // open up an external port and forward it to the port lotus is running on. + // When this works (i.e., when your router supports NAT port forwarding), + // it makes the local lotus node accessible from the public internet + DisableNatPortMap bool + ConnMgrLow uint ConnMgrHigh uint ConnMgrGrace Duration From 3708a7ac30fb36d4fe7c9eaa1dff854179cef347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 27 Aug 2021 19:16:50 +0200 Subject: [PATCH 050/122] config: Port some docstrings from go-ipfs --- node/config/doc_gen.go | 10 +++++++--- node/config/types.go | 11 +++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 2228393e4..dd6dda50d 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -361,19 +361,23 @@ it makes the local lotus node accessible from the public internet`, Name: "ConnMgrLow", Type: "uint", - Comment: ``, + Comment: `ConnMgrLow is the number of connections that the basic connection manager +will trim down to.`, }, { Name: "ConnMgrHigh", Type: "uint", - Comment: ``, + Comment: `ConnMgrHigh is the number of connections that, when exceeded, will trigger +a connection GC operation. Note: protected/recently formed connections don't +count towards this limit.`, }, { Name: "ConnMgrGrace", Type: "Duration", - Comment: ``, + Comment: `ConnMgrGrace is a time duration that new connections are immune from being +closed by the connection manager.`, }, }, "MinerAddressConfig": []DocField{ diff --git a/node/config/types.go b/node/config/types.go index 499912d40..97b0812d7 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -298,8 +298,15 @@ type Libp2p struct { // it makes the local lotus node accessible from the public internet DisableNatPortMap bool - ConnMgrLow uint - ConnMgrHigh uint + // ConnMgrLow is the number of connections that the basic connection manager + // will trim down to. + ConnMgrLow uint + // ConnMgrHigh is the number of connections that, when exceeded, will trigger + // a connection GC operation. Note: protected/recently formed connections don't + // count towards this limit. + ConnMgrHigh uint + // ConnMgrGrace is a time duration that new connections are immune from being + // closed by the connection manager. ConnMgrGrace Duration } From 2293ecd8e8cbc9c780128e595eb0665fd682d8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 27 Aug 2021 19:41:54 +0200 Subject: [PATCH 051/122] Reduce lotus-miner startup spam --- extern/sector-storage/manager.go | 1 - extern/storage-sealing/states_proving.go | 1 - 2 files changed, 2 deletions(-) diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index bf676bffa..a8de586e1 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -575,7 +575,6 @@ func (m *Manager) FinalizeSector(ctx context.Context, sector storage.SectorRef, } func (m *Manager) ReleaseUnsealed(ctx context.Context, sector storage.SectorRef, safeToFree []storage.Range) error { - log.Warnw("ReleaseUnsealed todo") return nil } diff --git a/extern/storage-sealing/states_proving.go b/extern/storage-sealing/states_proving.go index 2deefa80f..e74119976 100644 --- a/extern/storage-sealing/states_proving.go +++ b/extern/storage-sealing/states_proving.go @@ -129,7 +129,6 @@ func (m *Sealing) handleRemoving(ctx statemachine.Context, sector SectorInfo) er func (m *Sealing) handleProvingSector(ctx statemachine.Context, sector SectorInfo) error { // TODO: track sector health / expiration - log.Infof("Proving sector %d", sector.SectorNumber) cfg, err := m.getConfig() if err != nil { From 8f2d6ac042915bf8daad5d2227d2a4f74c0d3024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 27 Aug 2021 20:50:16 +0100 Subject: [PATCH 052/122] upgrade go-data-transfer; propagate deal cancellations. --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 62a96e02c..6dda78999 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ 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.6 + github.com/filecoin-project/go-data-transfer v1.7.8 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.9.0 diff --git a/go.sum b/go.sum index 507ca4d2d..b3edfe394 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,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.6 h1:QmAJwzjVxbvBDmYeGWnzE6aL+gjWpCmAAlBAF0YEbnE= github.com/filecoin-project/go-data-transfer v1.7.6/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= +github.com/filecoin-project/go-data-transfer v1.7.8 h1:s4cF9nX9sEy7RgZd3NW92YN/hKyIy2fQl+7dVOAS8r8= +github.com/filecoin-project/go-data-transfer v1.7.8/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= 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= From 165735d01a2934be2efd286f93213491f2c542ad Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 26 Aug 2021 17:23:04 -0700 Subject: [PATCH 053/122] fix: correctly handle null blocks when detecting an expensive fork Also improve/fix documentation to reflect the _actual_ tipset that's passed into upgrades. And update some comments. fixes #7192 --- chain/stmgr/call.go | 69 ++++++++++++++++++++++++--------------- chain/stmgr/execute.go | 1 - chain/stmgr/forks.go | 23 +++++++++++-- chain/stmgr/forks_test.go | 40 +++++++++++++++++++++-- chain/stmgr/utils.go | 5 +-- 5 files changed, 103 insertions(+), 35 deletions(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index caa815132..0f045c5f4 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/ipfs/go-cid" "go.opencensus.io/trace" @@ -20,41 +21,49 @@ import ( var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch") +// Call applies the given message to the given tipset's parent state, at the epoch following the +// tipset's parent. In the presence of null blocks, the height at which the message is invoked may +// be less than the specified tipset. +// +// - If no tipset is specified, the first tipset without an expensive migration is used. +// - If executing a message at a given tipset would trigger an expensive migration, the call will +// fail with ErrExpensiveFork. func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { ctx, span := trace.StartSpan(ctx, "statemanager.Call") defer span.End() + var pheight abi.ChainEpoch = -1 + // If no tipset is provided, try to find one without a fork. if ts == nil { ts = sm.cs.GetHeaviestTipSet() - // Search back till we find a height with no fork, or we reach the beginning. - for ts.Height() > 0 && sm.hasExpensiveFork(ctx, ts.Height()-1) { - var err error - ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) + for ts.Height() > 0 { + pts, err := sm.cs.GetTipSetFromKey(ts.Parents()) if err != nil { return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) } + if !sm.hasExpensiveFork(pts.Height()) { + pheight = pts.Height() + break + } + ts = pts + } + } else { + pts, err := sm.cs.LoadTipSet(ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load parent tipset: %w", err) + } + pheight = pts.Height() + if sm.hasExpensiveFork(pheight) { + return nil, ErrExpensiveFork } } bstate := ts.ParentState() - pts, err := sm.cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, xerrors.Errorf("failed to load parent tipset: %w", err) - } - pheight := pts.Height() - - // If we have to run an expensive migration, and we're not at genesis, - // return an error because the migration will take too long. - // - // We allow this at height 0 for at-genesis migrations (for testing). - if pheight > 0 && sm.hasExpensiveFork(ctx, pheight) { - return nil, ErrExpensiveFork - } // Run the (not expensive) migration. - bstate, err = sm.handleStateForks(ctx, bstate, pheight, nil, ts) + bstate, err := sm.handleStateForks(ctx, bstate, pheight, nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) } @@ -140,18 +149,25 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri // run the fork logic in `sm.TipSetState`. We need the _current_ // height to have no fork, because we'll run it inside this // function before executing the given message. - for ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) { - var err error - ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) + for ts.Height() > 0 { + pts, err := sm.cs.GetTipSetFromKey(ts.Parents()) if err != nil { return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) } - } - } + if !sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { + break + } - // When we're not at the genesis block, make sure we don't have an expensive migration. - if ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) { - return nil, ErrExpensiveFork + ts = pts + } + } else if ts.Height() > 0 { + pts, err := sm.cs.GetTipSetFromKey(ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) + } + if sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { + return nil, ErrExpensiveFork + } } state, _, err := sm.TipSetState(ctx, ts) @@ -159,6 +175,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri return nil, xerrors.Errorf("computing tipset state: %w", err) } + // Technically, the tipset we're passing in here should be ts+1, but that may not exist. state, err = sm.handleStateForks(ctx, state, ts.Height(), nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index 3191a45db..a8675e347 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -99,7 +99,6 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEp } // 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) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 212272a95..b6c6985e2 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -41,8 +41,11 @@ type MigrationCache interface { // - The oldState is the state produced by the upgrade epoch. // - The returned newState is the new state that will be used by the next epoch. // - The height is the upgrade epoch height (already executed). -// - The tipset is the tipset for the last non-null block before the upgrade. Do -// not assume that ts.Height() is the upgrade height. +// - The tipset is the first non-null tipset after the upgrade height (the tipset in +// which the upgrade is executed). Do not assume that ts.Height() is the upgrade height. +// +// NOTE: In StateCompute and CallWithGas, the passed tipset is actually the tipset _before_ the +// upgrade. The tipset should really only be used for referencing the "current chain". type MigrationFunc func( ctx context.Context, sm *StateManager, cache MigrationCache, @@ -208,7 +211,21 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig return retCid, nil } -func (sm *StateManager) hasExpensiveFork(ctx context.Context, height abi.ChainEpoch) bool { +// Returns true if executing the current tipset would trigger an expensive fork. +// +// - If the tipset is the genesis, this function always returns false. +// - If inclusive is true, this function will also return true if applying a message on-top-of the +// tipset would trigger a fork. +func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) bool { + for h := parent; h < height; h++ { + if _, ok := sm.expensiveUpgrades[h]; ok { + return true + } + } + return false +} + +func (sm *StateManager) hasExpensiveFork(height abi.ChainEpoch) bool { _, ok := sm.expensiveUpgrades[height] return ok } diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 0df6ce397..97ec8643b 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -242,6 +242,19 @@ func TestForkHeightTriggers(t *testing.T) { func TestForkRefuseCall(t *testing.T) { logging.SetAllLoggers(logging.LevelInfo) + for after := 0; after < 3; after++ { + for before := 0; before < 3; before++ { + // Makes the lints happy... + after := after + before := before + t.Run(fmt.Sprintf("after:%d,before:%d", after, before), func(t *testing.T) { + testForkRefuseCall(t, before, after) + }) + } + } + +} +func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { ctx := context.TODO() cg, err := gen.NewGenerator() @@ -249,6 +262,7 @@ func TestForkRefuseCall(t *testing.T) { t.Fatal(err) } + var migrationCount int sm, err := NewStateManagerWithUpgradeSchedule( cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, @@ -256,6 +270,7 @@ func TestForkRefuseCall(t *testing.T) { Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + migrationCount++ return root, nil }}}) if err != nil { @@ -292,14 +307,20 @@ func TestForkRefuseCall(t *testing.T) { GasFeeCap: types.NewInt(0), } + nullStart := abi.ChainEpoch(testForkHeight - nullsBefore) + nullLength := abi.ChainEpoch(nullsBefore + nullsAfter) + for i := 0; i < 50; i++ { - ts, err := cg.NextTipSet() + pts := cg.CurTipset.TipSet() + skip := abi.ChainEpoch(0) + if pts.Height() == nullStart { + skip = nullLength + } + ts, err := cg.NextTipSetFromMiners(pts, cg.Miners, skip) if err != nil { t.Fatal(err) } - pts, err := cg.ChainStore().LoadTipSet(ts.TipSet.TipSet().Parents()) - require.NoError(t, err) parentHeight := pts.Height() currentHeight := ts.TipSet.TipSet().Height() @@ -321,7 +342,20 @@ func TestForkRefuseCall(t *testing.T) { require.NoError(t, err) require.True(t, ret.MsgRct.ExitCode.IsSuccess()) } + + // Calls without a tipset should walk back to the last non-fork tipset. + // We _verify_ that the migration wasn't run multiple times at the end of the + // test. + ret, err = sm.CallWithGas(ctx, m, nil, nil) + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + + ret, err = sm.Call(ctx, m, nil) + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) } + // Make sure we didn't execute the migration multiple times. + require.Equal(t, migrationCount, 1) } func TestForkPreMigration(t *testing.T) { diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index a4d78f997..8776fbcd6 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -143,13 +143,14 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, } for i := ts.Height(); i < height; i++ { - // handle state forks + // Technically, the tipset we're passing in here should be ts+1, but that may not exist. base, err = sm.handleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) if err != nil { return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err) } - // TODO: should we also run cron here? + // We intentionally don't run cron here, as we may be trying to look into the + // future. It's not guaranteed to be accurate... but that's fine. } r := store.NewChainRand(sm.cs, ts.Cids()) From 8e52bf30c1aeeb7554270baa3ed039c930062e07 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 27 Aug 2021 15:43:55 -0700 Subject: [PATCH 054/122] test: re-enable disabled tests 1. Rewrite VRF test to actually test win counts, and enable it. 2. Stop skipping some tests that now pass. --- chain/types/electionproof_test.go | 29 ++++++++++++++++++----------- chain/vectors/vectors_test.go | 3 --- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/chain/types/electionproof_test.go b/chain/types/electionproof_test.go index 9344ff6a6..21385868c 100644 --- a/chain/types/electionproof_test.go +++ b/chain/types/electionproof_test.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "math/big" - "os" "testing" "github.com/stretchr/testify/assert" @@ -129,17 +128,25 @@ func BenchmarkWinCounts(b *testing.B) { } func TestWinCounts(t *testing.T) { - t.SkipNow() totalPower := NewInt(100) - power := NewInt(30) + power := NewInt(20) - f, _ := os.Create("output.wins") - fmt.Fprintf(f, "wins\n") - ep := &ElectionProof{VRFProof: nil} - for i := uint64(0); i < 1000000; i++ { - i := i + 1000000 - ep.VRFProof = []byte{byte(i), byte(i >> 8), byte(i >> 16), byte(i >> 24), byte(i >> 32)} - j := ep.ComputeWinCount(power, totalPower) - fmt.Fprintf(f, "%d\n", j) + count := uint64(1000000) + total := uint64(0) + ep := &ElectionProof{VRFProof: make([]byte, 5)} + for i := uint64(0); i < count; i++ { + w := i + count + ep.VRFProof[0] = byte(w) + ep.VRFProof[1] = byte(w >> 8) + ep.VRFProof[2] = byte(w >> 16) + ep.VRFProof[3] = byte(w >> 24) + ep.VRFProof[4] = byte(w >> 32) + + total += uint64(ep.ComputeWinCount(power, totalPower)) } + // We have 1/5 of the power, so we expect to win 1 block per epoch on average. Plus or minus + // 1%. + avgWins := float64(total) / float64(count) + assert.GreaterOrEqual(t, avgWins, 1.0-0.01) + assert.LessOrEqual(t, avgWins, 1.0+0.01) } diff --git a/chain/vectors/vectors_test.go b/chain/vectors/vectors_test.go index 68cc5ac45..974a2c8de 100644 --- a/chain/vectors/vectors_test.go +++ b/chain/vectors/vectors_test.go @@ -26,7 +26,6 @@ func LoadVector(t *testing.T, f string, out interface{}) { } func TestBlockHeaderVectors(t *testing.T) { - t.Skip("we need to regenerate for beacon") var headers []HeaderVector LoadVector(t, "block_headers.json", &headers) @@ -65,8 +64,6 @@ func TestMessageSigningVectors(t *testing.T) { } func TestUnsignedMessageVectors(t *testing.T) { - t.Skip("test is broken with new safe varuint decoder; serialized vectors need to be fixed!") - var msvs []UnsignedMessageVector LoadVector(t, "unsigned_messages.json", &msvs) From 7b7a5b0b21597084266dcdfbe3dc649549dc8455 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 30 Aug 2021 10:42:41 +0200 Subject: [PATCH 055/122] revert: changes to OnDealExpiredOrChanged in #5431 #7201 --- .circleci/config.yml | 10 -- chain/events/events_called.go | 14 +- chain/events/events_test.go | 153 +++++++++++----------- go.mod | 2 +- go.sum | 4 +- itests/deals_expiry_test.go | 140 -------------------- itests/deals_slash_test.go | 166 ------------------------ itests/kit/deals.go | 4 - markets/storageadapter/client.go | 107 +++++++++++++-- markets/storageadapter/ondealexpired.go | 152 ---------------------- markets/storageadapter/provider.go | 98 ++++++++++++-- node/modules/storageminer.go | 2 +- 12 files changed, 270 insertions(+), 582 deletions(-) delete mode 100644 itests/deals_expiry_test.go delete mode 100644 itests/deals_slash_test.go delete mode 100644 markets/storageadapter/ondealexpired.go diff --git a/.circleci/config.yml b/.circleci/config.yml index dfaa3fb09..d570e303c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -820,11 +820,6 @@ workflows: suite: itest-deals_concurrent target: "./itests/deals_concurrent_test.go" - - test: - name: test-itest-deals_expiry - suite: itest-deals_expiry - target: "./itests/deals_expiry_test.go" - - test: name: test-itest-deals_offline suite: itest-deals_offline @@ -850,11 +845,6 @@ workflows: suite: itest-deals_publish target: "./itests/deals_publish_test.go" - - test: - name: test-itest-deals_slash - suite: itest-deals_slash - target: "./itests/deals_slash_test.go" - - test: name: test-itest-deals suite: itest-deals diff --git a/chain/events/events_called.go b/chain/events/events_called.go index e783f7800..1f0b80169 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -157,7 +157,7 @@ func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error { // Apply any queued events and timeouts that were targeted at the // current chain height e.applyWithConfidence(ts, at) - e.applyTimeouts(at) + e.applyTimeouts(ts) } // Update the latest known tipset @@ -273,8 +273,8 @@ func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) } // Apply any timeouts that expire at this height -func (e *hcEvents) applyTimeouts(at abi.ChainEpoch) { - triggers, ok := e.timeouts[at] +func (e *hcEvents) applyTimeouts(ts *types.TipSet) { + triggers, ok := e.timeouts[ts.Height()] if !ok { return // nothing to do } @@ -288,14 +288,14 @@ func (e *hcEvents) applyTimeouts(at abi.ChainEpoch) { continue } - timeoutTs, err := e.tsc.get(at - abi.ChainEpoch(trigger.confidence)) + timeoutTs, err := e.tsc.get(ts.Height() - abi.ChainEpoch(trigger.confidence)) if err != nil { - log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", at-abi.ChainEpoch(trigger.confidence), at) + log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height()) } - more, err := trigger.handle(nil, nil, timeoutTs, at) + more, err := trigger.handle(nil, nil, timeoutTs, ts.Height()) if err != nil { - log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), at, err) + log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err) continue // don't revert failed calls } diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 6f73dfe58..04f938055 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -1293,88 +1293,81 @@ func TestStateChangedRevert(t *testing.T) { } func TestStateChangedTimeout(t *testing.T) { - timeoutHeight := abi.ChainEpoch(20) - confidence := 3 + fcs := &fakeCS{ + t: t, + h: 1, - testCases := []struct { - name string - checkFn CheckFunc - nilBlocks []int - expectTimeout bool - }{{ - // Verify that the state changed timeout is called at the expected height - name: "state changed timeout", - checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { - return false, true, nil - }, - expectTimeout: true, - }, { - // Verify that the state changed timeout is called even if the timeout - // falls on nil block - name: "state changed timeout falls on nil block", - checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { - return false, true, nil - }, - nilBlocks: []int{20, 21, 22, 23}, - expectTimeout: true, - }, { - // Verify that the state changed timeout is not called if the check - // function reports that it's complete - name: "no timeout callback if check func reports done", - checkFn: func(ts *types.TipSet) (d bool, m bool, e error) { - return true, true, nil - }, - expectTimeout: false, - }} - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) - - // Track whether the callback was called - called := false - - // Set up state change tracking that will timeout at the given height - err := events.StateChanged( - tc.checkFn, - func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { - // Expect the callback to be called at the timeout height with nil data - called = true - require.Nil(t, data) - require.Equal(t, timeoutHeight, newTs.Height()) - require.Equal(t, timeoutHeight+abi.ChainEpoch(confidence), curH) - return false, nil - }, func(_ context.Context, ts *types.TipSet) error { - t.Fatal("revert on timeout") - return nil - }, confidence, timeoutHeight, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { - return false, nil, nil - }) - - require.NoError(t, err) - - // Advance to timeout height - fcs.advance(0, int(timeoutHeight)+1, nil) - require.False(t, called) - - // Advance past timeout height - fcs.advance(0, 5, nil, tc.nilBlocks...) - require.Equal(t, tc.expectTimeout, called) - called = false - }) + msgs: map[cid.Cid]fakeMsg{}, + blkMsgs: map[cid.Cid]cid.Cid{}, + tsc: newTSCache(2*build.ForkLengthThreshold, nil), + callNumber: map[string]int{}, } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + + events := NewEvents(context.Background(), fcs) + + called := false + + err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + called = true + require.Nil(t, data) + require.Equal(t, abi.ChainEpoch(20), newTs.Height()) + require.Equal(t, abi.ChainEpoch(23), curH) + return false, nil + }, func(_ context.Context, ts *types.TipSet) error { + t.Fatal("revert on timeout") + return nil + }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + return false, nil, nil + }) + + require.NoError(t, err) + + fcs.advance(0, 21, nil) + require.False(t, called) + + fcs.advance(0, 5, nil) + require.True(t, called) + called = false + + // with check func reporting done + + fcs = &fakeCS{ + t: t, + h: 1, + + msgs: map[cid.Cid]fakeMsg{}, + blkMsgs: map[cid.Cid]cid.Cid{}, + callNumber: map[string]int{}, + tsc: newTSCache(2*build.ForkLengthThreshold, nil), + } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + + events = NewEvents(context.Background(), fcs) + + err = events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + return true, true, nil + }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + called = true + require.Nil(t, data) + require.Equal(t, abi.ChainEpoch(20), newTs.Height()) + require.Equal(t, abi.ChainEpoch(23), curH) + return false, nil + }, func(_ context.Context, ts *types.TipSet) error { + t.Fatal("revert on timeout") + return nil + }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + return false, nil, nil + }) + require.NoError(t, err) + + fcs.advance(0, 21, nil) + require.False(t, called) + + fcs.advance(0, 5, nil) + require.False(t, called) } func TestCalledMultiplePerEpoch(t *testing.T) { diff --git a/go.mod b/go.mod index 62a96e02c..be7d911e1 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/filecoin-project/go-data-transfer v1.7.6 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.9.0 + github.com/filecoin-project/go-fil-markets v1.8.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 diff --git a/go.sum b/go.sum index 507ca4d2d..f16a81a9f 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,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.9.0 h1:atoORQmjN1SEjB4RKj3uvPCqL9Jcs2RZ1GHKefstkxw= -github.com/filecoin-project/go-fil-markets v1.9.0/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= +github.com/filecoin-project/go-fil-markets v1.8.1 h1:nNJB5EIp5c6yo/z51DloVaL7T24SslCoxSDOXwNQr9k= +github.com/filecoin-project/go-fil-markets v1.8.1/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= 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= diff --git a/itests/deals_expiry_test.go b/itests/deals_expiry_test.go deleted file mode 100644 index b8b3c4b5a..000000000 --- a/itests/deals_expiry_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package itests - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - market3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/market" - market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" - market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" - - "github.com/filecoin-project/lotus/itests/kit" -) - -// Test that the deal state eventually moves to "Expired" on both client and miner -func TestDealExpiry(t *testing.T) { - kit.QuietMiningLogs() - - resetMinDealDuration(t) - - ctx := context.Background() - - var ( - client kit.TestFullNode - miner1 kit.TestMiner - ) - - ens := kit.NewEnsemble(t, kit.MockProofs()) - ens.FullNode(&client) - ens.Miner(&miner1, &client, kit.WithAllSubsystems()) - bm := ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) - - dh := kit.NewDealHarness(t, &client, &miner1, &miner1) - - client.WaitTillChain(ctx, kit.HeightAtLeast(5)) - - // Make a deal with a short duration - dealProposalCid, _, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ - Rseed: 0, - FastRet: true, - // Needs to be far enough in the future to ensure the deal has been sealed - StartEpoch: 3000, - // Short deal duration - MinBlocksDuration: 50, - }) - - // Inject null blocks each time the chain advances by a block so as to - // get to deal expiration faster - go func() { - ch, _ := client.ChainNotify(ctx) - for range ch { - bm[0].InjectNulls(10) - } - }() - - clientExpired := false - minerExpired := false - for { - ts, err := client.ChainHead(ctx) - require.NoError(t, err) - - t.Logf("Chain height: %d", ts.Height()) - - // Get the miner deal from the proposal CID - minerDeal := getMinerDeal(ctx, t, miner1, *dealProposalCid) - - t.Logf("Miner deal:") - t.Logf(" %s -> %s", minerDeal.Proposal.Client, minerDeal.Proposal.Provider) - t.Logf(" StartEpoch: %d", minerDeal.Proposal.StartEpoch) - t.Logf(" EndEpoch: %d", minerDeal.Proposal.EndEpoch) - t.Logf(" State: %s", storagemarket.DealStates[minerDeal.State]) - //spew.Dump(d) - - // Get the client deal - clientDeals, err := client.ClientListDeals(ctx) - require.NoError(t, err) - - t.Logf("Client deal state: %s\n", storagemarket.DealStates[clientDeals[0].State]) - - // Expect the deal to eventually expire on the client and the miner - if clientDeals[0].State == storagemarket.StorageDealExpired { - t.Logf("Client deal expired") - clientExpired = true - } - if minerDeal.State == storagemarket.StorageDealExpired { - t.Logf("Miner deal expired") - minerExpired = true - } - if clientExpired && minerExpired { - t.Logf("PASS: Client and miner deal expired") - return - } - - if ts.Height() > 5000 { - t.Fatalf("Reached height %d without client and miner deals expiring", ts.Height()) - } - - time.Sleep(2 * time.Second) - } -} - -func getMinerDeal(ctx context.Context, t *testing.T, miner1 kit.TestMiner, dealProposalCid cid.Cid) storagemarket.MinerDeal { - minerDeals, err := miner1.MarketListIncompleteDeals(ctx) - require.NoError(t, err) - require.Greater(t, len(minerDeals), 0) - - for _, d := range minerDeals { - if d.ProposalCid == dealProposalCid { - return d - } - } - t.Fatalf("miner deal with proposal CID %s not found", dealProposalCid) - return storagemarket.MinerDeal{} -} - -// reset minimum deal duration to 0, so we can make very short-lived deals. -// NOTE: this will need updating with every new specs-actors version. -func resetMinDealDuration(t *testing.T) { - m2 := market2.DealMinDuration - m3 := market3.DealMinDuration - m4 := market4.DealMinDuration - m5 := market5.DealMinDuration - - market2.DealMinDuration = 0 - market3.DealMinDuration = 0 - market4.DealMinDuration = 0 - market5.DealMinDuration = 0 - - t.Cleanup(func() { - market2.DealMinDuration = m2 - market3.DealMinDuration = m3 - market4.DealMinDuration = m4 - market5.DealMinDuration = m5 - }) -} diff --git a/itests/deals_slash_test.go b/itests/deals_slash_test.go deleted file mode 100644 index 929b69754..000000000 --- a/itests/deals_slash_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package itests - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "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/modules/dtypes" -) - -// Test that when a miner terminates a sector containing a deal, the deal state -// eventually moves to "Slashed" on both client and miner -func TestDealSlashing(t *testing.T) { - kit.QuietMiningLogs() - _ = logging.SetLogLevel("sectors", "debug") - - ctx := context.Background() - - var ( - client kit.TestFullNode - miner1 kit.TestMiner - ) - - // Set up sealing config so that there is no batching of terminate actions - var sealingCfgFn dtypes.GetSealingConfigFunc = func() (sealiface.Config, error) { - return sealiface.Config{ - MaxWaitDealsSectors: 2, - MaxSealingSectors: 0, - MaxSealingSectorsForDeals: 0, - WaitDealsDelay: time.Second, - AlwaysKeepUnsealedCopy: true, - - BatchPreCommits: true, - MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, - PreCommitBatchWait: time.Second, - PreCommitBatchSlack: time.Second, - - AggregateCommits: true, - MinCommitBatch: 1, - MaxCommitBatch: 1, - CommitBatchWait: time.Second, - CommitBatchSlack: time.Second, - - AggregateAboveBaseFee: types.BigMul(types.PicoFil, types.NewInt(150)), // 0.15 nFIL - - TerminateBatchMin: 1, - TerminateBatchMax: 1, - TerminateBatchWait: time.Second, - }, nil - } - fn := func() dtypes.GetSealingConfigFunc { return sealingCfgFn } - sealingCfg := kit.ConstructorOpts(node.Override(new(dtypes.GetSealingConfigFunc), fn)) - - // Set up a client and miner - ens := kit.NewEnsemble(t, kit.MockProofs()) - ens.FullNode(&client) - ens.Miner(&miner1, &client, kit.WithAllSubsystems(), sealingCfg) - ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) - - dh := kit.NewDealHarness(t, &client, &miner1, &miner1) - - client.WaitTillChain(ctx, kit.HeightAtLeast(5)) - - // Make a storage deal - dealProposalCid, _, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ - Rseed: 0, - FastRet: true, - }) - - // Get the miner deal from the proposal CID - minerDeal := getDealByProposalCid(ctx, t, miner1, *dealProposalCid) - - // Terminate the sector containing the deal - t.Logf("Terminating sector %d containing deal %s", minerDeal.SectorNumber, dealProposalCid) - err := miner1.SectorTerminate(ctx, minerDeal.SectorNumber) - require.NoError(t, err) - - clientExpired := false - minerExpired := false - for { - ts, err := client.ChainHead(ctx) - require.NoError(t, err) - - t.Logf("Chain height: %d", ts.Height()) - - // Get the miner deal from the proposal CID - minerDeal := getDealByProposalCid(ctx, t, miner1, *dealProposalCid) - // Get the miner state from the piece CID - mktDeal := getMarketDeal(ctx, t, miner1, minerDeal.Proposal.PieceCID) - - t.Logf("Miner deal:") - t.Logf(" %s -> %s", minerDeal.Proposal.Client, minerDeal.Proposal.Provider) - t.Logf(" StartEpoch: %d", minerDeal.Proposal.StartEpoch) - t.Logf(" EndEpoch: %d", minerDeal.Proposal.EndEpoch) - t.Logf(" SlashEpoch: %d", mktDeal.State.SlashEpoch) - t.Logf(" LastUpdatedEpoch: %d", mktDeal.State.LastUpdatedEpoch) - t.Logf(" State: %s", storagemarket.DealStates[minerDeal.State]) - //spew.Dump(d) - - // Get the client deal - clientDeals, err := client.ClientListDeals(ctx) - require.NoError(t, err) - - t.Logf("Client deal state: %s\n", storagemarket.DealStates[clientDeals[0].State]) - - // Expect the deal to eventually be slashed on the client and the miner - if clientDeals[0].State == storagemarket.StorageDealSlashed { - t.Logf("Client deal slashed") - clientExpired = true - } - if minerDeal.State == storagemarket.StorageDealSlashed { - t.Logf("Miner deal slashed") - minerExpired = true - } - if clientExpired && minerExpired { - t.Logf("PASS: Client and miner deal slashed") - return - } - - if ts.Height() > 4000 { - t.Fatalf("Reached height %d without client and miner deals being slashed", ts.Height()) - } - - time.Sleep(2 * time.Second) - } -} - -func getMarketDeal(ctx context.Context, t *testing.T, miner1 kit.TestMiner, pieceCid cid.Cid) api.MarketDeal { - mktDeals, err := miner1.MarketListDeals(ctx) - require.NoError(t, err) - require.Greater(t, len(mktDeals), 0) - - for _, d := range mktDeals { - if d.Proposal.PieceCID == pieceCid { - return d - } - } - t.Fatalf("miner deal with piece CID %s not found", pieceCid) - return api.MarketDeal{} -} - -func getDealByProposalCid(ctx context.Context, t *testing.T, miner1 kit.TestMiner, dealProposalCid cid.Cid) storagemarket.MinerDeal { - minerDeals, err := miner1.MarketListIncompleteDeals(ctx) - require.NoError(t, err) - require.Greater(t, len(minerDeals), 0) - - for _, d := range minerDeals { - if d.ProposalCid == dealProposalCid { - return d - } - } - t.Fatalf("miner deal with proposal CID %s not found", dealProposalCid) - return storagemarket.MinerDeal{} -} diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 804c18165..7d78d8c02 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -36,7 +36,6 @@ type MakeFullDealParams struct { Rseed int FastRet bool StartEpoch abi.ChainEpoch - MinBlocksDuration uint64 UseCARFileForStorageDeal bool // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon @@ -98,9 +97,6 @@ func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealPa dp.Data.Root = res.Root dp.DealStartEpoch = params.StartEpoch dp.FastRetrieval = params.FastRet - if params.MinBlocksDuration > 0 { - dp.MinBlocksDuration = params.MinBlocksDuration - } deal = dh.StartDeal(ctx, dp) // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go index 9b375cf08..80ead2be3 100644 --- a/markets/storageadapter/client.go +++ b/markets/storageadapter/client.go @@ -18,7 +18,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" - market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" @@ -38,10 +38,10 @@ import ( type ClientNodeAdapter struct { *clientApi - fundmgr *market.FundManager - ev *events.Events - scMgr *SectorCommittedManager - deMgr *DealExpiryManager + fundmgr *market.FundManager + ev *events.Events + dsMatcher *dealStateMatcher + scMgr *SectorCommittedManager } type clientApi struct { @@ -58,12 +58,11 @@ func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi ful a := &ClientNodeAdapter{ clientApi: capi, - fundmgr: fundmgr, - ev: ev, + fundmgr: fundmgr, + ev: ev, + dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))), } a.scMgr = NewSectorCommittedManager(ev, a, &apiWrapper{api: capi}) - dsMatcher := newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))) - a.deMgr = NewDealExpiryManager(ev, capi, capi, dsMatcher) return a } @@ -250,8 +249,94 @@ func (c *ClientNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider return c.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, marketactor.DealProposal(proposal), *publishCid, cb) } -func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - return c.deMgr.OnDealExpiredOrSlashed(ctx, publishCid, proposal, onDealExpired, onDealSlashed) +// TODO: Replace dealID parameter with DealProposal +func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { + head, err := c.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("client: failed to get chain head: %w", err) + } + + sd, err := c.StateMarketStorageDeal(ctx, dealID, head.Key()) + if err != nil { + return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) + } + + // Called immediately to check if the deal has already expired or been slashed + checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + if ts == nil { + // keep listening for events + return false, true, nil + } + + // Check if the deal has already expired + if sd.Proposal.EndEpoch <= ts.Height() { + onDealExpired(nil) + return true, false, nil + } + + // If there is no deal assume it's already been slashed + if sd.State.SectorStartEpoch < 0 { + onDealSlashed(ts.Height(), nil) + return true, false, nil + } + + // No events have occurred yet, so return + // done: false, more: true (keep listening for events) + return false, true, nil + } + + // Called when there was a match against the state change we're looking for + // and the chain has advanced to the confidence height + stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { + // Check if the deal has already expired + if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { + onDealExpired(nil) + return false, nil + } + + // Timeout waiting for state change + if states == nil { + log.Error("timed out waiting for deal expiry") + return false, nil + } + + changedDeals, ok := states.(state.ChangedDeals) + if !ok { + panic("Expected state.ChangedDeals") + } + + deal, ok := changedDeals[dealID] + if !ok { + // No change to deal + return true, nil + } + + // Deal was slashed + if deal.To == nil { + onDealSlashed(ts2.Height(), nil) + return false, nil + } + + return true, nil + } + + // Called when there was a chain reorg and the state change was reverted + revert := func(ctx context.Context, ts *types.TipSet) error { + // TODO: Is it ok to just ignore this? + log.Warn("deal state reverted; TODO: actually handle this!") + return nil + } + + // Watch for state changes to the deal + match := c.dsMatcher.matcher(ctx, dealID) + + // Wait until after the end epoch for the deal and then timeout + timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 + if err := c.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { + return xerrors.Errorf("failed to set up state changed handler: %w", err) + } + + return nil } func (c *ClientNodeAdapter) SignProposal(ctx context.Context, signer address.Address, proposal market2.DealProposal) (*market2.ClientDealProposal, error) { diff --git a/markets/storageadapter/ondealexpired.go b/markets/storageadapter/ondealexpired.go deleted file mode 100644 index 6767c6d34..000000000 --- a/markets/storageadapter/ondealexpired.go +++ /dev/null @@ -1,152 +0,0 @@ -package storageadapter - -import ( - "context" - - market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" -) - -type demEventsAPI interface { - Called(check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error - StateChanged(check events.CheckFunc, scHnd events.StateChangeHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.StateMatchFunc) error -} - -type demChainAPI interface { - ChainHead(context.Context) (*types.TipSet, error) -} - -type DealExpiryManagerAPI interface { - demEventsAPI - demChainAPI - GetCurrentDealInfo(ctx context.Context, tok sealing.TipSetToken, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, error) -} - -type dealExpiryManagerAdapter struct { - demEventsAPI - demChainAPI - *sealing.CurrentDealInfoManager -} - -type DealExpiryManager struct { - demAPI DealExpiryManagerAPI - dsMatcher *dealStateMatcher -} - -func NewDealExpiryManager(ev demEventsAPI, ch demChainAPI, tskAPI sealing.CurrentDealInfoTskAPI, dsMatcher *dealStateMatcher) *DealExpiryManager { - dim := &sealing.CurrentDealInfoManager{ - CDAPI: &sealing.CurrentDealInfoAPIAdapter{CurrentDealInfoTskAPI: tskAPI}, - } - - adapter := &dealExpiryManagerAdapter{ - demEventsAPI: ev, - demChainAPI: ch, - CurrentDealInfoManager: dim, - } - return newDealExpiryManager(adapter, dsMatcher) -} - -func newDealExpiryManager(demAPI DealExpiryManagerAPI, dsMatcher *dealStateMatcher) *DealExpiryManager { - return &DealExpiryManager{demAPI: demAPI, dsMatcher: dsMatcher} -} - -func (mgr *DealExpiryManager) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - head, err := mgr.demAPI.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("client: failed to get chain head: %w", err) - } - - prop := market.DealProposal(proposal) - res, err := mgr.demAPI.GetCurrentDealInfo(ctx, head.Key().Bytes(), &prop, publishCid) - if err != nil { - return xerrors.Errorf("awaiting deal expired: getting deal info: %w", err) - } - - // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { - if ts == nil { - // keep listening for events - return false, true, nil - } - - // Check if the deal has already expired - if res.MarketDeal.Proposal.EndEpoch <= ts.Height() { - onDealExpired(nil) - return true, false, nil - } - - // If there is no deal assume it's already been slashed - if res.MarketDeal.State.SectorStartEpoch < 0 { - onDealSlashed(ts.Height(), nil) - return true, false, nil - } - - // No events have occurred yet, so return - // done: false, more: true (keep listening for events) - return false, true, nil - } - - // Called when there was a match against the state change we're looking for - // and the chain has advanced to the confidence height - stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { - // Check if the deal has already expired - if res.MarketDeal.Proposal.EndEpoch <= h { - onDealExpired(nil) - return false, nil - } - - // Timeout waiting for state change - if states == nil { - log.Errorf("timed out waiting for deal expiry for deal with piece CID %s", proposal.PieceCID) - return false, nil - } - - changedDeals, ok := states.(state.ChangedDeals) - if !ok { - return false, xerrors.Errorf("OnDealExpireOrSlashed stateChanged: Expected state.ChangedDeals") - } - - deal, ok := changedDeals[res.DealID] - if !ok { - // No change to deal - return true, nil - } - - // Deal was slashed - if deal.To == nil || deal.To.SlashEpoch > 0 { - onDealSlashed(ts2.Height(), nil) - return false, nil - } - - return true, nil - } - - // Called when there was a chain reorg and the state change was reverted - revert := func(ctx context.Context, ts *types.TipSet) error { - // TODO: Is it ok to just ignore this? - log.Warn("deal state reverted; TODO: actually handle this!") - return nil - } - - // Watch for state changes to the deal - match := mgr.dsMatcher.matcher(ctx, res.DealID) - - // Wait until after the end epoch for the deal and then timeout - timeout := res.MarketDeal.Proposal.EndEpoch + 1 - if err := mgr.demAPI.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { - return xerrors.Errorf("failed to set up state changed handler: %w", err) - } - - return nil -} diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go index d3d442623..23a3c32a8 100644 --- a/markets/storageadapter/provider.go +++ b/markets/storageadapter/provider.go @@ -18,8 +18,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/lotus/chain/events/state" - market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/filecoin-project/lotus/api" @@ -28,6 +26,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/events" + "github.com/filecoin-project/lotus/chain/events/state" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/lib/sigs" @@ -52,8 +51,8 @@ type ProviderNodeAdapter struct { addBalanceSpec *api.MessageSendSpec maxDealCollateralMultiplier uint64 + dsMatcher *dealStateMatcher scMgr *SectorCommittedManager - deMgr *DealExpiryManager } func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { @@ -67,6 +66,7 @@ func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConf secb: secb, ev: ev, dealPublisher: dealPublisher, + dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))), } if fc != nil { na.addBalanceSpec = &api.MessageSendSpec{MaxFee: abi.TokenAmount(fc.MaxMarketBalanceAddFee)} @@ -77,9 +77,6 @@ func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConf } na.scMgr = NewSectorCommittedManager(ev, na, &apiWrapper{api: full}) - dsMatcher := newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))) - na.deMgr = NewDealExpiryManager(ev, full, full, dsMatcher) - return na } } @@ -331,8 +328,93 @@ func (n *ProviderNodeAdapter) GetDataCap(ctx context.Context, addr address.Addre return sp, err } -func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, publishCid cid.Cid, proposal market0.DealProposal, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - return n.deMgr.OnDealExpiredOrSlashed(ctx, publishCid, proposal, onDealExpired, onDealSlashed) +func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { + head, err := n.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("client: failed to get chain head: %w", err) + } + + sd, err := n.StateMarketStorageDeal(ctx, dealID, head.Key()) + if err != nil { + return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) + } + + // Called immediately to check if the deal has already expired or been slashed + checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + if ts == nil { + // keep listening for events + return false, true, nil + } + + // Check if the deal has already expired + if sd.Proposal.EndEpoch <= ts.Height() { + onDealExpired(nil) + return true, false, nil + } + + // If there is no deal assume it's already been slashed + if sd.State.SectorStartEpoch < 0 { + onDealSlashed(ts.Height(), nil) + return true, false, nil + } + + // No events have occurred yet, so return + // done: false, more: true (keep listening for events) + return false, true, nil + } + + // Called when there was a match against the state change we're looking for + // and the chain has advanced to the confidence height + stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { + // Check if the deal has already expired + if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { + onDealExpired(nil) + return false, nil + } + + // Timeout waiting for state change + if states == nil { + log.Error("timed out waiting for deal expiry") + return false, nil + } + + changedDeals, ok := states.(state.ChangedDeals) + if !ok { + panic("Expected state.ChangedDeals") + } + + deal, ok := changedDeals[dealID] + if !ok { + // No change to deal + return true, nil + } + + // Deal was slashed + if deal.To == nil { + onDealSlashed(ts2.Height(), nil) + return false, nil + } + + return true, nil + } + + // Called when there was a chain reorg and the state change was reverted + revert := func(ctx context.Context, ts *types.TipSet) error { + // TODO: Is it ok to just ignore this? + log.Warn("deal state reverted; TODO: actually handle this!") + return nil + } + + // Watch for state changes to the deal + match := n.dsMatcher.matcher(ctx, dealID) + + // Wait until after the end epoch for the deal and then timeout + timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 + if err := n.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { + return xerrors.Errorf("failed to set up state changed handler: %w", err) + } + + return nil } var _ storagemarket.StorageProviderNode = &ProviderNodeAdapter{} diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 13e751be4..075eed99d 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -525,7 +525,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside earliest := abi.ChainEpoch(sealEpochs) + ht if deal.Proposal.StartEpoch < earliest { log.Warnw("proposed deal would start before sealing can be completed; rejecting storage deal proposal from client", "piece_cid", deal.Proposal.PieceCID, "client", deal.Client.String(), "seal_duration", sealDuration, "earliest", earliest, "curepoch", ht) - return false, fmt.Sprintf("proposed deal start epoch %s too early, cannot seal a sector before %s", deal.Proposal.StartEpoch, earliest), nil + return false, fmt.Sprintf("cannot seal a sector before %s", deal.Proposal.StartEpoch), nil } sd, err := startDelay() From 0789a2927a04ebe860e76f1c9d32724f479696b2 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Aug 2021 08:57:24 -0700 Subject: [PATCH 056/122] fix: make lotus soup use the correct dependencies 1. Add a replace directive for lotus. 2. Make sure we're not depending on commits that don't exist. --- testplans/lotus-soup/go.mod | 19 +-- testplans/lotus-soup/go.sum | 251 ++++++++++++++++++++++++------------ 2 files changed, 181 insertions(+), 89 deletions(-) diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index 713426aa0..953bc43c6 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -8,18 +8,17 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/drand/drand v1.2.1 github.com/filecoin-project/go-address v0.0.5 - github.com/filecoin-project/go-data-transfer v1.7.0 - github.com/filecoin-project/go-fil-markets v1.6.0-rc1.0.20210715124641-2412ccd89114 + github.com/filecoin-project/go-data-transfer v1.7.8 + github.com/filecoin-project/go-fil-markets v1.8.1 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec - github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 + github.com/filecoin-project/go-state-types v0.1.1-0.20210810190654-139e0e79e69e github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d + github.com/filecoin-project/lotus v0.0.0-00010101000000-000000000000 github.com/filecoin-project/specs-actors v0.9.14 - github.com/google/uuid v1.1.2 + github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.7.4 github.com/hashicorp/go-multierror v1.1.0 - github.com/influxdata/influxdb v1.8.3 // indirect - github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672 + github.com/ipfs/go-cid v0.1.0 github.com/ipfs/go-datastore v0.4.5 github.com/ipfs/go-ipfs-files v0.0.8 github.com/ipfs/go-ipld-format v0.2.0 @@ -29,9 +28,9 @@ require ( github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d github.com/kpacha/opencensus-influxdb v0.0.0-20181102202715-663e2683a27c github.com/libp2p/go-libp2p v0.14.2 - github.com/libp2p/go-libp2p-core v0.8.5 + github.com/libp2p/go-libp2p-core v0.8.6 github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200626141350-e730b32bf1e6 - github.com/multiformats/go-multiaddr v0.3.1 + github.com/multiformats/go-multiaddr v0.3.3 github.com/multiformats/go-multiaddr-net v0.2.0 github.com/testground/sdk-go v0.2.6 go.opencensus.io v0.23.0 @@ -42,3 +41,5 @@ require ( // On docker:go and exec:go, it maps to /extra/filecoin-ffi, as it's picked up // as an "extra source" in the manifest. replace github.com/filecoin-project/filecoin-ffi => ../../extern/filecoin-ffi + +replace github.com/filecoin-project/lotus => ../../ diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index 8123b533a..28bb0d517 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -31,8 +31,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= @@ -210,8 +211,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= @@ -265,14 +267,14 @@ github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGj github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= -github.com/filecoin-project/dagstore v0.1.0 h1:lENA+8LlO2TtGBTP2MzZGF3kmjmzE9hB7hZ+bDGsnPY= -github.com/filecoin-project/dagstore v0.1.0/go.mod h1:cqqORk5fbkKVwwZkFk3D7XfeLpsTbWkX5Uj1GrsBOmM= +github.com/filecoin-project/dagstore v0.4.2/go.mod h1:WY5OoLfnwISCk6eASSF927KKPqLPIlTwmG1qHpA08KY= +github.com/filecoin-project/dagstore v0.4.3 h1:yeFl6+2BRY1gOVp/hrZuFa24s7LY0Qqkqx/Gh8lidZs= +github.com/filecoin-project/dagstore v0.4.3/go.mod h1:dm/91AO5UaDd3bABFjg/5fmRH99vvpS7g1mykqvz6KQ= github.com/filecoin-project/go-address v0.0.3/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= github.com/filecoin-project/go-address v0.0.5 h1:SSaFT/5aLfPXycUlFyemoHYhRgdyXClXCyDdNJKPlDM= github.com/filecoin-project/go-address v0.0.5/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= +github.com/filecoin-project/go-amt-ipld/v2 v2.1.0 h1:t6qDiuGYYngDqaLc2ZUvdtAg4UNxPeOYaXhBWSNsVaM= github.com/filecoin-project/go-amt-ipld/v2 v2.1.0/go.mod h1:nfFPoGyX0CU9SkXX8EoCcSuHN1XcbN0c6KBh7yvP5fs= -github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 h1:pIuR0dnMD0i+as8wNnjjHyQrnhP5O5bmba/lmgQeRgU= -github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349/go.mod h1:vgmwKBkx+ca5OIeEvstiQgzAZnb7R6QaqE1oEDSqa6g= github.com/filecoin-project/go-amt-ipld/v3 v3.0.0/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 h1:ZNJ9tEG5bE72vBWYiuh5bkxJVM3ViHNOmQ7qew9n6RE= github.com/filecoin-project/go-amt-ipld/v3 v3.1.0/go.mod h1:UjM2QhDFrrjD5s1CdnkJkat4ga+LqZBZgTMniypABRo= @@ -282,22 +284,25 @@ github.com/filecoin-project/go-bitfield v0.2.4 h1:uZ7MeE+XfM5lqrHJZ93OnhQKc/rveW github.com/filecoin-project/go-bitfield v0.2.4/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 h1:av5fw6wmm58FYMgJeoB/lK9XXrgdugYiTqkdxjTy9k8= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2/go.mod h1:pqTiPHobNkOVM5thSRsHYjyQfq7O5QSCMhvuu9JoDlg= -github.com/filecoin-project/go-commp-utils v0.1.0/go.mod h1:6s95K91mCyHY51RPWECZieD3SGWTqIFLf1mPOes9l5U= github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 h1:U9Z+76pHCKBmtdxFV7JFZJj7OVm12I6dEKwtMVbq5p0= github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7/go.mod h1:6s95K91mCyHY51RPWECZieD3SGWTqIFLf1mPOes9l5U= 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.6/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= +github.com/filecoin-project/go-data-transfer v1.7.8 h1:s4cF9nX9sEy7RgZd3NW92YN/hKyIy2fQl+7dVOAS8r8= +github.com/filecoin-project/go-data-transfer v1.7.8/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= -github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a h1:hyJ+pUm/4U4RdEZBlg6k8Ma4rDiuvqyGpoICXAxwsTg= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= +github.com/filecoin-project/go-fil-commcid v0.1.0 h1:3R4ds1A9r6cr8mvZBfMYxTS88OqLYEo6roi+GiIeOh8= +github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= +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-rc1.0.20210715124641-2412ccd89114 h1:uEJghQAwCTCPpR/aQLGvnqahWPDOLYL4jnYsdeItsKc= -github.com/filecoin-project/go-fil-markets v1.6.0-rc1.0.20210715124641-2412ccd89114/go.mod h1:iegdHk34YkHHpgGVB/dKij1emfhoTb2lat80WMWw3Ag= +github.com/filecoin-project/go-fil-markets v1.8.1 h1:nNJB5EIp5c6yo/z51DloVaL7T24SslCoxSDOXwNQr9k= +github.com/filecoin-project/go-fil-markets v1.8.1/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= 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= @@ -309,8 +314,9 @@ github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec h1:r github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0DzzQwqsL0XarpnI= github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= -github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+/4aUeUoKr6AKfPE3mBhXA5spIV6UcKdTYDPNU2Tdmg= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= +github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 h1:0BogtftbcgyBx4lP2JWM00ZK7/pXmgnrDqKp9aLTgVs= +github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1/go.mod h1:VYVPJqwpsfmtoHnAmPx6MUwmrK6HIcDqZJiuZhtmfLQ= github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498 h1:G10ezOvpH1CLXQ19EA9VWNwyL0mg536ujSayjV0yg0k= github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498/go.mod h1:1FH85P8U+DUEmWk1Jkw3Bw7FrwTVUNHk/95PSPG+dts= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= @@ -318,17 +324,17 @@ github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= -github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 h1:Jc4OprDp3bRDxbsrXNHPwJabZJM3iDy+ri8/1e0ZnX4= github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= -github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe h1:dF8u+LEWeIcTcfUcCf3WFVlc81Fr2JKg8zPzIbBDKDw= +github.com/filecoin-project/go-state-types v0.1.1-0.20210810190654-139e0e79e69e h1:XAgb6HmgXaGRklNjhZoNMSIYriKLqjWXIqYMotg6iSs= +github.com/filecoin-project/go-state-types v0.1.1-0.20210810190654-139e0e79e69e/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= +github.com/filecoin-project/go-statemachine v1.0.1 h1:LQ60+JDVjMdLxXmVFM2jjontzOYnfVE7u02CXV3WKSw= +github.com/filecoin-project/go-statemachine v1.0.1/go.mod h1:jZdXXiHa61n4NmgWFG4w8tnqgvZVHYbJ3yW7+y8bF54= 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= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= -github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d h1:hGVeAKlfdyk6cjiU/vK8pl9+Oj8OKM4PLt3j6cAGMvg= -github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d/go.mod h1:ZU8NxaMnfUp5uPL+8sYwBsL2qQX6ZxeIEzVz76soAPE= github.com/filecoin-project/specs-actors v0.9.4/go.mod h1:BStZQzx5x7TmCkLv0Bpa07U6cPKol6fd3w9KjMPZ6Z4= github.com/filecoin-project/specs-actors v0.9.12/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= @@ -345,8 +351,8 @@ github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIP github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.1 h1:PrYm5AKdMlJ/55eRW5laWcnaX66gyyDYBWvH38kNAMo= -github.com/filecoin-project/specs-actors/v5 v5.0.1/go.mod h1:74euMDIXorusOBs/QL/LNkYsXZdDpLJwojWw6T03pdE= +github.com/filecoin-project/specs-actors/v5 v5.0.4 h1:OY7BdxJWlUfUFXWV/kpNBYGXNPasDIedf42T3sGx08s= +github.com/filecoin-project/specs-actors/v5 v5.0.4/go.mod h1:5BAKRAMsOOlD8+qCw4UvT/lTLInCJ3JwOWZbX8Ipwq4= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5/go.mod h1:iQ9QXLpYWL3m7warwvK1JC/pTri8mnfEmKygNDqqY6E= @@ -408,6 +414,8 @@ github.com/go-sql-driver/mysql v1.4.1/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= @@ -443,8 +451,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= @@ -457,8 +465,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= @@ -473,6 +483,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/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= @@ -490,8 +501,9 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -592,8 +604,8 @@ github.com/ipfs/go-bitswap v0.0.9/go.mod h1:kAPf5qgn2W2DrgAcscZ3HrM9qh4pH+X8Fkk3 github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= github.com/ipfs/go-bitswap v0.1.8/go.mod h1:TOWoxllhccevbWFUR2N7B1MTSVVge1s6XSMiCSA4MzM= -github.com/ipfs/go-bitswap v0.3.2 h1:TdKx7lpidYe2dMAKfdeNS26y6Pc/AZX/i8doI1GV210= -github.com/ipfs/go-bitswap v0.3.2/go.mod h1:AyWWfN3moBzQX0banEtfKOfbXb3ZeoOeXnZGNPV9S6w= +github.com/ipfs/go-bitswap v0.3.4 h1:AhJhRrG8xkxh6x87b4wWs+4U4y3DVB3doI8yFNqgQME= +github.com/ipfs/go-bitswap v0.3.4/go.mod h1:4T7fvNv/LmOys+21tnLzGKncMeeXUYUd1nUiJ2teMvI= github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= @@ -603,8 +615,8 @@ github.com/ipfs/go-blockservice v0.0.7/go.mod h1:EOfb9k/Y878ZTRY/CH0x5+ATtaipfbR github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.3/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= -github.com/ipfs/go-blockservice v0.1.4 h1:Vq+MlsH8000KbbUciRyYMEw/NNP8UAGmcqKi4uWmFGA= -github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= +github.com/ipfs/go-blockservice v0.1.5 h1:euqZu96CCbToPyYVwVshu8ENURi8BhFd7FUFfTLi+fQ= +github.com/ipfs/go-blockservice v0.1.5/go.mod h1:yLk8lBJCBRWRqerqCSVi3cE/Dncdt3vGC/PJMVKhLTY= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -614,8 +626,9 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672 h1:PabVicIEIt7qUwx5gu80wZsALHUZ4Zux37M+x0n/Erk= -github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= +github.com/ipfs/go-cid v0.0.8-0.20210716091050-de6c03deae1c/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= +github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0= +github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-cidutil v0.0.2 h1:CNOboQf1t7Qp0nuNh8QMmhJs0+Q//bRL1axtCnIB1Yo= github.com/ipfs/go-cidutil v0.0.2/go.mod h1:ewllrvrxG6AMYStla3GD7Cqn+XYSLqjK0vc+086tB6s= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= @@ -636,8 +649,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= @@ -656,16 +670,18 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.6.4 h1:g6wFRK2BkLPnx8nfoSdnokp5gtpuGyWZjbqI6q3NGb8= -github.com/ipfs/go-graphsync v0.6.4/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= +github.com/ipfs/go-graphsync v0.6.8/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.9 h1:I15gVcZuqsaeaj64/SjlwiIAc9MkOgfSv0M1CgcoFRE= +github.com/ipfs/go-graphsync v0.6.9/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= github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86HgIYJW/D/MwqeYQ= github.com/ipfs/go-ipfs-blockstore v1.0.0/go.mod h1:knLVdhVU9L7CC4T+T4nvGdeUIPAXlnd9zmXfp+9MIjU= github.com/ipfs/go-ipfs-blockstore v1.0.1/go.mod h1:MGNZlHNEnR4KGgPHM3/k8lBySIOK2Ve+0KjZubKlaOE= -github.com/ipfs/go-ipfs-blockstore v1.0.3 h1:RDhK6fdg5YsonkpMuMpdvk/pRtOQlrIRIybuQfkvB2M= github.com/ipfs/go-ipfs-blockstore v1.0.3/go.mod h1:MGNZlHNEnR4KGgPHM3/k8lBySIOK2Ve+0KjZubKlaOE= +github.com/ipfs/go-ipfs-blockstore v1.0.4 h1:DZdeya9Vu4ttvlGheQPGrj6kWehXnYZRFCp9EsZQ1hI= +github.com/ipfs/go-ipfs-blockstore v1.0.4/go.mod h1:uL7/gTJ8QIZ3MtA3dWf+s1a0U3fJy2fcEZAsovpRp+w= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= @@ -774,9 +790,10 @@ github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBH github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= -github.com/ipld/go-car/v2 v2.0.0-20210708104948-d79de78d9567/go.mod h1:Ueq4zx/SNx7yHwmfr9xKlKpXxRCMM6wyqC8B0rv9oig= -github.com/ipld/go-car/v2 v2.0.0-20210715123707-a315bb047f6b h1:jr7cFCEeu+rDhkivLTI5BX1JrPNLvzYtsOpIHAcfdR8= -github.com/ipld/go-car/v2 v2.0.0-20210715123707-a315bb047f6b/go.mod h1:0nAH3QhJOua+Dz6SkD6zOYtoZMNCJSDHY4IrbYe3AQs= +github.com/ipld/go-car/v2 v2.0.0-beta1.0.20210721090610-5a9d1b217d25/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= +github.com/ipld/go-car/v2 v2.0.2/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= +github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 h1:6Z0beJSZNsRY+7udoqUl4gQ/tqtrPuRvDySrlsvbqZA= +github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= @@ -836,6 +853,7 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY 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/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/kabukky/httpscerts v0.0.0-20150320125433-617593d7dcb3 h1:Iy7Ifq2ysilWU4QlCx/97OoI4xT1IV7i8byT/EyIT/M= @@ -853,10 +871,14 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI 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.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +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 v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -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/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.8 h1:bhR2mgIlno/Sfk4oUbH4sPlc83z1yGrN9bvqiq3C33I= +github.com/klauspost/cpuid/v2 v2.0.8/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -876,10 +898,10 @@ 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.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -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= @@ -915,6 +937,8 @@ github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El github.com/libp2p/go-libp2p v0.9.2/go.mod h1:cunHNLDVus66Ct9iXXcjKRLdmHdFdHVe1TAnbubJQqQ= github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8= github.com/libp2p/go-libp2p v0.12.0/go.mod h1:FpHZrfC1q7nA8jitvdjKBDF31hguaC676g/nT9PgQM0= +github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= +github.com/libp2p/go-libp2p v0.14.0/go.mod h1:dsQrWLAoIn+GkHPN/U+yypizkHiB9tnv79Os+kSgQ4Q= github.com/libp2p/go-libp2p v0.14.2 h1:qs0ABtjjNjS+RIXT1uM7sMJEvIc0pq2nKR0VQxFXhHI= github.com/libp2p/go-libp2p v0.14.2/go.mod h1:0PQMADQEjCM2l8cSMYDpTgsb8gr6Zq7i4LUgq1mlW2E= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= @@ -980,8 +1004,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.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= @@ -993,8 +1018,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= @@ -1031,7 +1057,6 @@ github.com/libp2p/go-libp2p-netutil v0.0.1/go.mod h1:GdusFvujWZI9Vt0X5BKqwWWmZFx github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= -github.com/libp2p/go-libp2p-noise v0.1.2/go.mod h1:9B10b7ueo7TIxZHHcjcDCo5Hd6kfKT2m77by82SFRfE= github.com/libp2p/go-libp2p-noise v0.2.0 h1:wmk5nhB9a2w2RxMOyvsoKjizgJOEaJdfAakr0jN8gds= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.0.1/go.mod h1:nXQvOBbwVqoP+T5Y5nCjeH4sP9IX/J0AMzcDUVruVoo= @@ -1049,8 +1074,9 @@ 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= @@ -1058,14 +1084,15 @@ github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEX 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.3.2/go.mod h1:Uss7/Cfz872KggNb+doCVPHeCDmXB7z500m/R8DaAUk= -github.com/libp2p/go-libp2p-pubsub v0.4.2-0.20210212194758-6c1addf493eb h1:HExLcdXn8fgtXPciUw97O5NNhBn31dt6d9fVUD4cngo= -github.com/libp2p/go-libp2p-pubsub v0.4.2-0.20210212194758-6c1addf493eb/go.mod h1:izkeMLvz6Ht8yAISXjx60XUQZMq9ZMe5h2ih4dLIBIQ= +github.com/libp2p/go-libp2p-pubsub v0.5.4 h1:rHl9/Xok4zX3zgi0pg0XnUj9Xj2OeXO8oTu85q2+YA8= +github.com/libp2p/go-libp2p-pubsub v0.5.4/go.mod h1:gVOzwebXVdSMDQBTfH8ACO5EJ4SQrvsHqCmYsCZpD0E= github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200626141350-e730b32bf1e6 h1:2lH7rMlvDPSvXeOR+g7FE6aqiEwxtpxWKQL8uigk5fQ= github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200626141350-e730b32bf1e6/go.mod h1:8ZodgKS4qRLayfw9FDKDd9DX4C16/GMofDxSldG8QPI= 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= @@ -1093,8 +1120,10 @@ 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.5.0 h1:HIK0z3Eqoo8ugmN8YqWAhD2RORgR+3iNXYG4U2PFd1E= +github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= 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= @@ -1103,8 +1132,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= @@ -1115,8 +1145,10 @@ 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.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= 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.1.2/go.mod h1:xUoV/RmYkg6BW/qGxA9XJyg+HzXFYkeXbnhjmnYzKp8= github.com/libp2p/go-libp2p-yamux v0.1.3/go.mod h1:VGSQVrqkh6y4nm0189qqxMtvyBft44MOYYPpYKXiVt4= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= @@ -1126,8 +1158,9 @@ github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= -github.com/libp2p/go-libp2p-yamux v0.4.1/go.mod h1:FA/NjRYRVNjqOzpGuGqcruH7jAU2mYIjtKBicVOL3dc= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= +github.com/libp2p/go-libp2p-yamux v0.5.3/go.mod h1:Vy3TMonBAfTMXHWopsMc8iX/XGRYrRlpUaMzaeuHV/s= github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-maddr-filter v0.0.1/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= @@ -1156,6 +1189,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= @@ -1170,8 +1204,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= @@ -1187,8 +1222,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= @@ -1211,14 +1247,17 @@ github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= +github.com/libp2p/go-yamux/v2 v2.1.1/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= 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= @@ -1235,10 +1274,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= @@ -1278,6 +1324,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= @@ -1320,8 +1372,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= @@ -1345,8 +1398,10 @@ github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/g github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multicodec v0.2.1-0.20210713081508-b421db6850ae h1:wfljHPpiR0UDOjeqld9ds0Zxl3Nt/j+0wnvyBc01JgY= github.com/multiformats/go-multicodec v0.2.1-0.20210713081508-b421db6850ae/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= +github.com/multiformats/go-multicodec v0.2.1-0.20210714093213-b2b5bd6fe68b/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= +github.com/multiformats/go-multicodec v0.3.0 h1:tstDwfIjiHbnIjeM5Lp+pMrSeN+LCMsEwOrkPmWm03A= +github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM= @@ -1371,6 +1426,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= @@ -1381,13 +1437,13 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c h1:5bFTChQxSKNwy8ALwOebjekYExl9HTT9urdawqC95tA= 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= @@ -1398,8 +1454,10 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ 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= @@ -1407,8 +1465,9 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa 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/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= @@ -1466,8 +1525,10 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ 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/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= 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= @@ -1482,8 +1543,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= @@ -1495,8 +1558,10 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa 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/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= 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= @@ -1599,7 +1664,6 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1669,8 +1733,8 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 h1:bsUlNhdmbtlfdLVXAVfuvKQ01RnWAM09TVrJkI7NZs4= -github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8 h1:TEv7MId88TyIqIUL4hbf9otOookIolMxlEbN0ro671Y= +github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-ctrlnet v0.0.0-20180313164037-f564fbbdaa95/go.mod h1:SJqKCCPXRfBFCwXjfNT/skfsceF7+MBFLI2OrvuRA7g= @@ -1710,6 +1774,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.12.1 h1:hYRcyznPRJp+5mzF2sazTLP2nGvGjYDD2VzhHhFomLU= @@ -1736,7 +1801,6 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -1755,14 +1819,16 @@ 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= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -1810,8 +1876,10 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh 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/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/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1826,8 +1894,10 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20210615023648-acb5c1269671 h1:ddvpKwqE7dm58PoWjRCmaCiA3DANEW0zWGfNYQD212Y= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20210715201039-d37aa40e8013 h1:Jp57DBw4K7mimZNA3F9f7CndVcUt4kJjmyJf2rzJHoI= +golang.org/x/exp v0.0.0-20210715201039-d37aa40e8013/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1902,9 +1972,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-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk= +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/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= @@ -1991,17 +2065,28 @@ golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210119212857-b64e53b001e4/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-20210403161142-5e06dd20ab57/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 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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= @@ -2062,9 +2147,12 @@ 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.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +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= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2151,13 +2239,14 @@ 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -2180,10 +2269,12 @@ 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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= From e6902304d5661de6a10eadeb5b2b85f4ae48ac1c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Aug 2021 16:20:23 -0700 Subject: [PATCH 057/122] fix: address expensive fork review --- chain/stmgr/forks.go | 8 +++----- chain/stmgr/forks_test.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index b6c6985e2..4fca13f96 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -211,11 +211,9 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig return retCid, nil } -// Returns true if executing the current tipset would trigger an expensive fork. -// -// - If the tipset is the genesis, this function always returns false. -// - If inclusive is true, this function will also return true if applying a message on-top-of the -// tipset would trigger a fork. +// Returns true executing tipsets between the specified heights would trigger an expensive +// migration. NOTE: migrations occuring _at_ the target height are not included, as they're executed +// _after_ the target height. func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) bool { for h := parent; h < height; h++ { if _, ok := sm.expensiveUpgrades[h]; ok { diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 97ec8643b..6c507a0c4 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -310,7 +310,7 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { nullStart := abi.ChainEpoch(testForkHeight - nullsBefore) nullLength := abi.ChainEpoch(nullsBefore + nullsAfter) - for i := 0; i < 50; i++ { + for i := 0; i < testForkHeight*2; i++ { pts := cg.CurTipset.TipSet() skip := abi.ChainEpoch(0) if pts.Height() == nullStart { From 08207912a5f6f6671f6f6c6e4bbfbad462d6abab Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Aug 2021 16:23:13 -0700 Subject: [PATCH 058/122] fix: check if at genesis when testing for expensive forks in Call Otherwise, we could try to lookup the parent of the block at height 0. --- chain/stmgr/call.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 0f045c5f4..42f9732fb 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -49,7 +49,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. } ts = pts } - } else { + } else if ts.Height() > 0 { pts, err := sm.cs.LoadTipSet(ts.Parents()) if err != nil { return nil, xerrors.Errorf("failed to load parent tipset: %w", err) @@ -58,6 +58,9 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. if sm.hasExpensiveFork(pheight) { return nil, ErrExpensiveFork } + } else { + // We can't get the parent tipset in this case. + pheight = ts.Height() - 1 } bstate := ts.ParentState() From 14754f1b1875a1477cd7153cc5cffc0665b6874d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 2 Aug 2021 14:08:44 -0700 Subject: [PATCH 059/122] chore: dedup datastore import --- chain/store/store.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chain/store/store.go b/chain/store/store.go index df5936c37..a8e378e64 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -31,7 +31,6 @@ import ( lru "github.com/hashicorp/golang-lru" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" dstore "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/query" cbor "github.com/ipfs/go-ipld-cbor" @@ -642,7 +641,7 @@ func (cs *ChainStore) FlushValidationCache() error { return FlushValidationCache(cs.metadataDs) } -func FlushValidationCache(ds datastore.Batching) error { +func FlushValidationCache(ds dstore.Batching) error { log.Infof("clearing block validation cache...") dsWalk, err := ds.Query(query.Query{ @@ -674,7 +673,7 @@ func FlushValidationCache(ds datastore.Batching) error { for _, k := range allKeys { if strings.HasPrefix(k.Key, blockValidationCacheKeyPrefix.String()) { delCnt++ - batch.Delete(datastore.RawKey(k.Key)) // nolint:errcheck + batch.Delete(dstore.RawKey(k.Key)) // nolint:errcheck } } From 43bbde1e6b922b7ac497bf9c2c7330d2681d1cba Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 2 Aug 2021 14:12:00 -0700 Subject: [PATCH 060/122] fix: close chain head subscription when the reader is slow The reader can just re-subscribe when they're ready to catch up. This prevents a slow reader from bogging down the entire system. --- chain/store/store.go | 29 +++++++++++++++++---------- itests/api_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ tools/stats/rpc.go | 5 ++++- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/chain/store/store.go b/chain/store/store.go index a8e378e64..1c90b7e0c 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -293,27 +293,36 @@ func (cs *ChainStore) SubHeadChanges(ctx context.Context) chan []*api.HeadChange }} go func() { - defer close(out) - var unsubOnce sync.Once + defer func() { + // Tell the caller we're done first, the following may block for a bit. + close(out) + + // Unsubscribe. + cs.bestTips.Unsub(subch) + + // Drain the channel. + for range subch { + } + }() for { select { case val, ok := <-subch: if !ok { - log.Warn("chain head sub exit loop") + // Shutting down. + return + } + select { + case out <- val.([]*api.HeadChange): + default: + log.Errorf("closing head change subscription due to slow reader") return } if len(out) > 5 { log.Warnf("head change sub is slow, has %d buffered entries", len(out)) } - select { - case out <- val.([]*api.HeadChange): - case <-ctx.Done(): - } case <-ctx.Done(): - unsubOnce.Do(func() { - go cs.bestTips.Unsub(subch) - }) + return } } }() diff --git a/itests/api_test.go b/itests/api_test.go index 01e006fed..9a21c9dfc 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" + logging "github.com/ipfs/go-log/v2" ) func TestAPI(t *testing.T) { @@ -39,6 +40,7 @@ func runAPITest(t *testing.T, opts ...interface{}) { t.Run("testConnectTwo", ts.testConnectTwo) t.Run("testMining", ts.testMining) t.Run("testMiningReal", ts.testMiningReal) + t.Run("testSlowNotify", ts.testSlowNotify) t.Run("testSearchMsg", ts.testSearchMsg) t.Run("testNonGenesisMiner", ts.testNonGenesisMiner) } @@ -169,6 +171,51 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { ts.testMining(t) } +func (ts *apiSuite) testSlowNotify(t *testing.T) { + _ = logging.SetLogLevel("rpc", "ERROR") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + full, miner, _ := kit.EnsembleMinimal(t, ts.opts...) + + // Subscribe a bunch of times to make sure we fill up any RPC buffers. + var newHeadsChans []<-chan []*lapi.HeadChange + for i := 0; i < 100; i++ { + newHeads, err := full.ChainNotify(ctx) + require.NoError(t, err) + newHeadsChans = append(newHeadsChans, newHeads) + } + + initHead := (<-newHeadsChans[0])[0] + baseHeight := initHead.Val.Height() + + bm := kit.NewBlockMiner(t, miner) + bm.MineBlocks(ctx, time.Microsecond) + + full.WaitTillChain(ctx, kit.HeightAtLeast(baseHeight+100)) + + // Make sure they were all closed. + for _, ch := range newHeadsChans { + var ok bool + for ok { + select { + case _, ok = <-ch: + default: + t.Fatal("expected new heads channel to be closed") + } + } + } + + // Make sure we can resubscribe and everything still works. + newHeads, err := full.ChainNotify(ctx) + require.NoError(t, err) + for i := 0; i < 10; i++ { + _, ok := <-newHeads + require.True(t, ok, "notify channel closed") + } +} + func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() diff --git a/tools/stats/rpc.go b/tools/stats/rpc.go index 0aa3d141e..4e503cb39 100644 --- a/tools/stats/rpc.go +++ b/tools/stats/rpc.go @@ -139,7 +139,10 @@ func GetTips(ctx context.Context, api v0api.FullNode, lastHeight abi.ChainEpoch, for { select { - case changes := <-notif: + case changes, ok := <-notif: + if !ok { + return + } for _, change := range changes { log.Infow("Head event", "height", change.Val.Height(), "type", change.Type) From a875e9ba732bffc05fb6890ca47a942871f0107c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 3 Aug 2021 16:30:14 -0700 Subject: [PATCH 061/122] fix: check parents when adding tipsets to the "cache" --- chain/events/tscache.go | 11 +- chain/events/tscache_test.go | 203 +++++++++++++++++++---------------- 2 files changed, 121 insertions(+), 93 deletions(-) diff --git a/chain/events/tscache.go b/chain/events/tscache.go index 44699684e..7beb80364 100644 --- a/chain/events/tscache.go +++ b/chain/events/tscache.go @@ -21,7 +21,7 @@ type tipSetCache struct { mu sync.RWMutex cache []*types.TipSet - start int + start int // chain head (end) len int storage tsCacheAPI @@ -42,9 +42,16 @@ func (tsc *tipSetCache) add(ts *types.TipSet) error { defer tsc.mu.Unlock() if tsc.len > 0 { - if tsc.cache[tsc.start].Height() >= ts.Height() { + best := tsc.cache[tsc.start] + if best.Height() >= ts.Height() { return xerrors.Errorf("tipSetCache.add: expected new tipset height to be at least %d, was %d", tsc.cache[tsc.start].Height()+1, ts.Height()) } + if best.Key() != ts.Parents() { + return xerrors.Errorf( + "tipSetCache.add: expected new tipset %s (%d) to follow %s (%d), its parents are %s", + ts.Key(), ts.Height(), best.Key(), best.Height(), best.Parents(), + ) + } } nextH := ts.Height() diff --git a/chain/events/tscache_test.go b/chain/events/tscache_test.go index ab6336f24..9ba9a556c 100644 --- a/chain/events/tscache_test.go +++ b/chain/events/tscache_test.go @@ -6,57 +6,13 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" ) -func TestTsCache(t *testing.T) { - tsc := newTSCache(50, &tsCacheAPIFailOnStorageCall{t: t}) - - h := abi.ChainEpoch(75) - - a, _ := address.NewFromString("t00") - - add := func() { - ts, err := types.NewTipSet([]*types.BlockHeader{{ - Miner: a, - Height: h, - ParentStateRoot: dummyCid, - Messages: dummyCid, - ParentMessageReceipts: dummyCid, - BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, - BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, - }}) - if err != nil { - t.Fatal(err) - } - if err := tsc.add(ts); err != nil { - t.Fatal(err) - } - h++ - } - - for i := 0; i < 9000; i++ { - if i%90 > 60 { - best, err := tsc.best() - if err != nil { - t.Fatal(err, "; i:", i) - return - } - if err := tsc.revert(best); err != nil { - t.Fatal(err, "; i:", i) - return - } - h-- - } else { - add() - } - } - -} - type tsCacheAPIFailOnStorageCall struct { t *testing.T } @@ -70,77 +26,123 @@ func (tc *tsCacheAPIFailOnStorageCall) ChainHead(ctx context.Context) (*types.Ti return &types.TipSet{}, nil } -func TestTsCacheNulls(t *testing.T) { - tsc := newTSCache(50, &tsCacheAPIFailOnStorageCall{t: t}) +type cacheHarness struct { + t *testing.T - h := abi.ChainEpoch(75) + miner address.Address + tsc *tipSetCache + height abi.ChainEpoch +} - a, _ := address.NewFromString("t00") - add := func() { - ts, err := types.NewTipSet([]*types.BlockHeader{{ - Miner: a, - Height: h, - ParentStateRoot: dummyCid, - Messages: dummyCid, - ParentMessageReceipts: dummyCid, - BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, - BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, - }}) - if err != nil { - t.Fatal(err) - } - if err := tsc.add(ts); err != nil { - t.Fatal(err) - } - h++ +func newCacheharness(t *testing.T) *cacheHarness { + a, err := address.NewFromString("t00") + require.NoError(t, err) + + h := &cacheHarness{ + t: t, + tsc: newTSCache(50, &tsCacheAPIFailOnStorageCall{t: t}), + height: 75, + miner: a, } + h.addWithParents(nil) + return h +} - add() - add() - add() - h += 5 +func (h *cacheHarness) addWithParents(parents []cid.Cid) { + ts, err := types.NewTipSet([]*types.BlockHeader{{ + Miner: h.miner, + Height: h.height, + ParentStateRoot: dummyCid, + Messages: dummyCid, + ParentMessageReceipts: dummyCid, + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + Parents: parents, + }}) + require.NoError(h.t, err) + require.NoError(h.t, h.tsc.add(ts)) + h.height++ +} - add() - add() +func (h *cacheHarness) add() { + last, err := h.tsc.best() + require.NoError(h.t, err) + h.addWithParents(last.Cids()) +} - best, err := tsc.best() +func (h *cacheHarness) revert() { + best, err := h.tsc.best() + require.NoError(h.t, err) + err = h.tsc.revert(best) + require.NoError(h.t, err) + h.height-- +} + +func (h *cacheHarness) skip(n abi.ChainEpoch) { + h.height += n +} + +func TestTsCache(t *testing.T) { + h := newCacheharness(t) + + for i := 0; i < 9000; i++ { + if i%90 > 60 { + h.revert() + } else { + h.add() + } + } +} + +func TestTsCacheNulls(t *testing.T) { + h := newCacheharness(t) + + h.add() + h.add() + h.add() + h.skip(5) + + h.add() + h.add() + + best, err := h.tsc.best() require.NoError(t, err) - require.Equal(t, h-1, best.Height()) + require.Equal(t, h.height-1, best.Height()) - ts, err := tsc.get(h - 1) + ts, err := h.tsc.get(h.height - 1) require.NoError(t, err) - require.Equal(t, h-1, ts.Height()) + require.Equal(t, h.height-1, ts.Height()) - ts, err = tsc.get(h - 2) + ts, err = h.tsc.get(h.height - 2) require.NoError(t, err) - require.Equal(t, h-2, ts.Height()) + require.Equal(t, h.height-2, ts.Height()) - ts, err = tsc.get(h - 3) + ts, err = h.tsc.get(h.height - 3) require.NoError(t, err) require.Nil(t, ts) - ts, err = tsc.get(h - 8) + ts, err = h.tsc.get(h.height - 8) require.NoError(t, err) - require.Equal(t, h-8, ts.Height()) + require.Equal(t, h.height-8, ts.Height()) - best, err = tsc.best() + best, err = h.tsc.best() require.NoError(t, err) - require.NoError(t, tsc.revert(best)) + require.NoError(t, h.tsc.revert(best)) - best, err = tsc.best() + best, err = h.tsc.best() require.NoError(t, err) - require.NoError(t, tsc.revert(best)) + require.NoError(t, h.tsc.revert(best)) - best, err = tsc.best() + best, err = h.tsc.best() require.NoError(t, err) - require.Equal(t, h-8, best.Height()) + require.Equal(t, h.height-8, best.Height()) - h += 50 - add() + h.skip(50) + h.add() - ts, err = tsc.get(h - 1) + ts, err = h.tsc.get(h.height - 1) require.NoError(t, err) - require.Equal(t, h-1, ts.Height()) + require.Equal(t, h.height-1, ts.Height()) } type tsCacheAPIStorageCallCounter struct { @@ -166,3 +168,22 @@ func TestTsCacheEmpty(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, callCounter.chainHead) } + +func TestTsCacheSkip(t *testing.T) { + h := newCacheharness(t) + + ts, err := types.NewTipSet([]*types.BlockHeader{{ + Miner: h.miner, + Height: h.height, + ParentStateRoot: dummyCid, + Messages: dummyCid, + ParentMessageReceipts: dummyCid, + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + // With parents that don't match the last block. + Parents: nil, + }}) + require.NoError(h.t, err) + err = h.tsc.add(ts) + require.Error(t, err) +} From 38461703028c86c8b7f8a55950cf117c82e0026c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 3 Aug 2021 17:10:30 -0700 Subject: [PATCH 062/122] refactor events system --- chain/events/cache.go | 33 ++ chain/events/events.go | 205 +-------- chain/events/events_called.go | 275 +++++------- chain/events/events_height.go | 395 +++++++++-------- chain/events/events_test.go | 401 +++++++----------- chain/events/message_cache.go | 42 ++ chain/events/observer.go | 234 ++++++++++ chain/events/tscache.go | 207 +++++---- chain/events/tscache_test.go | 61 ++- chain/events/utils.go | 6 +- itests/paych_api_test.go | 5 +- itests/paych_cli_test.go | 5 +- markets/storageadapter/client.go | 11 +- .../storageadapter/ondealsectorcommitted.go | 10 +- .../ondealsectorcommitted_test.go | 6 +- markets/storageadapter/provider.go | 13 +- paychmgr/settler/settler.go | 9 +- storage/adapter_events.go | 2 +- storage/miner.go | 38 +- 19 files changed, 1041 insertions(+), 917 deletions(-) create mode 100644 chain/events/cache.go create mode 100644 chain/events/message_cache.go create mode 100644 chain/events/observer.go diff --git a/chain/events/cache.go b/chain/events/cache.go new file mode 100644 index 000000000..ef4b5bba8 --- /dev/null +++ b/chain/events/cache.go @@ -0,0 +1,33 @@ +package events + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" +) + +type uncachedAPI interface { + ChainNotify(context.Context) (<-chan []*api.HeadChange, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) + StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) + + StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) // optional / for CalledMsg +} + +type cache struct { + *tipSetCache + *messageCache + uncachedAPI +} + +func newCache(api EventAPI, gcConfidence abi.ChainEpoch) *cache { + return &cache{ + newTSCache(api, gcConfidence), + newMessageCache(api), + api, + } +} diff --git a/chain/events/events.go b/chain/events/events.go index 8511de921..1e39d3646 100644 --- a/chain/events/events.go +++ b/chain/events/events.go @@ -2,18 +2,14 @@ package events import ( "context" - "sync" - "time" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" ) @@ -25,209 +21,50 @@ type ( RevertHandler func(ctx context.Context, ts *types.TipSet) error ) -type heightHandler struct { - confidence int - called bool - - handle HeightHandler - revert RevertHandler +// A TipSetObserver receives notifications of tipsets +type TipSetObserver interface { + Apply(ctx context.Context, from, to *types.TipSet) error + Revert(ctx context.Context, from, to *types.TipSet) error } type EventAPI interface { ChainNotify(context.Context) (<-chan []*api.HeadChange, error) ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) ChainHead(context.Context) (*types.TipSet, error) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) // optional / for CalledMsg } type Events struct { - api EventAPI - - tsc *tipSetCache - lk sync.Mutex - - ready chan struct{} - readyOnce sync.Once - - heightEvents + *observer + *heightEvents *hcEvents - - observers []TipSetObserver } -func NewEventsWithConfidence(ctx context.Context, api EventAPI, gcConfidence abi.ChainEpoch) *Events { - tsc := newTSCache(gcConfidence, api) +func NewEventsWithConfidence(ctx context.Context, api EventAPI, gcConfidence abi.ChainEpoch) (*Events, error) { + cache := newCache(api, gcConfidence) - e := &Events{ - api: api, + ob := newObserver(cache, gcConfidence) + he := newHeightEvents(cache, gcConfidence) + headChange := newHCEvents(cache) - tsc: tsc, - - heightEvents: heightEvents{ - tsc: tsc, - ctx: ctx, - gcConfidence: gcConfidence, - - heightTriggers: map[uint64]*heightHandler{}, - htTriggerHeights: map[abi.ChainEpoch][]uint64{}, - htHeights: map[abi.ChainEpoch][]uint64{}, - }, - - hcEvents: newHCEvents(ctx, api, tsc, uint64(gcConfidence)), - ready: make(chan struct{}), - observers: []TipSetObserver{}, + // Cache first. Observers are ordered and we always want to fill the cache first. + ob.Observe(cache.observer()) + ob.Observe(he.observer()) + ob.Observe(headChange.observer()) + if err := ob.start(ctx); err != nil { + return nil, err } - go e.listenHeadChanges(ctx) - - // Wait for the first tipset to be seen or bail if shutting down - select { - case <-e.ready: - case <-ctx.Done(): - } - - return e + return &Events{ob, he, headChange}, nil } -func NewEvents(ctx context.Context, api EventAPI) *Events { +func NewEvents(ctx context.Context, api EventAPI) (*Events, error) { gcConfidence := 2 * build.ForkLengthThreshold return NewEventsWithConfidence(ctx, api, gcConfidence) } - -func (e *Events) listenHeadChanges(ctx context.Context) { - for { - if err := e.listenHeadChangesOnce(ctx); err != nil { - log.Errorf("listen head changes errored: %s", err) - } else { - log.Warn("listenHeadChanges quit") - } - select { - case <-build.Clock.After(time.Second): - case <-ctx.Done(): - log.Warnf("not restarting listenHeadChanges: context error: %s", ctx.Err()) - return - } - - log.Info("restarting listenHeadChanges") - } -} - -func (e *Events) listenHeadChangesOnce(ctx context.Context) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - notifs, err := e.api.ChainNotify(ctx) - if err != nil { - // Retry is handled by caller - return xerrors.Errorf("listenHeadChanges ChainNotify call failed: %w", err) - } - - var cur []*api.HeadChange - var ok bool - - // Wait for first tipset or bail - select { - case cur, ok = <-notifs: - if !ok { - return xerrors.Errorf("notification channel closed") - } - case <-ctx.Done(): - return ctx.Err() - } - - if len(cur) != 1 { - return xerrors.Errorf("unexpected initial head notification length: %d", len(cur)) - } - - if cur[0].Type != store.HCCurrent { - return xerrors.Errorf("expected first head notification type to be 'current', was '%s'", cur[0].Type) - } - - if err := e.tsc.add(cur[0].Val); err != nil { - log.Warnf("tsc.add: adding current tipset failed: %v", err) - } - - e.readyOnce.Do(func() { - e.lastTs = cur[0].Val - // Signal that we have seen first tipset - close(e.ready) - }) - - for notif := range notifs { - var rev, app []*types.TipSet - for _, notif := range notif { - switch notif.Type { - case store.HCRevert: - rev = append(rev, notif.Val) - case store.HCApply: - app = append(app, notif.Val) - default: - log.Warnf("unexpected head change notification type: '%s'", notif.Type) - } - } - - if err := e.headChange(ctx, rev, app); err != nil { - log.Warnf("headChange failed: %s", err) - } - - // sync with fake chainstore (for tests) - if fcs, ok := e.api.(interface{ notifDone() }); ok { - fcs.notifDone() - } - } - - return nil -} - -func (e *Events) headChange(ctx context.Context, rev, app []*types.TipSet) error { - if len(app) == 0 { - return xerrors.New("events.headChange expected at least one applied tipset") - } - - e.lk.Lock() - defer e.lk.Unlock() - - if err := e.headChangeAt(rev, app); err != nil { - return err - } - - if err := e.observeChanges(ctx, rev, app); err != nil { - return err - } - return e.processHeadChangeEvent(rev, app) -} - -// A TipSetObserver receives notifications of tipsets -type TipSetObserver interface { - Apply(ctx context.Context, ts *types.TipSet) error - Revert(ctx context.Context, ts *types.TipSet) error -} - -// TODO: add a confidence level so we can have observers with difference levels of confidence -func (e *Events) Observe(obs TipSetObserver) error { - e.lk.Lock() - defer e.lk.Unlock() - e.observers = append(e.observers, obs) - return nil -} - -// observeChanges expects caller to hold e.lk -func (e *Events) observeChanges(ctx context.Context, rev, app []*types.TipSet) error { - for _, ts := range rev { - for _, o := range e.observers { - _ = o.Revert(ctx, ts) - } - } - - for _, ts := range app { - for _, o := range e.observers { - _ = o.Apply(ctx, ts) - } - } - - return nil -} diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 1f0b80169..2b1a76e84 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -5,9 +5,6 @@ import ( "math" "sync" - "github.com/filecoin-project/lotus/api" - lru "github.com/hashicorp/golang-lru" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/go-state-types/abi" @@ -35,7 +32,7 @@ type eventData interface{} // `prevTs` is the previous tipset, eg the "from" tipset for a state change. // `ts` is the event tipset, eg the tipset in which the `msg` is included. // `curH`-`ts.Height` = `confidence` -type EventHandler func(data eventData, prevTs, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) +type EventHandler func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) // CheckFunc is used for atomicity guarantees. If the condition the callbacks // wait for has already happened in tipset `ts` @@ -43,7 +40,7 @@ type EventHandler func(data eventData, prevTs, ts *types.TipSet, curH abi.ChainE // If `done` is true, timeout won't be triggered // If `more` is false, no messages will be sent to EventHandler (RevertHandler // may still be called) -type CheckFunc func(ts *types.TipSet) (done bool, more bool, err error) +type CheckFunc func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) // Keep track of information for an event handler type handlerInfo struct { @@ -60,10 +57,9 @@ type handlerInfo struct { // until the required confidence is reached type queuedEvent struct { trigger triggerID + data eventData - prevH abi.ChainEpoch - h abi.ChainEpoch - data eventData + prevTipset, tipset *types.TipSet called bool } @@ -71,10 +67,7 @@ type queuedEvent struct { // Manages chain head change events, which may be forward (new tipset added to // chain) or backward (chain branch discarded in favour of heavier branch) type hcEvents struct { - cs EventAPI - tsc *tipSetCache - ctx context.Context - gcConfidence uint64 + cs EventAPI lastTs *types.TipSet @@ -82,8 +75,10 @@ type hcEvents struct { ctr triggerID + // TODO: get rid of trigger IDs and just use pointers as keys. triggers map[triggerID]*handlerInfo + // TODO: instead of scheduling events in the future, look at the chain in the past. We can sip the "confidence" queue entirely. // maps block heights to events // [triggerH][msgH][event] confQueue map[triggerH]map[msgH][]*queuedEvent @@ -98,83 +93,76 @@ type hcEvents struct { watcherEvents } -func newHCEvents(ctx context.Context, cs EventAPI, tsc *tipSetCache, gcConfidence uint64) *hcEvents { - e := hcEvents{ - ctx: ctx, - cs: cs, - tsc: tsc, - gcConfidence: gcConfidence, - +func newHCEvents(api EventAPI) *hcEvents { + e := &hcEvents{ + cs: api, confQueue: map[triggerH]map[msgH][]*queuedEvent{}, revertQueue: map[msgH][]triggerH{}, triggers: map[triggerID]*handlerInfo{}, timeouts: map[abi.ChainEpoch]map[triggerID]int{}, } - e.messageEvents = newMessageEvents(ctx, &e, cs) - e.watcherEvents = newWatcherEvents(ctx, &e, cs) + e.messageEvents = newMessageEvents(e, api) + e.watcherEvents = newWatcherEvents(e, api) - return &e + return e } -// Called when there is a change to the head with tipsets to be -// reverted / applied -func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error { +type hcEventsObserver hcEvents + +func (e *hcEvents) observer() TipSetObserver { + return (*hcEventsObserver)(e) +} + +func (e *hcEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) error { e.lk.Lock() defer e.lk.Unlock() - for _, ts := range rev { - e.handleReverts(ts) - e.lastTs = ts + defer func() { e.lastTs = to }() + + // Check if the head change caused any state changes that we were + // waiting for + stateChanges := e.checkStateChanges(from, to) + + // Queue up calls until there have been enough blocks to reach + // confidence on the state changes + for tid, data := range stateChanges { + e.queueForConfidence(tid, data, from, to) } - for _, ts := range app { - // Check if the head change caused any state changes that we were - // waiting for - stateChanges := e.watcherEvents.checkStateChanges(e.lastTs, ts) + // Check if the head change included any new message calls + newCalls := e.checkNewCalls(ctx, from, to) - // Queue up calls until there have been enough blocks to reach - // confidence on the state changes - for tid, data := range stateChanges { - e.queueForConfidence(tid, data, e.lastTs, ts) + // Queue up calls until there have been enough blocks to reach + // confidence on the message calls + for tid, calls := range newCalls { + for _, data := range calls { + e.queueForConfidence(tid, data, nil, to) } - - // Check if the head change included any new message calls - newCalls, err := e.messageEvents.checkNewCalls(ts) - if err != nil { - return err - } - - // Queue up calls until there have been enough blocks to reach - // confidence on the message calls - for tid, calls := range newCalls { - for _, data := range calls { - e.queueForConfidence(tid, data, nil, ts) - } - } - - for at := e.lastTs.Height(); at <= ts.Height(); at++ { - // Apply any queued events and timeouts that were targeted at the - // current chain height - e.applyWithConfidence(ts, at) - e.applyTimeouts(ts) - } - - // Update the latest known tipset - e.lastTs = ts } + for at := from.Height() + 1; at <= to.Height(); at++ { + // Apply any queued events and timeouts that were targeted at the + // current chain height + e.applyWithConfidence(ctx, at) + e.applyTimeouts(ctx, to) + } return nil } -func (e *hcEvents) handleReverts(ts *types.TipSet) { - reverts, ok := e.revertQueue[ts.Height()] +func (e *hcEventsObserver) Revert(ctx context.Context, from, to *types.TipSet) error { + e.lk.Lock() + defer e.lk.Unlock() + + defer func() { e.lastTs = to }() + + reverts, ok := e.revertQueue[from.Height()] if !ok { - return // nothing to do + return nil // nothing to do } for _, triggerH := range reverts { - toRevert := e.confQueue[triggerH][ts.Height()] + toRevert := e.confQueue[triggerH][from.Height()] for _, event := range toRevert { if !event.called { continue // event wasn't apply()-ied yet @@ -182,24 +170,21 @@ func (e *hcEvents) handleReverts(ts *types.TipSet) { trigger := e.triggers[event.trigger] - if err := trigger.revert(e.ctx, ts); err != nil { - log.Errorf("reverting chain trigger (@H %d, triggered @ %d) failed: %s", ts.Height(), triggerH, err) + if err := trigger.revert(ctx, from); err != nil { + log.Errorf("reverting chain trigger (@H %d, triggered @ %d) failed: %s", from.Height(), triggerH, err) } } - delete(e.confQueue[triggerH], ts.Height()) + delete(e.confQueue[triggerH], from.Height()) } - delete(e.revertQueue, ts.Height()) + delete(e.revertQueue, from.Height()) + return nil } // Queue up events until the chain has reached a height that reflects the // desired confidence -func (e *hcEvents) queueForConfidence(trigID uint64, data eventData, prevTs, ts *types.TipSet) { +func (e *hcEventsObserver) queueForConfidence(trigID uint64, data eventData, prevTs, ts *types.TipSet) { trigger := e.triggers[trigID] - prevH := NoHeight - if prevTs != nil { - prevH = prevTs.Height() - } appliedH := ts.Height() triggerH := appliedH + abi.ChainEpoch(trigger.confidence) @@ -211,28 +196,23 @@ func (e *hcEvents) queueForConfidence(trigID uint64, data eventData, prevTs, ts } byOrigH[appliedH] = append(byOrigH[appliedH], &queuedEvent{ - trigger: trigID, - prevH: prevH, - h: appliedH, - data: data, + trigger: trigID, + data: data, + tipset: ts, + prevTipset: prevTs, }) e.revertQueue[appliedH] = append(e.revertQueue[appliedH], triggerH) } // Apply any events that were waiting for this chain height for confidence -func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) { +func (e *hcEventsObserver) applyWithConfidence(ctx context.Context, height abi.ChainEpoch) { byOrigH, ok := e.confQueue[height] if !ok { return // no triggers at this height } for origH, events := range byOrigH { - triggerTs, err := e.tsc.get(origH) - if err != nil { - log.Errorf("events: applyWithConfidence didn't find tipset for event; wanted %d; current %d", origH, height) - } - for _, event := range events { if event.called { continue @@ -243,18 +223,7 @@ func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) continue } - // Previous tipset - this is relevant for example in a state change - // from one tipset to another - var prevTs *types.TipSet - if event.prevH != NoHeight { - prevTs, err = e.tsc.get(event.prevH) - if err != nil { - log.Errorf("events: applyWithConfidence didn't find tipset for previous event; wanted %d; current %d", event.prevH, height) - continue - } - } - - more, err := trigger.handle(event.data, prevTs, triggerTs, height) + more, err := trigger.handle(ctx, event.data, event.prevTipset, event.tipset, height) if err != nil { log.Errorf("chain trigger (@H %d, triggered @ %d) failed: %s", origH, height, err) continue // don't revert failed calls @@ -273,7 +242,7 @@ func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) } // Apply any timeouts that expire at this height -func (e *hcEvents) applyTimeouts(ts *types.TipSet) { +func (e *hcEventsObserver) applyTimeouts(ctx context.Context, ts *types.TipSet) { triggers, ok := e.timeouts[ts.Height()] if !ok { return // nothing to do @@ -288,12 +257,13 @@ func (e *hcEvents) applyTimeouts(ts *types.TipSet) { continue } - timeoutTs, err := e.tsc.get(ts.Height() - abi.ChainEpoch(trigger.confidence)) + // This should be cached. + timeoutTs, err := e.cs.ChainGetTipSetAfterHeight(ctx, ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Key()) if err != nil { log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height()) } - more, err := trigger.handle(nil, nil, timeoutTs, ts.Height()) + more, err := trigger.handle(ctx, nil, nil, timeoutTs, ts.Height()) if err != nil { log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err) continue // don't revert failed calls @@ -309,24 +279,24 @@ func (e *hcEvents) applyTimeouts(ts *types.TipSet) { // - RevertHandler: called if the chain head changes causing the event to revert // - confidence: wait this many tipsets before calling EventHandler // - timeout: at this chain height, timeout on waiting for this event -func (e *hcEvents) onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) { +func (e *hcEvents) onHeadChanged(ctx context.Context, check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) { e.lk.Lock() defer e.lk.Unlock() // Check if the event has already occurred - ts, err := e.tsc.best() - if err != nil { - return 0, xerrors.Errorf("error getting best tipset: %w", err) - } - done, more, err := check(ts) - if err != nil { - return 0, xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err) + more := true + done := false + if e.lastTs != nil { + var err error + done, more, err = check(ctx, e.lastTs) + if err != nil { + return 0, xerrors.Errorf("called check error (h: %d): %w", e.lastTs.Height(), err) + } } if done { timeout = NoTimeout } - // Create a trigger for the event id := e.ctr e.ctr++ @@ -354,12 +324,11 @@ func (e *hcEvents) onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHa // headChangeAPI is used to allow the composed event APIs to call back to hcEvents // to listen for changes type headChangeAPI interface { - onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) + onHeadChanged(ctx context.Context, check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) } // watcherEvents watches for a state change type watcherEvents struct { - ctx context.Context cs EventAPI hcAPI headChangeAPI @@ -367,9 +336,8 @@ type watcherEvents struct { matchers map[triggerID]StateMatchFunc } -func newWatcherEvents(ctx context.Context, hcAPI headChangeAPI, cs EventAPI) watcherEvents { +func newWatcherEvents(hcAPI headChangeAPI, cs EventAPI) watcherEvents { return watcherEvents{ - ctx: ctx, cs: cs, hcAPI: hcAPI, matchers: make(map[triggerID]StateMatchFunc), @@ -438,7 +406,7 @@ type StateMatchFunc func(oldTs, newTs *types.TipSet) (bool, StateChange, error) // the state change is queued up until the confidence interval has elapsed (and // `StateChangeHandler` is called) func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf StateMatchFunc) error { - hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { + hnd := func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { states, ok := data.(StateChange) if data != nil && !ok { panic("expected StateChange") @@ -447,7 +415,7 @@ func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, return scHnd(prevTs, ts, states, height) } - id, err := we.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout) + id, err := we.hcAPI.onHeadChanged(context.TODO(), check, hnd, rev, confidence, timeout) if err != nil { return err } @@ -461,43 +429,29 @@ func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, // messageEvents watches for message calls to actors type messageEvents struct { - ctx context.Context cs EventAPI hcAPI headChangeAPI lk sync.RWMutex matchers map[triggerID]MsgMatchFunc - - blockMsgLk sync.Mutex - blockMsgCache *lru.ARCCache } -func newMessageEvents(ctx context.Context, hcAPI headChangeAPI, cs EventAPI) messageEvents { - blsMsgCache, _ := lru.NewARC(500) +func newMessageEvents(hcAPI headChangeAPI, cs EventAPI) messageEvents { return messageEvents{ - ctx: ctx, - cs: cs, - hcAPI: hcAPI, - matchers: make(map[triggerID]MsgMatchFunc), - blockMsgLk: sync.Mutex{}, - blockMsgCache: blsMsgCache, + cs: cs, + hcAPI: hcAPI, + matchers: make(map[triggerID]MsgMatchFunc), } } // Check if there are any new actor calls -func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID][]eventData, error) { - pts, err := me.cs.ChainGetTipSet(me.ctx, ts.Parents()) // we actually care about messages in the parent tipset here - if err != nil { - log.Errorf("getting parent tipset in checkNewCalls: %s", err) - return nil, err - } - +func (me *messageEvents) checkNewCalls(ctx context.Context, from, to *types.TipSet) map[triggerID][]eventData { me.lk.RLock() defer me.lk.RUnlock() // For each message in the tipset res := make(map[triggerID][]eventData) - me.messagesForTs(pts, func(msg *types.Message) { + me.messagesForTs(from, func(msg *types.Message) { // TODO: provide receipts // Run each trigger's matcher against the message @@ -516,47 +470,32 @@ func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID][]eventD } }) - return res, nil + return res } // Get the messages in a tipset func (me *messageEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) { seen := map[cid.Cid]struct{}{} - for _, tsb := range ts.Blocks() { - me.blockMsgLk.Lock() - msgsI, ok := me.blockMsgCache.Get(tsb.Cid()) - var err error - if !ok { - msgsI, err = me.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid()) - if err != nil { - log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", ts.Height(), tsb.Cid(), tsb.Messages, err) - // this is quite bad, but probably better than missing all the other updates - me.blockMsgLk.Unlock() - continue - } - me.blockMsgCache.Add(tsb.Cid(), msgsI) + for i, tsb := range ts.Cids() { + msgs, err := me.cs.ChainGetBlockMessages(context.TODO(), tsb) + if err != nil { + log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", + ts.Height(), tsb, ts.Blocks()[i].Messages, err) + continue } - me.blockMsgLk.Unlock() - msgs := msgsI.(*api.BlockMessages) - for _, m := range msgs.BlsMessages { - _, ok := seen[m.Cid()] + for i, c := range msgs.Cids { + // We iterate over the CIDs to avoid having to recompute them. + _, ok := seen[c] if ok { continue } - seen[m.Cid()] = struct{}{} - - consume(m) - } - - for _, m := range msgs.SecpkMessages { - _, ok := seen[m.Message.Cid()] - if ok { - continue + seen[c] = struct{}{} + if i < len(msgs.BlsMessages) { + consume(msgs.BlsMessages[i]) + } else { + consume(&msgs.SecpkMessages[i-len(msgs.BlsMessages)].Message) } - seen[m.Message.Cid()] = struct{}{} - - consume(&m.Message) } } } @@ -596,14 +535,14 @@ type MsgMatchFunc func(msg *types.Message) (matched bool, err error) // * `MsgMatchFunc` is called against each message. If there is a match, the // message is queued up until the confidence interval has elapsed (and // `MsgHandler` is called) -func (me *messageEvents) Called(check CheckFunc, msgHnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MsgMatchFunc) error { - hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { +func (me *messageEvents) Called(ctx context.Context, check CheckFunc, msgHnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MsgMatchFunc) error { + hnd := func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { msg, ok := data.(*types.Message) if data != nil && !ok { panic("expected msg") } - ml, err := me.cs.StateSearchMsg(me.ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) + ml, err := me.cs.StateSearchMsg(ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) if err != nil { return false, err } @@ -615,7 +554,7 @@ func (me *messageEvents) Called(check CheckFunc, msgHnd MsgHandler, rev RevertHa return msgHnd(msg, &ml.Receipt, ts, height) } - id, err := me.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout) + id, err := me.hcAPI.onHeadChanged(ctx, check, hnd, rev, confidence, timeout) if err != nil { return err } @@ -629,5 +568,5 @@ func (me *messageEvents) Called(check CheckFunc, msgHnd MsgHandler, rev RevertHa // Convenience function for checking and matching messages func (me *messageEvents) CalledMsg(ctx context.Context, hnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error { - return me.Called(me.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, me.MatchMsg(msg.VMMessage())) + return me.Called(ctx, me.CheckMsg(msg, hnd), hnd, rev, confidence, timeout, me.MatchMsg(msg.VMMessage())) } diff --git a/chain/events/events_height.go b/chain/events/events_height.go index 1fcff9e68..02c252bc9 100644 --- a/chain/events/events_height.go +++ b/chain/events/events_height.go @@ -11,136 +11,32 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -type heightEvents struct { - lk sync.Mutex - tsc *tipSetCache - gcConfidence abi.ChainEpoch +type heightHandler struct { + ts *types.TipSet + height abi.ChainEpoch + called bool - ctr triggerID - - heightTriggers map[triggerID]*heightHandler - - htTriggerHeights map[triggerH][]triggerID - htHeights map[msgH][]triggerID - - ctx context.Context + handle HeightHandler + revert RevertHandler } -func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { - ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange") - defer span.End() - span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height()))) - span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) - span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) +type heightEvents struct { + api EventAPI + gcConfidence abi.ChainEpoch - e.lk.Lock() - defer e.lk.Unlock() - for _, ts := range rev { - // TODO: log error if h below gcconfidence - // revert height-based triggers + lk sync.Mutex + head *types.TipSet + tsHeights, triggerHeights map[abi.ChainEpoch][]*heightHandler + lastGc abi.ChainEpoch //nolint:structcheck +} - revert := func(h abi.ChainEpoch, ts *types.TipSet) { - for _, tid := range e.htHeights[h] { - ctx, span := trace.StartSpan(ctx, "events.HeightRevert") - - rev := e.heightTriggers[tid].revert - e.lk.Unlock() - err := rev(ctx, ts) - e.lk.Lock() - e.heightTriggers[tid].called = false - - span.End() - - if err != nil { - log.Errorf("reverting chain trigger (@H %d): %s", h, err) - } - } - } - revert(ts.Height(), ts) - - subh := ts.Height() - 1 - for { - cts, err := e.tsc.get(subh) - if err != nil { - return err - } - - if cts != nil { - break - } - - revert(subh, ts) - subh-- - } - - if err := e.tsc.revert(ts); err != nil { - return err - } +func newHeightEvents(api EventAPI, gcConfidence abi.ChainEpoch) *heightEvents { + return &heightEvents{ + api: api, + gcConfidence: gcConfidence, + tsHeights: map[abi.ChainEpoch][]*heightHandler{}, + triggerHeights: map[abi.ChainEpoch][]*heightHandler{}, } - - for i := range app { - ts := app[i] - - if err := e.tsc.add(ts); err != nil { - return err - } - - // height triggers - - apply := func(h abi.ChainEpoch, ts *types.TipSet) error { - for _, tid := range e.htTriggerHeights[h] { - hnd := e.heightTriggers[tid] - if hnd.called { - return nil - } - - triggerH := h - abi.ChainEpoch(hnd.confidence) - - incTs, err := e.tsc.getNonNull(triggerH) - if err != nil { - return err - } - - ctx, span := trace.StartSpan(ctx, "events.HeightApply") - span.AddAttributes(trace.BoolAttribute("immediate", false)) - handle := hnd.handle - e.lk.Unlock() - err = handle(ctx, incTs, h) - e.lk.Lock() - hnd.called = true - span.End() - - if err != nil { - log.Errorf("chain trigger (@H %d, called @ %d) failed: %+v", triggerH, ts.Height(), err) - } - } - return nil - } - - if err := apply(ts.Height(), ts); err != nil { - return err - } - subh := ts.Height() - 1 - for { - cts, err := e.tsc.get(subh) - if err != nil { - return err - } - - if cts != nil { - break - } - - if err := apply(subh, ts); err != nil { - return err - } - - subh-- - } - - } - - return nil } // ChainAt invokes the specified `HeightHandler` when the chain reaches the @@ -148,62 +44,211 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { // specified height, `RevertHandler` will be called. // // ts passed to handlers is the tipset at the specified, or above, if lower tipsets were null -func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { - e.lk.Lock() // Tricky locking, check your locks if you modify this function! - - best, err := e.tsc.best() - if err != nil { - e.lk.Unlock() - return xerrors.Errorf("error getting best tipset: %w", err) +// +// The context governs cancellations of this call, it won't cancel the event handler. +func (e *heightEvents) ChainAt(ctx context.Context, hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { + if abi.ChainEpoch(confidence) > e.gcConfidence { + // Need this to be able to GC effectively. + return xerrors.Errorf("confidence cannot be greater than gcConfidence: %d > %d", confidence, e.gcConfidence) } - - bestH := best.Height() - if bestH >= h+abi.ChainEpoch(confidence) { - ts, err := e.tsc.getNonNull(h) - if err != nil { - log.Warnf("events.ChainAt: calling HandleFunc with nil tipset, not found in cache: %s", err) - } - - e.lk.Unlock() - ctx, span := trace.StartSpan(e.ctx, "events.HeightApply") - span.AddAttributes(trace.BoolAttribute("immediate", true)) - - err = hnd(ctx, ts, bestH) - span.End() - - if err != nil { - return err - } - - e.lk.Lock() - best, err = e.tsc.best() - if err != nil { - e.lk.Unlock() - return xerrors.Errorf("error getting best tipset: %w", err) - } - bestH = best.Height() - } - - defer e.lk.Unlock() - - if bestH >= h+abi.ChainEpoch(confidence)+e.gcConfidence { - return nil - } - - triggerAt := h + abi.ChainEpoch(confidence) - - id := e.ctr - e.ctr++ - - e.heightTriggers[id] = &heightHandler{ - confidence: confidence, - + handler := &heightHandler{ + height: h, handle: hnd, revert: rev, } + triggerAt := h + abi.ChainEpoch(confidence) - e.htHeights[h] = append(e.htHeights[h], id) - e.htTriggerHeights[triggerAt] = append(e.htTriggerHeights[triggerAt], id) + // Here we try to jump onto a moving train. To avoid stopping the train, we release the lock + // while calling the API and/or the trigger functions. Unfortunately, it's entirely possible + // (although unlikely) to go back and forth across the trigger heights, so we need to keep + // going back and forth here till we're synced. + // + // TODO: Consider using a worker goroutine so we can just drop the handler in a channel? The + // downside is that we'd either need a tipset cache, or we'd need to potentially fetch + // tipsets in-line inside the event loop. + e.lk.Lock() + for { + head := e.head + // If we haven't initialized yet, store the trigger and move on. + if head == nil { + e.triggerHeights[triggerAt] = append(e.triggerHeights[triggerAt], handler) + e.tsHeights[h] = append(e.tsHeights[h], handler) + e.lk.Unlock() + return nil + } + + if head.Height() >= h { + // Head is past the handler height. We at least need to stash the tipset to + // avoid doing this from the main event loop. + e.lk.Unlock() + + var ts *types.TipSet + if head.Height() == h { + ts = head + } else { + var err error + ts, err = e.api.ChainGetTipSetAfterHeight(ctx, handler.height, head.Key()) + if err != nil { + return xerrors.Errorf("events.ChainAt: failed to get tipset: %s", err) + } + } + + // If we've applied the handler on the wrong tipset, revert. + if handler.called && !ts.Equals(handler.ts) { + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.revert(ctx, handler.ts) + span.End() + if err != nil { + return err + } + handler.called = false + } + + // Save the tipset. + handler.ts = ts + + // If we've reached confidence and haven't called, call. + if !handler.called && head.Height() >= triggerAt { + ctx, span := trace.StartSpan(ctx, "events.HeightApply") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.handle(ctx, handler.ts, head.Height()) + span.End() + if err != nil { + return err + } + + handler.called = true + + // If we've reached gcConfidence, return without saving anything. + if head.Height() >= h+e.gcConfidence { + return nil + } + } + + e.lk.Lock() + } else if handler.called { + // We're not passed the head (anymore) but have applied the handler. Revert, try again. + e.lk.Unlock() + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.revert(ctx, handler.ts) + span.End() + if err != nil { + return err + } + handler.called = false + e.lk.Lock() + } // otherwise, we changed heads but the change didn't matter. + + // If we managed to get through this without the head changing, we're finally done. + if head.Equals(e.head) { + e.triggerHeights[triggerAt] = append(e.triggerHeights[triggerAt], handler) + e.tsHeights[h] = append(e.tsHeights[h], handler) + e.lk.Unlock() + return nil + } + } +} + +func (e *heightEvents) observer() TipSetObserver { + return (*heightEventsObserver)(e) +} + +// Updates the head and garbage collects if we're 2x over our garbage collection confidence period. +func (e *heightEventsObserver) updateHead(h *types.TipSet) { + e.lk.Lock() + defer e.lk.Unlock() + e.head = h + + if e.head.Height() < e.lastGc+e.gcConfidence*2 { + return + } + e.lastGc = h.Height() + + targetGcHeight := e.head.Height() - e.gcConfidence + for h := range e.tsHeights { + if h >= targetGcHeight { + continue + } + delete(e.tsHeights, h) + } + for h := range e.triggerHeights { + if h >= targetGcHeight { + continue + } + delete(e.triggerHeights, h) + } +} + +type heightEventsObserver heightEvents + +func (e *heightEventsObserver) Revert(ctx context.Context, from, to *types.TipSet) error { + // Update the head first so we don't accidental skip reverting a concurrent call to ChainAt. + e.updateHead(to) + + // Call revert on all hights between the two tipsets, handling empty tipsets. + for h := from.Height(); h > to.Height(); h-- { + e.lk.Lock() + triggers := e.tsHeights[h] + e.lk.Unlock() + + // 1. Triggers are only invoked from the global event loop, we don't need to hold the lock while calling. + // 2. We only ever append to or replace the trigger slice, so it's safe to iterate over it without the lock. + for _, handler := range triggers { + handler.ts = nil // invalidate + if !handler.called { + // We haven't triggered this yet, or there has been a concurrent call to ChainAt. + continue + } + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + err := handler.revert(ctx, from) + span.End() + + if err != nil { + log.Errorf("reverting chain trigger (@H %d): %s", h, err) + } + handler.called = false + } + } + return nil +} + +func (e *heightEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) error { + // Update the head first so we don't accidental skip applying a concurrent call to ChainAt. + e.updateHead(to) + + for h := from.Height() + 1; h <= to.Height(); h++ { + e.lk.Lock() + triggers := e.triggerHeights[h] + tipsets := e.tsHeights[h] + e.lk.Unlock() + + // Stash the tipset for future triggers. + for _, handler := range tipsets { + handler.ts = to + } + + // Trigger the ready triggers. + for _, handler := range triggers { + if handler.called { + // We may have reverted past the trigger point, but not past the call point. + // Or there has been a concurrent call to ChainAt. + continue + } + + ctx, span := trace.StartSpan(ctx, "events.HeightApply") + span.AddAttributes(trace.BoolAttribute("immediate", false)) + err := handler.handle(ctx, handler.ts, h) + span.End() + + if err != nil { + log.Errorf("chain trigger (@H %d, called @ %d) failed: %+v", h, to.Height(), err) + } + + handler.called = true + } + } return nil } diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 04f938055..0f4687c8d 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -41,8 +41,6 @@ type fakeCS struct { msgs map[cid.Cid]fakeMsg blkMsgs map[cid.Cid]cid.Cid - sync sync.Mutex - tipsets map[types.TipSetKey]*types.TipSet sub func(rev, app []*types.TipSet) @@ -51,6 +49,20 @@ type fakeCS struct { callNumber map[string]int } +func newFakeCS(t *testing.T) *fakeCS { + fcs := &fakeCS{ + t: t, + h: 1, + msgs: make(map[cid.Cid]fakeMsg), + blkMsgs: make(map[cid.Cid]cid.Cid), + tipsets: make(map[types.TipSetKey]*types.TipSet), + tsc: newTSCache(nil, 2*build.ForkLengthThreshold), + callNumber: map[string]int{}, + } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + return fcs +} + func (fcs *fakeCS) ChainHead(ctx context.Context) (*types.TipSet, error) { fcs.callNumberLk.Lock() defer fcs.callNumberLk.Unlock() @@ -58,6 +70,13 @@ func (fcs *fakeCS) ChainHead(ctx context.Context) (*types.TipSet, error) { panic("implement me") } +func (fcs *fakeCS) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { + fcs.callNumberLk.Lock() + defer fcs.callNumberLk.Unlock() + fcs.callNumber["ChainGetPath"] = fcs.callNumber["ChainGetPath"] + 1 + panic("Not Implemented") +} + func (fcs *fakeCS) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) { fcs.callNumberLk.Lock() defer fcs.callNumberLk.Unlock() @@ -85,6 +104,12 @@ func (fcs *fakeCS) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types fcs.callNumber["ChainGetTipSetByHeight"] = fcs.callNumber["ChainGetTipSetByHeight"] + 1 panic("Not Implemented") } +func (fcs *fakeCS) ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { + fcs.callNumberLk.Lock() + defer fcs.callNumberLk.Unlock() + fcs.callNumber["ChainGetTipSetAfterHeight"] = fcs.callNumber["ChainGetTipSetAfterHeight"] + 1 + panic("Not Implemented") +} func (fcs *fakeCS) makeTs(t *testing.T, parents []cid.Cid, h abi.ChainEpoch, msgcid cid.Cid) *types.TipSet { a, _ := address.NewFromString("t00") @@ -132,13 +157,13 @@ func (fcs *fakeCS) makeTs(t *testing.T, parents []cid.Cid, h abi.ChainEpoch, msg return ts } -func (fcs *fakeCS) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) { +func (fcs *fakeCS) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { fcs.callNumberLk.Lock() defer fcs.callNumberLk.Unlock() fcs.callNumber["ChainNotify"] = fcs.callNumber["ChainNotify"] + 1 out := make(chan []*api.HeadChange, 1) - best, err := fcs.tsc.best() + best, err := fcs.tsc.ChainHead(ctx) if err != nil { return nil, err } @@ -160,7 +185,12 @@ func (fcs *fakeCS) ChainNotify(context.Context) (<-chan []*api.HeadChange, error } } - out <- notif + select { + case out <- notif: + case <-ctx.Done(): + // TODO: fail test? + return + } } return out, nil @@ -180,7 +210,15 @@ func (fcs *fakeCS) ChainGetBlockMessages(ctx context.Context, blk cid.Cid) (*api return &api.BlockMessages{}, nil } - return &api.BlockMessages{BlsMessages: ms.bmsgs, SecpkMessages: ms.smsgs}, nil + cids := make([]cid.Cid, len(ms.bmsgs)+len(ms.smsgs)) + for i, m := range ms.bmsgs { + cids[i] = m.Cid() + } + for i, m := range ms.smsgs { + cids[i+len(ms.bmsgs)] = m.Cid() + } + + return &api.BlockMessages{BlsMessages: ms.bmsgs, SecpkMessages: ms.smsgs, Cids: cids}, nil } func (fcs *fakeCS) fakeMsgs(m fakeMsg) cid.Cid { @@ -202,6 +240,9 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / fcs.t.Fatal("sub not be nil") } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + nullm := map[int]struct{}{} for _, v := range nulls { nullm[v] = struct{}{} @@ -209,12 +250,14 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / var revs []*types.TipSet for i := 0; i < rev; i++ { - ts, err := fcs.tsc.best() + fcs.t.Log("revert", fcs.h) + from, err := fcs.tsc.ChainHead(ctx) require.NoError(fcs.t, err) - if _, ok := nullm[int(ts.Height())]; !ok { - revs = append(revs, ts) - require.NoError(fcs.t, fcs.tsc.revert(ts)) + if _, ok := nullm[int(from.Height())]; !ok { + revs = append(revs, from) + + require.NoError(fcs.t, fcs.tsc.revert(from)) } fcs.h-- } @@ -222,6 +265,7 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / var apps []*types.TipSet for i := 0; i < app; i++ { fcs.h++ + fcs.t.Log("apply", fcs.h) mc, hasMsgs := msgs[i] if !hasMsgs { @@ -232,7 +276,7 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / continue } - best, err := fcs.tsc.best() + best, err := fcs.tsc.ChainHead(ctx) require.NoError(fcs.t, err) ts := fcs.makeTs(fcs.t, best.Key().Cids(), fcs.h, mc) require.NoError(fcs.t, fcs.tsc.add(ts)) @@ -244,35 +288,24 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / apps = append(apps, ts) } - fcs.sync.Lock() - fcs.sub(revs, apps) - fcs.sync.Lock() - fcs.sync.Unlock() //nolint:staticcheck -} - -func (fcs *fakeCS) notifDone() { - fcs.sync.Unlock() + // Wait for the last round to finish. + fcs.sub(nil, nil) + fcs.sub(nil, nil) } var _ EventAPI = &fakeCS{} func TestAt(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + fcs := newFakeCS(t) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -325,20 +358,18 @@ func TestAt(t *testing.T) { } func TestAtDoubleTrigger(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - events := NewEvents(context.Background(), fcs) + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(ctx, func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -368,20 +399,14 @@ func TestAtDoubleTrigger(t *testing.T) { } func TestAtNullTrigger(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + fcs := newFakeCS(t) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, abi.ChainEpoch(6), ts.Height()) require.Equal(t, 8, int(curH)) applied = true @@ -403,20 +428,18 @@ func TestAtNullTrigger(t *testing.T) { } func TestAtNullConf(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - events := NewEvents(context.Background(), fcs) + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(ctx, func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -443,22 +466,17 @@ func TestAtNullConf(t *testing.T) { } func TestAtStart(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) fcs.advance(0, 5, nil) // 6 var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -478,22 +496,17 @@ func TestAtStart(t *testing.T) { } func TestAtStartConfidence(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) fcs.advance(0, 10, nil) // 11 var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 11, int(curH)) applied = true @@ -509,21 +522,16 @@ func TestAtStartConfidence(t *testing.T) { } func TestAtChained(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + return events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 10, int(ts.Height())) applied = true return nil @@ -544,23 +552,18 @@ func TestAtChained(t *testing.T) { } func TestAtChainedConfidence(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) fcs.advance(0, 15, nil) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + return events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 10, int(ts.Height())) applied = true return nil @@ -579,22 +582,17 @@ func TestAtChainedConfidence(t *testing.T) { } func TestAtChainedConfidenceNull(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) fcs.advance(0, 15, nil, 5) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { applied = true require.Equal(t, 6, int(ts.Height())) return nil @@ -615,18 +613,10 @@ func matchAddrMethod(to address.Address, m abi.MethodNum) func(msg *types.Messag } func TestCalled(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -637,7 +627,7 @@ func TestCalled(t *testing.T) { var appliedTs *types.TipSet var appliedH abi.ChainEpoch - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -828,25 +818,17 @@ func TestCalled(t *testing.T) { } func TestCalledTimeout(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) called := false - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { called = true @@ -869,20 +851,16 @@ func TestCalledTimeout(t *testing.T) { // with check func reporting done - fcs = &fakeCS{ - t: t, - h: 1, + fcs = newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - callNumber: map[string]int{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + events, err = NewEvents(context.Background(), fcs) + require.NoError(t, err) - events = NewEvents(context.Background(), fcs) + // XXX: Needed to set the latest head so "check" succeeds". Is that OK? Or do we expect + // check to work _before_ we've received any events. + fcs.advance(0, 1, nil) - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { called = true @@ -904,25 +882,17 @@ func TestCalledTimeout(t *testing.T) { } func TestCalledOrder(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) at := 0 - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { switch at { @@ -968,18 +938,10 @@ func TestCalledOrder(t *testing.T) { } func TestCalledNull(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -987,7 +949,7 @@ func TestCalledNull(t *testing.T) { more := true var applied, reverted bool - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -1034,18 +996,10 @@ func TestCalledNull(t *testing.T) { } func TestRemoveTriggersOnMessage(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -1053,7 +1007,7 @@ func TestRemoveTriggersOnMessage(t *testing.T) { more := true var applied, reverted bool - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -1125,18 +1079,10 @@ type testStateChange struct { } func TestStateChanged(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) more := true var applied, reverted bool @@ -1149,7 +1095,7 @@ func TestStateChanged(t *testing.T) { confidence := 3 timeout := abi.ChainEpoch(20) - err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -1214,18 +1160,10 @@ func TestStateChanged(t *testing.T) { } func TestStateChangedRevert(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) more := true var applied, reverted bool @@ -1234,7 +1172,7 @@ func TestStateChangedRevert(t *testing.T) { confidence := 1 timeout := abi.ChainEpoch(20) - err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -1293,22 +1231,14 @@ func TestStateChangedRevert(t *testing.T) { } func TestStateChangedTimeout(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - callNumber: map[string]int{}, - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) called := false - err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { called = true @@ -1334,20 +1264,15 @@ func TestStateChangedTimeout(t *testing.T) { // with check func reporting done - fcs = &fakeCS{ - t: t, - h: 1, + fcs = newFakeCS(t) + events, err = NewEvents(context.Background(), fcs) + require.NoError(t, err) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - callNumber: map[string]int{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + // XXX: Needed to set the latest head so "check" succeeds". Is that OK? Or do we expect + // check to work _before_ we've received any events. + fcs.advance(0, 1, nil) - events = NewEvents(context.Background(), fcs) - - err = events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { called = true @@ -1371,25 +1296,17 @@ func TestStateChangedTimeout(t *testing.T) { } func TestCalledMultiplePerEpoch(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - callNumber: map[string]int{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) at := 0 - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { switch at { @@ -1431,18 +1348,10 @@ func TestCalledMultiplePerEpoch(t *testing.T) { } func TestCachedSameBlock(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - callNumber: map[string]int{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - _ = NewEvents(context.Background(), fcs) + _, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) fcs.advance(0, 10, map[int]cid.Cid{}) assert.Assert(t, fcs.callNumber["ChainGetBlockMessages"] == 20, "expect call ChainGetBlockMessages %d but got ", 20, fcs.callNumber["ChainGetBlockMessages"]) diff --git a/chain/events/message_cache.go b/chain/events/message_cache.go new file mode 100644 index 000000000..75e179ad9 --- /dev/null +++ b/chain/events/message_cache.go @@ -0,0 +1,42 @@ +package events + +import ( + "context" + "sync" + + "github.com/filecoin-project/lotus/api" + lru "github.com/hashicorp/golang-lru" + "github.com/ipfs/go-cid" +) + +type messageCache struct { + api EventAPI + + blockMsgLk sync.Mutex + blockMsgCache *lru.ARCCache +} + +func newMessageCache(api EventAPI) *messageCache { + blsMsgCache, _ := lru.NewARC(500) + + return &messageCache{ + api: api, + blockMsgCache: blsMsgCache, + } +} + +func (c *messageCache) ChainGetBlockMessages(ctx context.Context, blkCid cid.Cid) (*api.BlockMessages, error) { + c.blockMsgLk.Lock() + defer c.blockMsgLk.Unlock() + + msgsI, ok := c.blockMsgCache.Get(blkCid) + var err error + if !ok { + msgsI, err = c.api.ChainGetBlockMessages(ctx, blkCid) + if err != nil { + return nil, err + } + c.blockMsgCache.Add(blkCid, msgsI) + } + return msgsI.(*api.BlockMessages), nil +} diff --git a/chain/events/observer.go b/chain/events/observer.go new file mode 100644 index 000000000..cd25b4874 --- /dev/null +++ b/chain/events/observer.go @@ -0,0 +1,234 @@ +package events + +import ( + "context" + "sync" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +type observer struct { + api EventAPI + + lk sync.Mutex + gcConfidence abi.ChainEpoch + + ready chan struct{} + + head *types.TipSet + maxHeight abi.ChainEpoch + + observers []TipSetObserver +} + +func newObserver(api EventAPI, gcConfidence abi.ChainEpoch) *observer { + return &observer{ + api: api, + gcConfidence: gcConfidence, + + ready: make(chan struct{}), + observers: []TipSetObserver{}, + } +} + +func (o *observer) start(ctx context.Context) error { + go o.listenHeadChanges(ctx) + + // Wait for the first tipset to be seen or bail if shutting down + select { + case <-o.ready: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (o *observer) listenHeadChanges(ctx context.Context) { + for { + if err := o.listenHeadChangesOnce(ctx); err != nil { + log.Errorf("listen head changes errored: %s", err) + } else { + log.Warn("listenHeadChanges quit") + } + select { + case <-build.Clock.After(time.Second): + case <-ctx.Done(): + log.Warnf("not restarting listenHeadChanges: context error: %s", ctx.Err()) + return + } + + log.Info("restarting listenHeadChanges") + } +} + +func (o *observer) listenHeadChangesOnce(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + notifs, err := o.api.ChainNotify(ctx) + if err != nil { + // Retry is handled by caller + return xerrors.Errorf("listenHeadChanges ChainNotify call failed: %w", err) + } + + var cur []*api.HeadChange + var ok bool + + // Wait for first tipset or bail + select { + case cur, ok = <-notifs: + if !ok { + return xerrors.Errorf("notification channel closed") + } + case <-ctx.Done(): + return ctx.Err() + } + + if len(cur) != 1 { + return xerrors.Errorf("unexpected initial head notification length: %d", len(cur)) + } + + if cur[0].Type != store.HCCurrent { + return xerrors.Errorf("expected first head notification type to be 'current', was '%s'", cur[0].Type) + } + + head := cur[0].Val + if o.head == nil { + o.head = head + close(o.ready) + } else if !o.head.Equals(head) { + changes, err := o.api.ChainGetPath(ctx, o.head.Key(), head.Key()) + if err != nil { + return xerrors.Errorf("failed to get path from last applied tipset to head: %w", err) + } + + if err := o.applyChanges(ctx, changes); err != nil { + return xerrors.Errorf("failed to apply head changes: %w", err) + } + } + + for changes := range notifs { + if err := o.applyChanges(ctx, changes); err != nil { + return err + } + } + + return nil +} + +func (o *observer) applyChanges(ctx context.Context, changes []*api.HeadChange) error { + // Used to wait for a prior notification round to finish (by tests) + if len(changes) == 0 { + return nil + } + + var rev, app []*types.TipSet + for _, changes := range changes { + switch changes.Type { + case store.HCRevert: + rev = append(rev, changes.Val) + case store.HCApply: + app = append(app, changes.Val) + default: + log.Errorf("unexpected head change notification type: '%s'", changes.Type) + } + } + + if err := o.headChange(ctx, rev, app); err != nil { + return xerrors.Errorf("failed to apply head changes: %w", err) + } + return nil +} + +func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) error { + ctx, span := trace.StartSpan(ctx, "events.HeadChange") + span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) + span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) + defer func() { + span.AddAttributes(trace.Int64Attribute("endHeight", int64(o.head.Height()))) + span.End() + }() + + // NOTE: bailing out here if the head isn't what we expected is fine. We'll re-start the + // entire process and handle any strange reorgs. + for i, from := range rev { + if !from.Equals(o.head) { + return xerrors.Errorf( + "expected to revert %s (%d), reverting %s (%d)", + o.head.Key(), o.head.Height(), from.Key(), from.Height(), + ) + } + var to *types.TipSet + if i+1 < len(rev) { + // If we have more reverts, the next revert is the next head. + to = rev[i+1] + } else { + // At the end of the revert sequenece, we need to looup the joint tipset + // between the revert sequence and the apply sequence. + var err error + to, err = o.api.ChainGetTipSet(ctx, from.Parents()) + if err != nil { + // Well, this sucks. We'll bail and restart. + return xerrors.Errorf("failed to get tipset when reverting due to a SetHeead: %w", err) + } + } + + // Get the observers late in case an observer registers/unregisters itself. + o.lk.Lock() + observers := o.observers + o.lk.Unlock() + + for _, obs := range observers { + if err := obs.Revert(ctx, from, to); err != nil { + log.Errorf("observer %T failed to apply tipset %s (%d) with: %s", obs, from.Key(), from.Height(), err) + } + } + + if to.Height() < o.maxHeight-o.gcConfidence { + log.Errorf("reverted past finality, from %d to %d", o.maxHeight, to.Height()) + } + + o.head = to + } + + for _, to := range app { + if to.Parents() != o.head.Key() { + return xerrors.Errorf( + "cannot apply %s (%d) with parents %s on top of %s (%d)", + to.Key(), to.Height(), to.Parents(), o.head.Key(), o.head.Height(), + ) + } + + // Get the observers late in case an observer registers/unregisters itself. + o.lk.Lock() + observers := o.observers + o.lk.Unlock() + + for _, obs := range observers { + if err := obs.Apply(ctx, o.head, to); err != nil { + log.Errorf("observer %T failed to revert tipset %s (%d) with: %s", obs, to.Key(), to.Height(), err) + } + } + o.head = to + if to.Height() > o.maxHeight { + o.maxHeight = to.Height() + } + + } + return nil +} + +// TODO: add a confidence level so we can have observers with difference levels of confidence +func (o *observer) Observe(obs TipSetObserver) { + o.lk.Lock() + defer o.lk.Unlock() + o.observers = append(o.observers, obs) +} diff --git a/chain/events/tscache.go b/chain/events/tscache.go index 7beb80364..033c72a22 100644 --- a/chain/events/tscache.go +++ b/chain/events/tscache.go @@ -11,7 +11,9 @@ import ( ) type tsCacheAPI interface { + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) ChainHead(context.Context) (*types.TipSet, error) } @@ -20,68 +22,157 @@ type tsCacheAPI interface { type tipSetCache struct { mu sync.RWMutex - cache []*types.TipSet - start int // chain head (end) - len int + byKey map[types.TipSetKey]*types.TipSet + byHeight []*types.TipSet + start int // chain head (end) + len int storage tsCacheAPI } -func newTSCache(cap abi.ChainEpoch, storage tsCacheAPI) *tipSetCache { +func newTSCache(storage tsCacheAPI, cap abi.ChainEpoch) *tipSetCache { return &tipSetCache{ - cache: make([]*types.TipSet, cap), - start: 0, - len: 0, + byKey: make(map[types.TipSetKey]*types.TipSet, cap), + byHeight: make([]*types.TipSet, cap), + start: 0, + len: 0, storage: storage, } } +func (tsc *tipSetCache) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + if ts, ok := tsc.byKey[tsk]; ok { + return ts, nil + } + return tsc.storage.ChainGetTipSet(ctx, tsk) +} -func (tsc *tipSetCache) add(ts *types.TipSet) error { +func (tsc *tipSetCache) ChainGetTipSetByHeight(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + return tsc.get(ctx, height, tsk, true) +} + +func (tsc *tipSetCache) ChainGetTipSetAfterHeight(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + return tsc.get(ctx, height, tsk, false) +} + +func (tsc *tipSetCache) get(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey, prev bool) (*types.TipSet, error) { + fallback := tsc.storage.ChainGetTipSetAfterHeight + if prev { + fallback = tsc.storage.ChainGetTipSetByHeight + } + tsc.mu.RLock() + + // Nothing in the cache? + if tsc.len == 0 { + tsc.mu.RUnlock() + log.Warnf("tipSetCache.get: cache is empty, requesting from storage (h=%d)", height) + return fallback(ctx, height, tsk) + } + + // Resolve the head. + head := tsc.byHeight[tsc.start] + if !tsk.IsEmpty() { + // Not on this chain? + var ok bool + head, ok = tsc.byKey[tsk] + if !ok { + tsc.mu.RUnlock() + return fallback(ctx, height, tsk) + } + } + + headH := head.Height() + tailH := headH - abi.ChainEpoch(tsc.len) + + if headH == height { + tsc.mu.RUnlock() + return head, nil + } else if headH < height { + tsc.mu.RUnlock() + // If the user doesn't pass a tsk, we assume "head" is the last tipset we processed. + return nil, xerrors.Errorf("requested epoch is in the future") + } else if height < tailH { + log.Warnf("tipSetCache.get: requested tipset not in cache, requesting from storage (h=%d; tail=%d)", height, tailH) + tsc.mu.RUnlock() + return fallback(ctx, height, head.Key()) + } + + direction := 1 + if prev { + direction = -1 + } + var ts *types.TipSet + for i := 0; i < tsc.len && ts == nil; i += direction { + ts = tsc.byHeight[normalModulo(tsc.start-int(headH-height)+i, len(tsc.byHeight))] + } + tsc.mu.RUnlock() + return ts, nil +} + +func (tsc *tipSetCache) ChainHead(ctx context.Context) (*types.TipSet, error) { + tsc.mu.RLock() + best := tsc.byHeight[tsc.start] + tsc.mu.RUnlock() + if best == nil { + return tsc.storage.ChainHead(ctx) + } + return best, nil +} + +func (tsc *tipSetCache) add(to *types.TipSet) error { tsc.mu.Lock() defer tsc.mu.Unlock() if tsc.len > 0 { - best := tsc.cache[tsc.start] - if best.Height() >= ts.Height() { - return xerrors.Errorf("tipSetCache.add: expected new tipset height to be at least %d, was %d", tsc.cache[tsc.start].Height()+1, ts.Height()) + best := tsc.byHeight[tsc.start] + if best.Height() >= to.Height() { + return xerrors.Errorf("tipSetCache.add: expected new tipset height to be at least %d, was %d", tsc.byHeight[tsc.start].Height()+1, to.Height()) } - if best.Key() != ts.Parents() { + if best.Key() != to.Parents() { return xerrors.Errorf( "tipSetCache.add: expected new tipset %s (%d) to follow %s (%d), its parents are %s", - ts.Key(), ts.Height(), best.Key(), best.Height(), best.Parents(), + to.Key(), to.Height(), best.Key(), best.Height(), best.Parents(), ) } } - nextH := ts.Height() + nextH := to.Height() if tsc.len > 0 { - nextH = tsc.cache[tsc.start].Height() + 1 + nextH = tsc.byHeight[tsc.start].Height() + 1 } // fill null blocks - for nextH != ts.Height() { - tsc.start = normalModulo(tsc.start+1, len(tsc.cache)) - tsc.cache[tsc.start] = nil - if tsc.len < len(tsc.cache) { + for nextH != to.Height() { + tsc.start = normalModulo(tsc.start+1, len(tsc.byHeight)) + was := tsc.byHeight[tsc.start] + if was != nil { + tsc.byHeight[tsc.start] = nil + delete(tsc.byKey, was.Key()) + } + if tsc.len < len(tsc.byHeight) { tsc.len++ } nextH++ } - tsc.start = normalModulo(tsc.start+1, len(tsc.cache)) - tsc.cache[tsc.start] = ts - if tsc.len < len(tsc.cache) { + tsc.start = normalModulo(tsc.start+1, len(tsc.byHeight)) + was := tsc.byHeight[tsc.start] + if was != nil { + delete(tsc.byKey, was.Key()) + } + tsc.byHeight[tsc.start] = to + if tsc.len < len(tsc.byHeight) { tsc.len++ } + tsc.byKey[to.Key()] = to return nil } -func (tsc *tipSetCache) revert(ts *types.TipSet) error { +func (tsc *tipSetCache) revert(from *types.TipSet) error { tsc.mu.Lock() defer tsc.mu.Unlock() - return tsc.revertUnlocked(ts) + return tsc.revertUnlocked(from) } func (tsc *tipSetCache) revertUnlocked(ts *types.TipSet) error { @@ -89,75 +180,35 @@ func (tsc *tipSetCache) revertUnlocked(ts *types.TipSet) error { return nil // this can happen, and it's fine } - if !tsc.cache[tsc.start].Equals(ts) { + was := tsc.byHeight[tsc.start] + + if !was.Equals(ts) { return xerrors.New("tipSetCache.revert: revert tipset didn't match cache head") } + delete(tsc.byKey, was.Key()) - tsc.cache[tsc.start] = nil - tsc.start = normalModulo(tsc.start-1, len(tsc.cache)) + tsc.byHeight[tsc.start] = nil + tsc.start = normalModulo(tsc.start-1, len(tsc.byHeight)) tsc.len-- _ = tsc.revertUnlocked(nil) // revert null block gap return nil } -func (tsc *tipSetCache) getNonNull(height abi.ChainEpoch) (*types.TipSet, error) { - for { - ts, err := tsc.get(height) - if err != nil { - return nil, err - } - if ts != nil { - return ts, nil - } - height++ - } +func (tsc *tipSetCache) observer() TipSetObserver { + return (*tipSetCacheObserver)(tsc) } -func (tsc *tipSetCache) get(height abi.ChainEpoch) (*types.TipSet, error) { - tsc.mu.RLock() +type tipSetCacheObserver tipSetCache - if tsc.len == 0 { - tsc.mu.RUnlock() - log.Warnf("tipSetCache.get: cache is empty, requesting from storage (h=%d)", height) - return tsc.storage.ChainGetTipSetByHeight(context.TODO(), height, types.EmptyTSK) - } +var _ TipSetObserver = new(tipSetCacheObserver) - headH := tsc.cache[tsc.start].Height() - - if height > headH { - tsc.mu.RUnlock() - return nil, xerrors.Errorf("tipSetCache.get: requested tipset not in cache (req: %d, cache head: %d)", height, headH) - } - - clen := len(tsc.cache) - var tail *types.TipSet - for i := 1; i <= tsc.len; i++ { - tail = tsc.cache[normalModulo(tsc.start-tsc.len+i, clen)] - if tail != nil { - break - } - } - - if height < tail.Height() { - tsc.mu.RUnlock() - log.Warnf("tipSetCache.get: requested tipset not in cache, requesting from storage (h=%d; tail=%d)", height, tail.Height()) - return tsc.storage.ChainGetTipSetByHeight(context.TODO(), height, tail.Key()) - } - - ts := tsc.cache[normalModulo(tsc.start-int(headH-height), clen)] - tsc.mu.RUnlock() - return ts, nil +func (tsc *tipSetCacheObserver) Apply(_ context.Context, _, to *types.TipSet) error { + return (*tipSetCache)(tsc).add(to) } -func (tsc *tipSetCache) best() (*types.TipSet, error) { - tsc.mu.RLock() - best := tsc.cache[tsc.start] - tsc.mu.RUnlock() - if best == nil { - return tsc.storage.ChainHead(context.TODO()) - } - return best, nil +func (tsc *tipSetCacheObserver) Revert(ctx context.Context, from, _ *types.TipSet) error { + return (*tipSetCache)(tsc).revert(from) } func normalModulo(n, m int) int { diff --git a/chain/events/tscache_test.go b/chain/events/tscache_test.go index 9ba9a556c..c3779eb9e 100644 --- a/chain/events/tscache_test.go +++ b/chain/events/tscache_test.go @@ -17,6 +17,11 @@ type tsCacheAPIFailOnStorageCall struct { t *testing.T } +func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} + func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.t.Fatal("storage call") return &types.TipSet{}, nil @@ -25,6 +30,10 @@ func (tc *tsCacheAPIFailOnStorageCall) ChainHead(ctx context.Context) (*types.Ti tc.t.Fatal("storage call") return &types.TipSet{}, nil } +func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} type cacheHarness struct { t *testing.T @@ -40,7 +49,7 @@ func newCacheharness(t *testing.T) *cacheHarness { h := &cacheHarness{ t: t, - tsc: newTSCache(50, &tsCacheAPIFailOnStorageCall{t: t}), + tsc: newTSCache(&tsCacheAPIFailOnStorageCall{t: t}, 50), height: 75, miner: a, } @@ -65,13 +74,13 @@ func (h *cacheHarness) addWithParents(parents []cid.Cid) { } func (h *cacheHarness) add() { - last, err := h.tsc.best() + last, err := h.tsc.ChainHead(context.Background()) require.NoError(h.t, err) h.addWithParents(last.Cids()) } func (h *cacheHarness) revert() { - best, err := h.tsc.best() + best, err := h.tsc.ChainHead(context.Background()) require.NoError(h.t, err) err = h.tsc.revert(best) require.NoError(h.t, err) @@ -95,6 +104,7 @@ func TestTsCache(t *testing.T) { } func TestTsCacheNulls(t *testing.T) { + ctx := context.Background() h := newCacheharness(t) h.add() @@ -105,66 +115,77 @@ func TestTsCacheNulls(t *testing.T) { h.add() h.add() - best, err := h.tsc.best() + best, err := h.tsc.ChainHead(ctx) require.NoError(t, err) require.Equal(t, h.height-1, best.Height()) - ts, err := h.tsc.get(h.height - 1) + ts, err := h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-1, ts.Height()) - ts, err = h.tsc.get(h.height - 2) + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-2, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-2, ts.Height()) - ts, err = h.tsc.get(h.height - 3) - require.NoError(t, err) - require.Nil(t, ts) - - ts, err = h.tsc.get(h.height - 8) + // Should skip the nulls and walk back to the last tipset. + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-3, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-8, ts.Height()) - best, err = h.tsc.best() + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-8, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, h.height-8, ts.Height()) + + best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.NoError(t, h.tsc.revert(best)) - best, err = h.tsc.best() + best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.NoError(t, h.tsc.revert(best)) - best, err = h.tsc.best() + best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.Equal(t, h.height-8, best.Height()) h.skip(50) h.add() - ts, err = h.tsc.get(h.height - 1) + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-1, ts.Height()) } type tsCacheAPIStorageCallCounter struct { - t *testing.T - chainGetTipSetByHeight int - chainHead int + t *testing.T + chainGetTipSetByHeight int + chainGetTipSetAfterHeight int + chainGetTipSet int + chainHead int } func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.chainGetTipSetByHeight++ return &types.TipSet{}, nil } +func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.chainGetTipSetAfterHeight++ + return &types.TipSet{}, nil +} func (tc *tsCacheAPIStorageCallCounter) ChainHead(ctx context.Context) (*types.TipSet, error) { tc.chainHead++ return &types.TipSet{}, nil } +func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + tc.chainGetTipSet++ + return &types.TipSet{}, nil +} func TestTsCacheEmpty(t *testing.T) { // Calling best on an empty cache should just call out to the chain API callCounter := &tsCacheAPIStorageCallCounter{t: t} - tsc := newTSCache(50, callCounter) - _, err := tsc.best() + tsc := newTSCache(callCounter, 50) + _, err := tsc.ChainHead(context.Background()) require.NoError(t, err) require.Equal(t, 1, callCounter.chainHead) } diff --git a/chain/events/utils.go b/chain/events/utils.go index 91ea0cd7a..0bfb58e0a 100644 --- a/chain/events/utils.go +++ b/chain/events/utils.go @@ -10,10 +10,10 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -func (me *messageEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd MsgHandler) CheckFunc { +func (me *messageEvents) CheckMsg(smsg types.ChainMsg, hnd MsgHandler) CheckFunc { msg := smsg.VMMessage() - return func(ts *types.TipSet) (done bool, more bool, err error) { + return func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { fa, err := me.cs.StateGetActor(ctx, msg.From, ts.Key()) if err != nil { return false, true, err @@ -24,7 +24,7 @@ func (me *messageEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd return false, true, nil } - ml, err := me.cs.StateSearchMsg(me.ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) + ml, err := me.cs.StateSearchMsg(ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) if err != nil { return false, true, xerrors.Errorf("getting receipt in CheckMsg: %w", err) } diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 647db21e0..49c23545b 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -103,10 +103,11 @@ func TestPaymentChannelsAPI(t *testing.T) { creatorStore := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(paymentCreator))) // wait for the receiver to submit their vouchers - ev := events.NewEvents(ctx, paymentCreator) + ev, err := events.NewEvents(ctx, paymentCreator) + require.NoError(t, err) preds := state.NewStatePredicates(paymentCreator) finished := make(chan struct{}) - err = ev.StateChanged(func(ts *types.TipSet) (done bool, more bool, err error) { + err = ev.StateChanged(func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { act, err := paymentCreator.StateGetActor(ctx, channel, ts.Key()) if err != nil { return false, false, err diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 17e7bcbf6..a4ad1920b 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -381,8 +381,9 @@ func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) { // waitForHeight waits for the node to reach the given chain epoch func waitForHeight(ctx context.Context, t *testing.T, node kit.TestFullNode, height abi.ChainEpoch) { atHeight := make(chan struct{}) - chainEvents := events.NewEvents(ctx, node) - err := chainEvents.ChainAt(func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + chainEvents, err := events.NewEvents(ctx, node) + require.NoError(t, err) + err = chainEvents.ChainAt(ctx, func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { close(atHeight) return nil }, nil, 1, height) diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go index 80ead2be3..2ffa56f5f 100644 --- a/markets/storageadapter/client.go +++ b/markets/storageadapter/client.go @@ -50,11 +50,14 @@ type clientApi struct { full.MpoolAPI } -func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi full.StateAPI, chain full.ChainAPI, mpool full.MpoolAPI, fundmgr *market.FundManager) storagemarket.StorageClientNode { +func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi full.StateAPI, chain full.ChainAPI, mpool full.MpoolAPI, fundmgr *market.FundManager) (storagemarket.StorageClientNode, error) { capi := &clientApi{chain, stateapi, mpool} ctx := helpers.LifecycleCtx(mctx, lc) - ev := events.NewEvents(ctx, capi) + ev, err := events.NewEvents(ctx, capi) + if err != nil { + return nil, err + } a := &ClientNodeAdapter{ clientApi: capi, @@ -63,7 +66,7 @@ func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi ful dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))), } a.scMgr = NewSectorCommittedManager(ev, a, &apiWrapper{api: capi}) - return a + return a, nil } func (c *ClientNodeAdapter) ListStorageProviders(ctx context.Context, encodedTs shared.TipSetToken) ([]*storagemarket.StorageProviderInfo, error) { @@ -262,7 +265,7 @@ func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID a } // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { if ts == nil { // keep listening for events return false, true, nil diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index 31bc0b8bf..4cd0a2d68 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -22,7 +22,7 @@ import ( ) type eventsCalledAPI interface { - Called(check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error + Called(ctx context.Context, check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error } type dealInfoAPI interface { @@ -64,7 +64,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, } // First check if the deal is already active, and if so, bail out - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { dealInfo, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid) if err != nil { // Note: the error returned from here will end up being returned @@ -165,7 +165,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, return nil } - if err := mgr.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { + if err := mgr.ev.Called(ctx, checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { return xerrors.Errorf("failed to set up called handler: %w", err) } @@ -182,7 +182,7 @@ func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, pr } // First check if the deal is already active, and if so, bail out - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { _, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid) if err != nil { // Note: the error returned from here will end up being returned @@ -257,7 +257,7 @@ func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, pr return nil } - if err := mgr.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { + if err := mgr.ev.Called(ctx, checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { return xerrors.Errorf("failed to set up called handler: %w", err) } diff --git a/markets/storageadapter/ondealsectorcommitted_test.go b/markets/storageadapter/ondealsectorcommitted_test.go index db56ee651..86c01799a 100644 --- a/markets/storageadapter/ondealsectorcommitted_test.go +++ b/markets/storageadapter/ondealsectorcommitted_test.go @@ -477,13 +477,13 @@ type fakeEvents struct { DealStartEpochTimeout bool } -func (fe *fakeEvents) Called(check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error { +func (fe *fakeEvents) Called(ctx context.Context, check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error { if fe.DealStartEpochTimeout { msgHnd(nil, nil, nil, 100) // nolint:errcheck return nil } - _, more, err := check(fe.CheckTs) + _, more, err := check(ctx, fe.CheckTs) if err != nil { return err } @@ -506,7 +506,7 @@ func (fe *fakeEvents) Called(check events.CheckFunc, msgHnd events.MsgHandler, r return nil } if matchMessage.doesRevert { - err := rev(fe.Ctx, matchMessage.ts) + err := rev(ctx, matchMessage.ts) if err != nil { return err } diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go index 23a3c32a8..5c82d0dc8 100644 --- a/markets/storageadapter/provider.go +++ b/markets/storageadapter/provider.go @@ -55,11 +55,14 @@ type ProviderNodeAdapter struct { scMgr *SectorCommittedManager } -func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { +func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) (storagemarket.StorageProviderNode, error) { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) (storagemarket.StorageProviderNode, error) { ctx := helpers.LifecycleCtx(mctx, lc) - ev := events.NewEvents(ctx, full) + ev, err := events.NewEvents(ctx, full) + if err != nil { + return nil, err + } na := &ProviderNodeAdapter{ FullNode: full, @@ -77,7 +80,7 @@ func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConf } na.scMgr = NewSectorCommittedManager(ev, na, &apiWrapper{api: full}) - return na + return na, nil } } @@ -340,7 +343,7 @@ func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID } // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) { + checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { if ts == nil { // keep listening for events return false, true, nil diff --git a/paychmgr/settler/settler.go b/paychmgr/settler/settler.go index ce31ab223..38fcc3b92 100644 --- a/paychmgr/settler/settler.go +++ b/paychmgr/settler/settler.go @@ -56,8 +56,11 @@ func SettlePaymentChannels(mctx helpers.MetricsCtx, lc fx.Lifecycle, papi API) e lc.Append(fx.Hook{ OnStart: func(context.Context) error { pcs := newPaymentChannelSettler(ctx, &papi) - ev := events.NewEvents(ctx, papi) - return ev.Called(pcs.check, pcs.messageHandler, pcs.revertHandler, int(build.MessageConfidence+1), events.NoTimeout, pcs.matcher) + ev, err := events.NewEvents(ctx, &papi) + if err != nil { + return err + } + return ev.Called(ctx, pcs.check, pcs.messageHandler, pcs.revertHandler, int(build.MessageConfidence+1), events.NoTimeout, pcs.matcher) }, }) return nil @@ -70,7 +73,7 @@ func newPaymentChannelSettler(ctx context.Context, api settlerAPI) *paymentChann } } -func (pcs *paymentChannelSettler) check(ts *types.TipSet) (done bool, more bool, err error) { +func (pcs *paymentChannelSettler) check(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { return false, true, nil } diff --git a/storage/adapter_events.go b/storage/adapter_events.go index ff69c1e51..0f9c62039 100644 --- a/storage/adapter_events.go +++ b/storage/adapter_events.go @@ -21,7 +21,7 @@ func NewEventsAdapter(api *events.Events) EventsAdapter { } func (e EventsAdapter) ChainAt(hnd sealing.HeightHandler, rev sealing.RevertHandler, confidence int, h abi.ChainEpoch) error { - return e.delegate.ChainAt(func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + return e.delegate.ChainAt(context.TODO(), func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { return hnd(ctx, ts.Key().Bytes(), curH) }, func(ctx context.Context, ts *types.TipSet) error { return rev(ctx, ts.Key().Bytes()) diff --git a/storage/miner.go b/storage/miner.go index c4bf41c12..155f5f30d 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -116,6 +116,7 @@ type fullNodeFilteredAPI interface { ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) ChainHasObj(context.Context, cid.Cid) (bool, error) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) @@ -167,28 +168,29 @@ func (m *Miner) Run(ctx context.Context) error { return xerrors.Errorf("getting miner info: %w", err) } - var ( - // consumer of chain head changes. - evts = events.NewEvents(ctx, m.api) - evtsAdapter = NewEventsAdapter(evts) + // consumer of chain head changes. + evts, err := events.NewEvents(ctx, m.api) + if err != nil { + return xerrors.Errorf("failed to subscribe to events: %w", err) + } + evtsAdapter := NewEventsAdapter(evts) - // Create a shim to glue the API required by the sealing component - // with the API that Lotus is capable of providing. - // The shim translates between "tipset tokens" and tipset keys, and - // provides extra methods. - adaptedAPI = NewSealingAPIAdapter(m.api) + // Create a shim to glue the API required by the sealing component + // with the API that Lotus is capable of providing. + // The shim translates between "tipset tokens" and tipset keys, and + // provides extra methods. + adaptedAPI := NewSealingAPIAdapter(m.api) - // Instantiate a precommit policy. - cfg = sealing.GetSealingConfigFunc(m.getSealConfig) - provingBuffer = md.WPoStProvingPeriod * 2 + // Instantiate a precommit policy. + cfg := sealing.GetSealingConfigFunc(m.getSealConfig) + provingBuffer := md.WPoStProvingPeriod * 2 - pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBuffer) + pcp := sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBuffer) - // address selector. - 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) - } - ) + // address selector. + 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) + } // Instantiate the sealing FSM. m.sealing = sealing.New(ctx, adaptedAPI, m.feeCfg, evtsAdapter, m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, cfg, m.handleSealingNotifications, as) From 82ac0a24a01b55820927371c0710ab432063893f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 9 Aug 2021 22:15:38 -0700 Subject: [PATCH 063/122] test: improve chain event tests --- chain/events/events_called.go | 2 +- chain/events/events_test.go | 448 ++++++++++++++++++++-------------- chain/events/observer.go | 2 +- 3 files changed, 269 insertions(+), 183 deletions(-) diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 2b1a76e84..091b4b31a 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -393,7 +393,7 @@ type StateMatchFunc func(oldTs, newTs *types.TipSet) (bool, StateChange, error) // * `StateChangeHandler` is called when the specified state change was observed // on-chain, and a confidence threshold was reached, or the specified `timeout` // height was reached with no state change observed. When this callback is -// invoked on a timeout, `oldState` and `newState` are set to nil. +// invoked on a timeout, `oldTs` and `states are set to nil. // This callback returns a boolean specifying whether further notifications // should be sent, like `more` return param from `CheckFunc` above. // diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 0f4687c8d..a0c094244 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -43,10 +43,10 @@ type fakeCS struct { tipsets map[types.TipSetKey]*types.TipSet - sub func(rev, app []*types.TipSet) - - callNumberLk sync.Mutex - callNumber map[string]int + mu sync.Mutex + waitSub chan struct{} + subCh chan<- []*api.HeadChange + callNumber map[string]int } func newFakeCS(t *testing.T) *fakeCS { @@ -58,55 +58,82 @@ func newFakeCS(t *testing.T) *fakeCS { tipsets: make(map[types.TipSetKey]*types.TipSet), tsc: newTSCache(nil, 2*build.ForkLengthThreshold), callNumber: map[string]int{}, + waitSub: make(chan struct{}), } require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) return fcs } func (fcs *fakeCS) ChainHead(ctx context.Context) (*types.TipSet, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainHead"] = fcs.callNumber["ChainHead"] + 1 panic("implement me") } func (fcs *fakeCS) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() fcs.callNumber["ChainGetPath"] = fcs.callNumber["ChainGetPath"] + 1 - panic("Not Implemented") + fcs.mu.Unlock() + + fromTs, err := fcs.ChainGetTipSet(ctx, from) + if err != nil { + return nil, err + } + + toTs, err := fcs.ChainGetTipSet(ctx, to) + if err != nil { + return nil, err + } + + // copied from the chainstore + revert, apply, err := store.ReorgOps(func(tsk types.TipSetKey) (*types.TipSet, error) { + return fcs.ChainGetTipSet(ctx, tsk) + }, fromTs, toTs) + if err != nil { + return nil, err + } + + path := make([]*api.HeadChange, len(revert)+len(apply)) + for i, r := range revert { + path[i] = &api.HeadChange{Type: store.HCRevert, Val: r} + } + for j, i := 0, len(apply)-1; i >= 0; j, i = j+1, i-1 { + path[j+len(revert)] = &api.HeadChange{Type: store.HCApply, Val: apply[i]} + } + return path, nil } func (fcs *fakeCS) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainGetTipSet"] = fcs.callNumber["ChainGetTipSet"] + 1 return fcs.tipsets[key], nil } func (fcs *fakeCS) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["StateSearchMsg"] = fcs.callNumber["StateSearchMsg"] + 1 return nil, nil } func (fcs *fakeCS) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["StateGetActor"] = fcs.callNumber["StateGetActor"] + 1 panic("Not Implemented") } func (fcs *fakeCS) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainGetTipSetByHeight"] = fcs.callNumber["ChainGetTipSetByHeight"] + 1 panic("Not Implemented") } func (fcs *fakeCS) ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainGetTipSetAfterHeight"] = fcs.callNumber["ChainGetTipSetAfterHeight"] + 1 panic("Not Implemented") } @@ -158,47 +185,32 @@ func (fcs *fakeCS) makeTs(t *testing.T, parents []cid.Cid, h abi.ChainEpoch, msg } func (fcs *fakeCS) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainNotify"] = fcs.callNumber["ChainNotify"] + 1 out := make(chan []*api.HeadChange, 1) + if fcs.subCh != nil { + close(out) + fcs.t.Error("already subscribed to notifications") + return out, nil + } + best, err := fcs.tsc.ChainHead(ctx) if err != nil { return nil, err } + out <- []*api.HeadChange{{Type: store.HCCurrent, Val: best}} - - fcs.sub = func(rev, app []*types.TipSet) { - notif := make([]*api.HeadChange, len(rev)+len(app)) - - for i, r := range rev { - notif[i] = &api.HeadChange{ - Type: store.HCRevert, - Val: r, - } - } - for i, r := range app { - notif[i+len(rev)] = &api.HeadChange{ - Type: store.HCApply, - Val: r, - } - } - - select { - case out <- notif: - case <-ctx.Done(): - // TODO: fail test? - return - } - } + fcs.subCh = out + close(fcs.waitSub) return out, nil } func (fcs *fakeCS) ChainGetBlockMessages(ctx context.Context, blk cid.Cid) (*api.BlockMessages, error) { - fcs.callNumberLk.Lock() - defer fcs.callNumberLk.Unlock() + fcs.mu.Lock() + defer fcs.mu.Unlock() fcs.callNumber["ChainGetBlockMessages"] = fcs.callNumber["ChainGetBlockMessages"] + 1 messages, ok := fcs.blkMsgs[blk] if !ok { @@ -235,11 +247,44 @@ func (fcs *fakeCS) fakeMsgs(m fakeMsg) cid.Cid { return c } -func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { // todo: allow msgs - if fcs.sub == nil { +func (fcs *fakeCS) dropSub() { + fcs.mu.Lock() + + if fcs.subCh == nil { + fcs.mu.Unlock() fcs.t.Fatal("sub not be nil") } + waitCh := make(chan struct{}) + fcs.waitSub = waitCh + close(fcs.subCh) + fcs.subCh = nil + fcs.mu.Unlock() + + <-waitCh +} + +func (fcs *fakeCS) sub(rev, app []*types.TipSet) { + <-fcs.waitSub + notif := make([]*api.HeadChange, len(rev)+len(app)) + + for i, r := range rev { + notif[i] = &api.HeadChange{ + Type: store.HCRevert, + Val: r, + } + } + for i, r := range app { + notif[i+len(rev)] = &api.HeadChange{ + Type: store.HCApply, + Val: r, + } + } + + fcs.subCh <- notif +} + +func (fcs *fakeCS) advance(rev, app, drop int, msgs map[int]cid.Cid, nulls ...int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -255,9 +300,17 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / require.NoError(fcs.t, err) if _, ok := nullm[int(from.Height())]; !ok { - revs = append(revs, from) - require.NoError(fcs.t, fcs.tsc.revert(from)) + + if drop == 0 { + revs = append(revs, from) + } + } + if drop > 0 { + drop-- + if drop == 0 { + fcs.dropSub() + } } fcs.h-- } @@ -272,20 +325,27 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / mc = dummyCid } - if _, ok := nullm[int(fcs.h)]; ok { - continue + if _, ok := nullm[int(fcs.h)]; !ok { + best, err := fcs.tsc.ChainHead(ctx) + require.NoError(fcs.t, err) + ts := fcs.makeTs(fcs.t, best.Key().Cids(), fcs.h, mc) + require.NoError(fcs.t, fcs.tsc.add(ts)) + + if hasMsgs { + fcs.blkMsgs[ts.Blocks()[0].Cid()] = mc + } + + if drop == 0 { + apps = append(apps, ts) + } } - best, err := fcs.tsc.ChainHead(ctx) - require.NoError(fcs.t, err) - ts := fcs.makeTs(fcs.t, best.Key().Cids(), fcs.h, mc) - require.NoError(fcs.t, fcs.tsc.add(ts)) - - if hasMsgs { - fcs.blkMsgs[ts.Blocks()[0].Cid()] = mc + if drop > 0 { + drop-- + if drop == 0 { + fcs.dropSub() + } } - - apps = append(apps, ts) } fcs.sub(revs, apps) @@ -316,88 +376,47 @@ func TestAt(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(10, 10, nil) + fcs.advance(10, 10, 0, nil) require.Equal(t, true, applied) require.Equal(t, true, reverted) applied = false reverted = false - fcs.advance(10, 1, nil) + fcs.advance(10, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, true, reverted) reverted = false - fcs.advance(0, 1, nil) + fcs.advance(0, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 2, nil) + fcs.advance(0, 2, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 1, nil) // 8 + fcs.advance(0, 1, 0, nil) // 8 require.Equal(t, true, applied) require.Equal(t, false, reverted) } -func TestAtDoubleTrigger(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fcs := newFakeCS(t) - - events, err := NewEvents(ctx, fcs) - require.NoError(t, err) - - var applied bool - var reverted bool - - err = events.ChainAt(ctx, func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - require.Equal(t, 5, int(ts.Height())) - require.Equal(t, 8, int(curH)) - applied = true - return nil - }, func(_ context.Context, ts *types.TipSet) error { - reverted = true - return nil - }, 3, 5) - require.NoError(t, err) - - fcs.advance(0, 6, nil) - require.False(t, applied) - require.False(t, reverted) - - fcs.advance(0, 1, nil) - require.True(t, applied) - require.False(t, reverted) - applied = false - - fcs.advance(2, 2, nil) - require.False(t, applied) - require.False(t, reverted) - - fcs.advance(4, 4, nil) - require.True(t, applied) - require.True(t, reverted) -} - func TestAtNullTrigger(t *testing.T) { fcs := newFakeCS(t) events, err := NewEvents(context.Background(), fcs) @@ -417,11 +436,11 @@ func TestAtNullTrigger(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 6, nil, 5) + fcs.advance(0, 6, 0, nil, 5) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false @@ -450,16 +469,16 @@ func TestAtNullConf(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 6, nil) + fcs.advance(0, 6, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil, 8) + fcs.advance(0, 3, 0, nil, 8) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(7, 1, nil) + fcs.advance(7, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, true, reverted) reverted = false @@ -471,7 +490,7 @@ func TestAtStart(t *testing.T) { events, err := NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 5, nil) // 6 + fcs.advance(0, 5, 0, nil) // 6 var applied bool var reverted bool @@ -490,7 +509,7 @@ func TestAtStart(t *testing.T) { require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 5, nil) // 11 + fcs.advance(0, 5, 0, nil) // 11 require.Equal(t, true, applied) require.Equal(t, false, reverted) } @@ -501,7 +520,7 @@ func TestAtStartConfidence(t *testing.T) { events, err := NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 10, nil) // 11 + fcs.advance(0, 10, 0, nil) // 11 var applied bool var reverted bool @@ -545,7 +564,7 @@ func TestAtChained(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 15, nil) + fcs.advance(0, 15, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) @@ -557,7 +576,7 @@ func TestAtChainedConfidence(t *testing.T) { events, err := NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 15, nil) + fcs.advance(0, 15, 0, nil) var applied bool var reverted bool @@ -587,7 +606,7 @@ func TestAtChainedConfidenceNull(t *testing.T) { events, err := NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 15, nil, 5) + fcs.advance(0, 15, 0, nil, 5) var applied bool var reverted bool @@ -644,13 +663,13 @@ func TestCalled(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -663,14 +682,14 @@ func TestCalled(t *testing.T) { // create additional block so we are above confidence threshold - fcs.advance(0, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(0, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false // dip below confidence - fcs.advance(2, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(2, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, false, applied) require.Equal(t, false, reverted) @@ -684,13 +703,13 @@ func TestCalled(t *testing.T) { // revert some blocks, keep the message - fcs.advance(3, 1, nil) // H=8 (confidence=1) + fcs.advance(3, 1, 0, nil) // H=8 (confidence=1) require.Equal(t, false, applied) require.Equal(t, false, reverted) // revert the message - fcs.advance(2, 1, nil) // H=7, we reverted ts with the msg execution, but not the msg itself + fcs.advance(2, 1, 0, nil) // H=7, we reverted ts with the msg execution, but not the msg itself require.Equal(t, false, applied) require.Equal(t, true, reverted) @@ -704,7 +723,7 @@ func TestCalled(t *testing.T) { }, }) - fcs.advance(0, 3, map[int]cid.Cid{ // (n2msg confidence=1) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // (n2msg confidence=1) 0: n2msg, }) @@ -713,7 +732,7 @@ func TestCalled(t *testing.T) { require.Equal(t, abi.ChainEpoch(10), appliedH) applied = false - fcs.advance(0, 2, nil) // (confidence=3) + fcs.advance(0, 2, 0, nil) // (confidence=3) require.Equal(t, true, applied) require.Equal(t, false, reverted) @@ -728,7 +747,7 @@ func TestCalled(t *testing.T) { // revert and apply at different height - fcs.advance(8, 6, map[int]cid.Cid{ // (confidence=3) + fcs.advance(8, 6, 0, map[int]cid.Cid{ // (confidence=3) 1: n2msg, }) @@ -749,7 +768,7 @@ func TestCalled(t *testing.T) { // call method again - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: n2msg, }) @@ -758,7 +777,7 @@ func TestCalled(t *testing.T) { applied = false // send and revert below confidence, then cross confidence - fcs.advance(0, 2, map[int]cid.Cid{ + fcs.advance(0, 2, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 3}, @@ -766,14 +785,14 @@ func TestCalled(t *testing.T) { }), }) - fcs.advance(2, 5, nil) // H=19, but message reverted + fcs.advance(2, 5, 0, nil) // H=19, but message reverted require.Equal(t, false, applied) require.Equal(t, false, reverted) // test timeout (it's set to 20 in the call to `events.Called` above) - fcs.advance(0, 6, nil) + fcs.advance(0, 6, 0, nil) require.Equal(t, false, applied) // not calling timeout as we received messages require.Equal(t, false, reverted) @@ -781,7 +800,7 @@ func TestCalled(t *testing.T) { // test unregistering with more more = false - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 4}, // this signals we don't want more @@ -793,7 +812,7 @@ func TestCalled(t *testing.T) { require.Equal(t, false, reverted) applied = false - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 5}, @@ -806,12 +825,12 @@ func TestCalled(t *testing.T) { // revert after disabled - fcs.advance(5, 1, nil) // try reverting msg sent after disabling + fcs.advance(5, 1, 0, nil) // try reverting msg sent after disabling require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(5, 1, nil) // try reverting msg sent before disabling + fcs.advance(5, 1, 0, nil) // try reverting msg sent before disabling require.Equal(t, false, applied) require.Equal(t, true, reverted) @@ -842,10 +861,10 @@ func TestCalledTimeout(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.True(t, called) called = false @@ -856,9 +875,7 @@ func TestCalledTimeout(t *testing.T) { events, err = NewEvents(context.Background(), fcs) require.NoError(t, err) - // XXX: Needed to set the latest head so "check" succeeds". Is that OK? Or do we expect - // check to work _before_ we've received any events. - fcs.advance(0, 1, nil) + fcs.advance(0, 1, 0, nil) err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil @@ -874,10 +891,10 @@ func TestCalledTimeout(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.False(t, called) } @@ -921,7 +938,7 @@ func TestCalledOrder(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 10, map[int]cid.Cid{ + fcs.advance(0, 10, 0, map[int]cid.Cid{ 1: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -934,7 +951,7 @@ func TestCalledOrder(t *testing.T) { }), }) - fcs.advance(9, 1, nil) + fcs.advance(9, 1, 0, nil) } func TestCalledNull(t *testing.T) { @@ -963,13 +980,13 @@ func TestCalledNull(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -983,13 +1000,13 @@ func TestCalledNull(t *testing.T) { // create additional blocks so we are above confidence threshold, but with null tipset at the height // of application - fcs.advance(0, 3, nil, 10) // H=11 (confidence=3, apply) + fcs.advance(0, 3, 0, nil, 10) // H=11 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(5, 1, nil, 10) + fcs.advance(5, 1, 0, nil, 10) require.Equal(t, false, applied) require.Equal(t, true, reverted) @@ -1021,13 +1038,13 @@ func TestRemoveTriggersOnMessage(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=5, applied at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg occurs at H=5, applied at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -1039,19 +1056,19 @@ func TestRemoveTriggersOnMessage(t *testing.T) { require.Equal(t, false, reverted) // revert applied TS & message TS - fcs.advance(3, 1, nil) // H=6 (tipset message applied in reverted, AND message reverted) + fcs.advance(3, 1, 0, nil) // H=6 (tipset message applied in reverted, AND message reverted) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create additional blocks so we are above confidence threshold, but message not applied // as it was reverted - fcs.advance(0, 5, nil) // H=11 (confidence=3, apply) + fcs.advance(0, 5, 0, nil) // H=11 (confidence=3, apply) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message again (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=12, applied at H=13; H=15 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg occurs at H=12, applied at H=13; H=15 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 2}, @@ -1062,12 +1079,12 @@ func TestRemoveTriggersOnMessage(t *testing.T) { require.Equal(t, false, reverted) // revert applied height TS, but don't remove message trigger - fcs.advance(2, 1, nil) // H=13 (tipset message applied in reverted, by tipset with message not reverted) + fcs.advance(2, 1, 0, nil) // H=13 (tipset message applied in reverted, by tipset with message not reverted) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create additional blocks so we are above confidence threshold - fcs.advance(0, 4, nil) // H=18 (confidence=3, apply) + fcs.advance(0, 4, 0, nil) // H=18 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) @@ -1098,6 +1115,9 @@ func TestStateChanged(t *testing.T) { err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } require.Equal(t, false, applied) applied = true appliedData = data @@ -1109,6 +1129,7 @@ func TestStateChanged(t *testing.T) { reverted = true return nil }, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) if matchData == nil { return false, matchData, nil } @@ -1121,27 +1142,27 @@ func TestStateChanged(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create state change (but below confidence threshold) matchData = testStateChange{from: "a", to: "b"} - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create additional block so we are above confidence threshold - fcs.advance(0, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(0, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false // dip below confidence (should not apply again) - fcs.advance(2, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(2, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, false, applied) require.Equal(t, false, reverted) @@ -1175,6 +1196,9 @@ func TestStateChangedRevert(t *testing.T) { err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } require.Equal(t, false, applied) applied = true return more, nil @@ -1182,6 +1206,8 @@ func TestStateChangedRevert(t *testing.T) { reverted = true return nil }, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) + if matchData == nil { return false, matchData, nil } @@ -1192,18 +1218,18 @@ func TestStateChangedRevert(t *testing.T) { }) require.NoError(t, err) - fcs.advance(0, 2, nil) // H=3 + fcs.advance(0, 2, 0, nil) // H=3 // Make a state change from TS at height 3 to TS at height 4 matchData = testStateChange{from: "a", to: "b"} - fcs.advance(0, 1, nil) // H=4 + fcs.advance(0, 1, 0, nil) // H=4 // Haven't yet reached confidence require.Equal(t, false, applied) require.Equal(t, false, reverted) // Advance to reach confidence level - fcs.advance(0, 1, nil) // H=5 + fcs.advance(0, 1, 0, nil) // H=5 // Should now have called the handler require.Equal(t, true, applied) @@ -1211,19 +1237,19 @@ func TestStateChangedRevert(t *testing.T) { applied = false // Advance 3 more TS - fcs.advance(0, 3, nil) // H=8 + fcs.advance(0, 3, 0, nil) // H=8 require.Equal(t, false, applied) require.Equal(t, false, reverted) // Regress but not so far as to cause a revert - fcs.advance(3, 1, nil) // H=6 + fcs.advance(3, 1, 0, nil) // H=6 require.Equal(t, false, applied) require.Equal(t, false, reverted) // Regress back to state where change happened - fcs.advance(3, 1, nil) // H=4 + fcs.advance(3, 1, 0, nil) // H=4 // Expect revert to have happened require.Equal(t, false, applied) @@ -1241,6 +1267,9 @@ func TestStateChangedTimeout(t *testing.T) { err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } called = true require.Nil(t, data) require.Equal(t, abi.ChainEpoch(20), newTs.Height()) @@ -1250,15 +1279,16 @@ func TestStateChangedTimeout(t *testing.T) { t.Fatal("revert on timeout") return nil }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) return false, nil, nil }) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.True(t, called) called = false @@ -1268,13 +1298,14 @@ func TestStateChangedTimeout(t *testing.T) { events, err = NewEvents(context.Background(), fcs) require.NoError(t, err) - // XXX: Needed to set the latest head so "check" succeeds". Is that OK? Or do we expect - // check to work _before_ we've received any events. - fcs.advance(0, 1, nil) + fcs.advance(0, 1, 0, nil) err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } called = true require.Nil(t, data) require.Equal(t, abi.ChainEpoch(20), newTs.Height()) @@ -1284,14 +1315,15 @@ func TestStateChangedTimeout(t *testing.T) { t.Fatal("revert on timeout") return nil }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) return false, nil, nil }) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.False(t, called) } @@ -1335,7 +1367,7 @@ func TestCalledMultiplePerEpoch(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 10, map[int]cid.Cid{ + fcs.advance(0, 10, 0, map[int]cid.Cid{ 1: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -1344,7 +1376,7 @@ func TestCalledMultiplePerEpoch(t *testing.T) { }), }) - fcs.advance(9, 1, nil) + fcs.advance(9, 1, 0, nil) } func TestCachedSameBlock(t *testing.T) { @@ -1353,9 +1385,63 @@ func TestCachedSameBlock(t *testing.T) { _, err := NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 10, map[int]cid.Cid{}) + fcs.advance(0, 10, 0, map[int]cid.Cid{}) assert.Assert(t, fcs.callNumber["ChainGetBlockMessages"] == 20, "expect call ChainGetBlockMessages %d but got ", 20, fcs.callNumber["ChainGetBlockMessages"]) - fcs.advance(5, 10, map[int]cid.Cid{}) + fcs.advance(5, 10, 0, map[int]cid.Cid{}) assert.Assert(t, fcs.callNumber["ChainGetBlockMessages"] == 30, "expect call ChainGetBlockMessages %d but got ", 30, fcs.callNumber["ChainGetBlockMessages"]) } + +type testObserver struct { + t *testing.T + head *types.TipSet +} + +func (t *testObserver) Apply(_ context.Context, from, to *types.TipSet) error { + if t.head != nil { + require.True(t.t, t.head.Equals(from)) + } + t.head = to + return nil +} + +func (t *testObserver) Revert(_ context.Context, from, to *types.TipSet) error { + if t.head != nil { + require.True(t.t, t.head.Equals(from)) + } + t.head = to + return nil +} + +func TestReconnect(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) + + fcs.advance(0, 1, 0, nil) + + events.Observe(&testObserver{t: t}) + + fcs.advance(0, 3, 0, nil) + + // Drop on apply + fcs.advance(0, 6, 2, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 1) + + // drop across revert/apply boundary + fcs.advance(4, 2, 3, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 2) + fcs.advance(0, 6, 0, nil) + + // drop on revert + fcs.advance(3, 0, 2, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 3) + + // drop with nulls + fcs.advance(0, 5, 2, nil, 0, 1, 3) + require.True(t, fcs.callNumber["ChainGetPath"] == 4) +} diff --git a/chain/events/observer.go b/chain/events/observer.go index cd25b4874..52fc1de25 100644 --- a/chain/events/observer.go +++ b/chain/events/observer.go @@ -111,7 +111,7 @@ func (o *observer) listenHeadChangesOnce(ctx context.Context) error { } if err := o.applyChanges(ctx, changes); err != nil { - return xerrors.Errorf("failed to apply head changes: %w", err) + return xerrors.Errorf("failed catch-up head changes: %w", err) } } From f518e34131a54910df3ebcd93455d5a012802cc7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 10 Aug 2021 23:53:57 -0700 Subject: [PATCH 064/122] fix: atomically get head when registering an observer This lets us always call check (accurately). --- chain/events/events.go | 10 ++---- chain/events/events_called.go | 25 ++++++-------- chain/events/events_height.go | 21 ++++-------- chain/events/events_test.go | 4 --- chain/events/observer.go | 63 +++++++++++++++++++++++------------ 5 files changed, 61 insertions(+), 62 deletions(-) diff --git a/chain/events/events.go b/chain/events/events.go index 1e39d3646..5c494fcb0 100644 --- a/chain/events/events.go +++ b/chain/events/events.go @@ -50,17 +50,13 @@ func NewEventsWithConfidence(ctx context.Context, api EventAPI, gcConfidence abi cache := newCache(api, gcConfidence) ob := newObserver(cache, gcConfidence) - he := newHeightEvents(cache, gcConfidence) - headChange := newHCEvents(cache) - - // Cache first. Observers are ordered and we always want to fill the cache first. - ob.Observe(cache.observer()) - ob.Observe(he.observer()) - ob.Observe(headChange.observer()) if err := ob.start(ctx); err != nil { return nil, err } + he := newHeightEvents(cache, ob, gcConfidence) + headChange := newHCEvents(cache, ob) + return &Events{ob, he, headChange}, nil } diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 091b4b31a..026ad8a4e 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -69,10 +69,9 @@ type queuedEvent struct { type hcEvents struct { cs EventAPI + lk sync.Mutex lastTs *types.TipSet - lk sync.Mutex - ctr triggerID // TODO: get rid of trigger IDs and just use pointers as keys. @@ -93,7 +92,7 @@ type hcEvents struct { watcherEvents } -func newHCEvents(api EventAPI) *hcEvents { +func newHCEvents(api EventAPI, obs *observer) *hcEvents { e := &hcEvents{ cs: api, confQueue: map[triggerH]map[msgH][]*queuedEvent{}, @@ -105,15 +104,16 @@ func newHCEvents(api EventAPI) *hcEvents { e.messageEvents = newMessageEvents(e, api) e.watcherEvents = newWatcherEvents(e, api) + // We need to take the lock as the observer could immediately try calling us. + e.lk.Lock() + e.lastTs = obs.Observe((*hcEventsObserver)(e)) + e.lk.Unlock() + return e } type hcEventsObserver hcEvents -func (e *hcEvents) observer() TipSetObserver { - return (*hcEventsObserver)(e) -} - func (e *hcEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) error { e.lk.Lock() defer e.lk.Unlock() @@ -284,14 +284,9 @@ func (e *hcEvents) onHeadChanged(ctx context.Context, check CheckFunc, hnd Event defer e.lk.Unlock() // Check if the event has already occurred - more := true - done := false - if e.lastTs != nil { - var err error - done, more, err = check(ctx, e.lastTs) - if err != nil { - return 0, xerrors.Errorf("called check error (h: %d): %w", e.lastTs.Height(), err) - } + done, more, err := check(ctx, e.lastTs) + if err != nil { + return 0, xerrors.Errorf("called check error (h: %d): %w", e.lastTs.Height(), err) } if done { timeout = NoTimeout diff --git a/chain/events/events_height.go b/chain/events/events_height.go index 02c252bc9..73df04be6 100644 --- a/chain/events/events_height.go +++ b/chain/events/events_height.go @@ -30,13 +30,17 @@ type heightEvents struct { lastGc abi.ChainEpoch //nolint:structcheck } -func newHeightEvents(api EventAPI, gcConfidence abi.ChainEpoch) *heightEvents { - return &heightEvents{ +func newHeightEvents(api EventAPI, obs *observer, gcConfidence abi.ChainEpoch) *heightEvents { + he := &heightEvents{ api: api, gcConfidence: gcConfidence, tsHeights: map[abi.ChainEpoch][]*heightHandler{}, triggerHeights: map[abi.ChainEpoch][]*heightHandler{}, } + he.lk.Lock() + he.head = obs.Observe((*heightEventsObserver)(he)) + he.lk.Unlock() + return he } // ChainAt invokes the specified `HeightHandler` when the chain reaches the @@ -69,15 +73,6 @@ func (e *heightEvents) ChainAt(ctx context.Context, hnd HeightHandler, rev Rever e.lk.Lock() for { head := e.head - - // If we haven't initialized yet, store the trigger and move on. - if head == nil { - e.triggerHeights[triggerAt] = append(e.triggerHeights[triggerAt], handler) - e.tsHeights[h] = append(e.tsHeights[h], handler) - e.lk.Unlock() - return nil - } - if head.Height() >= h { // Head is past the handler height. We at least need to stash the tipset to // avoid doing this from the main event loop. @@ -152,10 +147,6 @@ func (e *heightEvents) ChainAt(ctx context.Context, hnd HeightHandler, rev Rever } } -func (e *heightEvents) observer() TipSetObserver { - return (*heightEventsObserver)(e) -} - // Updates the head and garbage collects if we're 2x over our garbage collection confidence period. func (e *heightEventsObserver) updateHead(h *types.TipSet) { e.lk.Lock() diff --git a/chain/events/events_test.go b/chain/events/events_test.go index a0c094244..0536b5ebb 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -875,8 +875,6 @@ func TestCalledTimeout(t *testing.T) { events, err = NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 1, 0, nil) - err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { @@ -1298,8 +1296,6 @@ func TestStateChangedTimeout(t *testing.T) { events, err = NewEvents(context.Background(), fcs) require.NoError(t, err) - fcs.advance(0, 1, 0, nil) - err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { diff --git a/chain/events/observer.go b/chain/events/observer.go index 52fc1de25..c67d821b5 100644 --- a/chain/events/observer.go +++ b/chain/events/observer.go @@ -18,25 +18,26 @@ import ( type observer struct { api EventAPI - lk sync.Mutex gcConfidence abi.ChainEpoch ready chan struct{} + lk sync.Mutex head *types.TipSet maxHeight abi.ChainEpoch - observers []TipSetObserver } -func newObserver(api EventAPI, gcConfidence abi.ChainEpoch) *observer { - return &observer{ +func newObserver(api *cache, gcConfidence abi.ChainEpoch) *observer { + obs := &observer{ api: api, gcConfidence: gcConfidence, ready: make(chan struct{}), observers: []TipSetObserver{}, } + obs.Observe(api.observer()) + return obs } func (o *observer) start(ctx context.Context) error { @@ -100,12 +101,18 @@ func (o *observer) listenHeadChangesOnce(ctx context.Context) error { return xerrors.Errorf("expected first head notification type to be 'current', was '%s'", cur[0].Type) } - head := cur[0].Val + curHead := cur[0].Val + + o.lk.Lock() if o.head == nil { - o.head = head + o.head = curHead close(o.ready) - } else if !o.head.Equals(head) { - changes, err := o.api.ChainGetPath(ctx, o.head.Key(), head.Key()) + } + startHead := o.head + o.lk.Unlock() + + if !startHead.Equals(curHead) { + changes, err := o.api.ChainGetPath(ctx, startHead.Key(), curHead.Key()) if err != nil { return xerrors.Errorf("failed to get path from last applied tipset to head: %w", err) } @@ -152,18 +159,23 @@ func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) err ctx, span := trace.StartSpan(ctx, "events.HeadChange") span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) + + o.lk.Lock() + head := o.head + o.lk.Unlock() + defer func() { - span.AddAttributes(trace.Int64Attribute("endHeight", int64(o.head.Height()))) + span.AddAttributes(trace.Int64Attribute("endHeight", int64(head.Height()))) span.End() }() // NOTE: bailing out here if the head isn't what we expected is fine. We'll re-start the // entire process and handle any strange reorgs. for i, from := range rev { - if !from.Equals(o.head) { + if !from.Equals(head) { return xerrors.Errorf( "expected to revert %s (%d), reverting %s (%d)", - o.head.Key(), o.head.Height(), from.Key(), from.Height(), + head.Key(), head.Height(), from.Key(), from.Height(), ) } var to *types.TipSet @@ -171,7 +183,7 @@ func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) err // If we have more reverts, the next revert is the next head. to = rev[i+1] } else { - // At the end of the revert sequenece, we need to looup the joint tipset + // At the end of the revert sequenece, we need to lookup the joint tipset // between the revert sequence and the apply sequence. var err error to, err = o.api.ChainGetTipSet(ctx, from.Parents()) @@ -181,9 +193,14 @@ func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) err } } - // Get the observers late in case an observer registers/unregisters itself. + // Get the current observers and atomically set the head. + // + // 1. We need to get the observers every time in case some registered/deregistered. + // 2. We need to atomically set the head so new observers don't see events twice or + // skip them. o.lk.Lock() observers := o.observers + o.head = to o.lk.Unlock() for _, obs := range observers { @@ -196,39 +213,43 @@ func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) err log.Errorf("reverted past finality, from %d to %d", o.maxHeight, to.Height()) } - o.head = to + head = to } for _, to := range app { - if to.Parents() != o.head.Key() { + if to.Parents() != head.Key() { return xerrors.Errorf( "cannot apply %s (%d) with parents %s on top of %s (%d)", - to.Key(), to.Height(), to.Parents(), o.head.Key(), o.head.Height(), + to.Key(), to.Height(), to.Parents(), head.Key(), head.Height(), ) } - // Get the observers late in case an observer registers/unregisters itself. o.lk.Lock() observers := o.observers + o.head = to o.lk.Unlock() for _, obs := range observers { - if err := obs.Apply(ctx, o.head, to); err != nil { + if err := obs.Apply(ctx, head, to); err != nil { log.Errorf("observer %T failed to revert tipset %s (%d) with: %s", obs, to.Key(), to.Height(), err) } } - o.head = to if to.Height() > o.maxHeight { o.maxHeight = to.Height() } + head = to } return nil } -// TODO: add a confidence level so we can have observers with difference levels of confidence -func (o *observer) Observe(obs TipSetObserver) { +// Observe registers the observer, and returns the current tipset. The observer is guaranteed to +// observe events starting at this tipset. +// +// Returns nil if the observer hasn't started yet (but still registers). +func (o *observer) Observe(obs TipSetObserver) *types.TipSet { o.lk.Lock() defer o.lk.Unlock() o.observers = append(o.observers, obs) + return o.head } From 003eae81ced7bbf78bb9b1d4454962caed099837 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 27 Aug 2021 12:12:55 -0700 Subject: [PATCH 065/122] fix: address review --- chain/events/events_height.go | 2 +- itests/api_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chain/events/events_height.go b/chain/events/events_height.go index 73df04be6..6734d3ca0 100644 --- a/chain/events/events_height.go +++ b/chain/events/events_height.go @@ -47,7 +47,7 @@ func newHeightEvents(api EventAPI, obs *observer, gcConfidence abi.ChainEpoch) * // specified height+confidence threshold. If the chain is rolled-back under the // specified height, `RevertHandler` will be called. // -// ts passed to handlers is the tipset at the specified, or above, if lower tipsets were null +// ts passed to handlers is the tipset at the specified epoch, or above if lower tipsets were null. // // The context governs cancellations of this call, it won't cancel the event handler. func (e *heightEvents) ChainAt(ctx context.Context, hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { diff --git a/itests/api_test.go b/itests/api_test.go index 9a21c9dfc..c380a6ed8 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -195,7 +195,7 @@ func (ts *apiSuite) testSlowNotify(t *testing.T) { full.WaitTillChain(ctx, kit.HeightAtLeast(baseHeight+100)) - // Make sure they were all closed. + // Make sure they were all closed, draining any buffered events first. for _, ch := range newHeadsChans { var ok bool for ok { From 1cf556c3a28dd62951f0727e92c288466a0641b0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 27 Aug 2021 12:25:43 -0700 Subject: [PATCH 066/122] feat: expose ChainGetPath on the gateway --- api/api_gateway.go | 1 + api/proxy_gen.go | 13 +++++++++++++ build/openrpc/full.json.gz | Bin 25415 -> 25416 bytes build/openrpc/miner.json.gz | Bin 10374 -> 10378 bytes build/openrpc/worker.json.gz | Bin 2709 -> 2710 bytes build/params_2k.go | 1 + build/params_butterfly.go | 1 + build/params_calibnet.go | 1 + build/params_debug.go | 1 + build/params_interop.go | 1 + build/params_mainnet.go | 9 ++------- build/params_nerpanet.go | 1 + build/params_shared_vals.go | 1 + build/params_testground.go | 1 + build/tools.go | 3 ++- chain/types/message_fuzz.go | 3 ++- cmd/lotus/daemon.go | 1 + cmd/lotus/daemon_nodaemon.go | 1 + cmd/lotus/debug_advance.go | 1 + .../sector-storage/ffiwrapper/prover_cgo.go | 3 ++- .../sector-storage/ffiwrapper/sealer_cgo.go | 3 ++- .../sector-storage/ffiwrapper/verifier_cgo.go | 3 ++- extern/sector-storage/fsutil/dealloc_other.go | 1 + gateway/node.go | 5 +++++ lib/ulimit/ulimit_freebsd.go | 1 + lib/ulimit/ulimit_test.go | 1 + lib/ulimit/ulimit_unix.go | 1 + 27 files changed, 46 insertions(+), 12 deletions(-) diff --git a/api/api_gateway.go b/api/api_gateway.go index 29cd8ce24..862c6ddb5 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -33,6 +33,7 @@ type Gateway interface { ChainHead(ctx context.Context) (*types.TipSet, error) ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*HeadChange, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) diff --git a/api/proxy_gen.go b/api/proxy_gen.go index f2dc7c560..c03b83531 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -480,6 +480,8 @@ type GatewayStruct struct { ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `` + ChainGetPath func(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) `` + ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `` ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` @@ -3039,6 +3041,17 @@ func (s *GatewayStub) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Me return nil, ErrNotSupported } +func (s *GatewayStruct) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + if s.Internal.ChainGetPath == nil { + return *new([]*HeadChange), ErrNotSupported + } + return s.Internal.ChainGetPath(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + return *new([]*HeadChange), ErrNotSupported +} + func (s *GatewayStruct) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { if s.Internal.ChainGetTipSet == nil { return nil, ErrNotSupported diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 4892e090148247a49af2cd354ba5a370f055ae76..610a0591a8706d1b00af4e488208b8522eee0011 100644 GIT binary patch delta 15686 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 L4XWP$LV)~#hz?qT delta 15644 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 Re*gdg|NpH_;jt^$0RWeeu=M}{ delta 10184 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 C-{o%r diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 06d91c92acef746dc35147e130020f271601ca56..84b1b1b1da844a925216e4850a5f3d95f5e9c7e5 100644 GIT binary patch literal 2710 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 Q1pom5|M(5c#94X(06mH~Z2$lO literal 2709 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 Date: Thu, 26 Aug 2021 17:36:06 +0200 Subject: [PATCH 067/122] fix events API timeout handling for nil blocks (#7184) --- chain/events/events_called.go | 14 ++-- chain/events/events_test.go | 124 ++++++++++++++++++---------------- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 026ad8a4e..ffca57d5b 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -145,7 +145,7 @@ func (e *hcEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) er // Apply any queued events and timeouts that were targeted at the // current chain height e.applyWithConfidence(ctx, at) - e.applyTimeouts(ctx, to) + e.applyTimeouts(ctx, at, to) } return nil } @@ -242,8 +242,8 @@ func (e *hcEventsObserver) applyWithConfidence(ctx context.Context, height abi.C } // Apply any timeouts that expire at this height -func (e *hcEventsObserver) applyTimeouts(ctx context.Context, ts *types.TipSet) { - triggers, ok := e.timeouts[ts.Height()] +func (e *hcEventsObserver) applyTimeouts(ctx context.Context, at abi.ChainEpoch, ts *types.TipSet) { + triggers, ok := e.timeouts[at] if !ok { return // nothing to do } @@ -258,14 +258,14 @@ func (e *hcEventsObserver) applyTimeouts(ctx context.Context, ts *types.TipSet) } // This should be cached. - timeoutTs, err := e.cs.ChainGetTipSetAfterHeight(ctx, ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Key()) + timeoutTs, err := e.cs.ChainGetTipSetAfterHeight(ctx, at-abi.ChainEpoch(trigger.confidence), ts.Key()) if err != nil { - log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height()) + log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", at-abi.ChainEpoch(trigger.confidence), at) } - more, err := trigger.handle(ctx, nil, nil, timeoutTs, ts.Height()) + more, err := trigger.handle(ctx, nil, nil, timeoutTs, at) if err != nil { - log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err) + log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), at, err) continue // don't revert failed calls } diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 0536b5ebb..61dd25fbb 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -1255,72 +1255,80 @@ func TestStateChangedRevert(t *testing.T) { } func TestStateChangedTimeout(t *testing.T) { - fcs := newFakeCS(t) + timeoutHeight := abi.ChainEpoch(20) + confidence := 3 - events, err := NewEvents(context.Background(), fcs) - require.NoError(t, err) + testCases := []struct { + name string + checkFn CheckFunc + nilBlocks []int + expectTimeout bool + }{{ + // Verify that the state changed timeout is called at the expected height + name: "state changed timeout", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + expectTimeout: true, + }, { + // Verify that the state changed timeout is called even if the timeout + // falls on nil block + name: "state changed timeout falls on nil block", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + nilBlocks: []int{20, 21, 22, 23}, + expectTimeout: true, + }, { + // Verify that the state changed timeout is not called if the check + // function reports that it's complete + name: "no timeout callback if check func reports done", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return true, true, nil + }, + expectTimeout: false, + }} - called := false + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + fcs := newFakeCS(t) - err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { - return false, true, nil - }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { - if data != nil { - require.Equal(t, oldTs.Key(), newTs.Parents()) - } - called = true - require.Nil(t, data) - require.Equal(t, abi.ChainEpoch(20), newTs.Height()) - require.Equal(t, abi.ChainEpoch(23), curH) - return false, nil - }, func(_ context.Context, ts *types.TipSet) error { - t.Fatal("revert on timeout") - return nil - }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { - require.Equal(t, oldTs.Key(), newTs.Parents()) - return false, nil, nil - }) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) - require.NoError(t, err) + // Track whether the callback was called + called := false - fcs.advance(0, 21, 0, nil) - require.False(t, called) + // Set up state change tracking that will timeout at the given height + err = events.StateChanged( + tc.checkFn, + func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + // Expect the callback to be called at the timeout height with nil data + called = true + require.Nil(t, data) + require.Equal(t, timeoutHeight, newTs.Height()) + require.Equal(t, timeoutHeight+abi.ChainEpoch(confidence), curH) + return false, nil + }, func(_ context.Context, ts *types.TipSet) error { + t.Fatal("revert on timeout") + return nil + }, confidence, timeoutHeight, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + return false, nil, nil + }) - fcs.advance(0, 5, 0, nil) - require.True(t, called) - called = false + require.NoError(t, err) - // with check func reporting done + // Advance to timeout height + fcs.advance(0, int(timeoutHeight)+1, 0, nil) + require.False(t, called) - fcs = newFakeCS(t) - events, err = NewEvents(context.Background(), fcs) - require.NoError(t, err) - - err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { - return true, true, nil - }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { - if data != nil { - require.Equal(t, oldTs.Key(), newTs.Parents()) - } - called = true - require.Nil(t, data) - require.Equal(t, abi.ChainEpoch(20), newTs.Height()) - require.Equal(t, abi.ChainEpoch(23), curH) - return false, nil - }, func(_ context.Context, ts *types.TipSet) error { - t.Fatal("revert on timeout") - return nil - }, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { - require.Equal(t, oldTs.Key(), newTs.Parents()) - return false, nil, nil - }) - require.NoError(t, err) - - fcs.advance(0, 21, 0, nil) - require.False(t, called) - - fcs.advance(0, 5, 0, nil) - require.False(t, called) + // Advance past timeout height + fcs.advance(0, 5, 0, nil, tc.nilBlocks...) + require.Equal(t, tc.expectTimeout, called) + called = false + }) + } } func TestCalledMultiplePerEpoch(t *testing.T) { From 41dfd010b39480761983763a7b0b01dcf378b16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 12:51:13 +0200 Subject: [PATCH 068/122] gateway: check tipsets in ChainGetPath --- gateway/node.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gateway/node.go b/gateway/node.go index 69a9d8731..37e7af411 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -3,6 +3,7 @@ package gateway import ( "context" "fmt" + "golang.org/x/xerrors" "time" "github.com/filecoin-project/go-address" @@ -218,6 +219,14 @@ func (gw *Node) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, erro } func (gw *Node) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { + if err := gw.checkTipsetKey(ctx, from); err != nil { + return nil, xerrors.Errorf("gateway: checking 'from' tipset: %w", err) + } + + if err := gw.checkTipsetKey(ctx, to); err != nil { + return nil, xerrors.Errorf("gateway: checking 'to' tipset: %w", err) + } + return gw.target.ChainGetPath(ctx, from, to) } From 52e93371bdf5d4edea186404938b47a7b6b9f070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 13:25:18 +0200 Subject: [PATCH 069/122] builder: Handle chainstore config in ConfigFullNode --- node/builder.go | 51 ------------------------------------------- node/builder_chain.go | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 51 deletions(-) diff --git a/node/builder.go b/node/builder.go index 5810910a7..6c42aad2d 100644 --- a/node/builder.go +++ b/node/builder.go @@ -3,7 +3,6 @@ package node import ( "context" "errors" - "os" "time" metricsi "github.com/ipfs/go-metrics-interface" @@ -293,59 +292,9 @@ func Repo(r repo.Repo) Option { if err != nil { return err } - - var cfg *config.Chainstore - switch settings.nodeType { - case repo.FullNode: - cfgp, ok := c.(*config.FullNode) - if !ok { - return xerrors.Errorf("invalid config from repo, got: %T", c) - } - cfg = &cfgp.Chainstore - default: - cfg = &config.Chainstore{} - } - return Options( Override(new(repo.LockedRepo), modules.LockedRepo(lr)), // module handles closing - Override(new(dtypes.UniversalBlockstore), modules.UniversalBlockstore), - - If(cfg.EnableSplitstore, - If(cfg.Splitstore.ColdStoreType == "universal", - Override(new(dtypes.ColdBlockstore), From(new(dtypes.UniversalBlockstore)))), - If(cfg.Splitstore.ColdStoreType == "discard", - Override(new(dtypes.ColdBlockstore), modules.DiscardColdBlockstore)), - If(cfg.Splitstore.HotStoreType == "badger", - Override(new(dtypes.HotBlockstore), modules.BadgerHotBlockstore)), - Override(new(dtypes.SplitBlockstore), modules.SplitBlockstore(cfg)), - Override(new(dtypes.BasicChainBlockstore), modules.ChainSplitBlockstore), - Override(new(dtypes.BasicStateBlockstore), modules.StateSplitBlockstore), - Override(new(dtypes.BaseBlockstore), From(new(dtypes.SplitBlockstore))), - Override(new(dtypes.ExposedBlockstore), modules.ExposedSplitBlockstore), - Override(new(dtypes.GCReferenceProtector), modules.SplitBlockstoreGCReferenceProtector), - ), - If(!cfg.EnableSplitstore, - Override(new(dtypes.BasicChainBlockstore), modules.ChainFlatBlockstore), - Override(new(dtypes.BasicStateBlockstore), modules.StateFlatBlockstore), - Override(new(dtypes.BaseBlockstore), From(new(dtypes.UniversalBlockstore))), - Override(new(dtypes.ExposedBlockstore), From(new(dtypes.UniversalBlockstore))), - Override(new(dtypes.GCReferenceProtector), modules.NoopGCReferenceProtector), - ), - - Override(new(dtypes.ChainBlockstore), From(new(dtypes.BasicChainBlockstore))), - Override(new(dtypes.StateBlockstore), From(new(dtypes.BasicStateBlockstore))), - - If(os.Getenv("LOTUS_ENABLE_CHAINSTORE_FALLBACK") == "1", - Override(new(dtypes.ChainBlockstore), modules.FallbackChainBlockstore), - Override(new(dtypes.StateBlockstore), modules.FallbackStateBlockstore), - Override(SetupFallbackBlockstoresKey, modules.InitFallbackBlockstores), - ), - - Override(new(dtypes.ClientImportMgr), modules.ClientImportMgr), - - Override(new(dtypes.ClientBlockstore), modules.ClientBlockstore), - Override(new(ci.PrivKey), lp2p.PrivKey), Override(new(ci.PubKey), ci.PrivKey.GetPublic), Override(new(peer.ID), peer.IDFromPublicKey), diff --git a/node/builder_chain.go b/node/builder_chain.go index f8eeaecb3..a98994846 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -1,6 +1,8 @@ package node import ( + "os" + "go.uber.org/fx" "golang.org/x/xerrors" @@ -167,6 +169,43 @@ func ConfigFullNode(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), + Override(new(dtypes.UniversalBlockstore), modules.UniversalBlockstore), + + If(cfg.Chainstore.EnableSplitstore, + If(cfg.Chainstore.Splitstore.ColdStoreType == "universal", + Override(new(dtypes.ColdBlockstore), From(new(dtypes.UniversalBlockstore)))), + If(cfg.Chainstore.Splitstore.ColdStoreType == "discard", + Override(new(dtypes.ColdBlockstore), modules.DiscardColdBlockstore)), + If(cfg.Chainstore.Splitstore.HotStoreType == "badger", + Override(new(dtypes.HotBlockstore), modules.BadgerHotBlockstore)), + Override(new(dtypes.SplitBlockstore), modules.SplitBlockstore(&cfg.Chainstore)), + Override(new(dtypes.BasicChainBlockstore), modules.ChainSplitBlockstore), + Override(new(dtypes.BasicStateBlockstore), modules.StateSplitBlockstore), + Override(new(dtypes.BaseBlockstore), From(new(dtypes.SplitBlockstore))), + Override(new(dtypes.ExposedBlockstore), modules.ExposedSplitBlockstore), + Override(new(dtypes.GCReferenceProtector), modules.SplitBlockstoreGCReferenceProtector), + ), + If(!cfg.Chainstore.EnableSplitstore, + Override(new(dtypes.BasicChainBlockstore), modules.ChainFlatBlockstore), + Override(new(dtypes.BasicStateBlockstore), modules.StateFlatBlockstore), + Override(new(dtypes.BaseBlockstore), From(new(dtypes.UniversalBlockstore))), + Override(new(dtypes.ExposedBlockstore), From(new(dtypes.UniversalBlockstore))), + Override(new(dtypes.GCReferenceProtector), modules.NoopGCReferenceProtector), + ), + + Override(new(dtypes.ChainBlockstore), From(new(dtypes.BasicChainBlockstore))), + Override(new(dtypes.StateBlockstore), From(new(dtypes.BasicStateBlockstore))), + + If(os.Getenv("LOTUS_ENABLE_CHAINSTORE_FALLBACK") == "1", + Override(new(dtypes.ChainBlockstore), modules.FallbackChainBlockstore), + Override(new(dtypes.StateBlockstore), modules.FallbackStateBlockstore), + Override(SetupFallbackBlockstoresKey, modules.InitFallbackBlockstores), + ), + + Override(new(dtypes.ClientImportMgr), modules.ClientImportMgr), + + Override(new(dtypes.ClientBlockstore), modules.ClientBlockstore), + If(cfg.Client.UseIpfs, Override(new(dtypes.ClientBlockstore), modules.IpfsClientBlockstore(ipfsMaddr, cfg.Client.IpfsOnlineMode)), Override(new(storagemarket.BlockstoreAccessor), modules.IpfsStorageBlockstoreAccessor), From ed05eaf1d1e82bdd782faf71b1ef47ac5ba9ea4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 13:26:46 +0200 Subject: [PATCH 070/122] gateway: Fix lint --- gateway/node.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gateway/node.go b/gateway/node.go index 37e7af411..56f95a31b 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -3,9 +3,11 @@ package gateway import ( "context" "fmt" - "golang.org/x/xerrors" "time" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" @@ -20,7 +22,6 @@ import ( _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" "github.com/filecoin-project/lotus/node/impl/full" - "github.com/ipfs/go-cid" ) const ( From 3118bd10396d93c80f439aecb7dc09986267a34d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 13:36:09 +0200 Subject: [PATCH 071/122] stores: Fix reserved disk usage log spam --- extern/sector-storage/stores/local.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extern/sector-storage/stores/local.go b/extern/sector-storage/stores/local.go index cac160139..6f37f4f7a 100644 --- a/extern/sector-storage/stores/local.go +++ b/extern/sector-storage/stores/local.go @@ -114,7 +114,12 @@ func (p *path) stat(ls LocalStorage) (fsutil.FsStat, error) { used, err = ls.DiskUsage(p) } if err != nil { - log.Debugf("getting disk usage of '%s': %+v", p.sectorPath(id, fileType), err) + // we don't care about 'not exist' errors, as storage can be + // reserved before any files are written, so this error is not + // unexpected + if !os.IsNotExist(err) { + log.Warnf("getting disk usage of '%s': %+v", p.sectorPath(id, fileType), err) + } continue } From daaa725e3bf513fd3eac18ee2f48c4b7665acc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 16:24:29 +0200 Subject: [PATCH 072/122] sectors expired: Handle precomitted and unproven sectors correctly --- chain/actors/builtin/miner/actor.go.template | 6 ++++++ chain/actors/builtin/miner/miner.go | 6 ++++++ chain/actors/builtin/miner/state.go.template | 4 ++++ chain/actors/builtin/miner/v0.go | 4 ++++ chain/actors/builtin/miner/v2.go | 4 ++++ chain/actors/builtin/miner/v3.go | 4 ++++ chain/actors/builtin/miner/v4.go | 4 ++++ chain/actors/builtin/miner/v5.go | 4 ++++ cmd/lotus-miner/sectors.go | 19 +++++++++++++++++++ 9 files changed, 55 insertions(+) diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 7ffe9f146..2669a05a6 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -157,6 +157,12 @@ type Partition interface { // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. ActiveSectors() (bitfield.BitField, error) + + // Unproven sectors in this partition. This bitfield will be cleared on + // a successful window post (or at the end of the partition's next + // deadline). At that time, any still unproven sectors will be added to + // the faulty sector bitfield. + UnprovenSectors() (bitfield.BitField, error) } type SectorOnChainInfo struct { diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 4621fa48b..e61b95eef 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -216,6 +216,12 @@ type Partition interface { // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. ActiveSectors() (bitfield.BitField, error) + + // Unproven sectors in this partition. This bitfield will be cleared on + // a successful window post (or at the end of the partition's next + // deadline). At that time, any still unproven sectors will be added to + // the faulty sector bitfield. + UnprovenSectors() (bitfield.BitField, error) } type SectorOnChainInfo struct { diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index b63a73a2c..2ea6a905e 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -549,6 +549,10 @@ func (p *partition{{.v}}) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition{{.v}}) UnprovenSectors() (bitfield.BitField, error) { + return {{if (ge .v 2)}}p.Partition.Unproven{{else}}bitfield.New(){{end}}, nil +} + func fromV{{.v}}SectorOnChainInfo(v{{.v}} miner{{.v}}.SectorOnChainInfo) SectorOnChainInfo { {{if (ge .v 2)}} return SectorOnChainInfo{ diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 422afec8a..564bcbbc2 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -500,6 +500,10 @@ func (p *partition0) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition0) UnprovenSectors() (bitfield.BitField, error) { + return bitfield.New(), nil +} + func fromV0SectorOnChainInfo(v0 miner0.SectorOnChainInfo) SectorOnChainInfo { return (SectorOnChainInfo)(v0) diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 81b32abb7..fe0863111 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -530,6 +530,10 @@ func (p *partition2) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition2) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + func fromV2SectorOnChainInfo(v2 miner2.SectorOnChainInfo) SectorOnChainInfo { return SectorOnChainInfo{ diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 8ac77915a..b0d5429ea 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -531,6 +531,10 @@ func (p *partition3) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition3) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + func fromV3SectorOnChainInfo(v3 miner3.SectorOnChainInfo) SectorOnChainInfo { return SectorOnChainInfo{ diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index 5f442962f..7e5a9761a 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -531,6 +531,10 @@ func (p *partition4) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition4) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + func fromV4SectorOnChainInfo(v4 miner4.SectorOnChainInfo) SectorOnChainInfo { return SectorOnChainInfo{ diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go index a3e03a7d4..7f4aaf168 100644 --- a/chain/actors/builtin/miner/v5.go +++ b/chain/actors/builtin/miner/v5.go @@ -531,6 +531,10 @@ func (p *partition5) RecoveringSectors() (bitfield.BitField, error) { return p.Partition.Recoveries, nil } +func (p *partition5) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + func fromV5SectorOnChainInfo(v5 miner5.SectorOnChainInfo) SectorOnChainInfo { return SectorOnChainInfo{ diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 194b1e554..ea091e1f4 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1628,12 +1628,31 @@ var sectorsExpiredCmd = &cli.Command{ } toCheck, err = bitfield.SubtractBitField(toCheck, live) + if err != nil { + return err + } + + unproven, err := part.UnprovenSectors() + if err != nil { + return err + } + + toCheck, err = bitfield.SubtractBitField(toCheck, unproven) + return err }) }); err != nil { return err } + err = mas.ForEachPrecommittedSector(func(pci miner.SectorPreCommitOnChainInfo) error { + toCheck.Unset(uint64(pci.Info.SectorNumber)) + return nil + }) + if err != nil { + return err + } + if cctx.Bool("remove-expired") { color.Red("Removing sectors:\n") } From 91da70fb7dd54283a4fe92424940fd506a2d3046 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 31 Aug 2021 10:37:51 -0700 Subject: [PATCH 073/122] fix: comment spelling --- chain/stmgr/forks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 4fca13f96..af0ac0316 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -212,8 +212,8 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig } // Returns true executing tipsets between the specified heights would trigger an expensive -// migration. NOTE: migrations occuring _at_ the target height are not included, as they're executed -// _after_ the target height. +// migration. NOTE: migrations occurring _at_ the target height are not included, as they're +// executed _after_ the target height. func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) bool { for h := parent; h < height; h++ { if _, ok := sm.expensiveUpgrades[h]; ok { From b280e294093c61c9580c513d005ecec8b18d7848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 1 Sep 2021 11:31:49 +0200 Subject: [PATCH 074/122] sealing: Fix retry loop in SubmitCommitAggregate --- extern/storage-sealing/fsm.go | 2 +- extern/storage-sealing/fsm_test.go | 2 +- extern/storage-sealing/states_sealing.go | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 0a4dedbf2..c2662bea8 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -113,7 +113,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorCommitFailed{}, CommitFailed), ), SubmitCommitAggregate: planOne( - on(SectorCommitAggregateSent{}, CommitWait), + on(SectorCommitAggregateSent{}, CommitAggregateWait), on(SectorCommitFailed{}, CommitFailed), on(SectorRetrySubmitCommit{}, SubmitCommit), ), diff --git a/extern/storage-sealing/fsm_test.go b/extern/storage-sealing/fsm_test.go index 1d2df2784..c36369053 100644 --- a/extern/storage-sealing/fsm_test.go +++ b/extern/storage-sealing/fsm_test.go @@ -135,7 +135,7 @@ func TestHappyPathFinalizeEarly(t *testing.T) { require.Equal(m.t, m.state.State, SubmitCommitAggregate) m.planSingle(SectorCommitAggregateSent{}) - require.Equal(m.t, m.state.State, CommitWait) + require.Equal(m.t, m.state.State, CommitAggregateWait) m.planSingle(SectorProving{}) require.Equal(m.t, m.state.State, FinalizeSector) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 0df000eda..f21db3368 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -710,11 +710,8 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S Proof: sector.Proof, // todo: this correct?? Spt: sector.SectorType, }) - if err != nil { - return ctx.Send(SectorRetrySubmitCommit{}) - } - if res.Error != "" { + if err != nil || res.Error != "" { tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleSubmitCommit: api error, not proceeding: %+v", err) From 41db98d49f155ddda58a57f70bc2b123429f112f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 1 Sep 2021 12:09:44 +0200 Subject: [PATCH 075/122] sealing: Fix tests --- extern/storage-sealing/fsm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/storage-sealing/fsm_test.go b/extern/storage-sealing/fsm_test.go index c36369053..5ddef0d53 100644 --- a/extern/storage-sealing/fsm_test.go +++ b/extern/storage-sealing/fsm_test.go @@ -143,7 +143,7 @@ func TestHappyPathFinalizeEarly(t *testing.T) { m.planSingle(SectorFinalized{}) require.Equal(m.t, m.state.State, Proving) - expected := []SectorState{Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, CommitFinalize, SubmitCommit, SubmitCommitAggregate, CommitWait, FinalizeSector, Proving} + expected := []SectorState{Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, CommitFinalize, SubmitCommit, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector, Proving} for i, n := range notif { if n.before.State != expected[i] { t.Fatalf("expected before state: %s, got: %s", expected[i], n.before.State) From dbee23c89586907094f3bfec8367ae5752c3b1fd Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 2 Sep 2021 14:06:14 +0200 Subject: [PATCH 076/122] feat: update to go-fil-markets v1.11.0 --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 8ab0a404c..092b375da 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,10 @@ 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.8 + github.com/filecoin-project/go-data-transfer v1.9.0 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.8.1 + github.com/filecoin-project/go-fil-markets v1.11.0 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 diff --git a/go.sum b/go.sum index 1e93c4cd2..5dbc1299a 100644 --- a/go.sum +++ b/go.sum @@ -280,9 +280,8 @@ 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.6/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= -github.com/filecoin-project/go-data-transfer v1.7.8 h1:s4cF9nX9sEy7RgZd3NW92YN/hKyIy2fQl+7dVOAS8r8= -github.com/filecoin-project/go-data-transfer v1.7.8/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= +github.com/filecoin-project/go-data-transfer v1.9.0 h1:qW6tCIfGdK2cRf2AhEBZD1TzuhQDrbLpvllhLiN8EXk= +github.com/filecoin-project/go-data-transfer v1.9.0/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= 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= @@ -292,8 +291,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.8.1 h1:nNJB5EIp5c6yo/z51DloVaL7T24SslCoxSDOXwNQr9k= -github.com/filecoin-project/go-fil-markets v1.8.1/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= +github.com/filecoin-project/go-fil-markets v1.11.0 h1:zcdeKtg9AnzLMkIPBu2WH/UYt1w1wjMFEIDp2Kdv9Mc= +github.com/filecoin-project/go-fil-markets v1.11.0/go.mod h1:j5oN0YqZF4XV1gh3be+2me9j1inKsKRrfbvvxwf70+Q= 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= From 95b128b7bc26dbf311fe914119f5fc12962af99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 18:07:23 +0200 Subject: [PATCH 077/122] chain: Cleanup consensus logic --- chain/beacon/drand/drand.go | 5 + chain/consensus/filcns/compute_state.go | 302 +++++++ chain/consensus/filcns/filecoin.go | 847 ++++++++++++++++++ .../mining.go => consensus/filcns/mine.go} | 75 +- chain/{stmgr => consensus/filcns}/upgrades.go | 113 ++- chain/{store => consensus/filcns}/weight.go | 23 +- chain/consensus/iface.go | 19 + chain/consensus/utils.go | 86 ++ chain/exchange/client.go | 12 +- chain/gen/gen.go | 27 +- chain/gen/genesis/genesis.go | 32 +- chain/gen/genesis/miners.go | 2 + chain/messagepool/messagepool.go | 3 +- chain/messagepool/messagepool_test.go | 23 +- chain/messagepool/repub_test.go | 3 +- chain/messagepool/selection.go | 6 +- chain/messagepool/selection_test.go | 3 +- chain/stmgr/call.go | 12 +- chain/stmgr/execute.go | 246 +---- chain/stmgr/forks.go | 41 +- chain/stmgr/forks_test.go | 19 +- chain/stmgr/stmgr.go | 34 +- chain/stmgr/utils.go | 87 +- chain/store/index_test.go | 3 +- chain/store/messages.go | 4 +- chain/store/store.go | 15 +- chain/store/store_test.go | 13 +- chain/sub/incoming.go | 242 +---- chain/sync.go | 689 ++------------ chain/sync_test.go | 11 +- chain/types/blockheader.go | 3 +- chain/vm/invoker.go | 82 +- chain/vm/vm.go | 3 +- cli/state.go | 3 +- cmd/lotus-bench/import.go | 8 +- cmd/lotus-shed/balances.go | 16 +- cmd/lotus-shed/export.go | 2 +- cmd/lotus-shed/genesis-verify.go | 3 +- cmd/lotus-shed/miner-types.go | 3 +- cmd/lotus-shed/msg.go | 1 - cmd/lotus-shed/pruning.go | 3 +- .../simulation/blockbuilder/blockbuilder.go | 2 + cmd/lotus-sim/simulation/node.go | 11 +- cmd/lotus-sim/simulation/simulation.go | 5 +- cmd/lotus/daemon.go | 8 +- conformance/driver.go | 29 +- go.sum | 2 + itests/kit/ensemble_opts_nv.go | 8 +- lib/async/error.go | 51 ++ node/builder_chain.go | 8 +- node/hello/hello.go | 5 +- node/impl/full/chain.go | 16 +- node/impl/full/state.go | 15 +- node/impl/full/sync.go | 10 +- node/modules/chain.go | 33 +- node/modules/services.go | 13 +- node/modules/stmgr.go | 4 +- node/options.go | 6 +- 58 files changed, 1858 insertions(+), 1492 deletions(-) create mode 100644 chain/consensus/filcns/compute_state.go create mode 100644 chain/consensus/filcns/filecoin.go rename chain/{gen/mining.go => consensus/filcns/mine.go} (57%) rename chain/{stmgr => consensus/filcns}/upgrades.go (84%) rename chain/{store => consensus/filcns}/weight.go (87%) create mode 100644 chain/consensus/iface.go create mode 100644 chain/consensus/utils.go create mode 100644 lib/async/error.go diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index e7f673d7f..7dfd02233 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -177,6 +177,11 @@ func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntr // TODO handle genesis better return nil } + + if curr.Round != prev.Round+1 { + return xerrors.Errorf("invalid beacon entry: cur (%d) != prev (%d) + 1", curr.Round, prev.Round) + } + if be := db.getCachedValue(curr.Round); be != nil { if !bytes.Equal(curr.Data, be.Data) { return xerrors.New("invalid beacon value, does not match cached good value") diff --git a/chain/consensus/filcns/compute_state.go b/chain/consensus/filcns/compute_state.go new file mode 100644 index 000000000..8f3ea474a --- /dev/null +++ b/chain/consensus/filcns/compute_state.go @@ -0,0 +1,302 @@ +package filcns + +import ( + "context" + "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" + + "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" + + 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/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/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/metrics" +) + +func init() { + // todo this is a hack, should fix the cli deps, and drop the map in stmgr + stmgr.MethodsMap = NewActorRegistry().Methods +} + +func NewActorRegistry() *vm.ActorRegistry { + inv := vm.NewActorRegistry() + + // TODO: define all these properties on the actors themselves, in specs-actors. + + inv.Register(vm.ActorsVersionPredicate(actors.Version0), exported0.BuiltinActors()...) + inv.Register(vm.ActorsVersionPredicate(actors.Version2), exported2.BuiltinActors()...) + inv.Register(vm.ActorsVersionPredicate(actors.Version3), exported3.BuiltinActors()...) + inv.Register(vm.ActorsVersionPredicate(actors.Version4), exported4.BuiltinActors()...) + inv.Register(vm.ActorsVersionPredicate(actors.Version5), exported5.BuiltinActors()...) + + return inv +} + +type tipSetExecutor struct{} + +func TipSetExecutor() *tipSetExecutor { + return &tipSetExecutor{} +} + +func (t *tipSetExecutor) NewActorRegistry() *vm.ActorRegistry { + return NewActorRegistry() +} + +type FilecoinBlockMessages struct { + store.BlockMessages + + WinCount int64 +} + +func (t *tipSetExecutor) ApplyBlocks(ctx context.Context, sm *stmgr.StateManager, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []FilecoinBlockMessages, epoch abi.ChainEpoch, r vm.Rand, em stmgr.ExecMonitor, 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.ChainStore().StateBlockstore(), + Actors: NewActorRegistry(), + Syscalls: sm.Syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: baseFee, + LookbackState: stmgr.LookbackStateGetterForTipset(sm, ts), + } + + return sm.VMConstructor()(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.ChainStore().ActorStore(ctx)) + for i, receipt := range receipts { + if err := rectarr.Set(uint64(i), receipt); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + } + rectroot, err := rectarr.Root() + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + + 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 (t *tipSetExecutor) ExecuteTipSet(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet, em stmgr.ExecMonitor) (stateroot cid.Cid, rectsroot cid.Cid, err error) { + ctx, span := trace.StartSpan(ctx, "computeTipSetState") + defer span.End() + + blks := ts.Blocks() + + for i := 0; i < len(blks); i++ { + for j := i + 1; j < len(blks); j++ { + if blks[i].Miner == blks[j].Miner { + return cid.Undef, cid.Undef, + xerrors.Errorf("duplicate miner in a tipset (%s %s)", + blks[i].Miner, blks[j].Miner) + } + } + } + + var parentEpoch abi.ChainEpoch + pstate := blks[0].ParentStateRoot + if blks[0].Height > 0 { + parent, err := sm.ChainStore().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.ChainStore(), ts.Cids()) + + blkmsgs, err := sm.ChainStore().BlockMsgsForTipset(ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) + } + fbmsgs := make([]FilecoinBlockMessages, len(blkmsgs)) + for i := range fbmsgs { + fbmsgs[i].BlockMessages = blkmsgs[i] + fbmsgs[i].WinCount = ts.Blocks()[i].ElectionProof.WinCount + } + baseFee := blks[0].ParentBaseFee + + return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, baseFee, ts) +} + +var _ stmgr.Executor = &tipSetExecutor{} diff --git a/chain/consensus/filcns/filecoin.go b/chain/consensus/filcns/filecoin.go new file mode 100644 index 000000000..4b10673fb --- /dev/null +++ b/chain/consensus/filcns/filecoin.go @@ -0,0 +1,847 @@ +package filcns + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + logging "github.com/ipfs/go-log/v2" + pubsub "github.com/libp2p/go-libp2p-pubsub" + 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/crypto" + "github.com/filecoin-project/go-state-types/network" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/lib/async" + "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/metrics" +) + +var log = logging.Logger("fil-consensus") + +type FilecoinEC struct { + // The interface for accessing and putting tipsets into local storage + store *store.ChainStore + + // handle to the random beacon for verification + beacon beacon.Schedule + + // the state manager handles making state queries + sm *stmgr.StateManager + + verifier ffiwrapper.Verifier + + genesis *types.TipSet +} + +// Blocks that are more than MaxHeightDrift epochs above +// the theoretical max height based on systime are quickly rejected +const MaxHeightDrift = 5 + +func NewFilecoinExpectedConsensus(sm *stmgr.StateManager, beacon beacon.Schedule, verifier ffiwrapper.Verifier, genesis chain.Genesis) consensus.Consensus { + if build.InsecurePoStValidation { + log.Warn("*********************************************************************************************") + log.Warn(" [INSECURE-POST-VALIDATION] Insecure test validation is enabled. If you see this outside of a test, it is a severe bug! ") + log.Warn("*********************************************************************************************") + } + + return &FilecoinEC{ + store: sm.ChainStore(), + beacon: beacon, + sm: sm, + verifier: verifier, + genesis: genesis, + } +} + +func (filec *FilecoinEC) ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) { + if err := blockSanityChecks(b.Header); err != nil { + return xerrors.Errorf("incoming header failed basic sanity checks: %w", err) + } + + h := b.Header + + baseTs, err := filec.store.LoadTipSet(types.NewTipSetKey(h.Parents...)) + if err != nil { + return xerrors.Errorf("load parent tipset failed (%s): %w", h.Parents, err) + } + + winPoStNv := filec.sm.GetNtwkVersion(ctx, baseTs.Height()) + + lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, baseTs, h.Height) + if err != nil { + return xerrors.Errorf("failed to get lookback tipset for block: %w", err) + } + + prevBeacon, err := filec.store.GetLatestBeaconEntry(baseTs) + if err != nil { + return xerrors.Errorf("failed to get latest beacon entry: %w", err) + } + + // fast checks first + if h.Height <= baseTs.Height() { + return xerrors.Errorf("block height not greater than parent height: %d != %d", h.Height, baseTs.Height()) + } + + nulls := h.Height - (baseTs.Height() + 1) + if tgtTs := baseTs.MinTimestamp() + build.BlockDelaySecs*uint64(nulls+1); h.Timestamp != tgtTs { + return xerrors.Errorf("block has wrong timestamp: %d != %d", h.Timestamp, tgtTs) + } + + now := uint64(build.Clock.Now().Unix()) + if h.Timestamp > now+build.AllowableClockDriftSecs { + return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, consensus.ErrTemporal) + } + if h.Timestamp > now { + log.Warn("Got block from the future, but within threshold", h.Timestamp, build.Clock.Now().Unix()) + } + + msgsCheck := async.Err(func() error { + if b.Cid() == build.WhitelistedBlock { + return nil + } + + if err := filec.checkBlockMessages(ctx, b, baseTs); err != nil { + return xerrors.Errorf("block had invalid messages: %w", err) + } + return nil + }) + + minerCheck := async.Err(func() error { + if err := filec.minerIsValid(ctx, h.Miner, baseTs); err != nil { + return xerrors.Errorf("minerIsValid failed: %w", err) + } + return nil + }) + + baseFeeCheck := async.Err(func() error { + baseFee, err := filec.store.ComputeBaseFee(ctx, baseTs) + if err != nil { + return xerrors.Errorf("computing base fee: %w", err) + } + if types.BigCmp(baseFee, b.Header.ParentBaseFee) != 0 { + return xerrors.Errorf("base fee doesn't match: %s (header) != %s (computed)", + b.Header.ParentBaseFee, baseFee) + } + return nil + }) + pweight, err := filec.store.Weight(ctx, baseTs) + if err != nil { + return xerrors.Errorf("getting parent weight: %w", err) + } + + if types.BigCmp(pweight, b.Header.ParentWeight) != 0 { + return xerrors.Errorf("parrent weight different: %s (header) != %s (computed)", + b.Header.ParentWeight, pweight) + } + + stateRootCheck := async.Err(func() error { + stateroot, precp, err := filec.sm.TipSetState(ctx, baseTs) + if err != nil { + return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err) + } + + if stateroot != h.ParentStateRoot { + msgs, err := filec.store.MessagesForTipset(baseTs) + if err != nil { + log.Error("failed to load messages for tipset during tipset state mismatch error: ", err) + } else { + log.Warn("Messages for tipset with mismatching state:") + for i, m := range msgs { + mm := m.VMMessage() + log.Warnf("Message[%d]: from=%s to=%s method=%d params=%x", i, mm.From, mm.To, mm.Method, mm.Params) + } + } + + return xerrors.Errorf("parent state root did not match computed state (%s != %s)", stateroot, h.ParentStateRoot) + } + + if precp != h.ParentMessageReceipts { + return xerrors.Errorf("parent receipts root did not match computed value (%s != %s)", precp, h.ParentMessageReceipts) + } + + return nil + }) + + // Stuff that needs worker address + waddr, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, h.Miner) + if err != nil { + return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err) + } + + winnerCheck := async.Err(func() error { + if h.ElectionProof.WinCount < 1 { + return xerrors.Errorf("block is not claiming to be a winner") + } + + eligible, err := stmgr.MinerEligibleToMine(ctx, filec.sm, h.Miner, baseTs, lbts) + if err != nil { + return xerrors.Errorf("determining if miner has min power failed: %w", err) + } + + if !eligible { + return xerrors.New("block's miner is ineligible to mine") + } + + rBeacon := *prevBeacon + if len(h.BeaconEntries) != 0 { + rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1] + } + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) + } + + vrfBase, err := store.DrawRandomness(rBeacon.Data, crypto.DomainSeparationTag_ElectionProofProduction, h.Height, buf.Bytes()) + if err != nil { + return xerrors.Errorf("could not draw randomness: %w", err) + } + + if err := VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil { + return xerrors.Errorf("validating block election proof failed: %w", err) + } + + slashed, err := stmgr.GetMinerSlashed(ctx, filec.sm, baseTs, h.Miner) + if err != nil { + return xerrors.Errorf("failed to check if block miner was slashed: %w", err) + } + + if slashed { + return xerrors.Errorf("received block was from slashed or invalid miner") + } + + mpow, tpow, _, err := stmgr.GetPowerRaw(ctx, filec.sm, lbst, h.Miner) + if err != nil { + return xerrors.Errorf("failed getting power: %w", err) + } + + j := h.ElectionProof.ComputeWinCount(mpow.QualityAdjPower, tpow.QualityAdjPower) + if h.ElectionProof.WinCount != j { + return xerrors.Errorf("miner claims wrong number of wins: miner: %d, computed: %d", h.ElectionProof.WinCount, j) + } + + return nil + }) + + blockSigCheck := async.Err(func() error { + if err := sigs.CheckBlockSignature(ctx, h, waddr); err != nil { + return xerrors.Errorf("check block signature failed: %w", err) + } + return nil + }) + + beaconValuesCheck := async.Err(func() error { + if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { + return nil + } + + if err := beacon.ValidateBlockValues(filec.beacon, h, baseTs.Height(), *prevBeacon); err != nil { + return xerrors.Errorf("failed to validate blocks random beacon values: %w", err) + } + return nil + }) + + tktsCheck := async.Err(func() error { + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) + } + + if h.Height > build.UpgradeSmokeHeight { + buf.Write(baseTs.MinTicket().VRFProof) + } + + beaconBase := *prevBeacon + if len(h.BeaconEntries) != 0 { + beaconBase = h.BeaconEntries[len(h.BeaconEntries)-1] + } + + vrfBase, err := store.DrawRandomness(beaconBase.Data, crypto.DomainSeparationTag_TicketProduction, h.Height-build.TicketRandomnessLookback, buf.Bytes()) + if err != nil { + return xerrors.Errorf("failed to compute vrf base for ticket: %w", err) + } + + err = VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof) + if err != nil { + return xerrors.Errorf("validating block tickets failed: %w", err) + } + return nil + }) + + wproofCheck := async.Err(func() error { + if err := filec.VerifyWinningPoStProof(ctx, winPoStNv, h, *prevBeacon, lbst, waddr); err != nil { + return xerrors.Errorf("invalid election post: %w", err) + } + return nil + }) + + await := []async.ErrorFuture{ + minerCheck, + tktsCheck, + blockSigCheck, + beaconValuesCheck, + wproofCheck, + winnerCheck, + msgsCheck, + baseFeeCheck, + stateRootCheck, + } + + var merr error + for _, fut := range await { + if err := fut.AwaitContext(ctx); err != nil { + merr = multierror.Append(merr, err) + } + } + if merr != nil { + mulErr := merr.(*multierror.Error) + mulErr.ErrorFormat = func(es []error) string { + if len(es) == 1 { + return fmt.Sprintf("1 error occurred:\n\t* %+v\n\n", es[0]) + } + + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %+v", err) + } + + return fmt.Sprintf( + "%d errors occurred:\n\t%s\n\n", + len(es), strings.Join(points, "\n\t")) + } + return mulErr + } + + return nil +} + +func blockSanityChecks(h *types.BlockHeader) error { + if h.ElectionProof == nil { + return xerrors.Errorf("block cannot have nil election proof") + } + + if h.Ticket == nil { + return xerrors.Errorf("block cannot have nil ticket") + } + + if h.BlockSig == nil { + return xerrors.Errorf("block had nil signature") + } + + if h.BLSAggregate == nil { + return xerrors.Errorf("block had nil bls aggregate signature") + } + + if h.Miner.Protocol() != address.ID { + return xerrors.Errorf("block had non-ID miner address") + } + + return nil +} + +func (filec *FilecoinEC) VerifyWinningPoStProof(ctx context.Context, nv network.Version, h *types.BlockHeader, prevBeacon types.BeaconEntry, lbst cid.Cid, waddr address.Address) error { + if build.InsecurePoStValidation { + if len(h.WinPoStProof) == 0 { + return xerrors.Errorf("[INSECURE-POST-VALIDATION] No winning post proof given") + } + + if string(h.WinPoStProof[0].ProofBytes) == "valid proof" { + return nil + } + return xerrors.Errorf("[INSECURE-POST-VALIDATION] winning post was invalid") + } + + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address: %w", err) + } + + rbase := prevBeacon + if len(h.BeaconEntries) > 0 { + rbase = h.BeaconEntries[len(h.BeaconEntries)-1] + } + + rand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, h.Height, buf.Bytes()) + if err != nil { + return xerrors.Errorf("failed to get randomness for verifying winning post proof: %w", err) + } + + mid, err := address.IDFromAddress(h.Miner) + if err != nil { + return xerrors.Errorf("failed to get ID from miner address %s: %w", h.Miner, err) + } + + sectors, err := stmgr.GetSectorsForWinningPoSt(ctx, nv, filec.verifier, filec.sm, lbst, h.Miner, rand) + if err != nil { + return xerrors.Errorf("getting winning post sector set: %w", err) + } + + ok, err := ffiwrapper.ProofVerifier.VerifyWinningPoSt(ctx, proof2.WinningPoStVerifyInfo{ + Randomness: rand, + Proofs: h.WinPoStProof, + ChallengedSectors: sectors, + Prover: abi.ActorID(mid), + }) + if err != nil { + return xerrors.Errorf("failed to verify election post: %w", err) + } + + if !ok { + log.Errorf("invalid winning post (block: %s, %x; %v)", h.Cid(), rand, sectors) + return xerrors.Errorf("winning post was invalid") + } + + return nil +} + +// TODO: We should extract this somewhere else and make the message pool and miner use the same logic +func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error { + { + var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type + var pubks [][]byte + + for _, m := range b.BlsMessages { + sigCids = append(sigCids, m.Cid()) + + pubk, err := filec.sm.GetBlsPublicKey(ctx, m.From, baseTs) + if err != nil { + return xerrors.Errorf("failed to load bls public to validate block: %w", err) + } + + pubks = append(pubks, pubk) + } + + if err := consensus.VerifyBlsAggregate(ctx, b.Header.BLSAggregate, sigCids, pubks); err != nil { + return xerrors.Errorf("bls aggregate signature was invalid: %w", err) + } + } + + nonces := make(map[address.Address]uint64) + + stateroot, _, err := filec.sm.TipSetState(ctx, baseTs) + if err != nil { + return err + } + + st, err := state.LoadStateTree(filec.store.ActorStore(ctx), stateroot) + if err != nil { + return xerrors.Errorf("failed to load base state tree: %w", err) + } + + nv := filec.sm.GetNtwkVersion(ctx, b.Header.Height) + pl := vm.PricelistByEpoch(baseTs.Height()) + var sumGasLimit int64 + checkMsg := func(msg types.ChainMsg) error { + m := msg.VMMessage() + + // Phase 1: syntactic validation, as defined in the spec + minGas := pl.OnChainMessage(msg.ChainLength()) + if err := m.ValidForBlockInclusion(minGas.Total(), nv); err != nil { + return err + } + + // ValidForBlockInclusion checks if any single message does not exceed BlockGasLimit + // So below is overflow safe + sumGasLimit += m.GasLimit + if sumGasLimit > build.BlockGasLimit { + return xerrors.Errorf("block gas limit exceeded") + } + + // Phase 2: (Partial) semantic validation: + // the sender exists and is an account actor, and the nonces make sense + var sender address.Address + if filec.sm.GetNtwkVersion(ctx, b.Header.Height) >= network.Version13 { + sender, err = st.LookupID(m.From) + if err != nil { + return err + } + } else { + sender = m.From + } + + if _, ok := nonces[sender]; !ok { + // `GetActor` does not validate that this is an account actor. + act, err := st.GetActor(sender) + if err != nil { + return xerrors.Errorf("failed to get actor: %w", err) + } + + if !builtin.IsAccountActor(act.Code) { + return xerrors.New("Sender must be an account actor") + } + nonces[sender] = act.Nonce + } + + if nonces[sender] != m.Nonce { + return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[sender], m.Nonce) + } + nonces[sender]++ + + return nil + } + + // Validate message arrays in a temporary blockstore. + tmpbs := bstore.NewMemory() + tmpstore := blockadt.WrapStore(ctx, cbor.NewCborStore(tmpbs)) + + bmArr := blockadt.MakeEmptyArray(tmpstore) + for i, m := range b.BlsMessages { + if err := checkMsg(m); err != nil { + return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err) + } + + c, err := store.PutMessage(tmpbs, m) + if err != nil { + return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) + } + + k := cbg.CborCid(c) + if err := bmArr.Set(uint64(i), &k); err != nil { + return xerrors.Errorf("failed to put bls message at index %d: %w", i, err) + } + } + + smArr := blockadt.MakeEmptyArray(tmpstore) + for i, m := range b.SecpkMessages { + if err := checkMsg(m); err != nil { + return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) + } + + // `From` being an account actor is only validated inside the `vm.ResolveToKeyAddr` call + // in `StateManager.ResolveToKeyAddress` here (and not in `checkMsg`). + kaddr, err := filec.sm.ResolveToKeyAddress(ctx, m.Message.From, baseTs) + if err != nil { + return xerrors.Errorf("failed to resolve key addr: %w", err) + } + + if err := sigs.Verify(&m.Signature, kaddr, m.Message.Cid().Bytes()); err != nil { + return xerrors.Errorf("secpk message %s has invalid signature: %w", m.Cid(), err) + } + + c, err := store.PutMessage(tmpbs, m) + if err != nil { + return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) + } + k := cbg.CborCid(c) + if err := smArr.Set(uint64(i), &k); err != nil { + return xerrors.Errorf("failed to put secpk message at index %d: %w", i, err) + } + } + + bmroot, err := bmArr.Root() + if err != nil { + return err + } + + smroot, err := smArr.Root() + if err != nil { + return err + } + + mrcid, err := tmpstore.Put(ctx, &types.MsgMeta{ + BlsMessages: bmroot, + SecpkMessages: smroot, + }) + if err != nil { + return err + } + + if b.Header.Messages != mrcid { + return fmt.Errorf("messages didnt match message root in header") + } + + // Finally, flush. + return vm.Copy(ctx, tmpbs, filec.store.ChainBlockstore(), mrcid) +} + +func (filec *FilecoinEC) IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool { + if filec.genesis == nil { + return false + } + + now := uint64(build.Clock.Now().Unix()) + return epoch > (abi.ChainEpoch((now-filec.genesis.MinTimestamp())/build.BlockDelaySecs) + MaxHeightDrift) +} + +func (filec *FilecoinEC) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error { + act, err := filec.sm.LoadActor(ctx, power.Address, baseTs) + if err != nil { + return xerrors.Errorf("failed to load power actor: %w", err) + } + + powState, err := power.Load(filec.store.ActorStore(ctx), act) + if err != nil { + return xerrors.Errorf("failed to load power actor state: %w", err) + } + + _, exist, err := powState.MinerPower(maddr) + if err != nil { + return xerrors.Errorf("failed to look up miner's claim: %w", err) + } + + if !exist { + return xerrors.New("miner isn't valid") + } + + return nil +} + +func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error { + return VerifyVRF(ctx, worker, rand, evrf) +} + +func VerifyVRF(ctx context.Context, worker address.Address, vrfBase, vrfproof []byte) error { + _, span := trace.StartSpan(ctx, "VerifyVRF") + defer span.End() + + sig := &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: vrfproof, + } + + if err := sigs.Verify(sig, worker, vrfBase); err != nil { + return xerrors.Errorf("vrf was invalid: %w", err) + } + + return nil +} + +var ErrSoftFailure = errors.New("soft validation failure") +var ErrInsufficientPower = errors.New("incoming block's miner does not have minimum power") + +func (filec *FilecoinEC) ValidateBlockPubsub(ctx context.Context, self bool, msg *pubsub.Message) (pubsub.ValidationResult, string) { + if self { + return filec.validateLocalBlock(ctx, msg) + } + + // track validation time + begin := build.Clock.Now() + defer func() { + log.Debugf("block validation time: %s", build.Clock.Since(begin)) + }() + + stats.Record(ctx, metrics.BlockReceived.M(1)) + + recordFailureFlagPeer := func(what string) { + // bv.Validate will flag the peer in that case + panic(what) + } + + blk, what, err := filec.decodeAndCheckBlock(msg) + if err != nil { + log.Error("got invalid block over pubsub: ", err) + recordFailureFlagPeer(what) + return pubsub.ValidationReject, what + } + + // validate the block meta: the Message CID in the header must match the included messages + err = filec.validateMsgMeta(ctx, blk) + if err != nil { + log.Warnf("error validating message metadata: %s", err) + recordFailureFlagPeer("invalid_block_meta") + return pubsub.ValidationReject, "invalid_block_meta" + } + + reject, err := filec.validateBlockHeader(ctx, blk.Header) + if err != nil { + if reject == "" { + log.Warn("ignoring block msg: ", err) + return pubsub.ValidationIgnore, reject + } + recordFailureFlagPeer(reject) + return pubsub.ValidationReject, reject + } + + // all good, accept the block + msg.ValidatorData = blk + stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) + return pubsub.ValidationAccept, "" +} + +func (filec *FilecoinEC) validateLocalBlock(ctx context.Context, msg *pubsub.Message) (pubsub.ValidationResult, string) { + stats.Record(ctx, metrics.BlockPublished.M(1)) + + if size := msg.Size(); size > 1<<20-1<<15 { + log.Errorf("ignoring oversize block (%dB)", size) + return pubsub.ValidationIgnore, "oversize_block" + } + + blk, what, err := filec.decodeAndCheckBlock(msg) + if err != nil { + log.Errorf("got invalid local block: %s", err) + return pubsub.ValidationIgnore, what + } + + msg.ValidatorData = blk + stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) + return pubsub.ValidationAccept, "" +} + +func (filec *FilecoinEC) decodeAndCheckBlock(msg *pubsub.Message) (*types.BlockMsg, string, error) { + blk, err := types.DecodeBlockMsg(msg.GetData()) + if err != nil { + return nil, "invalid", xerrors.Errorf("error decoding block: %w", err) + } + + if count := len(blk.BlsMessages) + len(blk.SecpkMessages); count > build.BlockMessageLimit { + return nil, "too_many_messages", fmt.Errorf("block contains too many messages (%d)", count) + } + + // make sure we have a signature + if blk.Header.BlockSig == nil { + return nil, "missing_signature", fmt.Errorf("block without a signature") + } + + return blk, "", nil +} + +func (filec *FilecoinEC) validateMsgMeta(ctx context.Context, msg *types.BlockMsg) error { + // TODO there has to be a simpler way to do this without the blockstore dance + // block headers use adt0 + store := blockadt.WrapStore(ctx, cbor.NewCborStore(bstore.NewMemory())) + bmArr := blockadt.MakeEmptyArray(store) + smArr := blockadt.MakeEmptyArray(store) + + for i, m := range msg.BlsMessages { + c := cbg.CborCid(m) + if err := bmArr.Set(uint64(i), &c); err != nil { + return err + } + } + + for i, m := range msg.SecpkMessages { + c := cbg.CborCid(m) + if err := smArr.Set(uint64(i), &c); err != nil { + return err + } + } + + bmroot, err := bmArr.Root() + if err != nil { + return err + } + + smroot, err := smArr.Root() + if err != nil { + return err + } + + mrcid, err := store.Put(store.Context(), &types.MsgMeta{ + BlsMessages: bmroot, + SecpkMessages: smroot, + }) + + if err != nil { + return err + } + + if msg.Header.Messages != mrcid { + return fmt.Errorf("messages didn't match root cid in header") + } + + return nil +} + +func (filec *FilecoinEC) validateBlockHeader(ctx context.Context, b *types.BlockHeader) (rejectReason string, err error) { + + // we want to ensure that it is a block from a known miner; we reject blocks from unknown miners + // to prevent spam attacks. + // the logic works as follows: we lookup the miner in the chain for its key. + // if we can find it then it's a known miner and we can validate the signature. + // if we can't find it, we check whether we are (near) synced in the chain. + // if we are not synced we cannot validate the block and we must ignore it. + // if we are synced and the miner is unknown, then the block is rejcected. + key, err := filec.checkPowerAndGetWorkerKey(ctx, b) + if err != nil { + if err != ErrSoftFailure && filec.isChainNearSynced() { + log.Warnf("received block from unknown miner or miner that doesn't meet min power over pubsub; rejecting message") + return "unknown_miner", err + } + + log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain: %s", b.Cid()) + return "", err // ignore + } + + if b.ElectionProof.WinCount < 1 { + log.Errorf("block is not claiming to be winning") + return "not_winning", xerrors.Errorf("block not winning") + } + + err = sigs.CheckBlockSignature(ctx, b, key) + if err != nil { + log.Errorf("block signature verification failed: %s", err) + return "signature_verification_failed", err + } + + return "", nil +} + +func (filec *FilecoinEC) checkPowerAndGetWorkerKey(ctx context.Context, bh *types.BlockHeader) (address.Address, error) { + // we check that the miner met the minimum power at the lookback tipset + + baseTs := filec.store.GetHeaviestTipSet() + lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, baseTs, bh.Height) + if err != nil { + log.Warnf("failed to load lookback tipset for incoming block: %s", err) + return address.Undef, ErrSoftFailure + } + + key, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, bh.Miner) + if err != nil { + log.Warnf("failed to resolve worker key for miner %s: %s", bh.Miner, err) + return address.Undef, ErrSoftFailure + } + + // NOTE: we check to see if the miner was eligible in the lookback + // tipset - 1 for historical reasons. DO NOT use the lookback state + // returned by GetLookbackTipSetForRound. + + eligible, err := stmgr.MinerEligibleToMine(ctx, filec.sm, bh.Miner, baseTs, lbts) + if err != nil { + log.Warnf("failed to determine if incoming block's miner has minimum power: %s", err) + return address.Undef, ErrSoftFailure + } + + if !eligible { + log.Warnf("incoming block's miner is ineligible") + return address.Undef, ErrInsufficientPower + } + + return key, nil +} + +func (filec *FilecoinEC) isChainNearSynced() bool { + ts := filec.store.GetHeaviestTipSet() + timestamp := ts.MinTimestamp() + timestampTime := time.Unix(int64(timestamp), 0) + return build.Clock.Since(timestampTime) < 6*time.Hour +} + +var _ consensus.Consensus = &FilecoinEC{} diff --git a/chain/gen/mining.go b/chain/consensus/filcns/mine.go similarity index 57% rename from chain/gen/mining.go rename to chain/consensus/filcns/mine.go index 1400c12c5..4ee90df40 100644 --- a/chain/gen/mining.go +++ b/chain/consensus/filcns/mine.go @@ -1,38 +1,35 @@ -package gen +package filcns import ( "context" + "github.com/filecoin-project/lotus/chain/consensus" - "github.com/filecoin-project/go-state-types/crypto" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - cid "github.com/ipfs/go-cid" - cbg "github.com/whyrusleeping/cbor-gen" + "github.com/ipfs/go-cid" "golang.org/x/xerrors" - ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) -func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w api.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) { - - pts, err := sm.ChainStore().LoadTipSet(bt.Parents) +func (filec *FilecoinEC) CreateBlock(ctx context.Context, w api.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) { + pts, err := filec.sm.ChainStore().LoadTipSet(bt.Parents) if err != nil { return nil, xerrors.Errorf("failed to load parent tipset: %w", err) } - st, recpts, err := sm.TipSetState(ctx, pts) + st, recpts, err := filec.sm.TipSetState(ctx, pts) if err != nil { return nil, xerrors.Errorf("failed to load tipset state: %w", err) } - _, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, sm, pts, bt.Epoch) + _, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, pts, bt.Epoch) if err != nil { return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) } - worker, err := stmgr.GetMinerWorkerRaw(ctx, sm, lbst, bt.Miner) + worker, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, bt.Miner) if err != nil { return nil, xerrors.Errorf("failed to get miner worker: %w", err) } @@ -61,14 +58,14 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w api.Wallet, blsSigs = append(blsSigs, msg.Signature) blsMessages = append(blsMessages, &msg.Message) - c, err := sm.ChainStore().PutMessage(&msg.Message) + c, err := filec.sm.ChainStore().PutMessage(&msg.Message) if err != nil { return nil, err } blsMsgCids = append(blsMsgCids, c) } else { - c, err := sm.ChainStore().PutMessage(msg) + c, err := filec.sm.ChainStore().PutMessage(msg) if err != nil { return nil, err } @@ -79,12 +76,12 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w api.Wallet, } } - store := sm.ChainStore().ActorStore(ctx) - blsmsgroot, err := toArray(store, blsMsgCids) + store := filec.sm.ChainStore().ActorStore(ctx) + blsmsgroot, err := consensus.ToMessagesArray(store, blsMsgCids) if err != nil { return nil, xerrors.Errorf("building bls amt: %w", err) } - secpkmsgroot, err := toArray(store, secpkMsgCids) + secpkmsgroot, err := consensus.ToMessagesArray(store, secpkMsgCids) if err != nil { return nil, xerrors.Errorf("building secpk amt: %w", err) } @@ -98,19 +95,19 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w api.Wallet, } next.Messages = mmcid - aggSig, err := aggregateSignatures(blsSigs) + aggSig, err := consensus.AggregateSignatures(blsSigs) if err != nil { return nil, err } next.BLSAggregate = aggSig - pweight, err := sm.ChainStore().Weight(ctx, pts) + pweight, err := filec.sm.ChainStore().Weight(ctx, pts) if err != nil { return nil, err } next.ParentWeight = pweight - baseFee, err := sm.ChainStore().ComputeBaseFee(ctx, pts) + baseFee, err := filec.sm.ChainStore().ComputeBaseFee(ctx, pts) if err != nil { return nil, xerrors.Errorf("computing base fee: %w", err) } @@ -138,41 +135,3 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w api.Wallet, return fullBlock, nil } - -func aggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) { - sigsS := make([]ffi.Signature, len(sigs)) - for i := 0; i < len(sigs); i++ { - copy(sigsS[i][:], sigs[i].Data[:ffi.SignatureBytes]) - } - - aggSig := ffi.Aggregate(sigsS) - if aggSig == nil { - if len(sigs) > 0 { - return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs)) - } - - zeroSig := ffi.CreateZeroSignature() - - // Note: for blst this condition should not happen - nil should not - // be returned - return &crypto.Signature{ - Type: crypto.SigTypeBLS, - Data: zeroSig[:], - }, nil - } - return &crypto.Signature{ - Type: crypto.SigTypeBLS, - Data: aggSig[:], - }, nil -} - -func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { - arr := blockadt.MakeEmptyArray(store) - for i, c := range cids { - oc := cbg.CborCid(c) - if err := arr.Set(uint64(i), &oc); err != nil { - return cid.Undef, err - } - } - return arr.Root() -} diff --git a/chain/stmgr/upgrades.go b/chain/consensus/filcns/upgrades.go similarity index 84% rename from chain/stmgr/upgrades.go rename to chain/consensus/filcns/upgrades.go index d2ccbad39..c12bb2090 100644 --- a/chain/stmgr/upgrades.go +++ b/chain/consensus/filcns/upgrades.go @@ -1,4 +1,4 @@ -package stmgr +package filcns import ( "context" @@ -13,6 +13,7 @@ import ( "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/go-state-types/rt" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" @@ -31,15 +32,16 @@ import ( "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/stmgr" "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 +func DefaultUpgradeSchedule() stmgr.UpgradeSchedule { + var us stmgr.UpgradeSchedule - updates := []Upgrade{{ + updates := []stmgr.Upgrade{{ Height: build.UpgradeBreezeHeight, Network: network.Version1, Migration: UpgradeFaucetBurnRecovery, @@ -88,7 +90,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradeTrustHeight, Network: network.Version10, Migration: UpgradeActorsV3, - PreMigrations: []PreMigration{{ + PreMigrations: []stmgr.PreMigration{{ PreMigration: PreUpgradeActorsV3, StartWithin: 120, DontStartWithin: 60, @@ -108,7 +110,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradeTurboHeight, Network: network.Version12, Migration: UpgradeActorsV4, - PreMigrations: []PreMigration{{ + PreMigrations: []stmgr.PreMigration{{ PreMigration: PreUpgradeActorsV4, StartWithin: 120, DontStartWithin: 60, @@ -124,7 +126,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradeHyperdriveHeight, Network: network.Version13, Migration: UpgradeActorsV5, - PreMigrations: []PreMigration{{ + PreMigrations: []stmgr.PreMigration{{ PreMigration: PreUpgradeActorsV5, StartWithin: 120, DontStartWithin: 60, @@ -147,7 +149,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { return us } -func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ MigrationCache, em ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { +func UpgradeFaucetBurnRecovery(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, em stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { // Some initial parameters FundsForMiners := types.FromFil(1_000_000) LookbackEpoch := abi.ChainEpoch(32000) @@ -249,7 +251,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio // Execute transfers from previous step for _, t := range transfers { - if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + if err := stmgr.DoTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) } } @@ -352,7 +354,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio } for _, t := range transfersBack { - if err := doTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + if err := stmgr.DoTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) } } @@ -362,7 +364,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio 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 { + if err := stmgr.DoTransfer(tree, builtin0.BurntFundsActorAddr, builtin.ReserveAddress, burntAct.Balance, transferCb); err != nil { return cid.Undef, xerrors.Errorf("failed to unburn funds: %w", err) } @@ -378,7 +380,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio } difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) - if err := doTransfer(tree, builtin.ReserveAddress, reimbAddr, difference, transferCb); err != nil { + if err := stmgr.DoTransfer(tree, builtin.ReserveAddress, reimbAddr, difference, transferCb); err != nil { return cid.Undef, xerrors.Errorf("failed to top up reimbursement account: %w", err) } @@ -400,14 +402,14 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio if em != nil { // record the transfer in execution traces - fakeMsg := makeFakeMsg(builtin.SystemActorAddr, builtin.SystemActorAddr, big.Zero(), uint64(epoch)) + fakeMsg := stmgr.MakeFakeMsg(builtin.SystemActorAddr, builtin.SystemActorAddr, big.Zero(), uint64(epoch)) if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ - MessageReceipt: *makeFakeRct(), + MessageReceipt: *stmgr.MakeFakeRct(), ActorErr: nil, ExecutionTrace: types.ExecutionTrace{ Msg: fakeMsg, - MsgRct: makeFakeRct(), + MsgRct: stmgr.MakeFakeRct(), Error: "", Duration: 0, GasCharges: nil, @@ -423,8 +425,8 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, _ Migratio 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) +func UpgradeIgnition(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + store := sm.ChainStore().ActorStore(ctx) if build.UpgradeLiftoffHeight <= epoch { return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") @@ -440,7 +442,7 @@ func UpgradeIgnition(ctx context.Context, sm *StateManager, _ MigrationCache, cb return cid.Undef, xerrors.Errorf("getting state tree: %w", err) } - err = setNetworkName(ctx, store, tree, "ignition") + err = stmgr.SetNetworkName(ctx, store, tree, "ignition") if err != nil { return cid.Undef, xerrors.Errorf("setting network name: %w", err) } @@ -478,7 +480,7 @@ func UpgradeIgnition(ctx context.Context, sm *StateManager, _ MigrationCache, cb 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 { +func splitGenesisMultisig0(ctx context.Context, em stmgr.ExecMonitor, addr address.Address, store adt0.Store, tree *state.StateTree, portions uint64, epoch abi.ChainEpoch, ts *types.TipSet) error { if portions < 1 { return xerrors.Errorf("cannot split into 0 portions") } @@ -553,7 +555,7 @@ func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Add } for i < portions { - keyAddr, err := makeKeyAddr(addr, i) + keyAddr, err := stmgr.MakeKeyAddr(addr, i) if err != nil { return xerrors.Errorf("creating key address: %w", err) } @@ -568,7 +570,7 @@ func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Add return xerrors.Errorf("setting new msig actor state: %w", err) } - if err := doTransfer(tree, addr, idAddr, newIbal, transferCb); err != nil { + if err := stmgr.DoTransfer(tree, addr, idAddr, newIbal, transferCb); err != nil { return xerrors.Errorf("transferring split msig balance: %w", err) } @@ -578,14 +580,14 @@ func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Add if em != nil { // record the transfer in execution traces - fakeMsg := makeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) + fakeMsg := stmgr.MakeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ - MessageReceipt: *makeFakeRct(), + MessageReceipt: *stmgr.MakeFakeRct(), ActorErr: nil, ExecutionTrace: types.ExecutionTrace{ Msg: fakeMsg, - MsgRct: makeFakeRct(), + MsgRct: stmgr.MakeFakeRct(), Error: "", Duration: 0, GasCharges: nil, @@ -602,8 +604,8 @@ func splitGenesisMultisig0(ctx context.Context, em ExecMonitor, addr address.Add } // 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() +func resetGenesisMsigs0(ctx context.Context, sm *stmgr.StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { + gb, err := sm.ChainStore().GetGenesis() if err != nil { return xerrors.Errorf("getting genesis block: %w", err) } @@ -613,7 +615,7 @@ func resetGenesisMsigs0(ctx context.Context, sm *StateManager, store adt0.Store, return xerrors.Errorf("getting genesis tipset: %w", err) } - cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + cst := cbor.NewCborStore(sm.ChainStore().StateBlockstore()) genesisTree, err := state.LoadStateTree(cst, gts.ParentState()) if err != nil { return xerrors.Errorf("loading state tree: %w", err) @@ -683,9 +685,9 @@ func resetMultisigVesting0(ctx context.Context, store adt0.Store, tree *state.St return nil } -func UpgradeRefuel(ctx context.Context, sm *StateManager, _ MigrationCache, cb ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { +func UpgradeRefuel(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { - store := sm.cs.ActorStore(ctx) + store := sm.ChainStore().ActorStore(ctx) tree, err := sm.StateTree(root) if err != nil { return cid.Undef, xerrors.Errorf("getting state tree: %w", err) @@ -709,8 +711,8 @@ func UpgradeRefuel(ctx context.Context, sm *StateManager, _ MigrationCache, cb E 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()) +func UpgradeActorsV2(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) store := store.ActorStore(ctx, buf) info, err := store.Put(ctx, new(types.StateInfo0)) @@ -755,13 +757,13 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, _ MigrationCache, cb 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) { +func UpgradeLiftoff(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { tree, err := sm.StateTree(root) if err != nil { return cid.Undef, xerrors.Errorf("getting state tree: %w", err) } - err = setNetworkName(ctx, sm.cs.ActorStore(ctx), tree, "mainnet") + err = stmgr.SetNetworkName(ctx, sm.ChainStore().ActorStore(ctx), tree, "mainnet") if err != nil { return cid.Undef, xerrors.Errorf("setting network name: %w", err) } @@ -769,12 +771,12 @@ func UpgradeLiftoff(ctx context.Context, sm *StateManager, _ MigrationCache, cb 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) { +func UpgradeCalico(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { if build.BuildType != build.BuildMainnet { return root, nil } - store := sm.cs.ActorStore(ctx) + store := sm.ChainStore().ActorStore(ctx) var stateRoot types.StateRoot if err := store.Get(ctx, root, &stateRoot); err != nil { return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) @@ -815,7 +817,7 @@ func UpgradeCalico(ctx context.Context, sm *StateManager, _ MigrationCache, cb E 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) { +func UpgradeActorsV3(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { // Use all the CPUs except 3. workerCount := runtime.NumCPU() - 3 if workerCount <= 0 { @@ -839,7 +841,7 @@ func UpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCache } if build.BuildType == build.BuildMainnet { - err := terminateActor(ctx, tree, build.ZeroAddress, cb, epoch, ts) + err := stmgr.TerminateActor(ctx, tree, build.ZeroAddress, cb, epoch, ts) if err != nil && !xerrors.Is(err, types.ErrActorNotFound) { return cid.Undef, xerrors.Errorf("deleting zero bls actor: %w", err) } @@ -853,7 +855,7 @@ func UpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCache return newRoot, nil } -func PreUpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { +func PreUpgradeActorsV3(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { // Use half the CPUs for pre-migration, but leave at least 3. workerCount := runtime.NumCPU() if workerCount <= 4 { @@ -867,11 +869,11 @@ func PreUpgradeActorsV3(ctx context.Context, sm *StateManager, cache MigrationCa } func upgradeActorsV3Common( - ctx context.Context, sm *StateManager, cache MigrationCache, + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, config nv10.Config, ) (cid.Cid, error) { - buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) store := store.ActorStore(ctx, buf) // Load the state root. @@ -917,7 +919,7 @@ func upgradeActorsV3Common( 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) { +func UpgradeActorsV4(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { // Use all the CPUs except 3. workerCount := runtime.NumCPU() - 3 if workerCount <= 0 { @@ -939,7 +941,7 @@ func UpgradeActorsV4(ctx context.Context, sm *StateManager, cache MigrationCache return newRoot, nil } -func PreUpgradeActorsV4(ctx context.Context, sm *StateManager, cache MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { +func PreUpgradeActorsV4(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { // Use half the CPUs for pre-migration, but leave at least 3. workerCount := runtime.NumCPU() if workerCount <= 4 { @@ -953,11 +955,11 @@ func PreUpgradeActorsV4(ctx context.Context, sm *StateManager, cache MigrationCa } func upgradeActorsV4Common( - ctx context.Context, sm *StateManager, cache MigrationCache, + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, config nv12.Config, ) (cid.Cid, error) { - buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) store := store.ActorStore(ctx, buf) // Load the state root. @@ -1003,7 +1005,7 @@ func upgradeActorsV4Common( 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) { +func UpgradeActorsV5(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { // Use all the CPUs except 3. workerCount := runtime.NumCPU() - 3 if workerCount <= 0 { @@ -1025,7 +1027,7 @@ func UpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache return newRoot, nil } -func PreUpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { +func PreUpgradeActorsV5(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { // Use half the CPUs for pre-migration, but leave at least 3. workerCount := runtime.NumCPU() if workerCount <= 4 { @@ -1039,11 +1041,11 @@ func PreUpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCa } func upgradeActorsV5Common( - ctx context.Context, sm *StateManager, cache MigrationCache, + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, config nv13.Config, ) (cid.Cid, error) { - buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) store := store.ActorStore(ctx, buf) // Load the state root. @@ -1088,3 +1090,18 @@ func upgradeActorsV5Common( return newRoot, nil } + +type migrationLogger struct{} + +func (ml migrationLogger) Log(level rt.LogLevel, msg string, args ...interface{}) { + switch level { + case rt.DEBUG: + log.Debugf(msg, args...) + case rt.INFO: + log.Infof(msg, args...) + case rt.WARN: + log.Warnf(msg, args...) + case rt.ERROR: + log.Errorf(msg, args...) + } +} diff --git a/chain/store/weight.go b/chain/consensus/filcns/weight.go similarity index 87% rename from chain/store/weight.go rename to chain/consensus/filcns/weight.go index 42546d5e3..f5966aa19 100644 --- a/chain/store/weight.go +++ b/chain/consensus/filcns/weight.go @@ -1,22 +1,25 @@ -package store +package filcns import ( "context" "math/big" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - - big2 "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/types" cbor "github.com/ipfs/go-ipld-cbor" "golang.org/x/xerrors" + + big2 "github.com/filecoin-project/go-state-types/big" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" ) var zero = types.NewInt(0) -func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { +func Weight(ctx context.Context, stateBs bstore.Blockstore, ts *types.TipSet) (types.BigInt, error) { if ts == nil { return types.NewInt(0), nil } @@ -28,7 +31,7 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn tpow := big2.Zero() { - cst := cbor.NewCborStore(cs.StateBlockstore()) + cst := cbor.NewCborStore(stateBs) state, err := state.LoadStateTree(cst, ts.ParentState()) if err != nil { return types.NewInt(0), xerrors.Errorf("load state tree: %w", err) @@ -39,7 +42,7 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn return types.NewInt(0), xerrors.Errorf("get power actor: %w", err) } - powState, err := power.Load(cs.ActorStore(ctx), act) + powState, err := power.Load(store.ActorStore(ctx, stateBs), act) if err != nil { return types.NewInt(0), xerrors.Errorf("failed to load power actor state: %w", err) } diff --git a/chain/consensus/iface.go b/chain/consensus/iface.go new file mode 100644 index 000000000..76b6d52f1 --- /dev/null +++ b/chain/consensus/iface.go @@ -0,0 +1,19 @@ +package consensus + +import ( + "context" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +type Consensus interface { + ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) + ValidateBlockPubsub(ctx context.Context, self bool, msg *pubsub.Message) (pubsub.ValidationResult, string) + IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool + + CreateBlock(ctx context.Context, w api.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) +} diff --git a/chain/consensus/utils.go b/chain/consensus/utils.go new file mode 100644 index 000000000..fcd4a1f35 --- /dev/null +++ b/chain/consensus/utils.go @@ -0,0 +1,86 @@ +package consensus + +import ( + "context" + "errors" + + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-state-types/crypto" +) + +var log = logging.Logger("consensus") + +var ErrTemporal = errors.New("temporal error") + +func VerifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks [][]byte) error { + _, span := trace.StartSpan(ctx, "syncer.VerifyBlsAggregate") + defer span.End() + span.AddAttributes( + trace.Int64Attribute("msgCount", int64(len(msgs))), + ) + + msgsS := make([]ffi.Message, len(msgs)) + pubksS := make([]ffi.PublicKey, len(msgs)) + for i := 0; i < len(msgs); i++ { + msgsS[i] = msgs[i].Bytes() + copy(pubksS[i][:], pubks[i][:ffi.PublicKeyBytes]) + } + + sigS := new(ffi.Signature) + copy(sigS[:], sig.Data[:ffi.SignatureBytes]) + + if len(msgs) == 0 { + return nil + } + + valid := ffi.HashVerify(sigS, msgsS, pubksS) + if !valid { + return xerrors.New("bls aggregate signature failed to verify") + } + return nil +} + +func AggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) { + sigsS := make([]ffi.Signature, len(sigs)) + for i := 0; i < len(sigs); i++ { + copy(sigsS[i][:], sigs[i].Data[:ffi.SignatureBytes]) + } + + aggSig := ffi.Aggregate(sigsS) + if aggSig == nil { + if len(sigs) > 0 { + return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs)) + } + + zeroSig := ffi.CreateZeroSignature() + + // Note: for blst this condition should not happen - nil should not + // be returned + return &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: zeroSig[:], + }, nil + } + return &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: aggSig[:], + }, nil +} + +func ToMessagesArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { + arr := blockadt.MakeEmptyArray(store) + for i, c := range cids { + oc := cbg.CborCid(c) + if err := arr.Set(uint64(i), &oc); err != nil { + return cid.Undef, err + } + } + return arr.Root() +} diff --git a/chain/exchange/client.go b/chain/exchange/client.go index fa9ed2974..b1e97292f 100644 --- a/chain/exchange/client.go +++ b/chain/exchange/client.go @@ -151,12 +151,20 @@ func (c *client) doRequest( // errors. Peer penalization should happen here then, before returning, so // we can apply the correct penalties depending on the cause of the error. // FIXME: Add the `peer` as argument once we implement penalties. -func (c *client) processResponse(req *Request, res *Response, tipsets []*types.TipSet) (*validatedResponse, error) { - err := res.statusToError() +func (c *client) processResponse(req *Request, res *Response, tipsets []*types.TipSet) (r *validatedResponse, err error) { + err = res.statusToError() if err != nil { return nil, xerrors.Errorf("status error: %s", err) } + defer func() { + if rerr := recover(); rerr != nil { + log.Errorf("process response error: %s", rerr) + err = xerrors.Errorf("process response error: %s", rerr) + return + } + }() + options := parseOptions(req.Options) if options.noOptionsSet() { // Safety check: this shouldn't have been sent, and even if it did diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 6b30f99ee..9de1c00b5 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -23,7 +23,6 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/ipfs/go-merkledag" "github.com/ipld/go-car" - "go.opencensus.io/trace" "golang.org/x/xerrors" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" @@ -33,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus/filcns" genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -43,7 +43,6 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/genesis" "github.com/filecoin-project/lotus/journal" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/node/repo" ) @@ -233,7 +232,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS return nil, xerrors.Errorf("make genesis block failed: %w", err) } - cs := store.NewChainStore(bs, bs, ds, j) + cs := store.NewChainStore(bs, bs, ds, filcns.Weight, j) genfb := &types.FullBlock{Header: genb.Genesis} gents := store.NewFullTipSet([]*types.FullBlock{genfb}) @@ -247,7 +246,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) + sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), sys, us) if err != nil { return nil, xerrors.Errorf("initing stmgr: %w", err) } @@ -289,7 +288,7 @@ func NewGenerator() (*ChainGen, error) { } func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { - return NewGeneratorWithSectorsAndUpgradeSchedule(numSectors, stmgr.DefaultUpgradeSchedule()) + return NewGeneratorWithSectorsAndUpgradeSchedule(numSectors, filcns.DefaultUpgradeSchedule()) } func NewGeneratorWithUpgradeSchedule(us stmgr.UpgradeSchedule) (*ChainGen, error) { @@ -487,7 +486,7 @@ func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticke ts = parents.MinTimestamp() + uint64(height-parents.Height())*build.BlockDelaySecs } - fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, &api.BlockTemplate{ + fblk, err := filcns.NewFilecoinExpectedConsensus(cg.sm, nil, nil, nil).CreateBlock(context.TODO(), cg.w, &api.BlockTemplate{ Miner: m, Parents: parents.Key(), Ticket: vrfticket, @@ -667,22 +666,6 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, type SignFunc func(context.Context, address.Address, []byte) (*crypto.Signature, error) -func VerifyVRF(ctx context.Context, worker address.Address, vrfBase, vrfproof []byte) error { - _, span := trace.StartSpan(ctx, "VerifyVRF") - defer span.End() - - sig := &crypto.Signature{ - Type: crypto.SigTypeBLS, - Data: vrfproof, - } - - if err := sigs.Verify(sig, worker, vrfBase); err != nil { - return xerrors.Errorf("vrf was invalid: %w", err) - } - - return nil -} - func ComputeVRF(ctx context.Context, sign SignFunc, worker address.Address, sigInput []byte) ([]byte, error) { sig, err := sign(ctx, worker, sigInput) if err != nil { diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index b737d319d..29f03e2af 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" + "github.com/filecoin-project/lotus/chain/consensus/filcns" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -222,7 +223,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return nil, nil, xerrors.Errorf("set verified registry actor: %w", err) } - bact, err := makeAccountActor(ctx, cst, av, builtin.BurntFundsActorAddr, big.Zero()) + bact, err := MakeAccountActor(ctx, cst, av, builtin.BurntFundsActorAddr, big.Zero()) if err != nil { return nil, nil, xerrors.Errorf("setup burnt funds actor state: %w", err) } @@ -235,7 +236,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge switch info.Type { case genesis.TAccount: - if err := createAccountActor(ctx, cst, state, info, keyIDs, av); err != nil { + if err := CreateAccountActor(ctx, cst, state, info, keyIDs, av); err != nil { return nil, nil, xerrors.Errorf("failed to create account actor: %w", err) } @@ -247,7 +248,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge } idStart++ - if err := createMultisigAccount(ctx, cst, state, ida, info, keyIDs, av); err != nil { + if err := CreateMultisigAccount(ctx, cst, state, ida, info, keyIDs, av); err != nil { return nil, nil, err } default: @@ -268,7 +269,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return nil, nil, fmt.Errorf("rootkey account has already been declared, cannot be assigned 80: %s", ainfo.Owner) } - vact, err := makeAccountActor(ctx, cst, av, ainfo.Owner, template.VerifregRootKey.Balance) + vact, err := MakeAccountActor(ctx, cst, av, ainfo.Owner, template.VerifregRootKey.Balance) if err != nil { return nil, nil, xerrors.Errorf("setup verifreg rootkey account state: %w", err) } @@ -276,7 +277,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return nil, nil, xerrors.Errorf("set verifreg rootkey account actor: %w", err) } case genesis.TMultisig: - if err = createMultisigAccount(ctx, cst, state, builtin.RootVerifierAddress, template.VerifregRootKey, keyIDs, av); err != nil { + if err = CreateMultisigAccount(ctx, cst, state, builtin.RootVerifierAddress, template.VerifregRootKey, keyIDs, av); err != nil { return nil, nil, xerrors.Errorf("failed to set up verified registry signer: %w", err) } default: @@ -305,7 +306,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return nil, nil, err } - verifierAct, err := makeAccountActor(ctx, cst, av, verifierAd, big.Zero()) + verifierAct, err := MakeAccountActor(ctx, cst, av, verifierAd, big.Zero()) if err != nil { return nil, nil, xerrors.Errorf("setup first verifier state: %w", err) } @@ -348,13 +349,13 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge } keyIDs[ainfo.Owner] = builtin.ReserveAddress - err = createAccountActor(ctx, cst, state, template.RemainderAccount, keyIDs, av) + err = CreateAccountActor(ctx, cst, state, template.RemainderAccount, keyIDs, av) if err != nil { return nil, nil, xerrors.Errorf("creating remainder acct: %w", err) } case genesis.TMultisig: - if err = createMultisigAccount(ctx, cst, state, builtin.ReserveAddress, template.RemainderAccount, keyIDs, av); err != nil { + if err = CreateMultisigAccount(ctx, cst, state, builtin.ReserveAddress, template.RemainderAccount, keyIDs, av); err != nil { return nil, nil, xerrors.Errorf("failed to set up remainder: %w", err) } default: @@ -364,7 +365,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return state, keyIDs, nil } -func makeAccountActor(ctx context.Context, cst cbor.IpldStore, av actors.Version, addr address.Address, bal types.BigInt) (*types.Actor, error) { +func MakeAccountActor(ctx context.Context, cst cbor.IpldStore, av actors.Version, addr address.Address, bal types.BigInt) (*types.Actor, error) { ast, err := account.MakeState(adt.WrapStore(ctx, cst), av, addr) if err != nil { return nil, err @@ -389,13 +390,13 @@ func makeAccountActor(ctx context.Context, cst cbor.IpldStore, av actors.Version return act, nil } -func createAccountActor(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, info genesis.Actor, keyIDs map[address.Address]address.Address, av actors.Version) error { +func CreateAccountActor(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, info genesis.Actor, keyIDs map[address.Address]address.Address, av actors.Version) error { var ainfo genesis.AccountMeta if err := json.Unmarshal(info.Meta, &ainfo); err != nil { return xerrors.Errorf("unmarshaling account meta: %w", err) } - aa, err := makeAccountActor(ctx, cst, av, ainfo.Owner, info.Balance) + aa, err := MakeAccountActor(ctx, cst, av, ainfo.Owner, info.Balance) if err != nil { return err } @@ -412,9 +413,9 @@ func createAccountActor(ctx context.Context, cst cbor.IpldStore, state *state.St return nil } -func createMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, ida address.Address, info genesis.Actor, keyIDs map[address.Address]address.Address, av actors.Version) error { +func CreateMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, ida address.Address, info genesis.Actor, keyIDs map[address.Address]address.Address, av actors.Version) error { if info.Type != genesis.TMultisig { - return fmt.Errorf("can only call createMultisigAccount with multisig Actor info") + return fmt.Errorf("can only call CreateMultisigAccount with multisig Actor info") } var ainfo genesis.MultisigMeta if err := json.Unmarshal(info.Meta, &ainfo); err != nil { @@ -436,7 +437,7 @@ func createMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state continue } - aa, err := makeAccountActor(ctx, cst, av, e, big.Zero()) + aa, err := MakeAccountActor(ctx, cst, av, e, big.Zero()) if err != nil { return err } @@ -483,6 +484,7 @@ func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, sys vm.Sysca Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), + Actors: filcns.NewActorRegistry(), Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: nil, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { @@ -562,7 +564,7 @@ func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blocksto } // temp chainstore - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), j) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil, j) // Verify PreSealed Data stateroot, err = VerifyPreSealedData(ctx, cs, sys, stateroot, template, keyIDs, template.NetworkVersion) diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index 1d95275df..f79d41824 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -37,6 +37,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -87,6 +88,7 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sys vm.Syscal Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), + Actors: filcns.NewActorRegistry(), Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: csc, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index ee2518ed9..f2ab998d4 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -354,7 +354,7 @@ func (ms *msgSet) toSlice() []*types.SignedMessage { return set } -func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { +func New(api Provider, ds dtypes.MetadataDS, us stmgr.UpgradeSchedule, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { cache, _ := lru.New2Q(build.BlsSignatureCacheSize) verifcache, _ := lru.New2Q(build.VerifSigCacheSize) @@ -366,7 +366,6 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journ if j == nil { j = journal.NilJournal() } - us := stmgr.DefaultUpgradeSchedule() mp := &MessagePool{ ds: ds, diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index e57212e7c..4a2bbfe94 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -11,17 +11,18 @@ import ( "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/assert" builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/messagepool/gasguess" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/mock" "github.com/filecoin-project/lotus/chain/wallet" _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" - "github.com/stretchr/testify/assert" ) func init() { @@ -232,7 +233,7 @@ func TestMessagePool(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -276,7 +277,7 @@ func TestCheckMessageBig(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) assert.NoError(t, err) to := mock.Address(1001) @@ -339,7 +340,7 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -388,7 +389,7 @@ func TestRevertMessages(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -451,7 +452,7 @@ func TestPruningSimple(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -495,7 +496,7 @@ func TestLoadLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -538,7 +539,7 @@ func TestLoadLocal(t *testing.T) { t.Fatal(err) } - mp, err = New(tma, ds, "mptest", nil) + mp, err = New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -567,7 +568,7 @@ func TestClearAll(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -621,7 +622,7 @@ func TestClearNonLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } @@ -682,7 +683,7 @@ func TestUpdates(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 580231f7a..fa27d68ed 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -9,6 +9,7 @@ import ( builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/messagepool/gasguess" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" @@ -24,7 +25,7 @@ func TestRepubMessages(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 611ab8e5f..acff7c4cf 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -20,8 +20,6 @@ import ( var bigBlockGasLimit = big.NewInt(build.BlockGasLimit) -var MaxBlockMessages = 16000 - const MaxBlocks = 15 type msgChain struct { @@ -58,8 +56,8 @@ func (mp *MessagePool) SelectMessages(ctx context.Context, ts *types.TipSet, tq return nil, err } - if len(msgs) > MaxBlockMessages { - msgs = msgs[:MaxBlockMessages] + if len(msgs) > build.BlockMessageLimit { + msgs = msgs[:build.BlockMessageLimit] } return msgs, nil diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index 463473229..0f8fd8ee6 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -21,6 +21,7 @@ import ( builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/messagepool/gasguess" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/mock" @@ -60,7 +61,7 @@ func makeTestMessage(w *wallet.LocalWallet, from, to address.Address, nonce uint func makeTestMpool() (*MessagePool, *testMpoolAPI) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "test", nil) + mp, err := New(tma, ds, filcns.DefaultUpgradeSchedule(), "test", nil) if err != nil { panic(err) } diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 42f9732fb..6a0186715 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -66,7 +66,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. bstate := ts.ParentState() // Run the (not expensive) migration. - bstate, err := sm.handleStateForks(ctx, bstate, pheight, nil, ts) + bstate, err := sm.HandleStateForks(ctx, bstate, pheight, nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) } @@ -76,7 +76,8 @@ 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.syscalls, + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: types.NewInt(0), @@ -179,7 +180,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri } // Technically, the tipset we're passing in here should be ts+1, but that may not exist. - state, err = sm.handleStateForks(ctx, state, ts.Height(), nil, ts) + state, err = sm.HandleStateForks(ctx, state, ts.Height(), nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) } @@ -199,7 +200,8 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri Epoch: ts.Height() + 1, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.syscalls, + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, @@ -272,7 +274,7 @@ func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.C // message to find finder.mcid = mcid - _, _, err := sm.computeTipSetState(ctx, ts, &finder) + _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder) if err != nil && !xerrors.Is(err, errHaltExecution) { return nil, nil, xerrors.Errorf("unexpected error during execution: %w", err) } diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index a8675e347..901d71068 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -3,217 +3,14 @@ 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" - - "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.syscalls, - 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 - 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() @@ -263,7 +60,7 @@ func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st c return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil } - st, rec, err = sm.computeTipSetState(ctx, ts, sm.tsExecMonitor) + st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor) if err != nil { return cid.Undef, cid.Undef, err } @@ -272,7 +69,7 @@ func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st c } func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { - st, _, err := sm.computeTipSetState(ctx, ts, em) + st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em) return st, err } @@ -284,42 +81,3 @@ func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (c } 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/forks.go b/chain/stmgr/forks.go index af0ac0316..454f781c4 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -15,8 +15,6 @@ import ( "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/go-state-types/rt" - "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -98,21 +96,6 @@ type Upgrade struct { type UpgradeSchedule []Upgrade -type migrationLogger struct{} - -func (ml migrationLogger) Log(level rt.LogLevel, msg string, args ...interface{}) { - switch level { - case rt.DEBUG: - log.Debugf(msg, args...) - case rt.INFO: - log.Infof(msg, args...) - case rt.WARN: - log.Warnf(msg, args...) - case rt.ERROR: - log.Errorf(msg, args...) - } -} - func (us UpgradeSchedule) Validate() error { // Make sure each upgrade is valid. for _, u := range us { @@ -181,7 +164,7 @@ func (us UpgradeSchedule) GetNtwkVersion(e abi.ChainEpoch) (network.Version, err return network.Version0, xerrors.Errorf("Epoch %d has no defined network version", e) } -func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, height abi.ChainEpoch, cb ExecMonitor, ts *types.TipSet) (cid.Cid, error) { +func (sm *StateManager) HandleStateForks(ctx context.Context, root cid.Cid, height abi.ChainEpoch, cb ExecMonitor, ts *types.TipSet) (cid.Cid, error) { retCid := root var err error u := sm.stateMigrations[height] @@ -331,7 +314,7 @@ func (sm *StateManager) preMigrationWorker(ctx context.Context) { } } -func doTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmount, cb func(trace types.ExecutionTrace)) error { +func DoTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmount, cb func(trace types.ExecutionTrace)) error { fromAct, err := tree.GetActor(from) if err != nil { return xerrors.Errorf("failed to get 'from' actor for transfer: %w", err) @@ -361,8 +344,8 @@ func doTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmo // record the transfer in execution traces cb(types.ExecutionTrace{ - Msg: makeFakeMsg(from, to, amt, 0), - MsgRct: makeFakeRct(), + Msg: MakeFakeMsg(from, to, amt, 0), + MsgRct: MakeFakeRct(), Error: "", Duration: 0, GasCharges: nil, @@ -373,7 +356,7 @@ func doTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmo return nil } -func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Address, em ExecMonitor, epoch abi.ChainEpoch, ts *types.TipSet) error { +func TerminateActor(ctx context.Context, tree *state.StateTree, addr address.Address, em ExecMonitor, epoch abi.ChainEpoch, ts *types.TipSet) error { a, err := tree.GetActor(addr) if xerrors.Is(err, types.ErrActorNotFound) { return types.ErrActorNotFound @@ -382,7 +365,7 @@ func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Add } var trace types.ExecutionTrace - if err := doTransfer(tree, addr, builtin.BurntFundsActorAddr, a.Balance, func(t types.ExecutionTrace) { + if err := DoTransfer(tree, addr, builtin.BurntFundsActorAddr, a.Balance, func(t types.ExecutionTrace) { trace = t }); err != nil { return xerrors.Errorf("transferring terminated actor's balance: %w", err) @@ -391,10 +374,10 @@ func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Add if em != nil { // record the transfer in execution traces - fakeMsg := makeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) + fakeMsg := MakeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ - MessageReceipt: *makeFakeRct(), + MessageReceipt: *MakeFakeRct(), ActorErr: nil, ExecutionTrace: trace, Duration: 0, @@ -433,7 +416,7 @@ func terminateActor(ctx context.Context, tree *state.StateTree, addr address.Add return tree.SetActor(init_.Address, ia) } -func setNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, name string) error { +func SetNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, name string) error { ia, err := tree.GetActor(init_.Address) if err != nil { return xerrors.Errorf("getting init actor: %w", err) @@ -460,7 +443,7 @@ func setNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, return nil } -func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, error) { +func MakeKeyAddr(splitAddr address.Address, count uint64) (address.Address, error) { var b bytes.Buffer if err := splitAddr.MarshalCBOR(&b); err != nil { return address.Undef, xerrors.Errorf("marshalling split address: %w", err) @@ -482,7 +465,7 @@ func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, erro return addr, nil } -func makeFakeMsg(from address.Address, to address.Address, amt abi.TokenAmount, nonce uint64) *types.Message { +func MakeFakeMsg(from address.Address, to address.Address, amt abi.TokenAmount, nonce uint64) *types.Message { return &types.Message{ From: from, To: to, @@ -491,7 +474,7 @@ func makeFakeMsg(from address.Address, to address.Address, amt abi.TokenAmount, } } -func makeFakeRct() *types.MessageReceipt { +func MakeFakeRct() *types.MessageReceipt { return &types.MessageReceipt{ ExitCode: 0, Return: nil, diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 6c507a0c4..133f2fe1e 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -28,6 +28,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/aerrors" _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" . "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" @@ -120,8 +121,8 @@ func TestForkHeightTriggers(t *testing.T) { t.Fatal(err) } - sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ + sm, err := NewStateManager( + cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, @@ -162,7 +163,7 @@ func TestForkHeightTriggers(t *testing.T) { t.Fatal(err) } - inv := vm.NewActorRegistry() + inv := filcns.NewActorRegistry() inv.Register(nil, testActor{}) sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) { @@ -263,8 +264,8 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { } var migrationCount int - sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ + sm, err := NewStateManager( + cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Expensive: true, Height: testForkHeight, @@ -277,7 +278,7 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { t.Fatal(err) } - inv := vm.NewActorRegistry() + inv := filcns.NewActorRegistry() inv.Register(nil, testActor{}) sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) { @@ -398,8 +399,8 @@ func TestForkPreMigration(t *testing.T) { counter := make(chan struct{}, 10) - sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ + sm, err := NewStateManager( + cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, @@ -496,7 +497,7 @@ func TestForkPreMigration(t *testing.T) { require.NoError(t, sm.Stop(context.Background())) }() - inv := vm.NewActorRegistry() + inv := filcns.NewActorRegistry() inv.Register(nil, testActor{}) sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) { diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 1748c341e..4a0f89141 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -2,7 +2,6 @@ package stmgr import ( "context" - "fmt" "sync" "github.com/ipfs/go-cid" @@ -52,6 +51,11 @@ type migration struct { cache *nv10.MemMigrationCache } +type Executor interface { + NewActorRegistry() *vm.ActorRegistry + ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor) (stateroot cid.Cid, rectsroot cid.Cid, err error) +} + type StateManager struct { cs *store.ChainStore @@ -75,7 +79,7 @@ type StateManager struct { stlk sync.Mutex genesisMsigLk sync.Mutex newVM func(context.Context, *vm.VMOpts) (*vm.VM, error) - syscalls vm.SyscallBuilder + Syscalls vm.SyscallBuilder preIgnitionVesting []msig0.State postIgnitionVesting []msig0.State postCalicoVesting []msig0.State @@ -83,6 +87,7 @@ type StateManager struct { genesisPledge abi.TokenAmount genesisMarketFunds abi.TokenAmount + tsExec Executor tsExecMonitor ExecMonitor } @@ -92,15 +97,7 @@ type treeCache struct { tree *state.StateTree } -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, sys vm.SyscallBuilder, us UpgradeSchedule) (*StateManager, error) { +func NewStateManager(cs *store.ChainStore, exec Executor, 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 @@ -142,8 +139,9 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, sys vm.SyscallBuil stateMigrations: stateMigrations, expensiveUpgrades: expensiveUpgrades, newVM: vm.NewVM, - syscalls: sys, + Syscalls: sys, cs: cs, + tsExec: exec, stCache: make(map[string][]cid.Cid), tCache: treeCache{ root: cid.Undef, @@ -153,8 +151,8 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, sys vm.SyscallBuil }, nil } -func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, sys vm.SyscallBuilder, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { - sm, err := NewStateManagerWithUpgradeSchedule(cs, sys, us) +func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { + sm, err := NewStateManager(cs, exec, sys, us) if err != nil { return nil, err } @@ -344,6 +342,12 @@ func (sm *StateManager) SetVMConstructor(nvm func(context.Context, *vm.VMOpts) ( sm.newVM = nvm } +func (sm *StateManager) VMConstructor() func(context.Context, *vm.VMOpts) (*vm.VM, error) { + return func(ctx context.Context, opts *vm.VMOpts) (*vm.VM, error) { + return sm.newVM(ctx, opts) + } +} + 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. @@ -356,5 +360,5 @@ func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoc } func (sm *StateManager) VMSys() vm.SyscallBuilder { - return sm.syscalls + return sm.Syscalls } diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 8776fbcd6..ad0310602 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "reflect" - "runtime" - "strings" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" @@ -14,16 +12,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/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/policy" "github.com/filecoin-project/lotus/chain/state" @@ -33,70 +22,7 @@ import ( "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 - } -} +var MethodsMap map[cid.Cid]map[abi.MethodNum]vm.MethodMeta 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) @@ -104,15 +30,15 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) } - m, found := MethodsMap[act.Code][method] + m, found := sm.tsExec.NewActorRegistry().Methods[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] +func GetParamType(ar *vm.ActorRegistry, actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) { + m, found := ar.Methods[actCode][method] if !found { return nil, fmt.Errorf("unknown method %d for actor %s", method, actCode) } @@ -144,7 +70,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, for i := ts.Height(); i < height; i++ { // Technically, the tipset we're passing in here should be ts+1, but that may not exist. - base, err = sm.handleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) + base, err = sm.HandleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) if err != nil { return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err) } @@ -159,7 +85,8 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, Epoch: height, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.syscalls, + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, diff --git a/chain/store/index_test.go b/chain/store/index_test.go index b74bc835b..9bc31e5a8 100644 --- a/chain/store/index_test.go +++ b/chain/store/index_test.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types/mock" @@ -31,7 +32,7 @@ func TestIndexSeeks(t *testing.T) { ctx := context.TODO() nbs := blockstore.NewMemorySync() - cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil) + cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), filcns.Weight, nil) defer cs.Close() //nolint:errcheck _, err = cs.Import(bytes.NewReader(gencar)) diff --git a/chain/store/messages.go b/chain/store/messages.go index 9f5160559..07ce83458 100644 --- a/chain/store/messages.go +++ b/chain/store/messages.go @@ -101,10 +101,11 @@ type BlockMessages struct { Miner address.Address BlsMessages []types.ChainMsg SecpkMessages []types.ChainMsg - WinCount int64 } func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, error) { + // returned BlockMessages match block order in tipset + applied := make(map[address.Address]uint64) cst := cbor.NewCborStore(cs.stateBlockstore) @@ -150,7 +151,6 @@ func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, err 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 { diff --git a/chain/store/store.go b/chain/store/store.go index 1c90b7e0c..4436c1217 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -87,6 +87,8 @@ type HeadChangeEvt struct { ApplyCount int } +type WeightFunc func(ctx context.Context, stateBs bstore.Blockstore, ts *types.TipSet) (types.BigInt, error) + // ChainStore is the main point of access to chain data. // // Raw chain data is stored in the Blockstore, with relevant markers (genesis, @@ -101,6 +103,8 @@ type ChainStore struct { stateBlockstore bstore.Blockstore metadataDs dstore.Batching + weight WeightFunc + chainLocalBlockstore bstore.Blockstore heaviestLk sync.RWMutex @@ -128,7 +132,7 @@ type ChainStore struct { wg sync.WaitGroup } -func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, j journal.Journal) *ChainStore { +func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, weight WeightFunc, j journal.Journal) *ChainStore { c, _ := lru.NewARC(DefaultMsgMetaCacheSize) tsc, _ := lru.NewARC(DefaultTipSetCacheSize) if j == nil { @@ -143,6 +147,7 @@ func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dsto chainBlockstore: chainBs, stateBlockstore: stateBs, chainLocalBlockstore: localbs, + weight: weight, metadataDs: ds, bestTips: pubsub.New(64), tipsets: make(map[abi.ChainEpoch][]cid.Cid), @@ -410,11 +415,11 @@ func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipS } defer cs.heaviestLk.Unlock() - w, err := cs.Weight(ctx, ts) + w, err := cs.weight(ctx, cs.StateBlockstore(), ts) if err != nil { return err } - heaviestW, err := cs.Weight(ctx, cs.heaviest) + heaviestW, err := cs.weight(ctx, cs.StateBlockstore(), cs.heaviest) if err != nil { return err } @@ -1156,3 +1161,7 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return cs.LoadTipSet(lbts.Parents()) } + +func (cs *ChainStore) Weight(ctx context.Context, hts *types.TipSet) (types.BigInt, error) { // todo remove + return cs.weight(ctx, cs.StateBlockstore(), hts) +} diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 2db2f061b..e8440068c 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -70,7 +71,7 @@ func BenchmarkGetRandomness(b *testing.B) { b.Fatal(err) } - cs := store.NewChainStore(bs, bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck b.ResetTimer() @@ -105,7 +106,7 @@ func TestChainExportImport(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) @@ -140,7 +141,7 @@ func TestChainExportImportFull(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) @@ -157,7 +158,11 @@ func TestChainExportImportFull(t *testing.T) { t.Fatal("imported chain differed from exported chain") } - sm := stmgr.NewStateManager(cs, nil) + sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 100; i++ { ts, err := cs.GetTipsetByHeight(context.TODO(), abi.ChainEpoch(i), nil, false) if err != nil { diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index ac0c15b3b..2e962a249 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -2,32 +2,26 @@ package sub import ( "context" - "errors" "fmt" "time" address "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/messagepool" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node/impl/client" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" lru "github.com/hashicorp/golang-lru" blocks "github.com/ipfs/go-block-format" bserv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" connmgr "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" - cbg "github.com/whyrusleeping/cbor-gen" "go.opencensus.io/stats" "go.opencensus.io/tag" "golang.org/x/xerrors" @@ -35,9 +29,6 @@ import ( var log = logging.Logger("sub") -var ErrSoftFailure = errors.New("soft validation failure") -var ErrInsufficientPower = errors.New("incoming block's miner does not have minimum power") - var msgCidPrefix = cid.Prefix{ Version: 1, Codec: cid.DagCBOR, @@ -225,11 +216,11 @@ type BlockValidator struct { blacklist func(peer.ID) // necessary for block validation - chain *store.ChainStore - stmgr *stmgr.StateManager + chain *store.ChainStore + consensus consensus.Consensus } -func NewBlockValidator(self peer.ID, chain *store.ChainStore, stmgr *stmgr.StateManager, blacklist func(peer.ID)) *BlockValidator { +func NewBlockValidator(self peer.ID, chain *store.ChainStore, cns consensus.Consensus, blacklist func(peer.ID)) *BlockValidator { p, _ := lru.New2Q(4096) return &BlockValidator{ self: self, @@ -238,7 +229,7 @@ func NewBlockValidator(self peer.ID, chain *store.ChainStore, stmgr *stmgr.State blacklist: blacklist, recvBlocks: newBlockReceiptCache(), chain: chain, - stmgr: stmgr, + consensus: cns, } } @@ -260,214 +251,35 @@ func (bv *BlockValidator) flagPeer(p peer.ID) { bv.peers.Add(p, v.(int)+1) } -func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - if pid == bv.self { - return bv.validateLocalBlock(ctx, msg) - } - - // track validation time - begin := build.Clock.Now() +func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) (res pubsub.ValidationResult) { defer func() { - log.Debugf("block validation time: %s", build.Clock.Since(begin)) + if rerr := recover(); rerr != nil { + err := xerrors.Errorf("validate block: %s", rerr) + recordFailure(ctx, metrics.BlockValidationFailure, err.Error()) + bv.flagPeer(pid) + res = pubsub.ValidationReject + return + } }() - stats.Record(ctx, metrics.BlockReceived.M(1)) + var what string + res, what = bv.consensus.ValidateBlockPubsub(ctx, pid == bv.self, msg) + if res == pubsub.ValidationAccept { + // it's a good block! make sure we've only seen it once + if count := bv.recvBlocks.add(msg.ValidatorData.(*types.BlockMsg).Cid()); count > 0 { + if pid == bv.self { + log.Warnf("local block has been seen %d times; ignoring", count) + } - recordFailureFlagPeer := func(what string) { + // TODO: once these changes propagate to the network, we can consider + // dropping peers who send us the same block multiple times + return pubsub.ValidationIgnore + } + } else { recordFailure(ctx, metrics.BlockValidationFailure, what) - bv.flagPeer(pid) } - blk, what, err := bv.decodeAndCheckBlock(msg) - if err != nil { - log.Error("got invalid block over pubsub: ", err) - recordFailureFlagPeer(what) - return pubsub.ValidationReject - } - - // validate the block meta: the Message CID in the header must match the included messages - err = bv.validateMsgMeta(ctx, blk) - if err != nil { - log.Warnf("error validating message metadata: %s", err) - recordFailureFlagPeer("invalid_block_meta") - return pubsub.ValidationReject - } - - // we want to ensure that it is a block from a known miner; we reject blocks from unknown miners - // to prevent spam attacks. - // the logic works as follows: we lookup the miner in the chain for its key. - // if we can find it then it's a known miner and we can validate the signature. - // if we can't find it, we check whether we are (near) synced in the chain. - // if we are not synced we cannot validate the block and we must ignore it. - // if we are synced and the miner is unknown, then the block is rejcected. - key, err := bv.checkPowerAndGetWorkerKey(ctx, blk.Header) - if err != nil { - if err != ErrSoftFailure && bv.isChainNearSynced() { - log.Warnf("received block from unknown miner or miner that doesn't meet min power over pubsub; rejecting message") - recordFailureFlagPeer("unknown_miner") - return pubsub.ValidationReject - } - - log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain: %s", blk.Header.Cid()) - return pubsub.ValidationIgnore - } - - err = sigs.CheckBlockSignature(ctx, blk.Header, key) - if err != nil { - log.Errorf("block signature verification failed: %s", err) - recordFailureFlagPeer("signature_verification_failed") - return pubsub.ValidationReject - } - - if blk.Header.ElectionProof.WinCount < 1 { - log.Errorf("block is not claiming to be winning") - recordFailureFlagPeer("not_winning") - return pubsub.ValidationReject - } - - // it's a good block! make sure we've only seen it once - if bv.recvBlocks.add(blk.Header.Cid()) > 0 { - // TODO: once these changes propagate to the network, we can consider - // dropping peers who send us the same block multiple times - return pubsub.ValidationIgnore - } - - // all good, accept the block - msg.ValidatorData = blk - stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) - return pubsub.ValidationAccept -} - -func (bv *BlockValidator) validateLocalBlock(ctx context.Context, msg *pubsub.Message) pubsub.ValidationResult { - stats.Record(ctx, metrics.BlockPublished.M(1)) - - if size := msg.Size(); size > 1<<20-1<<15 { - log.Errorf("ignoring oversize block (%dB)", size) - recordFailure(ctx, metrics.BlockValidationFailure, "oversize_block") - return pubsub.ValidationIgnore - } - - blk, what, err := bv.decodeAndCheckBlock(msg) - if err != nil { - log.Errorf("got invalid local block: %s", err) - recordFailure(ctx, metrics.BlockValidationFailure, what) - return pubsub.ValidationIgnore - } - - if count := bv.recvBlocks.add(blk.Header.Cid()); count > 0 { - log.Warnf("local block has been seen %d times; ignoring", count) - return pubsub.ValidationIgnore - } - - msg.ValidatorData = blk - stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) - return pubsub.ValidationAccept -} - -func (bv *BlockValidator) decodeAndCheckBlock(msg *pubsub.Message) (*types.BlockMsg, string, error) { - blk, err := types.DecodeBlockMsg(msg.GetData()) - if err != nil { - return nil, "invalid", xerrors.Errorf("error decoding block: %w", err) - } - - if count := len(blk.BlsMessages) + len(blk.SecpkMessages); count > build.BlockMessageLimit { - return nil, "too_many_messages", fmt.Errorf("block contains too many messages (%d)", count) - } - - // make sure we have a signature - if blk.Header.BlockSig == nil { - return nil, "missing_signature", fmt.Errorf("block without a signature") - } - - return blk, "", nil -} - -func (bv *BlockValidator) isChainNearSynced() bool { - ts := bv.chain.GetHeaviestTipSet() - timestamp := ts.MinTimestamp() - timestampTime := time.Unix(int64(timestamp), 0) - return build.Clock.Since(timestampTime) < 6*time.Hour -} - -func (bv *BlockValidator) validateMsgMeta(ctx context.Context, msg *types.BlockMsg) error { - // TODO there has to be a simpler way to do this without the blockstore dance - // block headers use adt0 - store := blockadt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewMemory())) - bmArr := blockadt.MakeEmptyArray(store) - smArr := blockadt.MakeEmptyArray(store) - - for i, m := range msg.BlsMessages { - c := cbg.CborCid(m) - if err := bmArr.Set(uint64(i), &c); err != nil { - return err - } - } - - for i, m := range msg.SecpkMessages { - c := cbg.CborCid(m) - if err := smArr.Set(uint64(i), &c); err != nil { - return err - } - } - - bmroot, err := bmArr.Root() - if err != nil { - return err - } - - smroot, err := smArr.Root() - if err != nil { - return err - } - - mrcid, err := store.Put(store.Context(), &types.MsgMeta{ - BlsMessages: bmroot, - SecpkMessages: smroot, - }) - - if err != nil { - return err - } - - if msg.Header.Messages != mrcid { - return fmt.Errorf("messages didn't match root cid in header") - } - - return nil -} - -func (bv *BlockValidator) checkPowerAndGetWorkerKey(ctx context.Context, bh *types.BlockHeader) (address.Address, error) { - // we check that the miner met the minimum power at the lookback tipset - - baseTs := bv.chain.GetHeaviestTipSet() - lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, bv.stmgr, baseTs, bh.Height) - if err != nil { - log.Warnf("failed to load lookback tipset for incoming block: %s", err) - return address.Undef, ErrSoftFailure - } - - key, err := stmgr.GetMinerWorkerRaw(ctx, bv.stmgr, lbst, bh.Miner) - if err != nil { - log.Warnf("failed to resolve worker key for miner %s: %s", bh.Miner, err) - return address.Undef, ErrSoftFailure - } - - // NOTE: we check to see if the miner was eligible in the lookback - // tipset - 1 for historical reasons. DO NOT use the lookback state - // returned by GetLookbackTipSetForRound. - - eligible, err := stmgr.MinerEligibleToMine(ctx, bv.stmgr, bh.Miner, baseTs, lbts) - if err != nil { - log.Warnf("failed to determine if incoming block's miner has minimum power: %s", err) - return address.Undef, ErrSoftFailure - } - - if !eligible { - log.Warnf("incoming block's miner is ineligible") - return address.Undef, ErrInsufficientPower - } - - return key, nil + return res } type blockReceiptCache struct { diff --git a/chain/sync.go b/chain/sync.go index 7914cc8d5..34867b136 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -5,13 +5,11 @@ import ( "context" "errors" "fmt" - "os" "sort" - "strings" "sync" "time" - "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -29,40 +27,23 @@ import ( "go.opencensus.io/trace" "golang.org/x/xerrors" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" - - ffi "github.com/filecoin-project/filecoin-ffi" - // named msgarray here to make it clear that these are the types used by // messages, regardless of specs-actors version. blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" - "github.com/filecoin-project/lotus/api" bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/exchange" - "github.com/filecoin-project/lotus/chain/gen" - "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/metrics" ) -// Blocks that are more than MaxHeightDrift epochs above -// the theoretical max height based on systime are quickly rejected -const MaxHeightDrift = 5 - var ( // LocalIncoming is the _local_ pubsub (unrelated to libp2p pubsub) topic // where the Syncer publishes candidate chain heads to be synced. @@ -108,6 +89,8 @@ type Syncer struct { // the state manager handles making state queries sm *stmgr.StateManager + consensus consensus.Consensus + // The known Genesis tipset Genesis *types.TipSet @@ -127,8 +110,6 @@ type Syncer struct { receiptTracker *blockReceiptTracker - verifier ffiwrapper.Verifier - tickerCtxCancel context.CancelFunc ds dtypes.MetadataDS @@ -136,40 +117,44 @@ type Syncer struct { type SyncManagerCtor func(syncFn SyncFunc) SyncManager -// NewSyncer creates a new Syncer object. -func NewSyncer(ds dtypes.MetadataDS, sm *stmgr.StateManager, exchange exchange.Client, syncMgrCtor SyncManagerCtor, connmgr connmgr.ConnManager, self peer.ID, beacon beacon.Schedule, verifier ffiwrapper.Verifier) (*Syncer, error) { +type Genesis *types.TipSet + +func LoadGenesis(sm *stmgr.StateManager) (Genesis, error) { gen, err := sm.ChainStore().GetGenesis() if err != nil { return nil, xerrors.Errorf("getting genesis block: %w", err) } - gent, err := types.NewTipSet([]*types.BlockHeader{gen}) - if err != nil { - return nil, err - } + return types.NewTipSet([]*types.BlockHeader{gen}) +} + +// NewSyncer creates a new Syncer object. +func NewSyncer(ds dtypes.MetadataDS, + sm *stmgr.StateManager, + exchange exchange.Client, + syncMgrCtor SyncManagerCtor, + connmgr connmgr.ConnManager, + self peer.ID, + beacon beacon.Schedule, + gent Genesis, + consensus consensus.Consensus) (*Syncer, error) { s := &Syncer{ ds: ds, beacon: beacon, bad: NewBadBlockCache(), Genesis: gent, + consensus: consensus, Exchange: exchange, store: sm.ChainStore(), sm: sm, self: self, receiptTracker: newBlockReceiptTracker(), connmgr: connmgr, - verifier: verifier, incoming: pubsub.New(50), } - if build.InsecurePoStValidation { - log.Warn("*********************************************************************************************") - log.Warn(" [INSECURE-POST-VALIDATION] Insecure test validation is enabled. If you see this outside of a test, it is a severe bug! ") - log.Warn("*********************************************************************************************") - } - s.syncmgr = syncMgrCtor(s.Sync) return s, nil } @@ -212,7 +197,7 @@ func (syncer *Syncer) Stop() { func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { defer func() { if err := recover(); err != nil { - log.Errorf("panic in InformNewHead: ", err) + log.Errorf("panic in InformNewHead: %s", err) } }() @@ -222,7 +207,7 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { return false } - if syncer.IsEpochBeyondCurrMax(fts.TipSet().Height()) { + if syncer.consensus.IsEpochBeyondCurrMax(fts.TipSet().Height()) { log.Errorf("Received block with impossibly large height %d", fts.TipSet().Height()) return false } @@ -399,33 +384,21 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types return nil, fmt.Errorf("msgincl length didnt match tipset size") } + if err := checkMsgMeta(ts, allbmsgs, allsmsgs, bmi, smi); err != nil { + return nil, err + } + fts := &store.FullTipSet{} for bi, b := range ts.Blocks() { - if msgc := len(bmi[bi]) + len(smi[bi]); msgc > build.BlockMessageLimit { - return nil, fmt.Errorf("block %q has too many messages (%d)", b.Cid(), msgc) - } var smsgs []*types.SignedMessage - var smsgCids []cid.Cid for _, m := range smi[bi] { smsgs = append(smsgs, allsmsgs[m]) - smsgCids = append(smsgCids, allsmsgs[m].Cid()) } var bmsgs []*types.Message - var bmsgCids []cid.Cid for _, m := range bmi[bi] { bmsgs = append(bmsgs, allbmsgs[m]) - bmsgCids = append(bmsgCids, allbmsgs[m].Cid()) - } - - mrcid, err := computeMsgMeta(bs, bmsgCids, smsgCids) - if err != nil { - return nil, err - } - - if b.Messages != mrcid { - return nil, fmt.Errorf("messages didnt match message root in header for ts %s", ts.Key()) } fb := &types.FullBlock{ @@ -584,7 +557,7 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { } func isPermanent(err error) bool { - return !errors.Is(err, ErrTemporal) + return !errors.Is(err, consensus.ErrTemporal) } func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet, useCache bool) error { @@ -624,55 +597,6 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet, return nil } -func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error { - act, err := syncer.sm.LoadActor(ctx, power.Address, baseTs) - if err != nil { - return xerrors.Errorf("failed to load power actor: %w", err) - } - - powState, err := power.Load(syncer.store.ActorStore(ctx), act) - if err != nil { - return xerrors.Errorf("failed to load power actor state: %w", err) - } - - _, exist, err := powState.MinerPower(maddr) - if err != nil { - return xerrors.Errorf("failed to look up miner's claim: %w", err) - } - - if !exist { - return xerrors.New("miner isn't valid") - } - - return nil -} - -var ErrTemporal = errors.New("temporal error") - -func blockSanityChecks(h *types.BlockHeader) error { - if h.ElectionProof == nil { - return xerrors.Errorf("block cannot have nil election proof") - } - - if h.Ticket == nil { - return xerrors.Errorf("block cannot have nil ticket") - } - - if h.BlockSig == nil { - return xerrors.Errorf("block had nil signature") - } - - if h.BLSAggregate == nil { - return xerrors.Errorf("block had nil bls aggregate signature") - } - - if h.Miner.Protocol() != address.ID { - return xerrors.Errorf("block had non-ID miner address") - } - - return nil -} - // ValidateBlock should match up with 'Semantical Validation' in validation.md in the spec func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, useCache bool) (err error) { defer func() { @@ -703,262 +627,8 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, use ctx, span := trace.StartSpan(ctx, "validateBlock") defer span.End() - if err := blockSanityChecks(b.Header); err != nil { - return xerrors.Errorf("incoming header failed basic sanity checks: %w", err) - } - - h := b.Header - - baseTs, err := syncer.store.LoadTipSet(types.NewTipSetKey(h.Parents...)) - if err != nil { - return xerrors.Errorf("load parent tipset failed (%s): %w", h.Parents, err) - } - - winPoStNv := syncer.sm.GetNtwkVersion(ctx, baseTs.Height()) - - lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, syncer.sm, baseTs, h.Height) - if err != nil { - return xerrors.Errorf("failed to get lookback tipset for block: %w", err) - } - - prevBeacon, err := syncer.store.GetLatestBeaconEntry(baseTs) - if err != nil { - return xerrors.Errorf("failed to get latest beacon entry: %w", err) - } - - // fast checks first - - if h.Height <= baseTs.Height() { - return xerrors.Errorf("block height not greater than parent height: %d != %d", h.Height, baseTs.Height()) - } - - nulls := h.Height - (baseTs.Height() + 1) - if tgtTs := baseTs.MinTimestamp() + build.BlockDelaySecs*uint64(nulls+1); h.Timestamp != tgtTs { - return xerrors.Errorf("block has wrong timestamp: %d != %d", h.Timestamp, tgtTs) - } - - now := uint64(build.Clock.Now().Unix()) - if h.Timestamp > now+build.AllowableClockDriftSecs { - return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, ErrTemporal) - } - if h.Timestamp > now { - log.Warn("Got block from the future, but within threshold", h.Timestamp, build.Clock.Now().Unix()) - } - - msgsCheck := async.Err(func() error { - if b.Cid() == build.WhitelistedBlock { - return nil - } - - if err := syncer.checkBlockMessages(ctx, b, baseTs); err != nil { - return xerrors.Errorf("block had invalid messages: %w", err) - } - return nil - }) - - minerCheck := async.Err(func() error { - if err := syncer.minerIsValid(ctx, h.Miner, baseTs); err != nil { - return xerrors.Errorf("minerIsValid failed: %w", err) - } - return nil - }) - - baseFeeCheck := async.Err(func() error { - baseFee, err := syncer.store.ComputeBaseFee(ctx, baseTs) - if err != nil { - return xerrors.Errorf("computing base fee: %w", err) - } - if types.BigCmp(baseFee, b.Header.ParentBaseFee) != 0 { - return xerrors.Errorf("base fee doesn't match: %s (header) != %s (computed)", - b.Header.ParentBaseFee, baseFee) - } - return nil - }) - pweight, err := syncer.store.Weight(ctx, baseTs) - if err != nil { - return xerrors.Errorf("getting parent weight: %w", err) - } - - if types.BigCmp(pweight, b.Header.ParentWeight) != 0 { - return xerrors.Errorf("parrent weight different: %s (header) != %s (computed)", - b.Header.ParentWeight, pweight) - } - - stateRootCheck := async.Err(func() error { - stateroot, precp, err := syncer.sm.TipSetState(ctx, baseTs) - if err != nil { - return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err) - } - - if stateroot != h.ParentStateRoot { - msgs, err := syncer.store.MessagesForTipset(baseTs) - if err != nil { - log.Error("failed to load messages for tipset during tipset state mismatch error: ", err) - } else { - log.Warn("Messages for tipset with mismatching state:") - for i, m := range msgs { - mm := m.VMMessage() - log.Warnf("Message[%d]: from=%s to=%s method=%d params=%x", i, mm.From, mm.To, mm.Method, mm.Params) - } - } - - return xerrors.Errorf("parent state root did not match computed state (%s != %s)", stateroot, h.ParentStateRoot) - } - - if precp != h.ParentMessageReceipts { - return xerrors.Errorf("parent receipts root did not match computed value (%s != %s)", precp, h.ParentMessageReceipts) - } - - return nil - }) - - // Stuff that needs worker address - waddr, err := stmgr.GetMinerWorkerRaw(ctx, syncer.sm, lbst, h.Miner) - if err != nil { - return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err) - } - - winnerCheck := async.Err(func() error { - if h.ElectionProof.WinCount < 1 { - return xerrors.Errorf("block is not claiming to be a winner") - } - - eligible, err := stmgr.MinerEligibleToMine(ctx, syncer.sm, h.Miner, baseTs, lbts) - if err != nil { - return xerrors.Errorf("determining if miner has min power failed: %w", err) - } - - if !eligible { - return xerrors.New("block's miner is ineligible to mine") - } - - rBeacon := *prevBeacon - if len(h.BeaconEntries) != 0 { - rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1] - } - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) - } - - vrfBase, err := store.DrawRandomness(rBeacon.Data, crypto.DomainSeparationTag_ElectionProofProduction, h.Height, buf.Bytes()) - if err != nil { - return xerrors.Errorf("could not draw randomness: %w", err) - } - - if err := VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil { - return xerrors.Errorf("validating block election proof failed: %w", err) - } - - slashed, err := stmgr.GetMinerSlashed(ctx, syncer.sm, baseTs, h.Miner) - if err != nil { - return xerrors.Errorf("failed to check if block miner was slashed: %w", err) - } - - if slashed { - return xerrors.Errorf("received block was from slashed or invalid miner") - } - - mpow, tpow, _, err := stmgr.GetPowerRaw(ctx, syncer.sm, lbst, h.Miner) - if err != nil { - return xerrors.Errorf("failed getting power: %w", err) - } - - j := h.ElectionProof.ComputeWinCount(mpow.QualityAdjPower, tpow.QualityAdjPower) - if h.ElectionProof.WinCount != j { - return xerrors.Errorf("miner claims wrong number of wins: miner: %d, computed: %d", h.ElectionProof.WinCount, j) - } - - return nil - }) - - blockSigCheck := async.Err(func() error { - if err := sigs.CheckBlockSignature(ctx, h, waddr); err != nil { - return xerrors.Errorf("check block signature failed: %w", err) - } - return nil - }) - - beaconValuesCheck := async.Err(func() error { - if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { - return nil - } - - if err := beacon.ValidateBlockValues(syncer.beacon, h, baseTs.Height(), *prevBeacon); err != nil { - return xerrors.Errorf("failed to validate blocks random beacon values: %w", err) - } - return nil - }) - - tktsCheck := async.Err(func() error { - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) - } - - if h.Height > build.UpgradeSmokeHeight { - buf.Write(baseTs.MinTicket().VRFProof) - } - - beaconBase := *prevBeacon - if len(h.BeaconEntries) != 0 { - beaconBase = h.BeaconEntries[len(h.BeaconEntries)-1] - } - - vrfBase, err := store.DrawRandomness(beaconBase.Data, crypto.DomainSeparationTag_TicketProduction, h.Height-build.TicketRandomnessLookback, buf.Bytes()) - if err != nil { - return xerrors.Errorf("failed to compute vrf base for ticket: %w", err) - } - - err = VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof) - if err != nil { - return xerrors.Errorf("validating block tickets failed: %w", err) - } - return nil - }) - - wproofCheck := async.Err(func() error { - if err := syncer.VerifyWinningPoStProof(ctx, winPoStNv, h, *prevBeacon, lbst, waddr); err != nil { - return xerrors.Errorf("invalid election post: %w", err) - } - return nil - }) - - await := []async.ErrorFuture{ - minerCheck, - tktsCheck, - blockSigCheck, - beaconValuesCheck, - wproofCheck, - winnerCheck, - msgsCheck, - baseFeeCheck, - stateRootCheck, - } - - var merr error - for _, fut := range await { - if err := fut.AwaitContext(ctx); err != nil { - merr = multierror.Append(merr, err) - } - } - if merr != nil { - mulErr := merr.(*multierror.Error) - mulErr.ErrorFormat = func(es []error) string { - if len(es) == 1 { - return fmt.Sprintf("1 error occurred:\n\t* %+v\n\n", es[0]) - } - - points := make([]string, len(es)) - for i, err := range es { - points[i] = fmt.Sprintf("* %+v", err) - } - - return fmt.Sprintf( - "%d errors occurred:\n\t%s\n\n", - len(es), strings.Join(points, "\n\t")) - } - return mulErr + if err := syncer.consensus.ValidateBlock(ctx, b); err != nil { + return err } if useCache { @@ -970,249 +640,6 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, use return nil } -func (syncer *Syncer) VerifyWinningPoStProof(ctx context.Context, nv network.Version, h *types.BlockHeader, prevBeacon types.BeaconEntry, lbst cid.Cid, waddr address.Address) error { - if build.InsecurePoStValidation { - if len(h.WinPoStProof) == 0 { - return xerrors.Errorf("[INSECURE-POST-VALIDATION] No winning post proof given") - } - - if string(h.WinPoStProof[0].ProofBytes) == "valid proof" { - return nil - } - return xerrors.Errorf("[INSECURE-POST-VALIDATION] winning post was invalid") - } - - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address: %w", err) - } - - rbase := prevBeacon - if len(h.BeaconEntries) > 0 { - rbase = h.BeaconEntries[len(h.BeaconEntries)-1] - } - - rand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, h.Height, buf.Bytes()) - if err != nil { - return xerrors.Errorf("failed to get randomness for verifying winning post proof: %w", err) - } - - mid, err := address.IDFromAddress(h.Miner) - if err != nil { - return xerrors.Errorf("failed to get ID from miner address %s: %w", h.Miner, err) - } - - sectors, err := stmgr.GetSectorsForWinningPoSt(ctx, nv, syncer.verifier, syncer.sm, lbst, h.Miner, rand) - if err != nil { - return xerrors.Errorf("getting winning post sector set: %w", err) - } - - ok, err := ffiwrapper.ProofVerifier.VerifyWinningPoSt(ctx, proof2.WinningPoStVerifyInfo{ - Randomness: rand, - Proofs: h.WinPoStProof, - ChallengedSectors: sectors, - Prover: abi.ActorID(mid), - }) - if err != nil { - return xerrors.Errorf("failed to verify election post: %w", err) - } - - if !ok { - log.Errorf("invalid winning post (block: %s, %x; %v)", h.Cid(), rand, sectors) - return xerrors.Errorf("winning post was invalid") - } - - return nil -} - -// TODO: We should extract this somewhere else and make the message pool and miner use the same logic -func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error { - { - var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type - var pubks [][]byte - - for _, m := range b.BlsMessages { - sigCids = append(sigCids, m.Cid()) - - pubk, err := syncer.sm.GetBlsPublicKey(ctx, m.From, baseTs) - if err != nil { - return xerrors.Errorf("failed to load bls public to validate block: %w", err) - } - - pubks = append(pubks, pubk) - } - - if err := syncer.verifyBlsAggregate(ctx, b.Header.BLSAggregate, sigCids, pubks); err != nil { - return xerrors.Errorf("bls aggregate signature was invalid: %w", err) - } - } - - nonces := make(map[address.Address]uint64) - - stateroot, _, err := syncer.sm.TipSetState(ctx, baseTs) - if err != nil { - return err - } - - st, err := state.LoadStateTree(syncer.store.ActorStore(ctx), stateroot) - if err != nil { - return xerrors.Errorf("failed to load base state tree: %w", err) - } - - nv := syncer.sm.GetNtwkVersion(ctx, b.Header.Height) - pl := vm.PricelistByEpoch(baseTs.Height()) - var sumGasLimit int64 - checkMsg := func(msg types.ChainMsg) error { - m := msg.VMMessage() - - // Phase 1: syntactic validation, as defined in the spec - minGas := pl.OnChainMessage(msg.ChainLength()) - if err := m.ValidForBlockInclusion(minGas.Total(), nv); err != nil { - return err - } - - // ValidForBlockInclusion checks if any single message does not exceed BlockGasLimit - // So below is overflow safe - sumGasLimit += m.GasLimit - if sumGasLimit > build.BlockGasLimit { - return xerrors.Errorf("block gas limit exceeded") - } - - // Phase 2: (Partial) semantic validation: - // the sender exists and is an account actor, and the nonces make sense - var sender address.Address - if nv >= network.Version13 { - sender, err = st.LookupID(m.From) - if err != nil { - return err - } - } else { - sender = m.From - } - - if _, ok := nonces[sender]; !ok { - // `GetActor` does not validate that this is an account actor. - act, err := st.GetActor(sender) - if err != nil { - return xerrors.Errorf("failed to get actor: %w", err) - } - - if !builtin.IsAccountActor(act.Code) { - return xerrors.New("Sender must be an account actor") - } - nonces[sender] = act.Nonce - } - - if nonces[sender] != m.Nonce { - return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[sender], m.Nonce) - } - nonces[sender]++ - - return nil - } - - // Validate message arrays in a temporary blockstore. - tmpbs := bstore.NewMemory() - tmpstore := blockadt.WrapStore(ctx, cbor.NewCborStore(tmpbs)) - - bmArr := blockadt.MakeEmptyArray(tmpstore) - for i, m := range b.BlsMessages { - if err := checkMsg(m); err != nil { - return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err) - } - - c, err := store.PutMessage(tmpbs, m) - if err != nil { - return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) - } - - k := cbg.CborCid(c) - if err := bmArr.Set(uint64(i), &k); err != nil { - return xerrors.Errorf("failed to put bls message at index %d: %w", i, err) - } - } - - smArr := blockadt.MakeEmptyArray(tmpstore) - for i, m := range b.SecpkMessages { - if err := checkMsg(m); err != nil { - return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) - } - - // `From` being an account actor is only validated inside the `vm.ResolveToKeyAddr` call - // in `StateManager.ResolveToKeyAddress` here (and not in `checkMsg`). - kaddr, err := syncer.sm.ResolveToKeyAddress(ctx, m.Message.From, baseTs) - if err != nil { - return xerrors.Errorf("failed to resolve key addr: %w", err) - } - - if err := sigs.Verify(&m.Signature, kaddr, m.Message.Cid().Bytes()); err != nil { - return xerrors.Errorf("secpk message %s has invalid signature: %w", m.Cid(), err) - } - - c, err := store.PutMessage(tmpbs, m) - if err != nil { - return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) - } - k := cbg.CborCid(c) - if err := smArr.Set(uint64(i), &k); err != nil { - return xerrors.Errorf("failed to put secpk message at index %d: %w", i, err) - } - } - - bmroot, err := bmArr.Root() - if err != nil { - return err - } - - smroot, err := smArr.Root() - if err != nil { - return err - } - - mrcid, err := tmpstore.Put(ctx, &types.MsgMeta{ - BlsMessages: bmroot, - SecpkMessages: smroot, - }) - if err != nil { - return err - } - - if b.Header.Messages != mrcid { - return fmt.Errorf("messages didnt match message root in header") - } - - // Finally, flush. - return vm.Copy(ctx, tmpbs, syncer.store.ChainBlockstore(), mrcid) -} - -func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks [][]byte) error { - _, span := trace.StartSpan(ctx, "syncer.verifyBlsAggregate") - defer span.End() - span.AddAttributes( - trace.Int64Attribute("msgCount", int64(len(msgs))), - ) - - msgsS := make([]ffi.Message, len(msgs)) - pubksS := make([]ffi.PublicKey, len(msgs)) - for i := 0; i < len(msgs); i++ { - msgsS[i] = msgs[i].Bytes() - copy(pubksS[i][:], pubks[i][:ffi.PublicKeyBytes]) - } - - sigS := new(ffi.Signature) - copy(sigS[:], sig.Data[:ffi.SignatureBytes]) - - if len(msgs) == 0 { - return nil - } - - valid := ffi.HashVerify(sigS, msgsS, pubksS) - if !valid { - return xerrors.New("bls aggregate signature failed to verify") - } - return nil -} - type syncStateKey struct{} func extractSyncState(ctx context.Context) *SyncerState { @@ -1374,7 +801,7 @@ loop: return nil, xerrors.Errorf("retrieved segments of the chain are not connected at heights %d/%d", blockSet[len(blockSet)-1].Height(), blks[0].Height()) // A successful `GetBlocks()` call is guaranteed to fetch at least - // one tipset so the acess `blks[0]` is safe. + // one tipset so the access `blks[0]` is safe. } for _, b := range blks { @@ -1598,6 +1025,35 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS return nil } +func checkMsgMeta(ts *types.TipSet, allbmsgs []*types.Message, allsmsgs []*types.SignedMessage, bmi, smi [][]uint64) error { + for bi, b := range ts.Blocks() { + if msgc := len(bmi[bi]) + len(smi[bi]); msgc > build.BlockMessageLimit { + return fmt.Errorf("block %q has too many messages (%d)", b.Cid(), msgc) + } + + var smsgCids []cid.Cid + for _, m := range smi[bi] { + smsgCids = append(smsgCids, allsmsgs[m].Cid()) + } + + var bmsgCids []cid.Cid + for _, m := range bmi[bi] { + bmsgCids = append(bmsgCids, allbmsgs[m].Cid()) + } + + mrcid, err := computeMsgMeta(cbor.NewCborStore(bstore.NewMemory()), bmsgCids, smsgCids) + if err != nil { + return err + } + + if b.Messages != mrcid { + return fmt.Errorf("messages didnt match message root in header for ts %s", ts.Key()) + } + } + + return nil +} + func (syncer *Syncer) fetchMessages(ctx context.Context, headers []*types.TipSet, startOffset int) ([]*exchange.CompactedMessages, error) { batchSize := len(headers) batch := make([]*exchange.CompactedMessages, batchSize) @@ -1636,7 +1092,19 @@ func (syncer *Syncer) fetchMessages(ctx context.Context, headers []*types.TipSet if err != nil { requestErr = multierror.Append(requestErr, err) } else { - requestResult = result + isGood := true + for index, ts := range headers[nextI:lastI] { + cm := result[index] + if err := checkMsgMeta(ts, cm.Bls, cm.Secpk, cm.BlsIncludes, cm.SecpkIncludes); err != nil { + log.Errorf("fetched messages not as expected: %s", err) + isGood = false + break + } + } + + if isGood { + requestResult = result + } } } @@ -1754,10 +1222,6 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet, hts *t return nil } -func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error { - return gen.VerifyVRF(ctx, worker, rand, evrf) -} - func (syncer *Syncer) State() []SyncerStateSnapshot { return syncer.syncmgr.State() } @@ -1802,12 +1266,3 @@ func (syncer *Syncer) getLatestBeaconEntry(_ context.Context, ts *types.TipSet) return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") } - -func (syncer *Syncer) IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool { - if syncer.Genesis == nil { - return false - } - - now := uint64(build.Clock.Now().Unix()) - return epoch > (abi.ChainEpoch((now-syncer.Genesis.MinTimestamp())/build.BlockDelaySecs) + MaxHeightDrift) -} diff --git a/chain/sync_test.go b/chain/sync_test.go index bda8c60ee..32b2184de 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -28,6 +28,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/lotus/chain/store" @@ -105,7 +106,7 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { mn: mocknet.New(ctx), g: g, - us: stmgr.DefaultUpgradeSchedule(), + us: filcns.DefaultUpgradeSchedule(), } tu.addSourceNode(h) @@ -125,19 +126,19 @@ func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syn // prepare for upgrade. Network: network.Version9, Height: 1, - Migration: stmgr.UpgradeActorsV2, + Migration: filcns.UpgradeActorsV2, }, { Network: network.Version10, Height: 2, - Migration: stmgr.UpgradeActorsV3, + Migration: filcns.UpgradeActorsV3, }, { Network: network.Version12, Height: 3, - Migration: stmgr.UpgradeActorsV4, + Migration: filcns.UpgradeActorsV4, }, { Network: network.Version13, Height: v5height, - Migration: stmgr.UpgradeActorsV5, + Migration: filcns.UpgradeActorsV5, }} g, err := gen.NewGeneratorWithUpgradeSchedule(sched) diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index 66e711cab..d36ee9314 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -47,7 +47,8 @@ func NewBeaconEntry(round uint64, data []byte) BeaconEntry { } type BlockHeader struct { - Miner address.Address // 0 unique per block/miner + Miner address.Address // 0 unique per block/miner + Ticket *Ticket // 1 unique per block/miner: should be a valid VRF ElectionProof *ElectionProof // 2 unique per block/miner: should be a valid VRF BeaconEntries []BeaconEntry // 3 identical for all blocks in same tipset diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index d31a9010f..85357e51b 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -5,6 +5,8 @@ import ( "encoding/hex" "fmt" "reflect" + "runtime" + "strings" "github.com/filecoin-project/go-state-types/network" @@ -14,11 +16,6 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" - 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" vmr "github.com/filecoin-project/specs-actors/v5/actors/runtime" "github.com/filecoin-project/go-state-types/abi" @@ -30,8 +27,17 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +type MethodMeta struct { + Name string + + Params reflect.Type + Ret reflect.Type +} + type ActorRegistry struct { actors map[cid.Cid]*actorInfo + + Methods map[cid.Cid]map[abi.MethodNum]MethodMeta } // An ActorPredicate returns an error if the given actor is not valid for the given runtime environment (e.g., chain height, version, etc.). @@ -61,18 +67,10 @@ type actorInfo struct { } func NewActorRegistry() *ActorRegistry { - inv := &ActorRegistry{actors: make(map[cid.Cid]*actorInfo)} - - // TODO: define all these properties on the actors themselves, in specs-actors. - - // add builtInCode using: register(cid, singleton) - inv.Register(ActorsVersionPredicate(actors.Version0), exported0.BuiltinActors()...) - inv.Register(ActorsVersionPredicate(actors.Version2), exported2.BuiltinActors()...) - inv.Register(ActorsVersionPredicate(actors.Version3), exported3.BuiltinActors()...) - inv.Register(ActorsVersionPredicate(actors.Version4), exported4.BuiltinActors()...) - inv.Register(ActorsVersionPredicate(actors.Version5), exported5.BuiltinActors()...) - - return inv + return &ActorRegistry{ + actors: make(map[cid.Cid]*actorInfo), + Methods: map[cid.Cid]map[abi.MethodNum]MethodMeta{}, + } } func (ar *ActorRegistry) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { @@ -96,6 +94,7 @@ func (ar *ActorRegistry) Register(pred ActorPredicate, actors ...rtt.VMActor) { pred = func(vmr.Runtime, rtt.VMActor) error { return nil } } for _, a := range actors { + // register in the `actors` map (for the invoker) code, err := ar.transform(a) if err != nil { panic(xerrors.Errorf("%s: %w", string(a.Code().Hash()), err)) @@ -105,6 +104,51 @@ func (ar *ActorRegistry) Register(pred ActorPredicate, actors ...rtt.VMActor) { vmActor: a, predicate: pred, } + + // register in the `Methods` map (used by statemanager utils) + exports := a.Exports() + methods := make(map[abi.MethodNum]MethodMeta, len(exports)) + + // Explicitly add send, it's special. + methods[builtin.MethodSend] = MethodMeta{ + Name: "Send", + Params: reflect.TypeOf(new(abi.EmptyValue)), + Ret: reflect.TypeOf(new(abi.EmptyValue)), + } + + // Iterate over exported methods. Some of these _may_ be nil and + // must be skipped. + for number, export := range exports { + if export == 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), + } + } + ar.Methods[a.Code()] = methods } } @@ -226,13 +270,11 @@ func DecodeParams(b []byte, out interface{}) error { return um.UnmarshalCBOR(bytes.NewReader(b)) } -func DumpActorState(act *types.Actor, b []byte) (interface{}, error) { +func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) { if builtin.IsAccountActor(act.Code) { // Account code special case return nil, nil } - i := NewActorRegistry() - actInfo, ok := i.actors[act.Code] if !ok { return nil, xerrors.Errorf("state type for actor %s not found", act.Code) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 199896671..80bad39dc 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -223,6 +223,7 @@ type VMOpts struct { Epoch abi.ChainEpoch Rand Rand Bstore blockstore.Blockstore + Actors *ActorRegistry Syscalls SyscallBuilder CircSupplyCalc CircSupplyCalculator NtwkVersion NtwkVersionGetter // TODO: stebalien: In what cases do we actually need this? It seems like even when creating new networks we want to use the 'global'/build-default version getter @@ -244,7 +245,7 @@ func NewVM(ctx context.Context, opts *VMOpts) (*VM, error) { cst: cst, buf: buf, blockHeight: opts.Epoch, - areg: NewActorRegistry(), + areg: opts.Actors, rand: opts.Rand, // TODO: Probably should be a syscall circSupplyCalc: opts.CircSupplyCalc, ntwkVersion: opts.NtwkVersion, diff --git a/cli/state.go b/cli/state.go index d5251fb85..375630c9f 100644 --- a/cli/state.go +++ b/cli/state.go @@ -18,6 +18,7 @@ import ( "time" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/api/v0api" @@ -1397,7 +1398,7 @@ func sumGas(changes []*types.GasTrace) types.GasTrace { } func JsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) { - p, err := stmgr.GetParamType(code, method) + p, err := stmgr.GetParamType(filcns.NewActorRegistry(), code, method) // todo use api for correct actor registry if err != nil { return "", err } diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index d8ef57138..454cd7d73 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -26,6 +26,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" badgerbs "github.com/filecoin-project/lotus/blockstore/badger" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -253,10 +254,13 @@ var importBenchCmd = &cli.Command{ } metadataDs := datastore.NewMapDatastore() - cs := store.NewChainStore(bs, bs, metadataDs, nil) + cs := store.NewChainStore(bs, bs, metadataDs, filcns.Weight, nil) defer cs.Close() //nolint:errcheck - stm := stmgr.NewStateManager(cs, vm.Syscalls(verifier)) + stm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(verifier), filcns.DefaultUpgradeSchedule()) + if err != nil { + return err + } 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 3a158483f..4691d287f 100644 --- a/cmd/lotus-shed/balances.go +++ b/cmd/lotus-shed/balances.go @@ -14,6 +14,7 @@ import ( "time" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen/genesis" @@ -510,13 +511,16 @@ var chainBalanceStateCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) + sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + if err != nil { + return err + } tree, err := state.LoadStateTree(cst, sroot) if err != nil { @@ -731,14 +735,16 @@ var chainPledgeCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) - + sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + if err != nil { + return err + } state, err := state.LoadStateTree(cst, sroot) if err != nil { return err diff --git a/cmd/lotus-shed/export.go b/cmd/lotus-shed/export.go index dc5cc3bd2..e711ba2bb 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) + cs := store.NewChainStore(bs, bs, mds, nil, 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 0b61b680b..4a692d4c9 100644 --- a/cmd/lotus-shed/genesis-verify.go +++ b/cmd/lotus-shed/genesis-verify.go @@ -9,6 +9,7 @@ import ( _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/fatih/color" "github.com/ipfs/go-datastore" @@ -54,7 +55,7 @@ var genesisVerifyCmd = &cli.Command{ } bs := blockstore.FromDatastore(datastore.NewMapDatastore()) - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck cf := cctx.Args().Get(0) diff --git a/cmd/lotus-shed/miner-types.go b/cmd/lotus-shed/miner-types.go index 491a77aa0..05ef7b0a7 100644 --- a/cmd/lotus-shed/miner-types.go +++ b/cmd/lotus-shed/miner-types.go @@ -7,6 +7,7 @@ import ( "math/big" big2 "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -74,7 +75,7 @@ var minerTypesCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 63cfc86b9..c966b2e97 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/fatih/color" - "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" "golang.org/x/xerrors" diff --git a/cmd/lotus-shed/pruning.go b/cmd/lotus-shed/pruning.go index 68488862a..186a3191a 100644 --- a/cmd/lotus-shed/pruning.go +++ b/cmd/lotus-shed/pruning.go @@ -6,6 +6,7 @@ import ( "io" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/ipfs/bbloom" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" @@ -167,7 +168,7 @@ var stateTreePruneCmd = &cli.Command{ return nil } - cs := store.NewChainStore(bs, bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck if err := cs.Load(); err != nil { diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index ebcaae2b6..1abf940e3 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -4,6 +4,7 @@ import ( "context" "math" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "go.uber.org/zap" "golang.org/x/xerrors" @@ -83,6 +84,7 @@ func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.S Epoch: parentTs.Height() + 1, Rand: r, Bstore: sm.ChainStore().StateBlockstore(), + Actors: filcns.NewActorRegistry(), Syscalls: sm.VMSys(), CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index c2a497bcb..3294fd71a 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -11,6 +11,7 @@ import ( "github.com/ipfs/go-datastore/query" "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -61,7 +62,7 @@ func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { } return &Node{ repo: lr, - Chainstore: store.NewChainStore(bs, bs, ds, nil), + Chainstore: store.NewChainStore(bs, bs, ds, filcns.Weight, nil), MetadataDS: ds, Blockstore: bs, }, err @@ -105,7 +106,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, vm.Syscalls(mock.Verifier), us) + sim.StateManager, err = stmgr.NewStateManager(nd.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -124,10 +125,14 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) if err != nil { return nil, err } + sm, err := stmgr.NewStateManager(nd.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), filcns.DefaultUpgradeSchedule()) + if err != nil { + return nil, xerrors.Errorf("creating state manager: %w", err) + } sim := &Simulation{ name: name, Node: nd, - StateManager: stmgr.NewStateManager(nd.Chainstore, vm.Syscalls(mock.Verifier)), + StateManager: sm, stages: stages, } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 83b45f942..c55724830 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -16,6 +16,7 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" @@ -35,7 +36,7 @@ type config struct { // upgradeSchedule constructs an stmgr.StateManager upgrade schedule, overriding any network upgrade // epochs as specified in the config. func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { - upgradeSchedule := stmgr.DefaultUpgradeSchedule() + upgradeSchedule := filcns.DefaultUpgradeSchedule() expected := make(map[network.Version]struct{}, len(c.Upgrades)) for nv := range c.Upgrades { expected[nv] = struct{}{} @@ -200,7 +201,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, vm.Syscalls(mock.Verifier), newUpgradeSchedule) + sm, err := stmgr.NewStateManager(sim.Node.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), newUpgradeSchedule) if err != nil { return err } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 6aa62be51..8dcba6b15 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -483,7 +484,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, j) + cst := store.NewChainStore(bs, bs, mds, filcns.Weight, j) defer cst.Close() //nolint:errcheck log.Infof("importing chain from %s...", fname) @@ -519,7 +520,10 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return err } - stm := stmgr.NewStateManager(cst, vm.Syscalls(ffiwrapper.ProofVerifier)) + stm, err := stmgr.NewStateManager(cst, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + if err != nil { + return err + } if !snapshot { log.Infof("validating imported chain...") diff --git a/conformance/driver.go b/conformance/driver.go index 0b3d42644..4574ca0b0 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -6,6 +6,7 @@ import ( "os" "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -101,9 +102,13 @@ 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, nil) - sm = stmgr.NewStateManager(cs, syscalls) + cs = store.NewChainStore(bs, bs, ds, filcns.Weight, nil) + tse = filcns.TipSetExecutor() + sm, err = stmgr.NewStateManager(cs, tse, syscalls, filcns.DefaultUpgradeSchedule()) ) + if err != nil { + return nil, err + } if params.Rand == nil { params.Rand = NewFixedRand() @@ -115,11 +120,10 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params defer cs.Close() //nolint:errcheck - blocks := make([]store.BlockMessages, 0, len(tipset.Blocks)) + blocks := make([]filcns.FilecoinBlockMessages, 0, len(tipset.Blocks)) for _, b := range tipset.Blocks { sb := store.BlockMessages{ - Miner: b.MinerAddr, - WinCount: b.WinCount, + Miner: b.MinerAddr, } for _, m := range b.Messages { msg, err := types.DecodeMessage(m) @@ -138,7 +142,10 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params sb.BlsMessages = append(sb.BlsMessages, msg) } } - blocks = append(blocks, sb) + blocks = append(blocks, filcns.FilecoinBlockMessages{ + BlockMessages: sb, + WinCount: b.WinCount, + }) } recordOutputs := &outputRecorder{ @@ -146,7 +153,8 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params results: []*vm.ApplyRet{}, } - postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), + postcid, receiptsroot, err := tse.ApplyBlocks(context.Background(), + sm, params.ParentEpoch, params.Preroot, blocks, @@ -196,7 +204,10 @@ 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, nil) + sm, err := stmgr.NewStateManager(nil, filcns.TipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) + if err != nil { + return nil, cid.Cid{}, err + } vmOpts := &vm.VMOpts{ StateBase: params.Preroot, @@ -216,7 +227,7 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP return nil, cid.Undef, err } - invoker := vm.NewActorRegistry() + invoker := filcns.NewActorRegistry() // register the chaos actor if required by the vector. if chaosOn, ok := d.selector["chaos_actor"]; ok && chaosOn == "true" { diff --git a/go.sum b/go.sum index 1e93c4cd2..f4ca0baae 100644 --- a/go.sum +++ b/go.sum @@ -169,6 +169,7 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -1513,6 +1514,7 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/itests/kit/ensemble_opts_nv.go b/itests/kit/ensemble_opts_nv.go index 651c3f324..a03e63f4a 100644 --- a/itests/kit/ensemble_opts_nv.go +++ b/itests/kit/ensemble_opts_nv.go @@ -3,6 +3,8 @@ package kit import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" ) @@ -28,7 +30,7 @@ func SDRUpgradeAt(calico, persian abi.ChainEpoch) EnsembleOpt { }, stmgr.Upgrade{ Network: network.Version7, Height: calico, - Migration: stmgr.UpgradeCalico, + Migration: filcns.UpgradeCalico, }, stmgr.Upgrade{ Network: network.Version8, Height: persian, @@ -42,7 +44,7 @@ func LatestActorsAt(upgradeHeight abi.ChainEpoch) EnsembleOpt { }, stmgr.Upgrade{ Network: network.Version13, Height: upgradeHeight, - Migration: stmgr.UpgradeActorsV5, + Migration: filcns.UpgradeActorsV5, }) } @@ -53,6 +55,6 @@ func TurboUpgradeAt(upgradeHeight abi.ChainEpoch) EnsembleOpt { }, stmgr.Upgrade{ Network: network.Version12, Height: upgradeHeight, - Migration: stmgr.UpgradeActorsV4, + Migration: filcns.UpgradeActorsV4, }) } diff --git a/lib/async/error.go b/lib/async/error.go new file mode 100644 index 000000000..0240b191b --- /dev/null +++ b/lib/async/error.go @@ -0,0 +1,51 @@ +package async + +// based on https://github.com/Gurpartap/async +// Apache-2.0 License, see https://github.com/Gurpartap/async/blob/master/License.txt + +import ( + "context" + "golang.org/x/xerrors" +) + +type ErrorFuture interface { + Await() error + AwaitContext(ctx context.Context) error +} + +type errorFuture struct { + await func(ctx context.Context) error +} + +func (f errorFuture) Await() error { + return f.await(context.Background()) +} + +func (f errorFuture) AwaitContext(ctx context.Context) error { + return f.await(ctx) +} + +func Err(f func() error) ErrorFuture { + var err error + c := make(chan struct{}, 1) + go func() { + defer close(c) + defer func() { + if rerr := recover(); rerr != nil { + err = xerrors.Errorf("async error: %s", rerr) + return + } + }() + err = f() + }() + return errorFuture{ + await: func(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-c: + return err + } + }, + } +} diff --git a/node/builder_chain.go b/node/builder_chain.go index f8eeaecb3..fbffd3e64 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -12,6 +12,8 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/exchange" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/lotus/chain/market" @@ -46,7 +48,7 @@ var ChainNode = Options( // Consensus settings Override(new(dtypes.DrandSchedule), modules.BuiltinDrandConfig), - Override(new(stmgr.UpgradeSchedule), stmgr.DefaultUpgradeSchedule()), + Override(new(stmgr.UpgradeSchedule), filcns.DefaultUpgradeSchedule()), Override(new(dtypes.NetworkName), modules.NetworkName), Override(new(modules.Genesis), modules.ErrorGenesis), Override(new(dtypes.AfterGenesisSet), modules.SetGenesis), @@ -65,6 +67,10 @@ var ChainNode = Options( Override(new(vm.SyscallBuilder), vm.Syscalls), // Consensus: Chain storage/access + Override(new(chain.Genesis), chain.LoadGenesis), + Override(new(store.WeightFunc), filcns.Weight), + Override(new(stmgr.Executor), filcns.TipSetExecutor()), + Override(new(consensus.Consensus), filcns.NewFilecoinExpectedConsensus), Override(new(*store.ChainStore), modules.ChainStore), Override(new(*stmgr.StateManager), modules.StateManager), Override(new(dtypes.ChainBitswap), modules.ChainBitswap), diff --git a/node/hello/hello.go b/node/hello/hello.go index e31b7d25b..e2278ced8 100644 --- a/node/hello/hello.go +++ b/node/hello/hello.go @@ -2,6 +2,7 @@ package hello import ( "context" + "github.com/filecoin-project/lotus/chain/consensus" "time" "github.com/filecoin-project/go-state-types/abi" @@ -48,10 +49,11 @@ type Service struct { cs *store.ChainStore syncer *chain.Syncer + cons consensus.Consensus pmgr *peermgr.PeerMgr } -func NewHelloService(h host.Host, cs *store.ChainStore, syncer *chain.Syncer, pmgr peermgr.MaybePeerMgr) *Service { +func NewHelloService(h host.Host, cs *store.ChainStore, syncer *chain.Syncer, cons consensus.Consensus, pmgr peermgr.MaybePeerMgr) *Service { if pmgr.Mgr == nil { log.Warn("running without peer manager") } @@ -61,6 +63,7 @@ func NewHelloService(h host.Host, cs *store.ChainStore, syncer *chain.Syncer, pm cs: cs, syncer: syncer, + cons: cons, pmgr: pmgr.Mgr, } } diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index 433573010..a37e4e990 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -11,6 +11,7 @@ import ( "sync" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/stmgr" "go.uber.org/fx" "golang.org/x/xerrors" @@ -78,7 +79,8 @@ type ChainAPI struct { WalletAPI ChainModuleAPI - Chain *store.ChainStore + Chain *store.ChainStore + TsExec stmgr.Executor // ExposedBlockstore is the global monolith blockstore that is safe to // expose externally. In the future, this will be segregated into two @@ -394,7 +396,7 @@ func (s stringKey) Key() string { // TODO: ActorUpgrade: this entire function is a problem (in theory) as we don't know the HAMT version. // In practice, hamt v0 should work "just fine" for reading. -func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) { +func resolveOnce(bs blockstore.Blockstore, tse stmgr.Executor) func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) { return func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) { store := adt.WrapStore(ctx, cbor.NewCborStore(bs)) @@ -468,7 +470,7 @@ func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.Nod }, nil, nil } - return resolveOnce(bs)(ctx, ds, n, names[1:]) + return resolveOnce(bs, tse)(ctx, ds, n, names[1:]) } if strings.HasPrefix(names[0], "@A:") { @@ -517,7 +519,7 @@ func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.Nod }, nil, nil } - return resolveOnce(bs)(ctx, ds, n, names[1:]) + return resolveOnce(bs, tse)(ctx, ds, n, names[1:]) } if names[0] == "@state" { @@ -531,7 +533,7 @@ func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.Nod return nil, nil, xerrors.Errorf("getting actor head for @state: %w", err) } - m, err := vm.DumpActorState(&act, head.RawData()) + m, err := vm.DumpActorState(tse.NewActorRegistry(), &act, head.RawData()) if err != nil { return nil, nil, err } @@ -565,7 +567,7 @@ func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.Nod }, nil, nil } - return resolveOnce(bs)(ctx, ds, n, names[1:]) + return resolveOnce(bs, tse)(ctx, ds, n, names[1:]) } return nd.ResolveLink(names) @@ -585,7 +587,7 @@ func (a *ChainAPI) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, r := &resolver.Resolver{ DAG: dag, - ResolveOnce: resolveOnce(bs), + ResolveOnce: resolveOnce(bs, a.TsExec), } node, err := r.ResolvePath(ctx, ip) diff --git a/node/impl/full/state.go b/node/impl/full/state.go index ba441c6cd..4b902565e 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -29,7 +29,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/beacon" - "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -88,6 +88,8 @@ type StateAPI struct { StateManager *stmgr.StateManager Chain *store.ChainStore Beacon beacon.Schedule + Consensus consensus.Consensus + TsExec stmgr.Executor } func (a *StateAPI) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { @@ -469,7 +471,7 @@ func (a *StateAPI) StateReadState(ctx context.Context, actor address.Address, ts return nil, xerrors.Errorf("getting actor head: %w", err) } - oif, err := vm.DumpActorState(act, blk.RawData()) + oif, err := vm.DumpActorState(a.TsExec.NewActorRegistry(), act, blk.RawData()) if err != nil { return nil, xerrors.Errorf("dumping actor state (a:%s): %w", actor, err) } @@ -487,7 +489,7 @@ func (a *StateAPI) StateDecodeParams(ctx context.Context, toAddr address.Address return nil, xerrors.Errorf("getting actor: %w", err) } - paramType, err := stmgr.GetParamType(act.Code, method) + paramType, err := stmgr.GetParamType(a.TsExec.NewActorRegistry(), act.Code, method) if err != nil { return nil, xerrors.Errorf("getting params type: %w", err) } @@ -500,7 +502,7 @@ func (a *StateAPI) StateDecodeParams(ctx context.Context, toAddr address.Address } func (a *StateAPI) StateEncodeParams(ctx context.Context, toActCode cid.Cid, method abi.MethodNum, params json.RawMessage) ([]byte, error) { - paramType, err := stmgr.GetParamType(toActCode, method) + paramType, err := stmgr.GetParamType(a.TsExec.NewActorRegistry(), toActCode, method) if err != nil { return nil, xerrors.Errorf("getting params type: %w", err) } @@ -524,10 +526,13 @@ func (a *StateAPI) MinerGetBaseInfo(ctx context.Context, maddr address.Address, } func (a *StateAPI) MinerCreateBlock(ctx context.Context, bt *api.BlockTemplate) (*types.BlockMsg, error) { - fblk, err := gen.MinerCreateBlock(ctx, a.StateManager, a.Wallet, bt) + fblk, err := a.Consensus.CreateBlock(ctx, a.Wallet, bt) if err != nil { return nil, err } + if fblk == nil { + return nil, nil + } var out types.BlockMsg out.Header = fblk.Header diff --git a/node/impl/full/sync.go b/node/impl/full/sync.go index 2c697483b..652ae3ecb 100644 --- a/node/impl/full/sync.go +++ b/node/impl/full/sync.go @@ -21,7 +21,7 @@ import ( type SyncAPI struct { fx.In - SlashFilter *slashfilter.SlashFilter + SlashFilter *slashfilter.SlashFilter `optional:"true"` Syncer *chain.Syncer PubSub *pubsub.PubSub NetName dtypes.NetworkName @@ -56,9 +56,11 @@ func (a *SyncAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) erro return xerrors.Errorf("loading parent block: %w", err) } - if err := a.SlashFilter.MinedBlock(blk.Header, parent.Height); err != nil { - log.Errorf(" SLASH FILTER ERROR: %s", err) - return xerrors.Errorf(" SLASH FILTER ERROR: %w", err) + if a.SlashFilter != nil { + if err := a.SlashFilter.MinedBlock(blk.Header, parent.Height); err != nil { + log.Errorf(" SLASH FILTER ERROR: %s", err) + return xerrors.Errorf(" SLASH FILTER ERROR: %w", err) + } } // TODO: should we have some sort of fast path to adding a local block? diff --git a/node/modules/chain.go b/node/modules/chain.go index a0e7f2f51..f9baf76cf 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -17,13 +17,13 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/exchange" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/stmgr" "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/journal" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/helpers" @@ -58,8 +58,8 @@ func ChainBlockService(bs dtypes.ExposedBlockstore, rem dtypes.ChainBitswap) dty return blockservice.New(bs, rem) } -func MessagePool(lc fx.Lifecycle, mpp messagepool.Provider, ds dtypes.MetadataDS, nn dtypes.NetworkName, j journal.Journal, protector dtypes.GCReferenceProtector) (*messagepool.MessagePool, error) { - mp, err := messagepool.New(mpp, ds, nn, j) +func MessagePool(lc fx.Lifecycle, us stmgr.UpgradeSchedule, mpp messagepool.Provider, ds dtypes.MetadataDS, nn dtypes.NetworkName, j journal.Journal, protector dtypes.GCReferenceProtector) (*messagepool.MessagePool, error) { + mp, err := messagepool.New(mpp, ds, us, nn, j) if err != nil { return nil, xerrors.Errorf("constructing mpool: %w", err) } @@ -72,8 +72,15 @@ 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, j journal.Journal) *store.ChainStore { - chain := store.NewChainStore(cbs, sbs, ds, j) +func ChainStore(lc fx.Lifecycle, + cbs dtypes.ChainBlockstore, + sbs dtypes.StateBlockstore, + ds dtypes.MetadataDS, + basebs dtypes.BaseBlockstore, + weight store.WeightFunc, + j journal.Journal) *store.ChainStore { + + chain := store.NewChainStore(cbs, sbs, ds, weight, j) if err := chain.Load(); err != nil { log.Warnf("loading chain state from disk: %s", err) @@ -100,14 +107,20 @@ func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlo return chain } -func NetworkName(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, syscalls vm.SyscallBuilder, us stmgr.UpgradeSchedule, _ dtypes.AfterGenesisSet) (dtypes.NetworkName, error) { +func NetworkName(mctx helpers.MetricsCtx, + lc fx.Lifecycle, + cs *store.ChainStore, + tsexec stmgr.Executor, + 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, syscalls, us) + sm, err := stmgr.NewStateManager(cs, tsexec, syscalls, us) if err != nil { return "", err } @@ -126,7 +139,8 @@ type SyncerParams struct { SyncMgrCtor chain.SyncManagerCtor Host host.Host Beacon beacon.Schedule - Verifier ffiwrapper.Verifier + Gent chain.Genesis + Consensus consensus.Consensus } func NewSyncer(params SyncerParams) (*chain.Syncer, error) { @@ -138,9 +152,8 @@ func NewSyncer(params SyncerParams) (*chain.Syncer, error) { smCtor = params.SyncMgrCtor h = params.Host b = params.Beacon - v = params.Verifier ) - syncer, err := chain.NewSyncer(ds, sm, ex, smCtor, h.ConnManager(), h.ID(), b, v) + syncer, err := chain.NewSyncer(ds, sm, ex, smCtor, h.ConnManager(), h.ID(), b, params.Gent, params.Consensus) if err != nil { return nil, err } diff --git a/node/modules/services.go b/node/modules/services.go index ebcacb247..17d4a7476 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" eventbus "github.com/libp2p/go-eventbus" @@ -136,11 +137,19 @@ func waitForSync(stmgr *stmgr.StateManager, epochs int, subscribe func()) { }) } -func HandleIncomingBlocks(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, s *chain.Syncer, bserv dtypes.ChainBlockService, chain *store.ChainStore, stmgr *stmgr.StateManager, h host.Host, nn dtypes.NetworkName) { +func HandleIncomingBlocks(mctx helpers.MetricsCtx, + lc fx.Lifecycle, + ps *pubsub.PubSub, + s *chain.Syncer, + bserv dtypes.ChainBlockService, + chain *store.ChainStore, + cns consensus.Consensus, + h host.Host, + nn dtypes.NetworkName) { ctx := helpers.LifecycleCtx(mctx, lc) v := sub.NewBlockValidator( - h.ID(), chain, stmgr, + h.ID(), chain, cns, func(p peer.ID) { ps.BlacklistPeer(p) h.ConnManager().TagPeer(p, "badblock", -1000) diff --git a/node/modules/stmgr.go b/node/modules/stmgr.go index af53457f9..d2f812ad4 100644 --- a/node/modules/stmgr.go +++ b/node/modules/stmgr.go @@ -8,8 +8,8 @@ import ( "github.com/filecoin-project/lotus/chain/store" ) -func StateManager(lc fx.Lifecycle, cs *store.ChainStore, sys vm.SyscallBuilder, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) +func StateManager(lc fx.Lifecycle, cs *store.ChainStore, exec stmgr.Executor, sys vm.SyscallBuilder, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { + sm, err := stmgr.NewStateManager(cs, exec, sys, us) if err != nil { return nil, err } diff --git a/node/options.go b/node/options.go index c92e209be..0793a150f 100644 --- a/node/options.go +++ b/node/options.go @@ -110,7 +110,9 @@ func as(in interface{}, as interface{}) interface{} { panic("outType is not a pointer") } - if reflect.TypeOf(in).Kind() != reflect.Func { + inType := reflect.TypeOf(in) + + if inType.Kind() != reflect.Func || inType.AssignableTo(outType.Elem()) { ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false) return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { @@ -121,8 +123,6 @@ func as(in interface{}, as interface{}) interface{} { }).Interface() } - inType := reflect.TypeOf(in) - ins := make([]reflect.Type, inType.NumIn()) outs := make([]reflect.Type, inType.NumOut()) From 30fccaa0bd8afd3ac8793b6a2a70a187cc16f0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 18:45:18 +0200 Subject: [PATCH 078/122] fix lint --- chain/consensus/filcns/compute_state.go | 14 +++++++------- chain/consensus/filcns/mine.go | 3 ++- chain/consensus/utils.go | 3 --- chain/gen/gen.go | 2 +- chain/stmgr/forks_test.go | 6 +++--- chain/store/store_test.go | 2 +- cmd/lotus-bench/import.go | 2 +- cmd/lotus-shed/balances.go | 4 ++-- cmd/lotus-sim/simulation/node.go | 4 ++-- cmd/lotus-sim/simulation/simulation.go | 2 +- cmd/lotus/daemon.go | 2 +- conformance/driver.go | 4 ++-- lib/async/error.go | 1 + node/builder_chain.go | 2 +- node/hello/hello.go | 4 ++-- 15 files changed, 27 insertions(+), 28 deletions(-) diff --git a/chain/consensus/filcns/compute_state.go b/chain/consensus/filcns/compute_state.go index 8f3ea474a..7e2007c69 100644 --- a/chain/consensus/filcns/compute_state.go +++ b/chain/consensus/filcns/compute_state.go @@ -51,13 +51,13 @@ func NewActorRegistry() *vm.ActorRegistry { return inv } -type tipSetExecutor struct{} +type TipSetExecutor struct{} -func TipSetExecutor() *tipSetExecutor { - return &tipSetExecutor{} +func NewTipSetExecutor() *TipSetExecutor { + return &TipSetExecutor{} } -func (t *tipSetExecutor) NewActorRegistry() *vm.ActorRegistry { +func (t *TipSetExecutor) NewActorRegistry() *vm.ActorRegistry { return NewActorRegistry() } @@ -67,7 +67,7 @@ type FilecoinBlockMessages struct { WinCount int64 } -func (t *tipSetExecutor) ApplyBlocks(ctx context.Context, sm *stmgr.StateManager, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []FilecoinBlockMessages, epoch abi.ChainEpoch, r vm.Rand, em stmgr.ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { +func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, sm *stmgr.StateManager, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []FilecoinBlockMessages, epoch abi.ChainEpoch, r vm.Rand, em stmgr.ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) defer done() @@ -256,7 +256,7 @@ func (t *tipSetExecutor) ApplyBlocks(ctx context.Context, sm *stmgr.StateManager return st, rectroot, nil } -func (t *tipSetExecutor) ExecuteTipSet(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet, em stmgr.ExecMonitor) (stateroot cid.Cid, rectsroot cid.Cid, err error) { +func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet, em stmgr.ExecMonitor) (stateroot cid.Cid, rectsroot cid.Cid, err error) { ctx, span := trace.StartSpan(ctx, "computeTipSetState") defer span.End() @@ -299,4 +299,4 @@ func (t *tipSetExecutor) ExecuteTipSet(ctx context.Context, sm *stmgr.StateManag return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, baseFee, ts) } -var _ stmgr.Executor = &tipSetExecutor{} +var _ stmgr.Executor = &TipSetExecutor{} diff --git a/chain/consensus/filcns/mine.go b/chain/consensus/filcns/mine.go index 4ee90df40..bbda35fcf 100644 --- a/chain/consensus/filcns/mine.go +++ b/chain/consensus/filcns/mine.go @@ -2,13 +2,14 @@ package filcns import ( "context" - "github.com/filecoin-project/lotus/chain/consensus" "github.com/ipfs/go-cid" "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) diff --git a/chain/consensus/utils.go b/chain/consensus/utils.go index fcd4a1f35..81e78bd88 100644 --- a/chain/consensus/utils.go +++ b/chain/consensus/utils.go @@ -6,7 +6,6 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" cbg "github.com/whyrusleeping/cbor-gen" "go.opencensus.io/trace" "golang.org/x/xerrors" @@ -15,8 +14,6 @@ import ( "github.com/filecoin-project/go-state-types/crypto" ) -var log = logging.Logger("consensus") - var ErrTemporal = errors.New("temporal error") func VerifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks [][]byte) error { diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 9de1c00b5..128421c18 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -246,7 +246,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), sys, us) + sm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), sys, us) if err != nil { return nil, xerrors.Errorf("initing stmgr: %w", err) } diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 133f2fe1e..a3e35da4b 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -122,7 +122,7 @@ func TestForkHeightTriggers(t *testing.T) { } sm, err := NewStateManager( - cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ + cg.ChainStore(), filcns.NewTipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, @@ -265,7 +265,7 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { var migrationCount int sm, err := NewStateManager( - cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ + cg.ChainStore(), filcns.NewTipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Expensive: true, Height: testForkHeight, @@ -400,7 +400,7 @@ func TestForkPreMigration(t *testing.T) { counter := make(chan struct{}, 10) sm, err := NewStateManager( - cg.ChainStore(), filcns.TipSetExecutor(), cg.StateManager().VMSys(), UpgradeSchedule{{ + cg.ChainStore(), filcns.NewTipSetExecutor(), 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 e8440068c..b393e8eb2 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -158,7 +158,7 @@ func TestChainExportImportFull(t *testing.T) { t.Fatal("imported chain differed from exported chain") } - sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) + sm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) if err != nil { t.Fatal(err) } diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 454cd7d73..7047866cb 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -257,7 +257,7 @@ var importBenchCmd = &cli.Command{ cs := store.NewChainStore(bs, bs, metadataDs, filcns.Weight, nil) defer cs.Close() //nolint:errcheck - stm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(verifier), filcns.DefaultUpgradeSchedule()) + stm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), vm.Syscalls(verifier), filcns.DefaultUpgradeSchedule()) if err != nil { return err } diff --git a/cmd/lotus-shed/balances.go b/cmd/lotus-shed/balances.go index 4691d287f..0de2e03b4 100644 --- a/cmd/lotus-shed/balances.go +++ b/cmd/lotus-shed/balances.go @@ -517,7 +517,7 @@ var chainBalanceStateCmd = &cli.Command{ cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + sm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) if err != nil { return err } @@ -741,7 +741,7 @@ var chainPledgeCmd = &cli.Command{ cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm, err := stmgr.NewStateManager(cs, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + sm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) if err != nil { return err } diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 3294fd71a..22463512a 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -106,7 +106,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.NewStateManager(nd.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), us) + sim.StateManager, err = stmgr.NewStateManager(nd.Chainstore, filcns.NewTipSetExecutor(), vm.Syscalls(mock.Verifier), us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -125,7 +125,7 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) if err != nil { return nil, err } - sm, err := stmgr.NewStateManager(nd.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), filcns.DefaultUpgradeSchedule()) + sm, err := stmgr.NewStateManager(nd.Chainstore, filcns.NewTipSetExecutor(), vm.Syscalls(mock.Verifier), filcns.DefaultUpgradeSchedule()) if err != nil { return nil, xerrors.Errorf("creating state manager: %w", err) } diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index c55724830..56030fa23 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -201,7 +201,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManager(sim.Node.Chainstore, filcns.TipSetExecutor(), vm.Syscalls(mock.Verifier), newUpgradeSchedule) + sm, err := stmgr.NewStateManager(sim.Node.Chainstore, filcns.NewTipSetExecutor(), vm.Syscalls(mock.Verifier), newUpgradeSchedule) if err != nil { return err } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 8dcba6b15..51aeca3c4 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -520,7 +520,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return err } - stm, err := stmgr.NewStateManager(cst, filcns.TipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) + stm, err := stmgr.NewStateManager(cst, filcns.NewTipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule()) if err != nil { return err } diff --git a/conformance/driver.go b/conformance/driver.go index 4574ca0b0..6fef5a76d 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -103,7 +103,7 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params syscalls = vm.Syscalls(ffiwrapper.ProofVerifier) cs = store.NewChainStore(bs, bs, ds, filcns.Weight, nil) - tse = filcns.TipSetExecutor() + tse = filcns.NewTipSetExecutor() sm, err = stmgr.NewStateManager(cs, tse, syscalls, filcns.DefaultUpgradeSchedule()) ) if err != nil { @@ -204,7 +204,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, err := stmgr.NewStateManager(nil, filcns.TipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) + sm, err := stmgr.NewStateManager(nil, filcns.NewTipSetExecutor(), nil, filcns.DefaultUpgradeSchedule()) if err != nil { return nil, cid.Cid{}, err } diff --git a/lib/async/error.go b/lib/async/error.go index 0240b191b..88e6b9b28 100644 --- a/lib/async/error.go +++ b/lib/async/error.go @@ -5,6 +5,7 @@ package async import ( "context" + "golang.org/x/xerrors" ) diff --git a/node/builder_chain.go b/node/builder_chain.go index fbffd3e64..a765c0223 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -69,7 +69,7 @@ var ChainNode = Options( // Consensus: Chain storage/access Override(new(chain.Genesis), chain.LoadGenesis), Override(new(store.WeightFunc), filcns.Weight), - Override(new(stmgr.Executor), filcns.TipSetExecutor()), + Override(new(stmgr.Executor), filcns.NewTipSetExecutor()), Override(new(consensus.Consensus), filcns.NewFilecoinExpectedConsensus), Override(new(*store.ChainStore), modules.ChainStore), Override(new(*stmgr.StateManager), modules.StateManager), diff --git a/node/hello/hello.go b/node/hello/hello.go index e2278ced8..5461dcc87 100644 --- a/node/hello/hello.go +++ b/node/hello/hello.go @@ -2,12 +2,12 @@ package hello import ( "context" - "github.com/filecoin-project/lotus/chain/consensus" "time" "github.com/filecoin-project/go-state-types/abi" "golang.org/x/xerrors" + cborutil "github.com/filecoin-project/go-cbor-util" "github.com/filecoin-project/go-state-types/big" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" @@ -16,9 +16,9 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" - cborutil "github.com/filecoin-project/go-cbor-util" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/peermgr" From 727bb845922ef87bddda934e96decf76f66a24bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 18:58:34 +0200 Subject: [PATCH 079/122] stmgr: drop MethdosMap --- chain/consensus/filcns/compute_state.go | 5 ----- chain/stmgr/utils.go | 2 -- cli/chain.go | 4 ++-- cli/multisig.go | 9 ++++----- cli/services.go | 4 ++-- cli/state.go | 4 ++-- cmd/lotus-shed/msg.go | 4 ++-- cmd/lotus-wallet/interactive.go | 6 +++--- cmd/tvx/extract_many.go | 5 ++--- lotuspond/front/src/chain/methodgen.go | 5 +++-- 10 files changed, 20 insertions(+), 28 deletions(-) diff --git a/chain/consensus/filcns/compute_state.go b/chain/consensus/filcns/compute_state.go index 7e2007c69..e0557b797 100644 --- a/chain/consensus/filcns/compute_state.go +++ b/chain/consensus/filcns/compute_state.go @@ -32,11 +32,6 @@ import ( "github.com/filecoin-project/lotus/metrics" ) -func init() { - // todo this is a hack, should fix the cli deps, and drop the map in stmgr - stmgr.MethodsMap = NewActorRegistry().Methods -} - func NewActorRegistry() *vm.ActorRegistry { inv := vm.NewActorRegistry() diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index ad0310602..42ef56e04 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -22,8 +22,6 @@ import ( "github.com/filecoin-project/lotus/node/modules/dtypes" ) -var MethodsMap map[cid.Cid]map[abi.MethodNum]vm.MethodMeta - 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 { diff --git a/cli/chain.go b/cli/chain.go index 875dcb21a..0cbdaa0f7 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -35,7 +35,7 @@ import ( "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/consensus/filcns" types "github.com/filecoin-project/lotus/chain/types" ) @@ -476,7 +476,7 @@ var ChainInspectUsage = &cli.Command{ return err } - mm := stmgr.MethodsMap[code][m.Message.Method] + mm := filcns.NewActorRegistry().Methods[code][m.Message.Method] // TODO: use remote map byMethod[mm.Name] += m.Message.GasLimit byMethodC[mm.Name]++ diff --git a/cli/multisig.go b/cli/multisig.go index c51677d85..7b93e55f9 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -10,10 +10,6 @@ import ( "strconv" "text/tabwriter" - "github.com/filecoin-project/lotus/chain/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/stmgr" cbg "github.com/whyrusleeping/cbor-gen" "github.com/filecoin-project/go-state-types/big" @@ -31,8 +27,11 @@ import ( "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/types" ) @@ -325,7 +324,7 @@ var msigInspectCmd = &cli.Command{ fmt.Fprintf(w, "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", txid, "pending", len(tx.Approved), target, types.FIL(tx.Value), "new account, unknown method", tx.Method, paramStr) } } else { - method := stmgr.MethodsMap[targAct.Code][tx.Method] + method := filcns.NewActorRegistry().Methods[targAct.Code][tx.Method] // TODO: use remote map if decParams && tx.Method != 0 { ptyp := reflect.New(method.Params.Elem()).Interface().(cbg.CBORUnmarshaler) diff --git a/cli/services.go b/cli/services.go index 0923680aa..8d131dfb1 100644 --- a/cli/services.go +++ b/cli/services.go @@ -12,7 +12,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/consensus/filcns" types "github.com/filecoin-project/lotus/chain/types" cid "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" @@ -86,7 +86,7 @@ func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address return nil, err } - methodMeta, found := stmgr.MethodsMap[act.Code][method] + methodMeta, found := filcns.NewActorRegistry().Methods[act.Code][method] // TODO: use remote map if !found { return nil, fmt.Errorf("method %d not found on actor %s", method, act.Code) } diff --git a/cli/state.go b/cli/state.go index 375630c9f..bac7efae8 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1367,7 +1367,7 @@ func codeStr(c cid.Cid) string { } func getMethod(code cid.Cid, method abi.MethodNum) string { - return stmgr.MethodsMap[code][method].Name + return filcns.NewActorRegistry().Methods[code][method].Name // todo: use remote } func toFil(f types.BigInt) types.FIL { @@ -1412,7 +1412,7 @@ func JsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, erro } func jsonReturn(code cid.Cid, method abi.MethodNum, ret []byte) (string, error) { - methodMeta, found := stmgr.MethodsMap[code][method] + methodMeta, found := filcns.NewActorRegistry().Methods[code][method] // TODO: use remote if !found { return "", fmt.Errorf("method %d not found on actor %s", method, code) } diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index c966b2e97..b640fb9c9 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -15,7 +15,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" @@ -140,7 +140,7 @@ func printMessage(cctx *cli.Context, msg *types.Message) error { return nil } - fmt.Println("Method:", stmgr.MethodsMap[toact.Code][msg.Method].Name) + fmt.Println("Method:", filcns.NewActorRegistry().Methods[toact.Code][msg.Method].Name) // todo use remote p, err := lcli.JsonParams(toact.Code, msg.Method, msg.Params) if err != nil { return err diff --git a/cmd/lotus-wallet/interactive.go b/cmd/lotus-wallet/interactive.go index e1ad2cbb2..40c3f8922 100644 --- a/cmd/lotus-wallet/interactive.go +++ b/cmd/lotus-wallet/interactive.go @@ -23,7 +23,7 @@ import ( "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" - "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -103,7 +103,7 @@ func (c *InteractiveWallet) WalletSign(ctx context.Context, k address.Address, m return xerrors.Errorf("looking up dest actor: %w", err) } - fmt.Println("Method:", stmgr.MethodsMap[toact.Code][cmsg.Method].Name) + fmt.Println("Method:", filcns.NewActorRegistry().Methods[toact.Code][cmsg.Method].Name) p, err := lcli.JsonParams(toact.Code, cmsg.Method, cmsg.Params) if err != nil { return err @@ -125,7 +125,7 @@ func (c *InteractiveWallet) WalletSign(ctx context.Context, k address.Address, m return xerrors.Errorf("looking up msig dest actor: %w", err) } - fmt.Println("\tMultiSig Proposal Method:", stmgr.MethodsMap[toact.Code][mp.Method].Name) + fmt.Println("\tMultiSig Proposal Method:", filcns.NewActorRegistry().Methods[toact.Code][mp.Method].Name) // todo use remote p, err := lcli.JsonParams(toact.Code, mp.Method, mp.Params) if err != nil { return err diff --git a/cmd/tvx/extract_many.go b/cmd/tvx/extract_many.go index 081678a17..ae196542e 100644 --- a/cmd/tvx/extract_many.go +++ b/cmd/tvx/extract_many.go @@ -13,12 +13,11 @@ import ( "github.com/fatih/color" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/hashicorp/go-multierror" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/urfave/cli/v2" - - "github.com/filecoin-project/lotus/chain/stmgr" ) var extractManyFlags struct { @@ -158,7 +157,7 @@ func runExtractMany(c *cli.Context) error { } // Lookup the method in actor method table. - if m, ok := stmgr.MethodsMap[codeCid]; !ok { + if m, ok := filcns.NewActorRegistry().Methods[codeCid]; !ok { return fmt.Errorf("unrecognized actor: %s", actorcode) } else if methodnum >= len(m) { return fmt.Errorf("unrecognized method number for actor %s: %d", actorcode, methodnum) diff --git a/lotuspond/front/src/chain/methodgen.go b/lotuspond/front/src/chain/methodgen.go index 5a00d5e6e..057ec7d01 100644 --- a/lotuspond/front/src/chain/methodgen.go +++ b/lotuspond/front/src/chain/methodgen.go @@ -8,7 +8,8 @@ import ( "github.com/multiformats/go-multihash" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/stmgr" + + "github.com/filecoin-project/lotus/chain/consensus/filcns" ) func main() { @@ -44,7 +45,7 @@ func main() { out := map[string][]string{} - for c, methods := range stmgr.MethodsMap { + for c, methods := range filcns.NewActorRegistry().Methods { cmh, err := multihash.Decode(c.Hash()) if err != nil { panic(err) From 7ef1b62b4187bed73ff3936f54477e4b423cef5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 19:27:10 +0200 Subject: [PATCH 080/122] sealing: Fix sector state accounting with FinalizeEarly --- extern/storage-sealing/sector_state.go | 12 ++++++++++-- extern/storage-sealing/stats.go | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/extern/storage-sealing/sector_state.go b/extern/storage-sealing/sector_state.go index deb5e9f28..34a0c6bbe 100644 --- a/extern/storage-sealing/sector_state.go +++ b/extern/storage-sealing/sector_state.go @@ -106,11 +106,19 @@ const ( Removed SectorState = "Removed" ) -func toStatState(st SectorState) statSectorState { +func toStatState(st SectorState, finEarly bool) statSectorState { switch st { case UndefinedSectorState, Empty, WaitDeals, AddPiece: return sstStaging - case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, SubmitPreCommitBatch, PreCommitBatchWait, WaitSeed, Committing, CommitFinalize, SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector: + case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, SubmitPreCommitBatch, PreCommitBatchWait, WaitSeed, Committing, CommitFinalize, FinalizeSector: + return sstSealing + case SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait: + if finEarly { + // we use statSectorState for throttling storage use. With FinalizeEarly + // we can consider sectors in states after CommitFinalize as finalized, so + // that more sectors can enter the sealing pipeline (and later be aggregated together) + return sstProving + } return sstSealing case Proving, Removed, Removing, Terminating, TerminateWait, TerminateFinality, TerminateFailed: return sstProving diff --git a/extern/storage-sealing/stats.go b/extern/storage-sealing/stats.go index 2688d8494..28556866a 100644 --- a/extern/storage-sealing/stats.go +++ b/extern/storage-sealing/stats.go @@ -37,7 +37,7 @@ func (ss *SectorStats) updateSector(cfg sealiface.Config, id abi.SectorID, st Se ss.totals[oldst]-- } - sst := toStatState(st) + sst := toStatState(st, cfg.FinalizeEarly) ss.bySector[id] = sst ss.totals[sst]++ From fea430a5535f2e8f50e1100d01c928659683ffc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 1 Sep 2021 11:59:47 +0200 Subject: [PATCH 081/122] Add partition info to the 'sectors status' command --- cmd/lotus-miner/sectors.go | 111 ++++++++++++++++++++++++++-- documentation/en/cli-lotus-miner.md | 8 +- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index ea091e1f4..d09605bd9 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -3,6 +3,7 @@ package main import ( "bufio" "encoding/json" + "errors" "fmt" "os" "sort" @@ -85,12 +86,23 @@ var sectorsStatusCmd = &cli.Command{ ArgsUsage: "", Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "log", - Usage: "display event log", + Name: "log", + Usage: "display event log", + Aliases: []string{"l"}, }, &cli.BoolFlag{ - Name: "on-chain-info", - Usage: "show sector on chain info", + Name: "on-chain-info", + Usage: "show sector on chain info", + Aliases: []string{"c"}, + }, + &cli.BoolFlag{ + Name: "partition-info", + Usage: "show partition related info", + Aliases: []string{"p"}, + }, + &cli.BoolFlag{ + Name: "proof", + Usage: "print snark proof bytes as hex", }, }, Action: func(cctx *cli.Context) error { @@ -126,7 +138,9 @@ var sectorsStatusCmd = &cli.Command{ fmt.Printf("SeedH:\t\t%d\n", status.Seed.Epoch) fmt.Printf("Precommit:\t%s\n", status.PreCommitMsg) fmt.Printf("Commit:\t\t%s\n", status.CommitMsg) - fmt.Printf("Proof:\t\t%x\n", status.Proof) + if cctx.Bool("proof") { + fmt.Printf("Proof:\t\t%x\n", status.Proof) + } fmt.Printf("Deals:\t\t%v\n", status.Deals) fmt.Printf("Retries:\t%d\n", status.Retries) if status.LastErr != "" { @@ -146,6 +160,93 @@ var sectorsStatusCmd = &cli.Command{ fmt.Printf("Early:\t\t%v\n", status.Early) } + if cctx.Bool("partition-info") { + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + 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 + } + + errFound := errors.New("found") + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + pas, err := part.AllSectors() + if err != nil { + return err + } + + set, err := pas.IsSet(id) + if err != nil { + return err + } + if set { + fmt.Printf("\nDeadline:\t%d\n", dlIdx) + fmt.Printf("Partition:\t%d\n", partIdx) + + checkIn := func(name string, bg func() (bitfield.BitField, error)) error { + bf, err := bg() + if err != nil { + return err + } + + set, err := bf.IsSet(id) + if err != nil { + return err + } + setstr := "no" + if set { + setstr = "yes" + } + fmt.Printf("%s: \t%s\n", name, setstr) + return nil + } + + if err := checkIn("Unproven", part.UnprovenSectors); err != nil { + return err + } + if err := checkIn("Live", part.LiveSectors); err != nil { + return err + } + if err := checkIn("Active", part.ActiveSectors); err != nil { + return err + } + if err := checkIn("Faulty", part.FaultySectors); err != nil { + return err + } + if err := checkIn("Recovering", part.RecoveringSectors); err != nil { + return err + } + + return errFound + } + + return nil + }) + }); err != errFound { + if err != nil { + return err + } + + fmt.Println("\nNot found in any partition") + } + } + if cctx.Bool("log") { fmt.Printf("--------\nEvent Log:\n") diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index c96b3140e..ea2e68c39 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -1515,9 +1515,11 @@ USAGE: lotus-miner sectors status [command options] OPTIONS: - --log display event log (default: false) - --on-chain-info show sector on chain info (default: false) - --help, -h show help (default: false) + --log, -l display event log (default: false) + --on-chain-info, -c show sector on chain info (default: false) + --partition-info, -p show partition related info (default: false) + --proof print snark proof bytes as hex (default: false) + --help, -h show help (default: false) ``` From 6379d5129f307e82957e26be0950f843ad804f6a Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 2 Sep 2021 15:40:15 -0400 Subject: [PATCH 082/122] integrate the proof patch: tag proofs-v9-revert-deps-hotfix --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 6e901ee3d..78366aeb8 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 6e901ee3dd5399f5aa3395d48f7d9540b661b944 +Subproject commit 78366aeb85796c0687a53107a6cd52da7bd8abd5 From 56acfe30afa17996a94a4cbeab761c3f94e7c620 Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Thu, 2 Sep 2021 16:03:55 -0400 Subject: [PATCH 083/122] 0.5% => 1% test threshold after seeing .75% in the wild --- .codecov.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 68a5ecc5a..c266bc3ea 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -19,7 +19,7 @@ coverage: project: tools-and-tests: target: auto - threshold: 0.5% + threshold: 1% informational: true paths: - "testplans" @@ -33,27 +33,27 @@ coverage: - "build" markets: target: auto - threshold: 0.5% + threshold: 1% informational: false paths: - "markets" - "paychmgr" miner: target: auto - threshold: 0.5% + threshold: 1% informational: false paths: - "miner" - "storage" chain: target: auto - threshold: 0.5% + threshold: 1% informational: false paths: - "chain" node: target: auto - threshold: 0.5% + threshold: 1% informational: false paths: - "node" @@ -66,7 +66,7 @@ coverage: - "journal" cli: target: auto - threshold: 0.5% + threshold: 1% informational: true paths: - "cli" From 386910589d2b30b85141af0c7751e781d87e9592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 13:56:25 +0200 Subject: [PATCH 084/122] dealpublisher: Fully validate deals before publishing --- markets/storageadapter/dealpublisher.go | 47 +++++++++++++++++++- markets/storageadapter/dealpublisher_test.go | 4 ++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index f458e7a4f..7508a5d33 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -14,6 +14,8 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/filecoin-project/lotus/api" @@ -35,6 +37,7 @@ type dealPublisherAPI interface { WalletHas(context.Context, address.Address) (bool, error) StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) + StateCall(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) } // DealPublisher batches deal publishing so that many deals can be included in @@ -295,7 +298,7 @@ func (p *DealPublisher) publishReady(ready []*pendingDeal) { // Validate the deal if err := p.validateDeal(pd.deal); err != nil { // Validation failed, complete immediately with an error - go onComplete(pd, cid.Undef, err) + go onComplete(pd, cid.Undef, xerrors.Errorf("publish validation failed: %w", err)) continue } @@ -315,6 +318,13 @@ func (p *DealPublisher) publishReady(ready []*pendingDeal) { // validateDeal checks that the deal proposal start epoch hasn't already // elapsed func (p *DealPublisher) validateDeal(deal market2.ClientDealProposal) error { + start := time.Now() + + pcid, err := deal.Proposal.Cid() + if err != nil { + return xerrors.Errorf("computing proposal cid: %w", err) + } + head, err := p.api.ChainHead(p.ctx) if err != nil { return err @@ -324,6 +334,41 @@ func (p *DealPublisher) validateDeal(deal market2.ClientDealProposal) error { "cannot publish deal with piece CID %s: current epoch %d has passed deal proposal start epoch %d", deal.Proposal.PieceCID, head.Height(), deal.Proposal.StartEpoch) } + + mi, err := p.api.StateMinerInfo(p.ctx, deal.Proposal.Provider, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting provider info: %w", err) + } + + params, err := actors.SerializeParams(&market2.PublishStorageDealsParams{ + Deals: []market0.ClientDealProposal{deal}, + }) + if err != nil { + return xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err) + } + + addr, _, err := p.as.AddressFor(p.ctx, p.api, mi, api.DealPublishAddr, big.Zero(), big.Zero()) + if err != nil { + return xerrors.Errorf("selecting address for publishing deals: %w", err) + } + + res, err := p.api.StateCall(p.ctx, &types.Message{ + To: market.Address, + From: addr, + Value: types.NewInt(0), + Method: market.Methods.PublishStorageDeals, + Params: params, + }, head.Key()) + if err != nil { + return xerrors.Errorf("simulating deal publish message: %w", err) + } + if res.MsgRct.ExitCode != exitcode.Ok { + return xerrors.Errorf("simulating deal publish message: non-zero exitcode %s; message: %s", res.MsgRct.ExitCode, res.Error) + } + + took := time.Now().Sub(start) + log.Infow("validating deal", "took", took, "proposal", pcid) + return nil } diff --git a/markets/storageadapter/dealpublisher_test.go b/markets/storageadapter/dealpublisher_test.go index a4991396a..9699b1739 100644 --- a/markets/storageadapter/dealpublisher_test.go +++ b/markets/storageadapter/dealpublisher_test.go @@ -381,6 +381,10 @@ func (d *dpAPI) StateLookupID(ctx context.Context, a address.Address, key types. panic("don't call me") } +func (d *dpAPI) StateCall(ctx context.Context, message *types.Message, key types.TipSetKey) (*api.InvocResult, error) { + return &api.InvocResult{MsgRct: &types.MessageReceipt{ExitCode: 0}}, nil +} + func getClientActor(t *testing.T) address.Address { return tutils.NewActorAddr(t, "client") } From b4e9bc50f8d95bcb94114ce7a68f0b37053c09c5 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Tue, 1 Jun 2021 15:34:06 -0700 Subject: [PATCH 085/122] feat(deps): update go-graphsync v0.8.0 Update to go-graphsync v0.8.0 with go-ipld-prime linksystem branch & trusted store. --- go.mod | 10 +++++----- go.sum | 28 ++++++++++++++++------------ node/modules/graphsync.go | 10 ++++------ node/modules/storageminer.go | 5 ++--- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 092b375da..cc683c532 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 github.com/filecoin-project/go-fil-markets v1.11.0 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-multistore v0.0.4-0.20210601185713-428a2691a567 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.20210810190654-139e0e79e69e @@ -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.9 + github.com/ipfs/go-graphsync v0.8.0 github.com/ipfs/go-ipfs-blockstore v1.0.4 github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 @@ -98,9 +98,9 @@ require ( github.com/ipfs/go-path v0.0.7 github.com/ipfs/go-unixfs v0.2.6 github.com/ipfs/interface-go-ipfs-core v0.2.3 - github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d + github.com/ipld/go-car v0.3.1-0.20210601190600-f512dac51e8e github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 - github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 + github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db github.com/kelseyhightower/envconfig v1.4.0 github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-eventbus v0.2.1 @@ -131,7 +131,7 @@ require ( github.com/multiformats/go-varint v0.0.6 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/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e github.com/prometheus/client_golang v1.10.0 github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 diff --git a/go.sum b/go.sum index 34c208b14..f502f649f 100644 --- a/go.sum +++ b/go.sum @@ -303,8 +303,10 @@ github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AG github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec h1:rGI5I7fdU4viManxmDdbk5deZO7afe6L1Wc04dAmlOM= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= -github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0DzzQwqsL0XarpnI= github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= +github.com/filecoin-project/go-multistore v0.0.4-0.20210601185713-428a2691a567 h1:7cmHp5eWyz1cOnfJ1kjUgjXWQNadJfJNkri/TJJTn+I= +github.com/filecoin-project/go-multistore v0.0.4-0.20210601185713-428a2691a567/go.mod h1:ya2COxasTn5LTaQ3Na++smn6FxMjW+oNtXsLI+tsqVk= + github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 h1:0BogtftbcgyBx4lP2JWM00ZK7/pXmgnrDqKp9aLTgVs= github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1/go.mod h1:VYVPJqwpsfmtoHnAmPx6MUwmrK6HIcDqZJiuZhtmfLQ= @@ -356,6 +358,8 @@ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJn github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -648,9 +652,8 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.6.8/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= -github.com/ipfs/go-graphsync v0.6.9 h1:I15gVcZuqsaeaj64/SjlwiIAc9MkOgfSv0M1CgcoFRE= -github.com/ipfs/go-graphsync v0.6.9/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.8.0 h1:Zhh6QdTqdipYHD71ncLO8eA6c8EGUTOoJ4Rqybw3K+o= +github.com/ipfs/go-graphsync v0.8.0/go.mod h1:CLxN859dUTcXCav1DvNvmAUWPZfmNLjlGLJYy+c3dlM= 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= @@ -767,21 +770,23 @@ github.com/ipfs/iptb-plugins v0.2.1 h1:au4HWn9/pRPbkxA08pDx2oRAs4cnbgQWgV0teYXuu github.com/ipfs/iptb-plugins v0.2.1/go.mod h1:QXMbtIWZ+jRsW8a4h13qAKU7jcM7qaittO8wOsTP0Rs= github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBHl3g= github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= -github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= +github.com/ipld/go-car v0.3.1-0.20210601190600-f512dac51e8e h1:iTAJWTWEMe0Lx8JwRaIYrYgDuI9bVzkgogGz43Yv9Eo= +github.com/ipld/go-car v0.3.1-0.20210601190600-f512dac51e8e/go.mod h1:wUxBdwOLA9/0HZBi3fnTBzla0MuwlqgJLyrhOg1XaKI= github.com/ipld/go-car/v2 v2.0.0-beta1.0.20210721090610-5a9d1b217d25/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= github.com/ipld/go-car/v2 v2.0.2/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 h1:6Z0beJSZNsRY+7udoqUl4gQ/tqtrPuRvDySrlsvbqZA= github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= -github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= +github.com/ipld/go-codec-dagpb v1.2.0 h1:2umV7ud8HBMkRuJgd8gXw95cLhwmcYrihS3cQEy9zpI= +github.com/ipld/go-codec-dagpb v1.2.0/go.mod h1:6nBN7X7h8EOsEejZGqC7tej5drsdBAXbMHyBT+Fne5s= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= -github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 h1:RbRHv8epkmvBYA5cGfz68GUSbOgx5j/7ObLIl4Rsif0= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= -github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= +github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= +github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db h1:kFwGn8rXa/Z31ev1OFNQsYeNKNCdifnTPl/NvPy5L38= +github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime-proto v0.0.0-20200428191222-c1ffdadc01e1/go.mod h1:OAV6xBmuTLsPZ+epzKkPB1e25FHk/vCtyatkdHcArLs= github.com/ipld/go-ipld-prime-proto v0.0.0-20200922192210-9a2bfd4440a6/go.mod h1:3pHYooM9Ea65jewRwrb2u5uHZCNkNTe9ABsVB+SrkH0= -github.com/ipld/go-ipld-prime-proto v0.1.0 h1:j7gjqrfwbT4+gXpHwEx5iMssma3mnctC7YaCimsFP70= github.com/ipld/go-ipld-prime-proto v0.1.0/go.mod h1:11zp8f3sHVgIqtb/c9Kr5ZGqpnCLF1IVTNOez9TopzE= github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 h1:QG4CGBqCeuBo6aZlGAamSkxWdgWfZGeE49eUOWJPA4c= github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52/go.mod h1:fdg+/X9Gg4AsAIzWpEHwnqd+QY3b7lajxyjE1m4hkq4= @@ -977,7 +982,6 @@ github.com/libp2p/go-libp2p-core v0.8.6 h1:3S8g006qG6Tjpj1JdRK2S+TWc2DJQKX/RG9fd 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= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-daemon v0.2.2/go.mod h1:kyrpsLB2JeNYR2rvXSVWyY0iZuRIMhqzWR3im9BV6NQ= github.com/libp2p/go-libp2p-discovery v0.0.1/go.mod h1:ZkkF9xIFRLA1xCc7bstYFkd80gBGK8Fc1JqGoU2i+zI= @@ -1029,7 +1033,6 @@ github.com/libp2p/go-libp2p-noise v0.2.0 h1:wmk5nhB9a2w2RxMOyvsoKjizgJOEaJdfAakr github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.0.1/go.mod h1:nXQvOBbwVqoP+T5Y5nCjeH4sP9IX/J0AMzcDUVruVoo= github.com/libp2p/go-libp2p-peer v0.1.1/go.mod h1:jkF12jGB4Gk/IOo+yomm+7oLWxF278F7UnrYUQ1Q8es= -github.com/libp2p/go-libp2p-peer v0.2.0 h1:EQ8kMjaCUwt/Y5uLgjT8iY2qg0mGUT0N1zUjer50DsY= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.0.1/go.mod h1:RabLyPVJLuNQ+GFyoEkfi8H4Ti6k/HtZJ7YKgtSq+20= github.com/libp2p/go-libp2p-peerstore v0.0.6/go.mod h1:RabLyPVJLuNQ+GFyoEkfi8H4Ti6k/HtZJ7YKgtSq+20= @@ -1447,8 +1450,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a h1:hjZfReYVLbqFkAtr2us7vdy04YWz3LVAirzP7reh8+M= github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= +github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e h1:ZOcivgkkFRnjfoTcGsDq3UQYiBmekwLA+qg0OjyB/ls= +github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= diff --git a/node/modules/graphsync.go b/node/modules/graphsync.go index cbd67ad1f..f0f4400b8 100644 --- a/node/modules/graphsync.go +++ b/node/modules/graphsync.go @@ -18,13 +18,11 @@ import ( func Graphsync(parallelTransfers uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, r repo.LockedRepo, clientBs dtypes.ClientBlockstore, chainBs dtypes.ExposedBlockstore, h host.Host) (dtypes.Graphsync, error) { return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, r repo.LockedRepo, clientBs dtypes.ClientBlockstore, chainBs dtypes.ExposedBlockstore, h host.Host) (dtypes.Graphsync, error) { graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - loader := storeutil.LoaderForBlockstore(clientBs) - storer := storeutil.StorerForBlockstore(clientBs) + lsys := storeutil.LinkSystemForBlockstore(clientBs) - gs := graphsyncimpl.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsyncimpl.RejectAllRequestsByDefault(), graphsyncimpl.MaxInProgressRequests(parallelTransfers)) - chainLoader := storeutil.LoaderForBlockstore(chainBs) - chainStorer := storeutil.StorerForBlockstore(chainBs) - err := gs.RegisterPersistenceOption("chainstore", chainLoader, chainStorer) + gs := graphsyncimpl.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, lsys, graphsyncimpl.RejectAllRequestsByDefault(), graphsyncimpl.MaxInProgressRequests(parallelTransfers)) + chainLinkSystem := storeutil.LinkSystemForBlockstore(chainBs) + err := gs.RegisterPersistenceOption("chainstore", chainLinkSystem) if err != nil { return nil, err } diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 075eed99d..4d84ffcaf 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -394,9 +394,8 @@ func StagingBlockstore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRe func StagingGraphsync(parallelTransfers uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - loader := storeutil.LoaderForBlockstore(ibs) - storer := storeutil.StorerForBlockstore(ibs) - gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsync.RejectAllRequestsByDefault(), graphsync.MaxInProgressRequests(parallelTransfers)) + lsys := storeutil.LinkSystemForBlockstore(ibs) + gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, lsys, graphsync.RejectAllRequestsByDefault(), graphsync.MaxInProgressRequests(parallelTransfers)) return gs } From 91804d5746d8cab798debb11c9b62398e2023222 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 26 Aug 2021 23:10:18 -0700 Subject: [PATCH 086/122] feat(deps): update go-graphsync v0.9.0 --- api/docgen/docgen.go | 4 ---- go.mod | 5 ++--- go.sum | 28 +++++++++++++++++----------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 4fd246289..ce22fefd1 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -13,7 +13,6 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-multistore" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" @@ -90,7 +89,6 @@ func init() { addExample(pid) addExample(&pid) - multistoreIDExample := multistore.StoreID(50) storeIDExample := imports.ID(50) addExample(bitfield.NewFromSet([]uint64{5})) @@ -124,8 +122,6 @@ func init() { addExample(datatransfer.Ongoing) addExample(storeIDExample) addExample(&storeIDExample) - addExample(multistoreIDExample) - addExample(&multistoreIDExample) addExample(retrievalmarket.ClientEventDealAccepted) addExample(retrievalmarket.DealStatusNew) addExample(network.ReachabilityPublic) diff --git a/go.mod b/go.mod index cc683c532..6a534a3b2 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,6 @@ require ( github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 github.com/filecoin-project/go-fil-markets v1.11.0 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec - github.com/filecoin-project/go-multistore v0.0.4-0.20210601185713-428a2691a567 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.20210810190654-139e0e79e69e @@ -78,7 +77,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.8.0 + github.com/ipfs/go-graphsync v0.9.0 github.com/ipfs/go-ipfs-blockstore v1.0.4 github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 @@ -100,7 +99,7 @@ require ( github.com/ipfs/interface-go-ipfs-core v0.2.3 github.com/ipld/go-car v0.3.1-0.20210601190600-f512dac51e8e github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 - github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db + github.com/ipld/go-ipld-prime v0.12.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-eventbus v0.2.1 diff --git a/go.sum b/go.sum index f502f649f..0e79a3c52 100644 --- a/go.sum +++ b/go.sum @@ -169,7 +169,6 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -304,9 +303,6 @@ github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBw github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec h1:rGI5I7fdU4viManxmDdbk5deZO7afe6L1Wc04dAmlOM= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= -github.com/filecoin-project/go-multistore v0.0.4-0.20210601185713-428a2691a567 h1:7cmHp5eWyz1cOnfJ1kjUgjXWQNadJfJNkri/TJJTn+I= -github.com/filecoin-project/go-multistore v0.0.4-0.20210601185713-428a2691a567/go.mod h1:ya2COxasTn5LTaQ3Na++smn6FxMjW+oNtXsLI+tsqVk= - github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 h1:0BogtftbcgyBx4lP2JWM00ZK7/pXmgnrDqKp9aLTgVs= github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1/go.mod h1:VYVPJqwpsfmtoHnAmPx6MUwmrK6HIcDqZJiuZhtmfLQ= @@ -631,6 +627,7 @@ github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaH 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/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-badger v0.2.6/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= 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= @@ -652,8 +649,10 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.8.0 h1:Zhh6QdTqdipYHD71ncLO8eA6c8EGUTOoJ4Rqybw3K+o= -github.com/ipfs/go-graphsync v0.8.0/go.mod h1:CLxN859dUTcXCav1DvNvmAUWPZfmNLjlGLJYy+c3dlM= +github.com/ipfs/go-graphsync v0.6.8/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.9/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.9.0 h1:T22kORlNbJUIm/+avUIfLnAf1BkwKG6aS19NsRVjVVY= +github.com/ipfs/go-graphsync v0.9.0/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= 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= @@ -777,14 +776,18 @@ github.com/ipld/go-car/v2 v2.0.0-beta1.0.20210721090610-5a9d1b217d25/go.mod h1:I github.com/ipld/go-car/v2 v2.0.2/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 h1:6Z0beJSZNsRY+7udoqUl4gQ/tqtrPuRvDySrlsvbqZA= github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= -github.com/ipld/go-codec-dagpb v1.2.0 h1:2umV7ud8HBMkRuJgd8gXw95cLhwmcYrihS3cQEy9zpI= github.com/ipld/go-codec-dagpb v1.2.0/go.mod h1:6nBN7X7h8EOsEejZGqC7tej5drsdBAXbMHyBT+Fne5s= +github.com/ipld/go-codec-dagpb v1.3.0 h1:czTcaoAuNNyIYWs6Qe01DJ+sEX7B+1Z0LcXjSatMGe8= +github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= +github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db h1:kFwGn8rXa/Z31ev1OFNQsYeNKNCdifnTPl/NvPy5L38= -github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= +github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= +github.com/ipld/go-ipld-prime v0.12.0 h1:JapyKWTsJgmhrPI7hfx4V798c/RClr85sXfBZnH1VIw= +github.com/ipld/go-ipld-prime v0.12.0/go.mod h1:hy8b93WleDMRKumOJnTIrr0MbbFbx9GD6Kzxa53Xppc= +github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= github.com/ipld/go-ipld-prime-proto v0.0.0-20200428191222-c1ffdadc01e1/go.mod h1:OAV6xBmuTLsPZ+epzKkPB1e25FHk/vCtyatkdHcArLs= github.com/ipld/go-ipld-prime-proto v0.0.0-20200922192210-9a2bfd4440a6/go.mod h1:3pHYooM9Ea65jewRwrb2u5uHZCNkNTe9ABsVB+SrkH0= github.com/ipld/go-ipld-prime-proto v0.1.0/go.mod h1:11zp8f3sHVgIqtb/c9Kr5ZGqpnCLF1IVTNOez9TopzE= @@ -1517,7 +1520,6 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1641,6 +1643,8 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/warpfork/go-testmark v0.3.0 h1:Q81c4u7hT+BR5kNfNQhEF0VT2pmL7+Kk0wD+ORYl7iA= +github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= @@ -1667,6 +1671,7 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8 h1:TEv7MId88TyIqIUL4hbf9otOookIolMxlEbN0ro671Y= github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= @@ -2156,8 +2161,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/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= From dc1f482a7db7068d6b76f694c37ba7ededdd461c Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Fri, 3 Sep 2021 16:19:54 +0200 Subject: [PATCH 087/122] feat: update to markets v1.12.0 --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 6a534a3b2..86d2b430a 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,10 @@ 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.9.0 + github.com/filecoin-project/go-data-transfer v1.10.0 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.11.0 + github.com/filecoin-project/go-fil-markets v1.12.0 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498 diff --git a/go.sum b/go.sum index 0e79a3c52..ec9132236 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,8 @@ 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.9.0 h1:qW6tCIfGdK2cRf2AhEBZD1TzuhQDrbLpvllhLiN8EXk= -github.com/filecoin-project/go-data-transfer v1.9.0/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= +github.com/filecoin-project/go-data-transfer v1.10.0 h1:hrD9ns4V+qIuc6u2dx8B0atzTaQgqEz/ks0+wcj8ca4= +github.com/filecoin-project/go-data-transfer v1.10.0/go.mod h1:uQtqy6vUAY5v70ZHdkF5mJ8CjVtjj/JA3aOoaqzWTVw= 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= @@ -291,8 +291,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.11.0 h1:zcdeKtg9AnzLMkIPBu2WH/UYt1w1wjMFEIDp2Kdv9Mc= -github.com/filecoin-project/go-fil-markets v1.11.0/go.mod h1:j5oN0YqZF4XV1gh3be+2me9j1inKsKRrfbvvxwf70+Q= +github.com/filecoin-project/go-fil-markets v1.12.0 h1:RpU5bLaMADVrU4CgLxKMGHC2ZUocNV35uINxogQCf00= +github.com/filecoin-project/go-fil-markets v1.12.0/go.mod h1:XuuZFaFujI47nrgfQJiq7jWB+6rRya6nm7Sj6uXQ80U= 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= @@ -649,8 +649,6 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.6.8/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= -github.com/ipfs/go-graphsync v0.6.9/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= github.com/ipfs/go-graphsync v0.9.0 h1:T22kORlNbJUIm/+avUIfLnAf1BkwKG6aS19NsRVjVVY= github.com/ipfs/go-graphsync v0.9.0/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= @@ -1854,6 +1852,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2077,6 +2076,7 @@ golang.org/x/tools v0.0.0-20201112185108-eeaa07dd7696/go.mod h1:emZCQorbCU4vsT4f 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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210225150353-54dc8c5edb56/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 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= From 42135ec7cc146c5e93cbe61928003cfbbc04cd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 31 Aug 2021 19:14:54 +0200 Subject: [PATCH 088/122] dealpublisher: Test deal verification --- markets/storageadapter/dealpublisher_test.go | 58 ++++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/markets/storageadapter/dealpublisher_test.go b/markets/storageadapter/dealpublisher_test.go index 9699b1739..351a00171 100644 --- a/markets/storageadapter/dealpublisher_test.go +++ b/markets/storageadapter/dealpublisher_test.go @@ -6,24 +6,25 @@ import ( "testing" "time" - "github.com/filecoin-project/go-state-types/crypto" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/ipfs/go-cid" "github.com/raulk/clock" + "golang.org/x/xerrors" "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" tutils "github.com/filecoin-project/specs-actors/v2/support/testing" - "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api" ) func TestDealPublisher(t *testing.T) { @@ -41,6 +42,7 @@ func TestDealPublisher(t *testing.T) { expiredDeals int dealCountAfterPublishPeriod int expectedDealsPerMsg []int + failOne bool }{{ name: "publish one deal within publish period", publishPeriod: 10 * time.Millisecond, @@ -93,6 +95,14 @@ func TestDealPublisher(t *testing.T) { ctxCancelledWithinPublishPeriod: 0, dealCountAfterPublishPeriod: 2, expectedDealsPerMsg: []int{1, 1, 1, 1}, + }, { + name: "one deal failing doesn't fail the entire batch", + publishPeriod: 10 * time.Millisecond, + maxDealsPerMsg: 5, + dealCountWithinPublishPeriod: 2, + dealCountAfterPublishPeriod: 0, + failOne: true, + expectedDealsPerMsg: []int{1}, }} for _, tc := range testCases { @@ -112,14 +122,18 @@ func TestDealPublisher(t *testing.T) { // Publish deals within publish period for i := 0; i < tc.dealCountWithinPublishPeriod; i++ { - deal := publishDeal(t, dp, false, false) - dealsToPublish = append(dealsToPublish, deal) + if tc.failOne && i == 1 { + publishDeal(t, dp, i, false, false) + } else { + deal := publishDeal(t, dp, 0, false, false) + dealsToPublish = append(dealsToPublish, deal) + } } for i := 0; i < tc.ctxCancelledWithinPublishPeriod; i++ { - publishDeal(t, dp, true, false) + publishDeal(t, dp, 0, true, false) } for i := 0; i < tc.expiredDeals; i++ { - publishDeal(t, dp, false, true) + publishDeal(t, dp, 0, false, true) } // Wait until publish period has elapsed @@ -151,7 +165,7 @@ func TestDealPublisher(t *testing.T) { // Publish deals after publish period for i := 0; i < tc.dealCountAfterPublishPeriod; i++ { - deal := publishDeal(t, dp, false, false) + deal := publishDeal(t, dp, 0, false, false) dealsToPublish = append(dealsToPublish, deal) } @@ -187,12 +201,12 @@ func TestForcePublish(t *testing.T) { // Queue three deals for publishing, one with a cancelled context var dealsToPublish []market.ClientDealProposal // 1. Regular deal - deal := publishDeal(t, dp, false, false) + deal := publishDeal(t, dp, 0, false, false) dealsToPublish = append(dealsToPublish, deal) // 2. Deal with cancelled context - publishDeal(t, dp, true, false) + publishDeal(t, dp, 0, true, false) // 3. Regular deal - deal = publishDeal(t, dp, false, false) + deal = publishDeal(t, dp, 0, false, false) dealsToPublish = append(dealsToPublish, deal) // Allow a moment for them to be queued @@ -217,7 +231,7 @@ func TestForcePublish(t *testing.T) { checkPublishedDeals(t, dpapi, dealsToPublish, []int{2}) } -func publishDeal(t *testing.T, dp *DealPublisher, ctxCancelled bool, expired bool) market.ClientDealProposal { +func publishDeal(t *testing.T, dp *DealPublisher, invalid int, ctxCancelled bool, expired bool) market.ClientDealProposal { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -238,6 +252,7 @@ func publishDeal(t *testing.T, dp *DealPublisher, ctxCancelled bool, expired boo Provider: getProviderActor(t), StartEpoch: startEpoch, EndEpoch: abi.ChainEpoch(120), + PieceSize: abi.PaddedPieceSize(invalid), // pass invalid into StateCall below }, ClientSignature: crypto.Signature{ Type: crypto.SigTypeSecp256k1, @@ -253,7 +268,7 @@ func publishDeal(t *testing.T, dp *DealPublisher, ctxCancelled bool, expired boo return } - if ctxCancelled || expired { + if ctxCancelled || expired || invalid == 1 { require.Error(t, err) } else { require.NoError(t, err) @@ -382,7 +397,16 @@ func (d *dpAPI) StateLookupID(ctx context.Context, a address.Address, key types. } func (d *dpAPI) StateCall(ctx context.Context, message *types.Message, key types.TipSetKey) (*api.InvocResult, error) { - return &api.InvocResult{MsgRct: &types.MessageReceipt{ExitCode: 0}}, nil + var p market2.PublishStorageDealsParams + if err := p.UnmarshalCBOR(bytes.NewReader(message.Params)); err != nil { + return nil, xerrors.Errorf("unmarshal market params: %w", err) + } + + exit := exitcode.Ok + if p.Deals[0].Proposal.PieceSize == 1 { + exit = exitcode.ErrIllegalState + } + return &api.InvocResult{MsgRct: &types.MessageReceipt{ExitCode: exit}}, nil } func getClientActor(t *testing.T) address.Address { From 179458efe93e868bb77135c95e52fd410e7a2bc1 Mon Sep 17 00:00:00 2001 From: frank <417329062@qq.com> Date: Mon, 6 Sep 2021 16:20:23 +0800 Subject: [PATCH 089/122] index out of range --- extern/storage-sealing/commit_batch.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 383562583..3a97f7d68 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -297,6 +297,10 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa infos = append(infos, p.Info) } + if len(infos) == 0 { + return nil, nil + } + sort.Slice(infos, func(i, j int) bool { return infos[i].Number < infos[j].Number }) From c69d1db1ebbac3762fff52ec5ec2aa80f564a195 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Mon, 6 Sep 2021 15:00:17 +0200 Subject: [PATCH 090/122] introduce MaxStagingDealsGiB - reject new deals if our staging deals area is "full" --- node/builder_miner.go | 4 ++-- node/config/doc_gen.go | 7 +++++++ node/config/types.go | 4 +++- node/modules/storageminer.go | 21 ++++++++++++++++++--- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/node/builder_miner.go b/node/builder_miner.go index c8ee5d48c..bd0784da2 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -165,7 +165,7 @@ func ConfigStorageMiner(c interface{}) Option { // Markets (storage) Override(new(dtypes.ProviderDataTransfer), modules.NewProviderDAGServiceDataTransfer), Override(new(*storedask.StoredAsk), modules.NewStorageAsk), - Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(nil)), + Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(cfg.Dealmaking, nil)), Override(new(storagemarket.StorageProvider), modules.StorageProvider), Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{})), Override(HandleMigrateProviderFundsKey, modules.HandleMigrateProviderFunds), @@ -192,7 +192,7 @@ func ConfigStorageMiner(c interface{}) Option { Override(new(dtypes.GetMaxDealStartDelayFunc), modules.NewGetMaxDealStartDelayFunc), If(cfg.Dealmaking.Filter != "", - Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(dealfilter.CliStorageDealFilter(cfg.Dealmaking.Filter))), + Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(cfg.Dealmaking, dealfilter.CliStorageDealFilter(cfg.Dealmaking.Filter))), ), If(cfg.Dealmaking.RetrievalFilter != "", diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index dd6dda50d..aae4b3c08 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -251,6 +251,13 @@ message`, Comment: `The maximum collateral that the provider will put up against a deal, as a multiplier of the minimum collateral bound`, + }, + { + Name: "MaxStagingDealsGiB", + Type: "int64", + + Comment: `The maximum size of staging deals not yet passed to the sealing node, +that the markets service can accept`, }, { Name: "SimultaneousTransfers", diff --git a/node/config/types.go b/node/config/types.go index 97b0812d7..7d56ddac1 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -126,7 +126,9 @@ type DealmakingConfig struct { // The maximum collateral that the provider will put up against a deal, // as a multiplier of the minimum collateral bound MaxProviderCollateralMultiplier uint64 - + // The maximum size of staging deals not yet passed to the sealing node, + // that the markets service can accept + MaxStagingDealsGiB int64 // The maximum number of parallel online data transfers (storage+retrieval) SimultaneousTransfers uint64 diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 075eed99d..fb2061ecd 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -442,14 +442,16 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi v1api.FullNode, ds dtypes.Metada storagemarket.MaxPieceSize(abi.PaddedPieceSize(mi.SectorSize))) } -func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, +func BasicDealFilter(cfg config.DealmakingConfig, user dtypes.StorageDealFilter) func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, startDelay dtypes.GetMaxDealStartDelayFunc, - spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { + spn storagemarket.StorageProviderNode, + r repo.LockedRepo, +) dtypes.StorageDealFilter { return func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, @@ -457,7 +459,9 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, startDelay dtypes.GetMaxDealStartDelayFunc, - spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { + spn storagemarket.StorageProviderNode, + r repo.LockedRepo, + ) dtypes.StorageDealFilter { return func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) { b, err := onlineOk() @@ -533,6 +537,17 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside return false, "miner error", err } + diskUsageBytes, err := r.DiskUsage(r.Path() + "/deal-staging") + if err != nil { + return false, "miner error", err + } + + diskUsageGiB := diskUsageBytes / 1024 / 1024 / 1024 + if cfg.MaxStagingDealsGiB != 0 && diskUsageGiB >= cfg.MaxStagingDealsGiB { + log.Errorw("proposed deal rejected because there are too many deals in the staging area at the moment", "MaxStagingDealsGiB", cfg.MaxStagingDealsGiB, "DiskUsageGiB", diskUsageGiB) + return false, "cannot accept deal as miner is overloaded at the moment - there are too many staging deals being processed", nil + } + // Reject if it's more than 7 days in the future // TODO: read from cfg maxStartEpoch := earliest + abi.ChainEpoch(uint64(sd.Seconds())/build.BlockDelaySecs) From 0b7793a927e6358e68ce575fb04f8841bd61467b Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Mon, 6 Sep 2021 15:52:25 +0200 Subject: [PATCH 091/122] GiB to Bytes --- node/config/doc_gen.go | 6 +++--- node/config/types.go | 6 +++--- node/modules/storageminer.go | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index aae4b3c08..29b182490 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -253,11 +253,11 @@ message`, as a multiplier of the minimum collateral bound`, }, { - Name: "MaxStagingDealsGiB", + Name: "MaxStagingDealsBytes", Type: "int64", - Comment: `The maximum size of staging deals not yet passed to the sealing node, -that the markets service can accept`, + Comment: `The maximum allowed disk usage size in bytes of staging deals not yet +passed to the sealing node by the markets service`, }, { Name: "SimultaneousTransfers", diff --git a/node/config/types.go b/node/config/types.go index 7d56ddac1..3c548301e 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -126,9 +126,9 @@ type DealmakingConfig struct { // The maximum collateral that the provider will put up against a deal, // as a multiplier of the minimum collateral bound MaxProviderCollateralMultiplier uint64 - // The maximum size of staging deals not yet passed to the sealing node, - // that the markets service can accept - MaxStagingDealsGiB int64 + // The maximum allowed disk usage size in bytes of staging deals not yet + // passed to the sealing node by the markets service + MaxStagingDealsBytes int64 // The maximum number of parallel online data transfers (storage+retrieval) SimultaneousTransfers uint64 diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index fb2061ecd..a4fc1d347 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -542,9 +542,8 @@ func BasicDealFilter(cfg config.DealmakingConfig, user dtypes.StorageDealFilter) return false, "miner error", err } - diskUsageGiB := diskUsageBytes / 1024 / 1024 / 1024 - if cfg.MaxStagingDealsGiB != 0 && diskUsageGiB >= cfg.MaxStagingDealsGiB { - log.Errorw("proposed deal rejected because there are too many deals in the staging area at the moment", "MaxStagingDealsGiB", cfg.MaxStagingDealsGiB, "DiskUsageGiB", diskUsageGiB) + if cfg.MaxStagingDealsBytes != 0 && diskUsageBytes >= cfg.MaxStagingDealsBytes { + log.Errorw("proposed deal rejected because there are too many deals in the staging area at the moment", "MaxStagingDealsBytes", cfg.MaxStagingDealsBytes, "DiskUsageBytes", diskUsageBytes) return false, "cannot accept deal as miner is overloaded at the moment - there are too many staging deals being processed", nil } From 9e2129338afdd0ab6ff327d0a305bfed7d24b7e1 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Mon, 6 Sep 2021 17:07:19 +0200 Subject: [PATCH 092/122] clarify that 0 is unlimited --- node/config/doc_gen.go | 2 +- node/config/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 29b182490..adb3b9485 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -257,7 +257,7 @@ as a multiplier of the minimum collateral bound`, Type: "int64", Comment: `The maximum allowed disk usage size in bytes of staging deals not yet -passed to the sealing node by the markets service`, +passed to the sealing node by the markets service. 0 is unlimited.`, }, { Name: "SimultaneousTransfers", diff --git a/node/config/types.go b/node/config/types.go index 3c548301e..845566537 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -127,7 +127,7 @@ type DealmakingConfig struct { // as a multiplier of the minimum collateral bound MaxProviderCollateralMultiplier uint64 // The maximum allowed disk usage size in bytes of staging deals not yet - // passed to the sealing node by the markets service + // passed to the sealing node by the markets service. 0 is unlimited. MaxStagingDealsBytes int64 // The maximum number of parallel online data transfers (storage+retrieval) SimultaneousTransfers uint64 From 992cc3ffbf135fa8ddc52d4a37e0585b3e225b06 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Mon, 6 Sep 2021 17:39:35 +0200 Subject: [PATCH 093/122] itests: MaxStagingDealsBytes integration test --- .circleci/config.yml | 5 ++ itests/deals_max_staging_deals_test.go | 65 ++++++++++++++++++++++++++ itests/kit/deals.go | 45 ++++++++++++++++++ itests/kit/ensemble.go | 1 + itests/kit/node_opts.go | 18 +++++-- 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 itests/deals_max_staging_deals_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index d570e303c..200792130 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -820,6 +820,11 @@ workflows: suite: itest-deals_concurrent target: "./itests/deals_concurrent_test.go" + - test: + name: test-itest-deals_max_staging_deals + suite: itest-deals_max_staging_deals + target: "./itests/deals_max_staging_deals_test.go" + - test: name: test-itest-deals_offline suite: itest-deals_offline diff --git a/itests/deals_max_staging_deals_test.go b/itests/deals_max_staging_deals_test.go new file mode 100644 index 000000000..6d7e85eb2 --- /dev/null +++ b/itests/deals_max_staging_deals_test.go @@ -0,0 +1,65 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestMaxStagingDeals(t *testing.T) { + ctx := context.Background() + + // enable 512MiB proofs so we can conduct larger transfers. + kit.EnableLargeSectors(t) + kit.QuietMiningLogs() + + client, miner, ens := kit.EnsembleMinimal(t, + kit.MockProofs(), + kit.WithMaxStagingDealsBytes(8192), // max 8KB staging deals + kit.SectorSize(512<<20), // 512MiB sectors. + ) + ens.InterconnectAll().BeginMining(200 * time.Millisecond) + + dh := kit.NewDealHarness(t, client, miner, miner) + + client.WaitTillChain(ctx, kit.HeightAtLeast(5)) + + res, _ := client.CreateImportFile(ctx, 0, 8192) // 8KB file + list, err := client.ClientListImports(ctx) + require.NoError(t, err) + require.Len(t, list, 1) + require.Equal(t, res.Root, *list[0].Root) + + res2, _ := client.CreateImportFile(ctx, 0, 4096) + list, err = client.ClientListImports(ctx) + require.NoError(t, err) + require.Len(t, list, 2) + require.Equal(t, res2.Root, *list[1].Root) + + // first deal stays in staging area, and is not yet passed to the sealing subsystem + dp := dh.DefaultStartDealParams() + dp.Data.Root = res.Root + dp.FastRetrieval = true + dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. + deal := dh.StartDeal(ctx, dp) + + time.Sleep(1 * time.Second) + + // expecting second deal to fail since staging area is full + dp.Data.Root = res2.Root + dp.FastRetrieval = true + dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. + deal2 := dh.StartDeal(ctx, dp) + + _ = deal + + err = dh.ExpectDealFailure(ctx, deal2, "cannot accept deal as miner is overloaded at the moment") + if err != nil { + t.Fatal(err) + } +} diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 7d78d8c02..1b1daa5e4 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -2,9 +2,11 @@ package kit import ( "context" + "errors" "fmt" "io/ioutil" "os" + "strings" "testing" "time" @@ -175,6 +177,49 @@ loop: } } +func (dh *DealHarness) ExpectDealFailure(ctx context.Context, deal *cid.Cid, errs string) error { + for { + di, err := dh.client.ClientGetDealInfo(ctx, *deal) + require.NoError(dh.t, err) + + switch di.State { + case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: + return fmt.Errorf("deal is sealing, and we expected an error: %s", errs) + case storagemarket.StorageDealProposalRejected: + if strings.Contains(di.Message, errs) { + return nil + } + return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) + case storagemarket.StorageDealFailing: + if strings.Contains(di.Message, errs) { + return nil + } + return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) + case storagemarket.StorageDealError: + if strings.Contains(di.Message, errs) { + return nil + } + return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) + case storagemarket.StorageDealActive: + return errors.New("expected to get an error, but didn't get one") + } + + mds, err := dh.market.MarketListIncompleteDeals(ctx) + require.NoError(dh.t, err) + + var minerState storagemarket.StorageDealStatus + for _, md := range mds { + if md.DealID == di.DealID { + minerState = md.State + break + } + } + + dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) + time.Sleep(time.Second / 2) + } +} + // WaitDealPublished waits until the deal is published. func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 9e5333794..b1e67fa13 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -453,6 +453,7 @@ func (n *Ensemble) Start() *Ensemble { cfg.Subsystems.EnableMining = m.options.subsystems.Has(SMining) cfg.Subsystems.EnableSealing = m.options.subsystems.Has(SSealing) cfg.Subsystems.EnableSectorStorage = m.options.subsystems.Has(SSectorStorage) + cfg.Dealmaking.MaxStagingDealsBytes = m.options.maxStagingDealsBytes if m.options.mainMiner != nil { token, err := m.options.mainMiner.FullNode.AuthNew(ctx, api.AllPermissions) diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index f56c2fb13..1fd449e5f 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -27,11 +27,12 @@ type nodeOpts struct { ownerKey *wallet.Key extraNodeOpts []node.Option - subsystems MinerSubsystem - mainMiner *TestMiner - disableLibp2p bool - optBuilders []OptBuilder - sectorSize abi.SectorSize + subsystems MinerSubsystem + mainMiner *TestMiner + disableLibp2p bool + optBuilders []OptBuilder + sectorSize abi.SectorSize + maxStagingDealsBytes int64 } // DefaultNodeOpts are the default options that will be applied to test nodes. @@ -68,6 +69,13 @@ func WithSubsystems(systems ...MinerSubsystem) NodeOpt { } } +func WithMaxStagingDealsBytes(size int64) NodeOpt { + return func(opts *nodeOpts) error { + opts.maxStagingDealsBytes = size + return nil + } +} + func DisableLibp2p() NodeOpt { return func(opts *nodeOpts) error { opts.disableLibp2p = true From 4f5fa3be3cb04afe1839951ac125f0b48fcd3518 Mon Sep 17 00:00:00 2001 From: KAYUII <577738@qq.com> Date: Tue, 7 Sep 2021 15:10:43 +0800 Subject: [PATCH 094/122] Update docker-lotus-entrypoint.sh Missing variable escape character --- scripts/docker-lotus-entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/docker-lotus-entrypoint.sh b/scripts/docker-lotus-entrypoint.sh index 308a4b6eb..de1dfbcc8 100755 --- a/scripts/docker-lotus-entrypoint.sh +++ b/scripts/docker-lotus-entrypoint.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -if [ ! -z DOCKER_LOTUS_IMPORT_SNAPSHOT ]; then +if [ ! -z $DOCKER_LOTUS_IMPORT_SNAPSHOT ]; then GATE="$LOTUS_PATH"/date_initialized # Don't init if already initialized. if [ ! -f "$GATE" ]; then @@ -12,7 +12,7 @@ if [ ! -z DOCKER_LOTUS_IMPORT_SNAPSHOT ]; then fi # import wallet, if provided -if [ ! -z DOCKER_LOTUS_IMPORT_WALLET ]; then +if [ ! -z $DOCKER_LOTUS_IMPORT_WALLET ]; then /usr/local/bin/lotus-shed keyinfo import "$DOCKER_LOTUS_IMPORT_WALLET" fi From a75d67a42cdf926f2e2c358674204c1d338268e8 Mon Sep 17 00:00:00 2001 From: KAYUII <577738@qq.com> Date: Tue, 7 Sep 2021 15:11:29 +0800 Subject: [PATCH 095/122] Update docker-lotus-miner-entrypoint.sh Missing variable escape character --- scripts/docker-lotus-miner-entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/docker-lotus-miner-entrypoint.sh b/scripts/docker-lotus-miner-entrypoint.sh index 1cb153176..8cdbaecce 100755 --- a/scripts/docker-lotus-miner-entrypoint.sh +++ b/scripts/docker-lotus-miner-entrypoint.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -if [ ! -z DOCKER_LOTUS_MINER_INIT ]; then +if [ ! -z $DOCKER_LOTUS_MINER_INIT ]; then GATE="$LOTUS_PATH"/date_initialized # Don't init if already initialized. - if [ -f "GATE" ]; then + if [ -f "$GATE" ]; then echo lotus-miner already initialized. exit 0 fi From 567d0546ec434afd16a741e37350a09de21b480e Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Tue, 7 Sep 2021 10:57:10 +0200 Subject: [PATCH 096/122] itests: remove equality comparison for cids --- itests/deals_max_staging_deals_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/itests/deals_max_staging_deals_test.go b/itests/deals_max_staging_deals_test.go index 6d7e85eb2..895a07954 100644 --- a/itests/deals_max_staging_deals_test.go +++ b/itests/deals_max_staging_deals_test.go @@ -33,13 +33,11 @@ func TestMaxStagingDeals(t *testing.T) { list, err := client.ClientListImports(ctx) require.NoError(t, err) require.Len(t, list, 1) - require.Equal(t, res.Root, *list[0].Root) res2, _ := client.CreateImportFile(ctx, 0, 4096) list, err = client.ClientListImports(ctx) require.NoError(t, err) require.Len(t, list, 2) - require.Equal(t, res2.Root, *list[1].Root) // first deal stays in staging area, and is not yet passed to the sealing subsystem dp := dh.DefaultStartDealParams() From a63bc10d26a553de51f4599f192d0c8133094455 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Tue, 7 Sep 2021 12:13:49 +0200 Subject: [PATCH 097/122] feat(deps): update go-graphsync v0.9.1 --- go.mod | 4 ++-- go.sum | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 86d2b430a..45cbc17c9 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ 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.10.0 + github.com/filecoin-project/go-data-transfer v1.10.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.12.0 @@ -77,7 +77,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.9.0 + github.com/ipfs/go-graphsync v0.9.1 github.com/ipfs/go-ipfs-blockstore v1.0.4 github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 diff --git a/go.sum b/go.sum index 53c97c63f..05de56ed2 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,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.10.0 h1:hrD9ns4V+qIuc6u2dx8B0atzTaQgqEz/ks0+wcj8ca4= github.com/filecoin-project/go-data-transfer v1.10.0/go.mod h1:uQtqy6vUAY5v70ZHdkF5mJ8CjVtjj/JA3aOoaqzWTVw= +github.com/filecoin-project/go-data-transfer v1.10.1 h1:YQNLwhizxkdfFxegAyrnn3l7WjgMjqDlqFzr18iWiYI= +github.com/filecoin-project/go-data-transfer v1.10.1/go.mod h1:CSDMCrPK2lVGodNB1wPEogjFvM9nVGyiL1GNbBRTSdw= 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= @@ -649,8 +650,9 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.9.0 h1:T22kORlNbJUIm/+avUIfLnAf1BkwKG6aS19NsRVjVVY= github.com/ipfs/go-graphsync v0.9.0/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= +github.com/ipfs/go-graphsync v0.9.1 h1:jo7ZaAZ3lal89RhKxKoRkPzIO8lmOY6KUWA1mDRZ2+U= +github.com/ipfs/go-graphsync v0.9.1/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= 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= @@ -2229,4 +2231,4 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= \ No newline at end of file +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= From e4044151f08d8c16c821b5b2efbd2682ee0279b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 21:44:26 +0200 Subject: [PATCH 098/122] Show deal sizes is sealing sectors --- api/api_storage.go | 6 ++++++ cmd/lotus-miner/sectors.go | 34 +++++++++++++++++++++++++--------- storage/miner_sealing.go | 7 +++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index a26080617..6ebee9908 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -267,6 +267,11 @@ type SectorLog struct { Message string } +type SectorPiece struct { + Piece abi.PieceInfo + DealInfo *PieceDealInfo // nil for pieces which do not appear in deals (e.g. filler pieces) +} + type SectorInfo struct { SectorID abi.SectorNumber State SectorState @@ -274,6 +279,7 @@ type SectorInfo struct { CommR *cid.Cid Proof []byte Deals []abi.DealID + Pieces []SectorPiece Ticket SealTicket Seed SealSeed PreCommitMsg *cid.Cid diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index d09605bd9..9770edb90 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -394,10 +394,20 @@ var sectorsListCmd = &cli.Command{ _, inASet := activeIDs[s] dw, vp := .0, .0 - if st.Expiration-st.Activation > 0 { + estimate := st.Expiration-st.Activation <= 0 + if !estimate { rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(9)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + } else { + for _, piece := range st.Pieces { + if piece.DealInfo != nil { + dw += float64(piece.Piece.Size) + if piece.DealInfo.DealProposal != nil && piece.DealInfo.DealProposal.VerifiedDeal { + vp += float64(piece.Piece.Size) * 9 + } + } + } } var deals int @@ -433,20 +443,26 @@ var sectorsListCmd = &cli.Command{ m["Expiration"] = "n/a" } else { m["Expiration"] = lcli.EpochTime(head.Height(), exp) - - if !fast && deals > 0 { - m["DealWeight"] = units.BytesSize(dw) - if vp > 0 { - m["VerifiedPower"] = color.GreenString(units.BytesSize(vp)) - } - } - if st.Early > 0 { m["RecoveryTimeout"] = color.YellowString(lcli.EpochTime(head.Height(), st.Early)) } } } + if !fast && deals > 0 { + estWrap := func(s string) string { + if !estimate { + return s + } + return fmt.Sprintf("[%s]", s) + } + + m["DealWeight"] = estWrap(units.BytesSize(dw)) + if vp > 0 { + m["VerifiedPower"] = estWrap(color.GreenString(units.BytesSize(vp))) + } + } + if cctx.Bool("events") { var events int for _, sectorLog := range st.Log { diff --git a/storage/miner_sealing.go b/storage/miner_sealing.go index 38b24e8c1..01b9546a6 100644 --- a/storage/miner_sealing.go +++ b/storage/miner_sealing.go @@ -94,10 +94,16 @@ func (m *Miner) SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnC } deals := make([]abi.DealID, len(info.Pieces)) + pieces := make([]api.SectorPiece, len(info.Pieces)) for i, piece := range info.Pieces { + pieces[i].Piece = piece.Piece if piece.DealInfo == nil { continue } + + pdi := *piece.DealInfo // copy + pieces[i].DealInfo = &pdi + deals[i] = piece.DealInfo.DealID } @@ -118,6 +124,7 @@ func (m *Miner) SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnC CommR: info.CommR, Proof: info.Proof, Deals: deals, + Pieces: pieces, Ticket: api.SealTicket{ Value: info.TicketValue, Epoch: info.TicketEpoch, From 186c4990dd7cf93f6767250b51fc6db9d7633e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 21:45:55 +0200 Subject: [PATCH 099/122] Reduce nesting in sectors list command --- cmd/lotus-miner/sectors.go | 244 +++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 121 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 9770edb90..c97bd0e66 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -389,128 +389,130 @@ var sectorsListCmd = &cli.Command{ continue } - if showRemoved || st.State != api.SectorState(sealing.Removed) { - _, inSSet := commitedIDs[s] - _, inASet := activeIDs[s] - - dw, vp := .0, .0 - estimate := st.Expiration-st.Activation <= 0 - if !estimate { - rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) - dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(9)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - } else { - for _, piece := range st.Pieces { - if piece.DealInfo != nil { - dw += float64(piece.Piece.Size) - if piece.DealInfo.DealProposal != nil && piece.DealInfo.DealProposal.VerifiedDeal { - vp += float64(piece.Piece.Size) * 9 - } - } - } - } - - var deals int - for _, deal := range st.Deals { - if deal != 0 { - deals++ - } - } - - exp := st.Expiration - if st.OnTime > 0 && st.OnTime < exp { - exp = st.OnTime // Can be different when the sector was CC upgraded - } - - m := map[string]interface{}{ - "ID": s, - "State": color.New(stateOrder[sealing.SectorState(st.State)].col).Sprint(st.State), - "OnChain": yesno(inSSet), - "Active": yesno(inASet), - } - - if deals > 0 { - m["Deals"] = color.GreenString("%d", deals) - } else { - m["Deals"] = color.BlueString("CC") - if st.ToUpgrade { - m["Deals"] = color.CyanString("CC(upgrade)") - } - } - - if !fast { - if !inSSet { - m["Expiration"] = "n/a" - } else { - m["Expiration"] = lcli.EpochTime(head.Height(), exp) - if st.Early > 0 { - m["RecoveryTimeout"] = color.YellowString(lcli.EpochTime(head.Height(), st.Early)) - } - } - } - - if !fast && deals > 0 { - estWrap := func(s string) string { - if !estimate { - return s - } - return fmt.Sprintf("[%s]", s) - } - - m["DealWeight"] = estWrap(units.BytesSize(dw)) - if vp > 0 { - m["VerifiedPower"] = estWrap(color.GreenString(units.BytesSize(vp))) - } - } - - if cctx.Bool("events") { - var events int - for _, sectorLog := range st.Log { - if !strings.HasPrefix(sectorLog.Kind, "event") { - continue - } - if sectorLog.Kind == "event;sealing.SectorRestart" { - continue - } - events++ - } - - pieces := len(st.Deals) - - switch { - case events < 12+pieces: - m["Events"] = color.GreenString("%d", events) - case events < 20+pieces: - m["Events"] = color.YellowString("%d", events) - default: - m["Events"] = color.RedString("%d", events) - } - } - - if cctx.Bool("seal-time") && len(st.Log) > 1 { - start := time.Unix(int64(st.Log[0].Timestamp), 0) - - for _, sectorLog := range st.Log { - if sectorLog.Kind == "event;sealing.SectorProving" { - end := time.Unix(int64(sectorLog.Timestamp), 0) - dur := end.Sub(start) - - switch { - case dur < 12*time.Hour: - m["SealTime"] = color.GreenString("%s", dur) - case dur < 24*time.Hour: - m["SealTime"] = color.YellowString("%s", dur) - default: - m["SealTime"] = color.RedString("%s", dur) - } - - break - } - } - } - - tw.Write(m) + if !showRemoved && st.State == api.SectorState(sealing.Removed) { + continue } + + _, inSSet := commitedIDs[s] + _, inASet := activeIDs[s] + + dw, vp := .0, .0 + estimate := st.Expiration-st.Activation <= 0 + if !estimate { + rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) + dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(9)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + } else { + for _, piece := range st.Pieces { + if piece.DealInfo != nil { + dw += float64(piece.Piece.Size) + if piece.DealInfo.DealProposal != nil && piece.DealInfo.DealProposal.VerifiedDeal { + vp += float64(piece.Piece.Size) * 9 + } + } + } + } + + var deals int + for _, deal := range st.Deals { + if deal != 0 { + deals++ + } + } + + exp := st.Expiration + if st.OnTime > 0 && st.OnTime < exp { + exp = st.OnTime // Can be different when the sector was CC upgraded + } + + m := map[string]interface{}{ + "ID": s, + "State": color.New(stateOrder[sealing.SectorState(st.State)].col).Sprint(st.State), + "OnChain": yesno(inSSet), + "Active": yesno(inASet), + } + + if deals > 0 { + m["Deals"] = color.GreenString("%d", deals) + } else { + m["Deals"] = color.BlueString("CC") + if st.ToUpgrade { + m["Deals"] = color.CyanString("CC(upgrade)") + } + } + + if !fast { + if !inSSet { + m["Expiration"] = "n/a" + } else { + m["Expiration"] = lcli.EpochTime(head.Height(), exp) + if st.Early > 0 { + m["RecoveryTimeout"] = color.YellowString(lcli.EpochTime(head.Height(), st.Early)) + } + } + } + + if !fast && deals > 0 { + estWrap := func(s string) string { + if !estimate { + return s + } + return fmt.Sprintf("[%s]", s) + } + + m["DealWeight"] = estWrap(units.BytesSize(dw)) + if vp > 0 { + m["VerifiedPower"] = estWrap(color.GreenString(units.BytesSize(vp))) + } + } + + if cctx.Bool("events") { + var events int + for _, sectorLog := range st.Log { + if !strings.HasPrefix(sectorLog.Kind, "event") { + continue + } + if sectorLog.Kind == "event;sealing.SectorRestart" { + continue + } + events++ + } + + pieces := len(st.Deals) + + switch { + case events < 12+pieces: + m["Events"] = color.GreenString("%d", events) + case events < 20+pieces: + m["Events"] = color.YellowString("%d", events) + default: + m["Events"] = color.RedString("%d", events) + } + } + + if cctx.Bool("seal-time") && len(st.Log) > 1 { + start := time.Unix(int64(st.Log[0].Timestamp), 0) + + for _, sectorLog := range st.Log { + if sectorLog.Kind == "event;sealing.SectorProving" { + end := time.Unix(int64(sectorLog.Timestamp), 0) + dur := end.Sub(start) + + switch { + case dur < 12*time.Hour: + m["SealTime"] = color.GreenString("%s", dur) + case dur < 24*time.Hour: + m["SealTime"] = color.YellowString("%s", dur) + default: + m["SealTime"] = color.RedString("%s", dur) + } + + break + } + } + } + + tw.Write(m) } return tw.Flush(os.Stdout) From 8b9e9fede44bc2f946c06f6a2457e889e4105ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 2 Sep 2021 21:47:51 +0200 Subject: [PATCH 100/122] docsgen --- build/openrpc/miner.json.gz | Bin 10378 -> 10424 bytes documentation/en/api-v0-methods-miner.md | 1 + 2 files changed, 1 insertion(+) diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index ce3a4562fddfb864603909f6653548d159031b2a..15dbe3829826b3643edeb650652e0b55304e5138 100644 GIT binary patch delta 2358 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 diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 1c6891329..dd7a1f88e 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -2019,6 +2019,7 @@ Response: "CommR": null, "Proof": "Ynl0ZSBhcnJheQ==", "Deals": null, + "Pieces": null, "Ticket": { "Value": null, "Epoch": 10101 From dfc039276d5d9a0b021e4ee80c270600e435db33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 7 Sep 2021 19:42:52 +0200 Subject: [PATCH 101/122] address review --- cmd/lotus-miner/sectors.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index c97bd0e66..9bfc84900 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -396,18 +396,20 @@ var sectorsListCmd = &cli.Command{ _, inSSet := commitedIDs[s] _, inASet := activeIDs[s] + const verifiedPowerGainMul = 9 + dw, vp := .0, .0 estimate := st.Expiration-st.Activation <= 0 if !estimate { rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(9)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(verifiedPowerGainMul)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) } else { for _, piece := range st.Pieces { if piece.DealInfo != nil { dw += float64(piece.Piece.Size) if piece.DealInfo.DealProposal != nil && piece.DealInfo.DealProposal.VerifiedDeal { - vp += float64(piece.Piece.Size) * 9 + vp += float64(piece.Piece.Size) * verifiedPowerGainMul } } } @@ -494,7 +496,7 @@ var sectorsListCmd = &cli.Command{ start := time.Unix(int64(st.Log[0].Timestamp), 0) for _, sectorLog := range st.Log { - if sectorLog.Kind == "event;sealing.SectorProving" { + if sectorLog.Kind == "event;sealing.SectorProving" { // todo: figure out a good way to not hardcode end := time.Unix(int64(sectorLog.Timestamp), 0) dur := end.Sub(start) From f751291f1a8a0fd4f2806a85bdfd094129310491 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 8 Sep 2021 01:03:11 -0400 Subject: [PATCH 102/122] update to proof v0.9.2 --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 78366aeb8..957547f15 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 78366aeb85796c0687a53107a6cd52da7bd8abd5 +Subproject commit 957547f15acc332a5d30b23a08f5c9dd3e58396b From 6907797783e929fca06728822ffe0fe1e7b067bc Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 8 Sep 2021 01:31:23 -0400 Subject: [PATCH 103/122] version bump v1.11.3-rc1 --- build/openrpc/full.json.gz | Bin 25416 -> 25416 bytes build/openrpc/miner.json.gz | Bin 10424 -> 10424 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2710 bytes build/version.go | 2 +- documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus-worker.md | 4 ++-- documentation/en/cli-lotus.md | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 610a0591a8706d1b00af4e488208b8522eee0011..c6066ded86f40f514f444d4ca14ca3af55e81b5a 100644 GIT binary patch delta 25290 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 delta 22 ecmdlHxFc{v8 Date: Wed, 8 Sep 2021 01:35:36 -0400 Subject: [PATCH 104/122] init v1.11.3 changelog --- CHANGELOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72584e87a..30054e004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,116 @@ # Lotus changelog +# v1.11.3-rc1 / 2021-09-08 + +This is the first release candidate for lotus v1.11.3. Changelog will be updated later. + +## Changelog + +- github.com/filecoin-project/lotus: + - version bump v1.11.3-rc1 + - update to proof v0.9.2 ([filecoin-project/lotus#7297](https://github.com/filecoin-project/lotus/pull/7297)) + - Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) + - docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) + - Update go-graphsync v0.9.1 ([filecoin-project/lotus#7294](https://github.com/filecoin-project/lotus/pull/7294)) + - itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) + - v1.11.2 -> master ([filecoin-project/lotus#7288](https://github.com/filecoin-project/lotus/pull/7288)) + - fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) + - dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) + - introduce MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) + - Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) + - Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) + - integrate the proof patch: tag proofs-v9-revert-deps-hotfix ([filecoin-project/lotus#7260](https://github.com/filecoin-project/lotus/pull/7260)) + - update to go-fil-markets v1.11.0 ([filecoin-project/lotus#7253](https://github.com/filecoin-project/lotus/pull/7253)) + - Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) + - sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) + - chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) + - builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) + - sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) + - fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) + - sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) + - stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) + - gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) + - Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) + - test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) + - fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) + - upgrade go-data-transfer; propagate deal cancellations. ([filecoin-project/lotus#7208](https://github.com/filecoin-project/lotus/pull/7208)) + - revert changes to OnDealExpiredOrChanged in #5431 #7201 ([filecoin-project/lotus#7220](https://github.com/filecoin-project/lotus/pull/7220)) + - Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) + - config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) + - Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) + - ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) + - fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) + - Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) + - sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) + - markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) + - Revert "Merge pull request #7187 from filecoin-project/test/disable-broken-testground" ([filecoin-project/lotus#7191](https://github.com/filecoin-project/lotus/pull/7191)) + - ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) + - fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) + - Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) + - Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) + - journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) + - call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github.com/filecoin-project/lotus/pull/7104)) + - Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) + - Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) + - fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) + - Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) + - fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) + - test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) + - itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) + - Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) + - miner: Command to list/remove expired sectors ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) + - Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) + - test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) +- github.com/filecoin-project/go-data-transfer (v1.7.8 -> v1.10.1): + - docs(CHANGELOG): update for 1.10.1 + - Fix parallel transfers between same two peers (#254) ([filecoin-project/go-data-transfer#254](https://github.com/filecoin-project/go-data-transfer/pull/254)) + - release: v1.10.0 ([filecoin-project/go-data-transfer#253](https://github.com/filecoin-project/go-data-transfer/pull/253)) + - feat: integrate graphsync-v0.9.0 (#252) ([filecoin-project/go-data-transfer#252](https://github.com/filecoin-project/go-data-transfer/pull/252)) + - release: v1.9.0 ([filecoin-project/go-data-transfer#251](https://github.com/filecoin-project/go-data-transfer/pull/251)) + - fix: ensure graphsync transport only closes complete channel once (#250) ([filecoin-project/go-data-transfer#250](https://github.com/filecoin-project/go-data-transfer/pull/250)) + - revert: integration of graphsync-v0.9.0 until we are ready to test the whole stack with it (#249) ([filecoin-project/go-data-transfer#249](https://github.com/filecoin-project/go-data-transfer/pull/249)) + - v1.8.0 release ([filecoin-project/go-data-transfer#247](https://github.com/filecoin-project/go-data-transfer/pull/247)) + - Update to unified go graphsync v0.9.0 (#246) ([filecoin-project/go-data-transfer#246](https://github.com/filecoin-project/go-data-transfer/pull/246)) +- github.com/filecoin-project/go-fil-markets (v1.8.1 -> v1.12.0): + - release: v1.12.0 + - Update to unified graphsync v0.9.0 (#627) ([filecoin-project/go-fil-markets#627](https://github.com/filecoin-project/go-fil-markets/pull/627)) + - release: v1.11.0 ([filecoin-project/go-fil-markets#626](https://github.com/filecoin-project/go-fil-markets/pull/626)) + - feat: upgrade to go-data-transfer v1.9.0 (#625) ([filecoin-project/go-fil-markets#625](https://github.com/filecoin-project/go-fil-markets/pull/625)) + - Revert "Update to unified graphsync v0.9.0 (#615)" (#624) ([filecoin-project/go-fil-markets#624](https://github.com/filecoin-project/go-fil-markets/pull/624)) + - Update to unified graphsync v0.9.0 (#615) ([filecoin-project/go-fil-markets#615](https://github.com/filecoin-project/go-fil-markets/pull/615)) + - fix: TestCancelDataTransfer (#622) ([filecoin-project/go-fil-markets#622](https://github.com/filecoin-project/go-fil-markets/pull/622)) + - rm go-multistore dependency. (#619) ([filecoin-project/go-fil-markets#619](https://github.com/filecoin-project/go-fil-markets/pull/619)) + - revert: OnDealExpiredOrSlashed changes (#620) ([filecoin-project/go-fil-markets#620](https://github.com/filecoin-project/go-fil-markets/pull/620)) + - fix(ci): include node in circle orb to fix docsgen (#618) ([filecoin-project/go-fil-markets#618](https://github.com/filecoin-project/go-fil-markets/pull/618)) + - release: v1.9.0 ([filecoin-project/go-fil-markets#617](https://github.com/filecoin-project/go-fil-markets/pull/617)) + - refactor: pass deal proposal instead of deal ID to OnDealExpiredOrSlashed (#616) ([filecoin-project/go-fil-markets#616](https://github.com/filecoin-project/go-fil-markets/pull/616)) + - fix: reject storage deals where the end epoch is too far in the future (#612) ([filecoin-project/go-fil-markets#612](https://github.com/filecoin-project/go-fil-markets/pull/612)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Łukasz Magiera | 38 | +3306/-1825 | 178 | +| Steven Allen | 23 | +1935/-1417 | 84 | +| dirkmc | 12 | +921/-732 | 111 | +| Dirk McCormick | 12 | +663/-790 | 30 | +| Hannah Howard | 3 | +482/-275 | 46 | +| Travis Person | 1 | +317/-65 | 5 | +| hannahhoward | 5 | +251/-50 | 12 | +| Anton Evangelatov | 7 | +198/-37 | 17 | +| Raúl Kripalani | 4 | +127/-36 | 13 | +| raulk | 1 | +43/-60 | 15 | +| Aayush Rajasekaran | 4 | +74/-8 | 10 | +| Frank | 2 | +68/-8 | 3 | +| Adrian Lanzafame | 1 | +16/-2 | 1 | +| Aarsh Shah | 2 | +11/-6 | 2 | +| Jennifer Wang | 3 | +7/-7 | 9 | +| ZenGround0 | 2 | +7/-6 | 2 | +| KAYUII | 2 | +4/-4 | 2 | +| lanzafame | 1 | +6/-0 | 1 | +| Jacob Heun | 1 | +3/-3 | 1 | +| frank | 1 | +4/-0 | 1 | + # v1.11.2 / 2021-09-06 lotus v1.11.2 is a feature release that's **highly recommended ALL lotus users to upgrade**, including node operators, From daf0b57528ef1cb4352978a0ec3c5cef85922dab Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 8 Sep 2021 01:42:51 -0400 Subject: [PATCH 105/122] make gen --- documentation/en/cli-lotus-worker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 1dd80439a..7b8ac4416 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -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: "~/.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) From d8a5812f76e990367b35b13b7adbf8194d7f129f Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 8 Sep 2021 01:57:33 -0400 Subject: [PATCH 106/122] increase miner codecov threshold from 1% to 1.5% --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index c266bc3ea..01070e230 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -40,7 +40,7 @@ coverage: - "paychmgr" miner: target: auto - threshold: 1% + threshold: 1.5% informational: false paths: - "miner" From 143b6d7a37a3bd9264c6fa2abcb8425b13264213 Mon Sep 17 00:00:00 2001 From: ognots Date: Thu, 9 Sep 2021 17:38:50 -0400 Subject: [PATCH 107/122] remove job to install jq jq is already installed now in either a newer version of CircleCI's MacOS VMs or in a previous CI step. I ran a failing macos job with ssh enabled, and inspected '/usr/local/bin' and found found the following output lrwxr-xr-x 1 distiller admin 23 Jun 22 14:50 jq -> ../Cellar/jq/1.6/bin/jq the existing symlink causes the 'Install jq' job to fail. removing this job should resolve the issue --- .circleci/config.yml | 5 ----- .circleci/template.yml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 200792130..7b85006d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -367,11 +367,6 @@ jobs: name: Install Rust command: | curl https://sh.rustup.rs -sSf | sh -s -- -y - - run: - name: Install jq - command: | - curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq - chmod +x /usr/local/bin/jq - run: name: Install hwloc command: | diff --git a/.circleci/template.yml b/.circleci/template.yml index 27036ab26..f1d0680c4 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -367,11 +367,6 @@ jobs: name: Install Rust command: | curl https://sh.rustup.rs -sSf | sh -s -- -y - - run: - name: Install jq - command: | - curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq - chmod +x /usr/local/bin/jq - run: name: Install hwloc command: | From cd08788792ba247f0094898bed177b21d044b976 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 9 Sep 2021 16:45:02 -0400 Subject: [PATCH 108/122] Bump the xcode version for ci and run build macos jobs more --- .circleci/config.yml | 11 ++--------- .circleci/template.yml | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b85006d1..cdc819793 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -348,7 +348,7 @@ jobs: build-macos: description: build darwin lotus binary macos: - xcode: "10.0.0" + xcode: "12.5.0" working_directory: ~/go/src/github.com/filecoin-project/lotus steps: - prepare: @@ -983,14 +983,7 @@ workflows: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-lotus-soup - - build-macos: - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-macos - build-appimage: filters: branches: diff --git a/.circleci/template.yml b/.circleci/template.yml index f1d0680c4..70f7ea51a 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -348,7 +348,7 @@ jobs: build-macos: description: build darwin lotus binary macos: - xcode: "10.0.0" + xcode: "12.5.0" working_directory: ~/go/src/github.com/filecoin-project/lotus steps: - prepare: @@ -838,14 +838,7 @@ workflows: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-lotus-soup - - build-macos: - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-macos - build-appimage: filters: branches: From 90827afa475824107e6c7a9aea31eaf7fc765c3d Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Tue, 14 Sep 2021 19:31:28 -0400 Subject: [PATCH 109/122] feat(ci): include version/cli checks in tagged releases --- .circleci/config.yml | 6 ++++++ .circleci/template.yml | 6 ++++++ scripts/version-check.sh | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100755 scripts/version-check.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index cdc819793..81bc59cf4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -92,6 +92,9 @@ jobs: - run: sudo apt-get install npm - run: command: make buildall + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus - store_artifacts: path: lotus - store_artifacts: @@ -383,6 +386,9 @@ jobs: - run: command: make build no_output_timeout: 30m + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus - store_artifacts: path: lotus - store_artifacts: diff --git a/.circleci/template.yml b/.circleci/template.yml index 70f7ea51a..a7bbf8d0a 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -92,6 +92,9 @@ jobs: - run: sudo apt-get install npm - run: command: make buildall + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus - store_artifacts: path: lotus - store_artifacts: @@ -383,6 +386,9 @@ jobs: - run: command: make build no_output_timeout: 30m + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus - store_artifacts: path: lotus - store_artifacts: diff --git a/scripts/version-check.sh b/scripts/version-check.sh new file mode 100755 index 000000000..ee4cce686 --- /dev/null +++ b/scripts/version-check.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -ex + +# Validate lotus version matches the current tag +# $1 - lotus path to execute +# $2 - lotus git tag for this release +function validate_lotus_version_matches_tag(){ + # sanity checks + if [[ $# != 2 ]]; then + echo "expected 2 args for validate_lotus_version, got ${$#}" + exit 100 + fi + + # extract version from `lotus --version` response + lotus_path=$1 + # get version + lotus_raw_version=`${lotus_path} --version` + # grep for version string + lotus_actual_version=`echo ${lotus_raw_version} | grep -oE '[0-9]+.[0-9]+.[0-9]+'` + + # trim leading 'v' + tag=${2#v} + # trim possible -rc[0-9] + expected_version=${tag%-*} + + # check the versions are consistent + if [[ ${expected_version} != ${lotus_actual_version} ]]; then + echo "lotus version does not match build tag" + exit 101 + fi +} + +_lotus_path=$1 + +if [[ ! -z "${CIRCLE_TAG}" ]]; then + validate_lotus_version_matches_tag "${_lotus_path}" "${CIRCLE_TAG}" +else + echo "No CI tag found. Skipping version check." +fi From 438d5ce78e401b36c84dde5662c01268c45f65eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 15 Sep 2021 18:13:01 +0200 Subject: [PATCH 110/122] fix a panic in HandleRecoverDealIDs --- extern/storage-sealing/states_failed.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index f1fd092b6..68b5950e9 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -394,6 +394,11 @@ func (m *Sealing) HandleRecoverDealIDs(ctx Context, sector SectorInfo) error { failed[i] = xerrors.Errorf("getting current deal info for piece %d: %w", i, err) } + if res.MarketDeal == nil { + failed[i] = xerrors.Errorf("nil market deal (%d,%d,%d,%s)", i, sector.SectorNumber, p.DealInfo.DealID, p.Piece.PieceCID) + continue + } + if res.MarketDeal.Proposal.PieceCID != p.Piece.PieceCID { failed[i] = xerrors.Errorf("recovered piece (%d) deal in sector %d (dealid %d) has different PieceCID %s != %s", i, sector.SectorNumber, p.DealInfo.DealID, p.Piece.PieceCID, res.MarketDeal.Proposal.PieceCID) continue From 3a1fc8e7da85e9bcfff1291c2257cc4ae447ffd0 Mon Sep 17 00:00:00 2001 From: ldoublewood Date: Thu, 16 Sep 2021 09:25:02 +0800 Subject: [PATCH 111/122] fix bug for CommittedCapacitySectorLifetime --- node/config/def.go | 2 +- node/modules/storageminer.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/node/config/def.go b/node/config/def.go index b75831eef..a1a4856a9 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -101,7 +101,7 @@ 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()), + CommittedCapacitySectorLifetime: Duration(builtin.EpochDurationSeconds * uint64(policy.GetMaxSectorExpirationExtension()) * uint64(time.Second)), 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 diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index b59a1143b..db122c116 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -869,6 +869,7 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, MaxSealingSectors: cfg.MaxSealingSectors, MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, + CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime), WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, FinalizeEarly: cfg.FinalizeEarly, @@ -903,6 +904,7 @@ func ToSealingConfig(cfg *config.StorageMiner) sealiface.Config { MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, MaxSealingSectors: cfg.Sealing.MaxSealingSectors, MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, + CommittedCapacitySectorLifetime: time.Duration(cfg.Sealing.CommittedCapacitySectorLifetime), WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, FinalizeEarly: cfg.Sealing.FinalizeEarly, From eb2bbfebe3f28c4b5bb98b917e03037939c9c512 Mon Sep 17 00:00:00 2001 From: ldoublewood Date: Fri, 17 Sep 2021 00:20:24 +0800 Subject: [PATCH 112/122] format code by make gen --- node/modules/storageminer.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index db122c116..a461ee5ec 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -866,13 +866,13 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error return func(cfg sealiface.Config) (err error) { err = mutateCfg(r, func(c *config.StorageMiner) { c.Sealing = config.SealingConfig{ - MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, - MaxSealingSectors: cfg.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, + MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, + MaxSealingSectors: cfg.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime), - WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), - AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.FinalizeEarly, + WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), + AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.FinalizeEarly, CollateralFromMinerBalance: cfg.CollateralFromMinerBalance, AvailableBalanceBuffer: types.FIL(cfg.AvailableBalanceBuffer), @@ -901,13 +901,13 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error func ToSealingConfig(cfg *config.StorageMiner) sealiface.Config { return sealiface.Config{ - MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, - MaxSealingSectors: cfg.Sealing.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, + MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, + MaxSealingSectors: cfg.Sealing.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, CommittedCapacitySectorLifetime: time.Duration(cfg.Sealing.CommittedCapacitySectorLifetime), - WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), - AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.Sealing.FinalizeEarly, + WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), + AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.Sealing.FinalizeEarly, CollateralFromMinerBalance: cfg.Sealing.CollateralFromMinerBalance, AvailableBalanceBuffer: types.BigInt(cfg.Sealing.AvailableBalanceBuffer), From e996c27619e40e38732bdd6a4b19a4c2393577a6 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 21 Sep 2021 17:43:27 -0400 Subject: [PATCH 113/122] update to ffi to update-bellperson-proofs-v9-0-2 --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 957547f15..dc585c486 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 957547f15acc332a5d30b23a08f5c9dd3e58396b +Subproject commit dc585c4860a56158202161b05610d54b18b3b54a From bf9d0bca40b5f829277e29f00f911f15fb7f373f Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Fri, 17 Sep 2021 14:55:56 +0200 Subject: [PATCH 114/122] GetCurrentDealInfo err: handle correctly err case --- extern/storage-sealing/states_failed.go | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index 68b5950e9..0c88cc384 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -392,6 +392,7 @@ func (m *Sealing) HandleRecoverDealIDs(ctx Context, sector SectorInfo) error { res, err := m.DealInfo.GetCurrentDealInfo(ctx.Context(), tok, dp, *p.DealInfo.PublishCid) if err != nil { failed[i] = xerrors.Errorf("getting current deal info for piece %d: %w", i, err) + continue } if res.MarketDeal == nil { From 0c1e29390a0e67b27bd8d812913ddc759332475b Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Fri, 17 Sep 2021 15:14:53 +0200 Subject: [PATCH 115/122] unit test where StateMarketStorageDeal return nil, err --- extern/storage-sealing/states_failed_test.go | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/extern/storage-sealing/states_failed_test.go b/extern/storage-sealing/states_failed_test.go index d73c597dc..8613269b3 100644 --- a/extern/storage-sealing/states_failed_test.go +++ b/extern/storage-sealing/states_failed_test.go @@ -3,6 +3,7 @@ package sealing_test import ( "bytes" "context" + "errors" "testing" "github.com/golang/mock/gomock" @@ -20,6 +21,64 @@ import ( "github.com/filecoin-project/lotus/extern/storage-sealing/mocks" ) +func TestStateRecoverDealIDsErredDealInfo(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + ctx := context.Background() + + api := mocks.NewMockSealingAPI(mockCtrl) + + fakeSealing := &sealing.Sealing{ + Api: api, + DealInfo: &sealing.CurrentDealInfoManager{CDAPI: api}, + } + + sctx := mocks.NewMockContext(mockCtrl) + sctx.EXPECT().Context().AnyTimes().Return(ctx) + + api.EXPECT().ChainHead(ctx).Times(1).Return(nil, abi.ChainEpoch(10), nil) + + var dealId abi.DealID = 12 + dealProposal := market.DealProposal{ + PieceCID: idCid("newPieceCID"), + } + + api.EXPECT().StateMarketStorageDealProposal(ctx, dealId, nil).Return(dealProposal, nil) + + pc := idCid("publishCID") + + // expect GetCurrentDealInfo + { + api.EXPECT().StateSearchMsg(ctx, pc).Return(&sealing.MsgLookup{ + Receipt: sealing.MessageReceipt{ + ExitCode: exitcode.Ok, + Return: cborRet(&market.PublishStorageDealsReturn{ + IDs: []abi.DealID{dealId}, + }), + }, + }, nil) + api.EXPECT().StateMarketStorageDeal(ctx, dealId, nil).Return(nil, errors.New("deal may not have completed sealing or slashed")) + } + + sctx.EXPECT().Send(sealing.SectorRemove{}).Return(nil) + + err := fakeSealing.HandleRecoverDealIDs(sctx, sealing.SectorInfo{ + Pieces: []sealing.Piece{ + { + DealInfo: &api2.PieceDealInfo{ + DealID: dealId, + PublishCid: &pc, + }, + Piece: abi.PieceInfo{ + PieceCID: idCid("oldPieceCID"), + }, + }, + }, + }) + require.NoError(t, err) +} + func TestStateRecoverDealIDs(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() From 490f70f0716890053c9e69b8b0b96c0729c8636b Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Mon, 20 Sep 2021 16:12:58 -0700 Subject: [PATCH 116/122] feat(deps): update go-graphsync v0.9.2 --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 45cbc17c9..c1c16e8c6 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,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.9.1 + github.com/ipfs/go-graphsync v0.9.2 github.com/ipfs/go-ipfs-blockstore v1.0.4 github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 diff --git a/go.sum b/go.sum index 05de56ed2..0b3cc0f0f 100644 --- a/go.sum +++ b/go.sum @@ -651,8 +651,9 @@ 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.9.0/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= -github.com/ipfs/go-graphsync v0.9.1 h1:jo7ZaAZ3lal89RhKxKoRkPzIO8lmOY6KUWA1mDRZ2+U= github.com/ipfs/go-graphsync v0.9.1/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= +github.com/ipfs/go-graphsync v0.9.2 h1:COioIHmQmhfjGQav1q0UQSg5EADvZCvhY/Iq/2rda3I= +github.com/ipfs/go-graphsync v0.9.2/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= 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 f77dc42ef0c88c85b9f080620903bcf1dbce5b54 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Tue, 21 Sep 2021 04:25:03 -0700 Subject: [PATCH 117/122] fix(deps): use go-graphsync v0.9.3 with hotfix --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1c16e8c6..ce859e20d 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,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.9.2 + github.com/ipfs/go-graphsync v0.9.3 github.com/ipfs/go-ipfs-blockstore v1.0.4 github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 diff --git a/go.sum b/go.sum index 0b3cc0f0f..2b9be83ae 100644 --- a/go.sum +++ b/go.sum @@ -652,8 +652,8 @@ github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZ github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= github.com/ipfs/go-graphsync v0.9.0/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= github.com/ipfs/go-graphsync v0.9.1/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= -github.com/ipfs/go-graphsync v0.9.2 h1:COioIHmQmhfjGQav1q0UQSg5EADvZCvhY/Iq/2rda3I= -github.com/ipfs/go-graphsync v0.9.2/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= +github.com/ipfs/go-graphsync v0.9.3 h1:oWqUuN3OYqLwu669fxYbgymBrIodB0fD7vFZfF//X7Y= +github.com/ipfs/go-graphsync v0.9.3/go.mod h1:J62ahWT9JbPsFL2UWsUM5rOu0lZJ0LOIH1chHdxGGcw= 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 153470c3d2473bb48ed41331d1304e3df9263492 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 21 Sep 2021 22:48:56 -0400 Subject: [PATCH 118/122] bump lotus version to v1.11.3-rc2 --- build/openrpc/full.json.gz | Bin 25416 -> 25416 bytes build/openrpc/miner.json.gz | Bin 10424 -> 10424 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2710 bytes build/version.go | 2 +- documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus-worker.md | 4 ++-- documentation/en/cli-lotus.md | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index c6066ded86f40f514f444d4ca14ca3af55e81b5a..becafecf6bf590e5c93020911c72dba50726ddb1 100644 GIT binary patch delta 23 fcmX?cjPb-V#tFTQw>S1}O5(V>cZ2!Q?<@=elQRq8 delta 23 fcmX?cjPb-V#tFTQ(Hr|VC2{=NGjaCM?<@=ej(!Wr diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index cf61819521d766a8c4c90f6395212fb89778575b..4894e7ed99c5d3ff623490959e24ebb197e50174 100644 GIT binary patch delta 22 ecmdlHxFc{vJ5zPS#!ht&j^E-sul#Q_G5`Q?Y6$B9 delta 22 ecmdlHxFc{vJJas`jh*Tm9F1CAp84NqWB>qj%?Vck diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 8176c12daa33af64eea871f664b6dfd2c1d06c21..1801396c39bdf6b1d0674060f53a224293a2972f 100644 GIT binary patch delta 21 ccmbOxI!$y!Go$s!7A`K1^<8)DBC8k}08(KG9RL6T delta 21 dcmbOxI!$y!Gvn=zEnHk2sb8nOjjUo|003Qi2t@z@ diff --git a/build/version.go b/build/version.go index 4a35cce8d..bcb82395a 100644 --- a/build/version.go +++ b/build/version.go @@ -40,7 +40,7 @@ func buildType() string { } // BuildVersion is the local build version -const BuildVersion = "1.11.3-rc1" +const BuildVersion = "1.11.3-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 4796e28ec..29a72ed45 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.3-rc1 + 1.11.3-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 7b8ac4416..c0436e69b 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.3-rc1 + 1.11.3-rc2 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: "~.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 446c83f6d..39bacda19 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.3-rc1 + 1.11.3-rc2 COMMANDS: daemon Start a lotus daemon process From 6b9aeca71d2a7383ed737f3f235a013b12280878 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 21 Sep 2021 22:51:01 -0400 Subject: [PATCH 119/122] update the changelog --- CHANGELOG.md | 184 +++++++++++++++++++++++++-------------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30054e004..705ad4c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,115 +1,115 @@ # Lotus changelog -# v1.11.3-rc1 / 2021-09-08 +# v1.11.3-rc2 / 2021-09-21 -This is the first release candidate for lotus v1.11.3. Changelog will be updated later. +This is the second release candidate for lotus v1.11.3. Detailed changelog will be updated upon final release. ## Changelog - github.com/filecoin-project/lotus: - - version bump v1.11.3-rc1 - - update to proof v0.9.2 ([filecoin-project/lotus#7297](https://github.com/filecoin-project/lotus/pull/7297)) - - Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) - - docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) - - Update go-graphsync v0.9.1 ([filecoin-project/lotus#7294](https://github.com/filecoin-project/lotus/pull/7294)) - - itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) - - v1.11.2 -> master ([filecoin-project/lotus#7288](https://github.com/filecoin-project/lotus/pull/7288)) - - fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) - - dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) - - introduce MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) - - Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) - - Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) - - integrate the proof patch: tag proofs-v9-revert-deps-hotfix ([filecoin-project/lotus#7260](https://github.com/filecoin-project/lotus/pull/7260)) - - update to go-fil-markets v1.11.0 ([filecoin-project/lotus#7253](https://github.com/filecoin-project/lotus/pull/7253)) - - Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) - - sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) - - chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) - - builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) - - sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) - - fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) - - sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) - - stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) - - gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) - - Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) - - test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) - - fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) - - upgrade go-data-transfer; propagate deal cancellations. ([filecoin-project/lotus#7208](https://github.com/filecoin-project/lotus/pull/7208)) - - revert changes to OnDealExpiredOrChanged in #5431 #7201 ([filecoin-project/lotus#7220](https://github.com/filecoin-project/lotus/pull/7220)) - - Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) - - config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) - - Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) - - ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) - - fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) - - Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) - - sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) - - markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) - - Revert "Merge pull request #7187 from filecoin-project/test/disable-broken-testground" ([filecoin-project/lotus#7191](https://github.com/filecoin-project/lotus/pull/7191)) - - ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) - - fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) - - Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) - - Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) - - journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) - - call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github.com/filecoin-project/lotus/pull/7104)) - - Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) - - Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) - - fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) - - Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) - - fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) - - test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) - - itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) - - Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) - - miner: Command to list/remove expired sectors ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) - - Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) - - test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) + - bump lotus version to v1.11.3-rc2 + - fix(deps): use go-graphsync v0.9.3 with hotfix + - feat(deps): update go-graphsync v0.9.2 + - unit test where StateMarketStorageDeal return nil, err + - GetCurrentDealInfo err: handle correctly err case + - update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) + - fix bug for CommittedCapacitySectorLifetime ([filecoin-project/lotus#7337](https://github.com/filecoin-project/lotus/pull/7337)) + - feat(ci): include version/cli checks in tagged releases ([filecoin-project/lotus#7331](https://github.com/filecoin-project/lotus/pull/7331)) + - fix a panic in HandleRecoverDealIDs ([filecoin-project/lotus#7336](https://github.com/filecoin-project/lotus/pull/7336)) + - build macOS CI ([filecoin-project/lotus#7307](https://github.com/filecoin-project/lotus/pull/7307)) + - remove job to install jq + - v1.11.3-rc1 ([filecoin-project/lotus#7299](https://github.com/filecoin-project/lotus/pull/7299)) + - update to proof v0.9.2 ([filecoin-project/lotus#7297](https://github.com/filecoin-project/lotus/pull/7297)) + - Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) + - docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) + - Update go-graphsync v0.9.1 ([filecoin-project/lotus#7294](https://github.com/filecoin-project/lotus/pull/7294)) + - itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) + - v1.11.2 -> master ([filecoin-project/lotus#7288](https://github.com/filecoin-project/lotus/pull/7288)) + - fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) + - dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) + - introduce MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) + - Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) + - Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) + - integrate the proof patch: tag proofs-v9-revert-deps-hotfix ([filecoin-project/lotus#7260](https://github.com/filecoin-project/lotus/pull/7260)) + - update to go-fil-markets v1.11.0 ([filecoin-project/lotus#7253](https://github.com/filecoin-project/lotus/pull/7253)) + - Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) + - sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) + - chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) + - builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) + - sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) + - fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) + - sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) + - stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) + - gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) + - Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) + - test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) + - fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) + - upgrade go-data-transfer; propagate deal cancellations. ([filecoin-project/lotus#7208](https://github.com/filecoin-project/lotus/pull/7208)) + - revert changes to OnDealExpiredOrChanged in #5431 #7201 ([filecoin-project/lotus#7220](https://github.com/filecoin-project/lotus/pull/7220)) + - Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) + - config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) + - Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) + - ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) + - fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) + - Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) + - sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) + - markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) + - Revert "Merge pull request #7187 from filecoin-project/test/disable-broken-testground" ([filecoin-project/lotus#7191](https://github.com/filecoin-project/lotus/pull/7191)) + - ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) + - fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) + - Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) + - Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) + - journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) + - call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github.com/filecoin-project/lotus/pull/7104)) + - Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) + - Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) + - fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) + - Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) + - fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) + - test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) + - itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) + - Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) + - miner: Command to list/remove expired sectors ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) + - Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) + - test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) - github.com/filecoin-project/go-data-transfer (v1.7.8 -> v1.10.1): - - docs(CHANGELOG): update for 1.10.1 - - Fix parallel transfers between same two peers (#254) ([filecoin-project/go-data-transfer#254](https://github.com/filecoin-project/go-data-transfer/pull/254)) - - release: v1.10.0 ([filecoin-project/go-data-transfer#253](https://github.com/filecoin-project/go-data-transfer/pull/253)) - - feat: integrate graphsync-v0.9.0 (#252) ([filecoin-project/go-data-transfer#252](https://github.com/filecoin-project/go-data-transfer/pull/252)) - - release: v1.9.0 ([filecoin-project/go-data-transfer#251](https://github.com/filecoin-project/go-data-transfer/pull/251)) - - fix: ensure graphsync transport only closes complete channel once (#250) ([filecoin-project/go-data-transfer#250](https://github.com/filecoin-project/go-data-transfer/pull/250)) - - revert: integration of graphsync-v0.9.0 until we are ready to test the whole stack with it (#249) ([filecoin-project/go-data-transfer#249](https://github.com/filecoin-project/go-data-transfer/pull/249)) - - v1.8.0 release ([filecoin-project/go-data-transfer#247](https://github.com/filecoin-project/go-data-transfer/pull/247)) - - Update to unified go graphsync v0.9.0 (#246) ([filecoin-project/go-data-transfer#246](https://github.com/filecoin-project/go-data-transfer/pull/246)) + - docs(CHANGELOG): update for 1.10.1 + - Fix parallel transfers between same two peers (#254) ([filecoin-project/go-data-transfer#254](https://github.com/filecoin-project/go-data-transfer/pull/254)) + - release: v1.10.0 ([filecoin-project/go-data-transfer#253](https://github.com/filecoin-project/go-data-transfer/pull/253)) + - feat: integrate graphsync-v0.9.0 (#252) ([filecoin-project/go-data-transfer#252](https://github.com/filecoin-project/go-data-transfer/pull/252)) + - release: v1.9.0 ([filecoin-project/go-data-transfer#251](https://github.com/filecoin-project/go-data-transfer/pull/251)) + - fix: ensure graphsync transport only closes complete channel once (#250) ([filecoin-project/go-data-transfer#250](https://github.com/filecoin-project/go-data-transfer/pull/250)) + - revert: integration of graphsync-v0.9.0 until we are ready to test the whole stack with it (#249) ([filecoin-project/go-data-transfer#249](https://github.com/filecoin-project/go-data-transfer/pull/249)) + - v1.8.0 release ([filecoin-project/go-data-transfer#247](https://github.com/filecoin-project/go-data-transfer/pull/247)) + - Update to unified go graphsync v0.9.0 (#246) ([filecoin-project/go-data-transfer#246](https://github.com/filecoin-project/go-data-transfer/pull/246)) - github.com/filecoin-project/go-fil-markets (v1.8.1 -> v1.12.0): - - release: v1.12.0 - - Update to unified graphsync v0.9.0 (#627) ([filecoin-project/go-fil-markets#627](https://github.com/filecoin-project/go-fil-markets/pull/627)) - - release: v1.11.0 ([filecoin-project/go-fil-markets#626](https://github.com/filecoin-project/go-fil-markets/pull/626)) - - feat: upgrade to go-data-transfer v1.9.0 (#625) ([filecoin-project/go-fil-markets#625](https://github.com/filecoin-project/go-fil-markets/pull/625)) - - Revert "Update to unified graphsync v0.9.0 (#615)" (#624) ([filecoin-project/go-fil-markets#624](https://github.com/filecoin-project/go-fil-markets/pull/624)) - - Update to unified graphsync v0.9.0 (#615) ([filecoin-project/go-fil-markets#615](https://github.com/filecoin-project/go-fil-markets/pull/615)) - - fix: TestCancelDataTransfer (#622) ([filecoin-project/go-fil-markets#622](https://github.com/filecoin-project/go-fil-markets/pull/622)) - - rm go-multistore dependency. (#619) ([filecoin-project/go-fil-markets#619](https://github.com/filecoin-project/go-fil-markets/pull/619)) - - revert: OnDealExpiredOrSlashed changes (#620) ([filecoin-project/go-fil-markets#620](https://github.com/filecoin-project/go-fil-markets/pull/620)) - - fix(ci): include node in circle orb to fix docsgen (#618) ([filecoin-project/go-fil-markets#618](https://github.com/filecoin-project/go-fil-markets/pull/618)) - - release: v1.9.0 ([filecoin-project/go-fil-markets#617](https://github.com/filecoin-project/go-fil-markets/pull/617)) - - refactor: pass deal proposal instead of deal ID to OnDealExpiredOrSlashed (#616) ([filecoin-project/go-fil-markets#616](https://github.com/filecoin-project/go-fil-markets/pull/616)) - - fix: reject storage deals where the end epoch is too far in the future (#612) ([filecoin-project/go-fil-markets#612](https://github.com/filecoin-project/go-fil-markets/pull/612)) + - release: v1.12.0 + - Update to unified graphsync v0.9.0 (#627) ([filecoin-project/go-fil-markets#627](https://github.com/filecoin-project/go-fil-markets/pull/627)) + - release: v1.11.0 ([filecoin-project/go-fil-markets#626](https://github.com/filecoin-project/go-fil-markets/pull/626)) + - feat: upgrade to go-data-transfer v1.9.0 (#625) ([filecoin-project/go-fil-markets#625](https://github.com/filecoin-project/go-fil-markets/pull/625)) + - Revert "Update to unified graphsync v0.9.0 (#615)" (#624) ([filecoin-project/go-fil-markets#624](https://github.com/filecoin-project/go-fil-markets/pull/624)) + - Update to unified graphsync v0.9.0 (#615) ([filecoin-project/go-fil-markets#615](https://github.com/filecoin-project/go-fil-markets/pull/615)) + - fix: TestCancelDataTransfer (#622) ([filecoin-project/go-fil-markets#622](https://github.com/filecoin-project/go-fil-markets/pull/622)) + - rm go-multistore dependency. (#619) ([filecoin-project/go-fil-markets#619](https://github.com/filecoin-project/go-fil-markets/pull/619)) + - revert: OnDealExpiredOrSlashed changes (#620) ([filecoin-project/go-fil-markets#620](https://github.com/filecoin-project/go-fil-markets/pull/620)) + - fix(ci): include node in circle orb to fix docsgen (#618) ([filecoin-project/go-fil-markets#618](https://github.com/filecoin-project/go-fil-markets/pull/618)) + - release: v1.9.0 ([filecoin-project/go-fil-markets#617](https://github.com/filecoin-project/go-fil-markets/pull/617)) + - refactor: pass deal proposal instead of deal ID to OnDealExpiredOrSlashed (#616) ([filecoin-project/go-fil-markets#616](https://github.com/filecoin-project/go-fil-markets/pull/616)) + - fix: reject storage deals where the end epoch is too far in the future (#612) ([filecoin-project/go-fil-markets#612](https://github.com/filecoin-project/go-fil-markets/pull/612)) Contributors | Contributor | Commits | Lines ± | Files Changed | |-------------|---------|---------|---------------| -| Łukasz Magiera | 38 | +3306/-1825 | 178 | +| Łukasz Magiera | 39 | +3311/-1825 | 179 | | Steven Allen | 23 | +1935/-1417 | 84 | | dirkmc | 12 | +921/-732 | 111 | | Dirk McCormick | 12 | +663/-790 | 30 | | Hannah Howard | 3 | +482/-275 | 46 | | Travis Person | 1 | +317/-65 | 5 | -| hannahhoward | 5 | +251/-50 | 12 | -| Anton Evangelatov | 7 | +198/-37 | 17 | +| hannahhoward | 7 | +257/-55 | 16 | +| Anton Evangelatov | 9 | +258/-37 | 19 | | Raúl Kripalani | 4 | +127/-36 | 13 | -| raulk | 1 | +43/-60 | 15 | -| Aayush Rajasekaran | 4 | +74/-8 | 10 | -| Frank | 2 | +68/-8 | 3 | -| Adrian Lanzafame | 1 | +16/-2 | 1 | -| Aarsh Shah | 2 | +11/-6 | 2 | -| Jennifer Wang | 3 | +7/-7 | 9 | -| ZenGround0 | 2 | +7/-6 | 2 | -| KAYUII | 2 | +4/-4 | 2 | -| lanzafame | 1 | +6/-0 | 1 | -| Jacob Heun | 1 | +3/-3 | 1 | -| frank | 1 | +4/-0 | 1 | # v1.11.2 / 2021-09-06 From 08cec487cfe1eb6bc14a0803ca3a7331dc684453 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 21 Sep 2021 23:01:43 -0400 Subject: [PATCH 120/122] make gen --- documentation/en/cli-lotus-worker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index c0436e69b..88112bc8a 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -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: "~/.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) From 67864334a389f63e097364b21d86e7d9c06fa0f1 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Wed, 22 Sep 2021 15:00:59 -0400 Subject: [PATCH 121/122] fix: escape periods to match actual periods in version --- scripts/version-check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/version-check.sh b/scripts/version-check.sh index ee4cce686..4f424ca0c 100755 --- a/scripts/version-check.sh +++ b/scripts/version-check.sh @@ -16,7 +16,7 @@ function validate_lotus_version_matches_tag(){ # get version lotus_raw_version=`${lotus_path} --version` # grep for version string - lotus_actual_version=`echo ${lotus_raw_version} | grep -oE '[0-9]+.[0-9]+.[0-9]+'` + lotus_actual_version=`echo ${lotus_raw_version} | grep -oE '[0-9]+\.[0-9]+\.[0-9]+'` # trim leading 'v' tag=${2#v} From 7c015840a6c4f208fe1d51a49d60130c070192e6 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 29 Sep 2021 02:42:38 -0400 Subject: [PATCH 122/122] v1.11.3 release prep --- CHANGELOG.md | 209 ++++++++++++++------------- build/openrpc/full.json.gz | Bin 25416 -> 25413 bytes build/openrpc/miner.json.gz | Bin 10424 -> 10421 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2707 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 +- 8 files changed, 111 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705ad4c72..bcedad7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,115 +1,120 @@ # Lotus changelog -# v1.11.3-rc2 / 2021-09-21 +# v1.11.3 / 2021-09-29 -This is the second release candidate for lotus v1.11.3. Detailed changelog will be updated upon final release. +lotus v1.11.3 is a feature release that's **highly recommended to ALL lotus users to upgrade**, including node +operators, storage providers and clients. It includes many improvements and bug fixes that result in perf +improvements in different area, like deal making, sealing and so on. -## Changelog +## Highlights + +- 🌟🌟Introduce `MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) + - Set `MaxStagingDealsBytes` under the [Dealmaking] section of the markets' subsystem's `config.toml` to reject new incoming deals when the `deal-staging` directory of market subsystem's repo gets too large. +- 🌟🌟miner: Command to list/remove expired sectors locally ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) + - run `./lotus-miner sectors expired -h` for more details. +- 🚀update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) + - MinerX fellows(early testers of lotus releases) have reported faster WindowPoSt computation! +- 🌟dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) + - This excludes the expired deals before sending out a PSD message which reduces the chances of PSD message failure due to invalid deals. +- 🌟Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) + +## New Features + +- feat(ci): include version/cli checks in tagged releases ([filecoin-project/lotus#7331](https://github.com/filecoin-project/lotus/pull/7331)) +- Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) +- config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) +- Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) +- Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) + +## Improvements + +- build macOS CI ([filecoin-project/lotus#7307](https://github.com/filecoin-project/lotus/pull/7307)) +- itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) +- Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) +- chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) +- builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) +- gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) +- Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) +- test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) +- Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) +- Catch deal slashed because sector was terminated ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) +- Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) +- sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) +- markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) +- Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) +- journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) +- call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github. com/filecoin-project/lotus/pull/7104)) +- itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) +- Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) + +## Bug Fixes + +- fix: escape periods to match actual periods in version +- fix bug for CommittedCapacitySectorLifetime ([filecoin-project/lotus#7337](https://github.com/filecoin-project/lotus/pull/7337)) +- fix a panic in HandleRecoverDealIDs ([filecoin-project/lotus#7336](https://github.com/filecoin-project/lotus/pull/7336)) +- fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) +- fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) +- fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) +- fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) +- fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) +- fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) +- fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) +- Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) +- sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) +- docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) +- sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) +- sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) +- stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) + + +## Dependency Updates -- github.com/filecoin-project/lotus: - - bump lotus version to v1.11.3-rc2 - - fix(deps): use go-graphsync v0.9.3 with hotfix - - feat(deps): update go-graphsync v0.9.2 - - unit test where StateMarketStorageDeal return nil, err - - GetCurrentDealInfo err: handle correctly err case - - update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) - - fix bug for CommittedCapacitySectorLifetime ([filecoin-project/lotus#7337](https://github.com/filecoin-project/lotus/pull/7337)) - - feat(ci): include version/cli checks in tagged releases ([filecoin-project/lotus#7331](https://github.com/filecoin-project/lotus/pull/7331)) - - fix a panic in HandleRecoverDealIDs ([filecoin-project/lotus#7336](https://github.com/filecoin-project/lotus/pull/7336)) - - build macOS CI ([filecoin-project/lotus#7307](https://github.com/filecoin-project/lotus/pull/7307)) - - remove job to install jq - - v1.11.3-rc1 ([filecoin-project/lotus#7299](https://github.com/filecoin-project/lotus/pull/7299)) - - update to proof v0.9.2 ([filecoin-project/lotus#7297](https://github.com/filecoin-project/lotus/pull/7297)) - - Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) - - docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) - - Update go-graphsync v0.9.1 ([filecoin-project/lotus#7294](https://github.com/filecoin-project/lotus/pull/7294)) - - itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) - - v1.11.2 -> master ([filecoin-project/lotus#7288](https://github.com/filecoin-project/lotus/pull/7288)) - - fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) - - dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) - - introduce MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) - - Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) - - Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) - - integrate the proof patch: tag proofs-v9-revert-deps-hotfix ([filecoin-project/lotus#7260](https://github.com/filecoin-project/lotus/pull/7260)) - - update to go-fil-markets v1.11.0 ([filecoin-project/lotus#7253](https://github.com/filecoin-project/lotus/pull/7253)) - - Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) - - sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) - - chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) - - builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) - - sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) - - fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) - - sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) - - stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) - - gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) - - Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) - - test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) - - fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) - - upgrade go-data-transfer; propagate deal cancellations. ([filecoin-project/lotus#7208](https://github.com/filecoin-project/lotus/pull/7208)) - - revert changes to OnDealExpiredOrChanged in #5431 #7201 ([filecoin-project/lotus#7220](https://github.com/filecoin-project/lotus/pull/7220)) - - Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) - - config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) - - Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) - - ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) - - fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) - - Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) - - sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) - - markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) - - Revert "Merge pull request #7187 from filecoin-project/test/disable-broken-testground" ([filecoin-project/lotus#7191](https://github.com/filecoin-project/lotus/pull/7191)) - - ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) - - fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) - - Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) - - Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) - - journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) - - call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github.com/filecoin-project/lotus/pull/7104)) - - Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) - - Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) - - fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) - - Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) - - fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) - - test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) - - itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) - - Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) - - miner: Command to list/remove expired sectors ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) - - Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) - - test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) -- github.com/filecoin-project/go-data-transfer (v1.7.8 -> v1.10.1): - - docs(CHANGELOG): update for 1.10.1 - - Fix parallel transfers between same two peers (#254) ([filecoin-project/go-data-transfer#254](https://github.com/filecoin-project/go-data-transfer/pull/254)) - - release: v1.10.0 ([filecoin-project/go-data-transfer#253](https://github.com/filecoin-project/go-data-transfer/pull/253)) - - feat: integrate graphsync-v0.9.0 (#252) ([filecoin-project/go-data-transfer#252](https://github.com/filecoin-project/go-data-transfer/pull/252)) - - release: v1.9.0 ([filecoin-project/go-data-transfer#251](https://github.com/filecoin-project/go-data-transfer/pull/251)) - - fix: ensure graphsync transport only closes complete channel once (#250) ([filecoin-project/go-data-transfer#250](https://github.com/filecoin-project/go-data-transfer/pull/250)) - - revert: integration of graphsync-v0.9.0 until we are ready to test the whole stack with it (#249) ([filecoin-project/go-data-transfer#249](https://github.com/filecoin-project/go-data-transfer/pull/249)) - - v1.8.0 release ([filecoin-project/go-data-transfer#247](https://github.com/filecoin-project/go-data-transfer/pull/247)) - - Update to unified go graphsync v0.9.0 (#246) ([filecoin-project/go-data-transfer#246](https://github.com/filecoin-project/go-data-transfer/pull/246)) - github.com/filecoin-project/go-fil-markets (v1.8.1 -> v1.12.0): - - release: v1.12.0 - - Update to unified graphsync v0.9.0 (#627) ([filecoin-project/go-fil-markets#627](https://github.com/filecoin-project/go-fil-markets/pull/627)) - - release: v1.11.0 ([filecoin-project/go-fil-markets#626](https://github.com/filecoin-project/go-fil-markets/pull/626)) - - feat: upgrade to go-data-transfer v1.9.0 (#625) ([filecoin-project/go-fil-markets#625](https://github.com/filecoin-project/go-fil-markets/pull/625)) - - Revert "Update to unified graphsync v0.9.0 (#615)" (#624) ([filecoin-project/go-fil-markets#624](https://github.com/filecoin-project/go-fil-markets/pull/624)) - - Update to unified graphsync v0.9.0 (#615) ([filecoin-project/go-fil-markets#615](https://github.com/filecoin-project/go-fil-markets/pull/615)) - - fix: TestCancelDataTransfer (#622) ([filecoin-project/go-fil-markets#622](https://github.com/filecoin-project/go-fil-markets/pull/622)) - - rm go-multistore dependency. (#619) ([filecoin-project/go-fil-markets#619](https://github.com/filecoin-project/go-fil-markets/pull/619)) - - revert: OnDealExpiredOrSlashed changes (#620) ([filecoin-project/go-fil-markets#620](https://github.com/filecoin-project/go-fil-markets/pull/620)) - - fix(ci): include node in circle orb to fix docsgen (#618) ([filecoin-project/go-fil-markets#618](https://github.com/filecoin-project/go-fil-markets/pull/618)) - - release: v1.9.0 ([filecoin-project/go-fil-markets#617](https://github.com/filecoin-project/go-fil-markets/pull/617)) - - refactor: pass deal proposal instead of deal ID to OnDealExpiredOrSlashed (#616) ([filecoin-project/go-fil-markets#616](https://github.com/filecoin-project/go-fil-markets/pull/616)) - - fix: reject storage deals where the end epoch is too far in the future (#612) ([filecoin-project/go-fil-markets#612](https://github.com/filecoin-project/go-fil-markets/pull/612)) +- github.com/filecoin-project/go-data-transfer (v1.7.8 -> v1.10.1): +- update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) +- fix(deps): use go-graphsync v0.9.3 with hotfix +- Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) -Contributors +## Others + +- v1.11.3-rc2 ([filecoin-project/lotus#7371](https://github.com/filecoin-project/lotus/pull/7371)) +- v1.11.3-rc1 ([filecoin-project/lotus#7299](https://github.com/filecoin-project/lotus/pull/7299)) +- Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) +- ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) +- Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) +- test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) +- Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) +- test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) + + +## Contributors | Contributor | Commits | Lines ± | Files Changed | |-------------|---------|---------|---------------| -| Łukasz Magiera | 39 | +3311/-1825 | 179 | -| Steven Allen | 23 | +1935/-1417 | 84 | -| dirkmc | 12 | +921/-732 | 111 | -| Dirk McCormick | 12 | +663/-790 | 30 | -| Hannah Howard | 3 | +482/-275 | 46 | -| Travis Person | 1 | +317/-65 | 5 | -| hannahhoward | 7 | +257/-55 | 16 | -| Anton Evangelatov | 9 | +258/-37 | 19 | -| Raúl Kripalani | 4 | +127/-36 | 13 | +| @magik6k | 39 | +3311/-1825 | 179 | +| @Stebalien | 23 | +1935/-1417 | 84 | +| @dirkmc | 12 | +921/-732 | 111 | +| @dirkmc | 12 | +663/-790 | 30 | +| @hannahhoward | 3 | +482/-275 | 46 | +| @travisperson | 1 | +317/-65 | 5 | +| @jennijuju | 11 | +223/-126 | 24 | +| @hannahhoward | 7 | +257/-55 | 16 | +| @nonsense| 9 | +258/-37 | 19 | +| @raulk | 4 | +127/-36 | 13 | +| @raulk | 1 | +43/-60 | 15 | +| @arajasek | 4 | +74/-8 | 10 | +| @Frank | 2 | +68/-8 | 3 | +| @placer14| 2 | +52/-1 | 4 | +| @ldoublewood | 2 | +15/-13 | 3 | +| @lanzafame | 1 | +16/-2 | 1 | +| @aarshkshah1992 | 2 | +11/-6 | 2 | +| @ZenGround0 | 2 | +7/-6 | 2 | +| @ognots | 1 | +0/-10 | 2 | +| @KAYUII | 2 | +4/-4 | 2 | +| @lanzafame | 1 | +6/-0 | 1 | +| @jacobheun | 1 | +3/-3 | 1 | +| @frank | 1 | +4/-0 | 1 | + # v1.11.2 / 2021-09-06 diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index becafecf6bf590e5c93020911c72dba50726ddb1..1bb364685273192b65cc8c7ecf93ce20173e06a5 100644 GIT binary patch literal 25413 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�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 delta 10379 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 1801396c39bdf6b1d0674060f53a224293a2972f..d9e4cd049a654174c6e4cba3ac3d4698c623eda5 100644 GIT binary patch delta 2606 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 delta 2609 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 Tchmm`00960uZrA0S$Y5fSyBO| diff --git a/build/version.go b/build/version.go index bcb82395a..6938bb8ee 100644 --- a/build/version.go +++ b/build/version.go @@ -40,7 +40,7 @@ func buildType() string { } // BuildVersion is the local build version -const BuildVersion = "1.11.3-rc2" +const BuildVersion = "1.11.3" 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 29a72ed45..2721fc421 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.3-rc2 + 1.11.3 COMMANDS: init Initialize a lotus miner repo diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 88112bc8a..bac57568f 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.3-rc2 + 1.11.3 COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 39bacda19..dec689d25 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.3-rc2 + 1.11.3 COMMANDS: daemon Start a lotus daemon process