From c66f087f4c4f67396fa24da377f41fac777c781c Mon Sep 17 00:00:00 2001 From: Travis Person Date: Tue, 22 Sep 2020 18:15:42 +0000 Subject: [PATCH 01/24] lotus-miner: add more help text to storage / attach --- cmd/lotus-storage-miner/storage.go | 19 +++++++++++++++++++ extern/sector-storage/stores/local.go | 9 +++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-storage-miner/storage.go b/cmd/lotus-storage-miner/storage.go index 8a3687877..0c366cb01 100644 --- a/cmd/lotus-storage-miner/storage.go +++ b/cmd/lotus-storage-miner/storage.go @@ -31,6 +31,9 @@ const metaFile = "sectorstore.json" var storageCmd = &cli.Command{ Name: "storage", Usage: "manage sector storage", + Description: `Sectors can be stored across many filesystem paths. These commands provide ways to + manage the storage the miner will used to store sectors long term for proving (refernces as 'store') + as well as how sectors will be stored while moving through the sealing pipeline (references as 'seal').`, Subcommands: []*cli.Command{ storageAttachCmd, storageListCmd, @@ -41,6 +44,22 @@ var storageCmd = &cli.Command{ var storageAttachCmd = &cli.Command{ Name: "attach", Usage: "attach local storage path", + Description: `Storage can be attach to the miner using this command. The storage volume list is stored local + to the miner in $LOTUS_MINER_PATH/storage.json. We do not recommend modifying this value without further + understanding of the storage system. + + Each storage volume contains a configuration file which descripbes the capabilities of the volume. When the + '--init' flag is provided, this file will be created using the additional flags. + + Weight + A high weight value means data will be more likely to be stored + + Seal + Intermittment data for the sealing process will be stored here + + Store + Finalized sectors that will be proved over will be stored here + `, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "init", diff --git a/extern/sector-storage/stores/local.go b/extern/sector-storage/stores/local.go index 75387acc8..70b37f10e 100644 --- a/extern/sector-storage/stores/local.go +++ b/extern/sector-storage/stores/local.go @@ -30,10 +30,15 @@ type StoragePath struct { // LocalStorageMeta [path]/sectorstore.json type LocalStorageMeta struct { - ID ID + ID ID + + // A height wait means data is more likely to be stored here Weight uint64 // 0 = readonly - CanSeal bool + // Intermittment data for the sealing process will be stored here + CanSeal bool + + // Finalized sectors that will be proved over will be stored here CanStore bool } From 1fc23fb466e61327d953a909411f3c954194b912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 3 Oct 2020 00:34:09 +0200 Subject: [PATCH 02/24] lotus-miner: Cleanup storage attach helptext a bit --- cmd/lotus-storage-miner/storage.go | 32 +++++++++++++++------------ extern/sector-storage/stores/local.go | 6 ++--- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/cmd/lotus-storage-miner/storage.go b/cmd/lotus-storage-miner/storage.go index 0c366cb01..77792f32a 100644 --- a/cmd/lotus-storage-miner/storage.go +++ b/cmd/lotus-storage-miner/storage.go @@ -31,9 +31,10 @@ const metaFile = "sectorstore.json" var storageCmd = &cli.Command{ Name: "storage", Usage: "manage sector storage", - Description: `Sectors can be stored across many filesystem paths. These commands provide ways to - manage the storage the miner will used to store sectors long term for proving (refernces as 'store') - as well as how sectors will be stored while moving through the sealing pipeline (references as 'seal').`, + Description: `Sectors can be stored across many filesystem paths. These +commands provide ways to manage the storage the miner will used to store sectors +long term for proving (references as 'store') as well as how sectors will be +stored while moving through the sealing pipeline (references as 'seal').`, Subcommands: []*cli.Command{ storageAttachCmd, storageListCmd, @@ -44,21 +45,24 @@ var storageCmd = &cli.Command{ var storageAttachCmd = &cli.Command{ Name: "attach", Usage: "attach local storage path", - Description: `Storage can be attach to the miner using this command. The storage volume list is stored local - to the miner in $LOTUS_MINER_PATH/storage.json. We do not recommend modifying this value without further - understanding of the storage system. + Description: `Storage can be attached to the miner using this command. The storage volume +list is stored local to the miner in $LOTUS_MINER_PATH/storage.json. We do not +recommend manually modifying this value without further understanding of the +storage system. - Each storage volume contains a configuration file which descripbes the capabilities of the volume. When the - '--init' flag is provided, this file will be created using the additional flags. +Each storage volume contains a configuration file which describes the +capabilities of the volume. When the '--init' flag is provided, this file will +be created using the additional flags. - Weight - A high weight value means data will be more likely to be stored +Weight +A high weight value means data will be more likely to be stored in this path - Seal - Intermittment data for the sealing process will be stored here +Seal +Data for the sealing process will be stored here - Store - Finalized sectors that will be proved over will be stored here +Store +Finalized sectors that will be moved here for long term storage and be proven +over time `, Flags: []cli.Flag{ &cli.BoolFlag{ diff --git a/extern/sector-storage/stores/local.go b/extern/sector-storage/stores/local.go index 70b37f10e..50968e7bd 100644 --- a/extern/sector-storage/stores/local.go +++ b/extern/sector-storage/stores/local.go @@ -32,13 +32,13 @@ type StoragePath struct { type LocalStorageMeta struct { ID ID - // A height wait means data is more likely to be stored here + // A high weight means data is more likely to be stored in this path Weight uint64 // 0 = readonly - // Intermittment data for the sealing process will be stored here + // Intermediate data for the sealing process will be stored here CanSeal bool - // Finalized sectors that will be proved over will be stored here + // Finalized sectors that will be proved over time will be stored here CanStore bool } From d4cbd4fb55077ccfd96cb5619b4bca2a29fef069 Mon Sep 17 00:00:00 2001 From: frrist Date: Mon, 5 Oct 2020 16:48:29 -0700 Subject: [PATCH 03/24] feat(miner): add miner deadline diffing - logic gathering all removed, faulted, recovered, and recovering sectors for a miner. --- chain/actors/builtin/miner/diff_deadlines.go | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 chain/actors/builtin/miner/diff_deadlines.go diff --git a/chain/actors/builtin/miner/diff_deadlines.go b/chain/actors/builtin/miner/diff_deadlines.go new file mode 100644 index 000000000..e1e839960 --- /dev/null +++ b/chain/actors/builtin/miner/diff_deadlines.go @@ -0,0 +1,180 @@ +package miner + +import ( + "errors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/exitcode" +) + +type DeadlinesDiff map[uint64]*DeadlineDiff + +func DiffDeadlines(pre, cur State) (*DeadlinesDiff, error) { + changed, err := pre.DeadlinesChanged(cur) + if err != nil { + return nil, err + } + if !changed { + return nil, nil + } + + numDl, err := pre.NumDeadlines() + if err != nil { + return nil, err + } + dlDiff := make(DeadlinesDiff, numDl) + if err := pre.ForEachDeadline(func(idx uint64, preDl Deadline) error { + curDl, err := cur.LoadDeadline(idx) + if err != nil { + return err + } + + diff, err := DiffDeadline(preDl, curDl) + if err != nil { + return err + } + + dlDiff[idx] = diff + return nil + }); err != nil { + return nil, err + } + return &dlDiff, nil +} + +type DeadlineDiff map[uint64]*PartitionDiff + +func DiffDeadline(pre, cur Deadline) (*DeadlineDiff, error) { + changed, err := pre.PartitionsChanged(cur) + if err != nil { + return nil, err + } + if !changed { + return nil, nil + } + + partDiff := make(DeadlineDiff) + if err := pre.ForEachPartition(func(idx uint64, prePart Partition) error { + // try loading current partition at this index + curPart, err := cur.LoadPartition(idx) + if err != nil { + if errors.Is(err, exitcode.ErrNotFound) { + // TODO correctness? + return nil // the partition was removed. + } + return err + } + + // compare it with the previous partition + diff, err := DiffPartition(prePart, curPart) + if err != nil { + return err + } + + partDiff[idx] = diff + return nil + }); err != nil { + return nil, err + } + + // all previous partitions have been walked. + // all partitions in cur and not in prev are new... can they be faulty already? + // TODO is this correct? + if err := cur.ForEachPartition(func(idx uint64, curPart Partition) error { + if _, found := partDiff[idx]; found { + return nil + } + faults, err := curPart.FaultySectors() + if err != nil { + return err + } + recovering, err := curPart.RecoveringSectors() + if err != nil { + return err + } + partDiff[idx] = &PartitionDiff{ + Removed: bitfield.New(), + Recovered: bitfield.New(), + Faulted: faults, + Recovering: recovering, + } + + return nil + }); err != nil { + return nil, err + } + + return &partDiff, nil +} + +type PartitionDiff struct { + Removed bitfield.BitField + Recovered bitfield.BitField + Faulted bitfield.BitField + Recovering bitfield.BitField +} + +func DiffPartition(pre, cur Partition) (*PartitionDiff, error) { + prevLiveSectors, err := pre.LiveSectors() + if err != nil { + return nil, err + } + curLiveSectors, err := cur.LiveSectors() + if err != nil { + return nil, err + } + + removed, err := bitfield.SubtractBitField(prevLiveSectors, curLiveSectors) + if err != nil { + return nil, err + } + + prevRecoveries, err := pre.RecoveringSectors() + if err != nil { + return nil, err + } + + curRecoveries, err := cur.RecoveringSectors() + if err != nil { + return nil, err + } + + recovering, err := bitfield.SubtractBitField(curRecoveries, prevRecoveries) + if err != nil { + return nil, err + } + + prevFaults, err := pre.FaultySectors() + if err != nil { + return nil, err + } + + curFaults, err := cur.FaultySectors() + if err != nil { + return nil, err + } + + faulted, err := bitfield.SubtractBitField(curFaults, prevFaults) + if err != nil { + return nil, err + } + + // all current good sectors + curActiveSectors, err := cur.ActiveSectors() + if err != nil { + return nil, err + } + + // sectors that were previously fault and are now currently active are considered recovered. + recovered, err := bitfield.IntersectBitField(prevFaults, curActiveSectors) + if err != nil { + return nil, err + } + + return &PartitionDiff{ + Removed: removed, + Recovered: recovered, + Faulted: faulted, + Recovering: recovering, + }, nil +} From 4a3081c77a99fc1d6ec989545ad4d55b532ab465 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Tue, 6 Oct 2020 20:13:48 +0000 Subject: [PATCH 04/24] lotus-pcr: limit refunds to properly priced messages --- cmd/lotus-pcr/main.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cmd/lotus-pcr/main.go b/cmd/lotus-pcr/main.go index 4ce5bbb9f..f446b2c9d 100644 --- a/cmd/lotus-pcr/main.go +++ b/cmd/lotus-pcr/main.go @@ -376,6 +376,18 @@ var runCmd = &cli.Command{ Usage: "percent of refund to issue", Value: 110, }, + &cli.StringFlag{ + Name: "pre-fee-cap-max", + EnvVars: []string{"LOTUS_PCR_PRE_FEE_CAP_MAX"}, + Usage: "messages with a fee cap larger than this will be skipped when processing pre commit messages", + Value: "0.0000000001", + }, + &cli.StringFlag{ + Name: "prove-fee-cap-max", + EnvVars: []string{"LOTUS_PCR_PROVE_FEE_CAP_MAX"}, + Usage: "messages with a prove cap larger than this will be skipped when processing pre commit messages", + Value: "0.0000000001", + }, }, Action: func(cctx *cli.Context) error { go func() { @@ -426,6 +438,16 @@ var runCmd = &cli.Command{ minerRecoveryCutoff := uint64(cctx.Int("miner-recovery-cutoff")) minerRecoveryBonus := uint64(cctx.Int("miner-recovery-bonus")) + preFeeCapMax, err := types.ParseFIL(cctx.String("pre-fee-cap-max")) + if err != nil { + return err + } + + proveFeeCapMax, err := types.ParseFIL(cctx.String("prove-fee-cap-max")) + if err != nil { + return err + } + rf := &refunder{ api: api, wallet: from, @@ -436,6 +458,8 @@ var runCmd = &cli.Command{ dryRun: dryRun, preCommitEnabled: preCommitEnabled, proveCommitEnabled: proveCommitEnabled, + preFeeCapMax: types.BigInt(preFeeCapMax), + proveFeeCapMax: types.BigInt(proveFeeCapMax), } var refunds *MinersRefund = NewMinersRefund() @@ -588,6 +612,9 @@ type refunder struct { preCommitEnabled bool proveCommitEnabled bool threshold big.Int + + preFeeCapMax big.Int + proveFeeCapMax big.Int } func (r *refunder) FindMiners(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund, owner, worker, control bool) (*MinersRefund, error) { @@ -869,6 +896,11 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } + if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) + continue + } + var sn abi.SectorNumber var proveCommitSector miner0.ProveCommitSectorParams @@ -916,6 +948,11 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } + if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) + continue + } + var precommitInfo miner.SectorPreCommitInfo if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) From 087030fe371476cfad576796a2060f81ec2b7487 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Tue, 6 Oct 2020 22:35:04 +0000 Subject: [PATCH 05/24] lotus-pcr: refund windowed post and storage deal gas fees --- cmd/lotus-pcr/main.go | 260 ++++++++++++++++++++++++++++-------------- 1 file changed, 174 insertions(+), 86 deletions(-) diff --git a/cmd/lotus-pcr/main.go b/cmd/lotus-pcr/main.go index f446b2c9d..14f4778c2 100644 --- a/cmd/lotus-pcr/main.go +++ b/cmd/lotus-pcr/main.go @@ -340,6 +340,18 @@ var runCmd = &cli.Command{ Usage: "process ProveCommitSector messages", Value: true, }, + &cli.BoolFlag{ + Name: "windowed-post", + EnvVars: []string{"LOTUS_PCR_WINDOWED_POST"}, + Usage: "process SubmitWindowedPoSt messages and refund gas fees", + Value: false, + }, + &cli.BoolFlag{ + Name: "storage-deals", + EnvVars: []string{"LOTUS_PCR_STORAGE_DEALS"}, + Usage: "process PublishStorageDeals messages and refund gas fees", + Value: false, + }, &cli.IntFlag{ Name: "head-delay", EnvVars: []string{"LOTUS_PCR_HEAD_DELAY"}, @@ -431,6 +443,8 @@ var runCmd = &cli.Command{ dryRun := cctx.Bool("dry-run") preCommitEnabled := cctx.Bool("pre-commit") proveCommitEnabled := cctx.Bool("prove-commit") + windowedPoStEnabled := cctx.Bool("windowed-post") + publishStorageDealsEnabled := cctx.Bool("storage-deals") aggregateTipsets := cctx.Int("aggregate-tipsets") minerRecoveryEnabled := cctx.Bool("miner-recovery") minerRecoveryPeriod := abi.ChainEpoch(int64(cctx.Int("miner-recovery-period"))) @@ -458,6 +472,8 @@ var runCmd = &cli.Command{ dryRun: dryRun, preCommitEnabled: preCommitEnabled, proveCommitEnabled: proveCommitEnabled, + windowedPoStEnabled: windowedPoStEnabled, + publishStorageDealsEnabled: publishStorageDealsEnabled, preFeeCapMax: types.BigInt(preFeeCapMax), proveFeeCapMax: types.BigInt(proveFeeCapMax), } @@ -611,6 +627,8 @@ type refunder struct { dryRun bool preCommitEnabled bool proveCommitEnabled bool + windowedPoStEnabled bool + publishStorageDealsEnabled bool threshold big.Int preFeeCapMax big.Int @@ -842,6 +860,147 @@ func (r *refunder) EnsureMinerMinimums(ctx context.Context, tipset *types.TipSet return refunds, nil } +func (r *refunder) processTipsetStorageMarketActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) { + + m := msg.Message + refundValue := types.NewInt(0) + var messageMethod string + + switch m.Method { + case builtin.MethodsMarket.PublishStorageDeals: + if !r.publishStorageDealsEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "PublishStorageDeals" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee) + } + + return true, messageMethod, refundValue, nil +} + +func (r *refunder) processTipsetStorageMinerActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) { + + m := msg.Message + refundValue := types.NewInt(0) + var messageMethod string + + switch m.Method { + case builtin.MethodsMiner.SubmitWindowedPoSt: + if !r.windowedPoStEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "SubmitWindowedPoSt" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee) + case builtin.MethodsMiner.ProveCommitSector: + if !r.proveCommitEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "ProveCommitSector" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) + return false, messageMethod, types.NewInt(0), nil + } + + var sn abi.SectorNumber + + var proveCommitSector miner0.ProveCommitSectorParams + if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { + log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) + return false, messageMethod, types.NewInt(0), nil + } + + sn = proveCommitSector.SectorNumber + + // We use the parent tipset key because precommit information is removed when ProveCommitSector is executed + precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents()) + if err != nil { + log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key()) + if err != nil { + log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key()) + if err != nil { + log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit) + if collateral.LessThan(big.Zero()) { + log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = collateral + if r.refundPercent > 0 { + refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + } + case builtin.MethodsMiner.PreCommitSector: + if !r.preCommitEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "PreCommitSector" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) + return false, messageMethod, types.NewInt(0), nil + } + + var precommitInfo miner.SectorPreCommitInfo + if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { + log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) + return false, messageMethod, types.NewInt(0), nil + } + + collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key()) + if err != nil { + log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = collateral + if r.refundPercent > 0 { + refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + } + default: + return false, messageMethod, types.NewInt(0), nil + } + + return true, messageMethod, refundValue, nil +} + func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund) (*MinersRefund, error) { cids := tipset.Cids() if len(cids) == 0 { @@ -877,101 +1036,30 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } - if !a.IsStorageMinerActor() { - continue - } - var messageMethod string - switch m.Method { - case builtin.MethodsMiner.ProveCommitSector: - if !r.proveCommitEnabled { - continue - } - - messageMethod = "ProveCommitSector" - - if recps[i].ExitCode != exitcode.Ok { - log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode) - continue - } - - if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { - log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) - continue - } - - var sn abi.SectorNumber - - var proveCommitSector miner0.ProveCommitSectorParams - if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { - log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) - continue - } - - sn = proveCommitSector.SectorNumber - - // We use the parent tipset key because precommit information is removed when ProveCommitSector is executed - precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents()) + if m.To == builtin.StorageMarketActorAddr { + var err error + var processed bool + processed, messageMethod, refundValue, err = r.processTipsetStorageMarketActor(ctx, tipset, msg, recps[i]) if err != nil { - log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) continue } - - precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key()) - if err != nil { - log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + if !processed { continue } - - collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key()) - if err != nil { - log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) - } - - collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit) - if collateral.LessThan(big.Zero()) { - log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) - continue - } - - refundValue = collateral - case builtin.MethodsMiner.PreCommitSector: - if !r.preCommitEnabled { - continue - } - - messageMethod = "PreCommitSector" - - if recps[i].ExitCode != exitcode.Ok { - log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode) - continue - } - - if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { - log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) - continue - } - - var precommitInfo miner.SectorPreCommitInfo - if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { - log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) - continue - } - - collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key()) - if err != nil { - log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber) - continue - } - - refundValue = collateral - default: - continue } - if r.refundPercent > 0 { - refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + if a.IsStorageMinerActor() { + var err error + var processed bool + processed, messageMethod, refundValue, err = r.processTipsetStorageMinerActor(ctx, tipset, msg, recps[i]) + if err != nil { + continue + } + if !processed { + continue + } } log.Debugw( From bd474617ed6f51726f645119f1da4bf2ece092bb Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Tue, 6 Oct 2020 16:06:24 -0700 Subject: [PATCH 06/24] implement command to get execution traces of any message --- cli/state.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/cli/state.go b/cli/state.go index 7baf57df2..371ef4af0 100644 --- a/cli/state.go +++ b/cli/state.go @@ -72,6 +72,7 @@ var stateCmd = &cli.Command{ stateMsgCostCmd, stateMinerInfo, stateMarketCmd, + stateExecTraceCmd, }, } @@ -315,6 +316,74 @@ var stateActiveSectorsCmd = &cli.Command{ }, } +var stateExecTraceCmd = &cli.Command{ + Name: "exec-trace", + Usage: "Get the execution trace of a given message", + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + if !cctx.Args().Present() { + return ShowHelp(cctx, fmt.Errorf("must pass message cid")) + } + + mcid, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("message cid was invalid: %s", err) + } + + capi, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + msg, err := capi.ChainGetMessage(ctx, mcid) + if err != nil { + return err + } + + lookup, err := capi.StateSearchMsg(ctx, mcid) + if err != nil { + return err + } + + ts, err := capi.ChainGetTipSet(ctx, lookup.TipSet) + if err != nil { + return err + } + + pts, err := capi.ChainGetTipSet(ctx, ts.Parents()) + if err != nil { + return err + } + + cso, err := capi.StateCompute(ctx, pts.Height(), nil, pts.Key()) + if err != nil { + return err + } + + var trace *api.InvocResult + for _, t := range cso.Trace { + if t.Msg.From == msg.From && t.Msg.Nonce == msg.Nonce { + trace = t + break + } + } + if trace == nil { + return fmt.Errorf("failed to find message in tipset trace output") + } + + out, err := json.MarshalIndent(trace, "", " ") + if err != nil { + return err + } + + fmt.Println(string(out)) + return nil + }, +} + var stateReplaySetCmd = &cli.Command{ Name: "replay", Usage: "Replay a particular message within a tipset", From be92dd9e63a840324a757d55bbdba7ff8f7c781e Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Tue, 6 Oct 2020 18:07:34 -0700 Subject: [PATCH 07/24] allow manual setting of noncefix fee cap --- cmd/lotus-shed/nonce-fix.go | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-shed/nonce-fix.go b/cmd/lotus-shed/nonce-fix.go index 3cd9726f4..8102fd8a9 100644 --- a/cmd/lotus-shed/nonce-fix.go +++ b/cmd/lotus-shed/nonce-fix.go @@ -5,6 +5,8 @@ import ( "math" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/urfave/cli/v2" "github.com/filecoin-project/lotus/chain/types" @@ -32,6 +34,10 @@ var noncefix = &cli.Command{ &cli.BoolFlag{ Name: "auto", }, + &cli.Int64Flag{ + Name: "gas-fee-cap", + Usage: "specify gas fee cap for nonce filling messages", + }, }, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetFullNodeAPI(cctx) @@ -84,15 +90,32 @@ var noncefix = &cli.Command{ } fmt.Printf("Creating %d filler messages (%d ~ %d)\n", end-start, start, end) + ts, err := api.ChainHead(ctx) + if err != nil { + return err + } + + feeCap := big.Mul(ts.Blocks()[0].ParentBaseFee, big.NewInt(2)) // default fee cap to 2 * parent base fee + if fcf := cctx.Int64("gas-fee-cap"); fcf != 0 { + feeCap = abi.NewTokenAmount(fcf) + } + for i := start; i < end; i++ { msg := &types.Message{ - From: addr, - To: addr, - Value: types.NewInt(1), - Nonce: i, + From: addr, + To: addr, + Value: types.NewInt(0), + Nonce: i, + GasLimit: 1000000, + GasFeeCap: feeCap, + GasPremium: abi.NewTokenAmount(5), + } + smsg, err := api.WalletSignMessage(ctx, addr, msg) + if err != nil { + return err } - _, err = api.MpoolPushMessage(ctx, msg, nil) + _, err = api.MpoolPush(ctx, smsg) if err != nil { return err } From d3dc560f53aee4eb843a3c081b44282e36c6dca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 7 Oct 2020 12:45:11 +0100 Subject: [PATCH 08/24] conformance: minor refactors. --- cmd/tvx/extract.go | 12 +++++----- conformance/driver.go | 47 +++++++++++++++++++++++++++------------ conformance/rand_fixed.go | 28 +++++++++++++++++++++++ conformance/runner.go | 20 ++++------------- conformance/stubs.go | 14 +----------- 5 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 conformance/rand_fixed.go diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index fef245858..afdeb9540 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -198,8 +198,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { Preroot: root, Epoch: execTs.Height(), Message: m, - CircSupply: &circSupplyDetail.FilCirculating, - BaseFee: &basefee, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, }) if err != nil { return fmt.Errorf("failed to execute precursor message: %w", err) @@ -229,8 +229,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { Preroot: preroot, Epoch: execTs.Height(), Message: msg, - CircSupply: &circSupplyDetail.FilCirculating, - BaseFee: &basefee, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, }) if err != nil { return fmt.Errorf("failed to execute message: %w", err) @@ -260,8 +260,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { Preroot: preroot, Epoch: execTs.Height(), Message: msg, - CircSupply: &circSupplyDetail.FilCirculating, - BaseFee: &basefee, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, }) if err != nil { return fmt.Errorf("failed to execute message: %w", err) diff --git a/conformance/driver.go b/conformance/driver.go index 9ced12d74..d51798a52 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -2,6 +2,7 @@ package conformance import ( "context" + gobig "math/big" "os" "github.com/filecoin-project/lotus/chain/state" @@ -14,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/lib/blockstore" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/test-vectors/schema" @@ -80,7 +82,7 @@ type ExecuteTipsetResult struct { func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset) (*ExecuteTipsetResult, error) { var ( syscalls = mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)) - vmRand = new(testRand) + vmRand = NewFixedRand() cs = store.NewChainStore(bs, ds, syscalls) sm = stmgr.NewStateManager(cs) @@ -143,8 +145,12 @@ type ExecuteMessageParams struct { Preroot cid.Cid Epoch abi.ChainEpoch Message *types.Message - CircSupply *abi.TokenAmount - BaseFee *abi.TokenAmount + CircSupply abi.TokenAmount + BaseFee abi.TokenAmount + + // Rand is an optional vm.Rand implementation to use. If nil, the driver + // will use a vm.Rand that returns a fixed value for all calls. + Rand vm.Rand } // ExecuteMessage executes a conformance test vector message in a temporary VM. @@ -155,14 +161,8 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP _ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea") } - basefee := DefaultBaseFee - if params.BaseFee != nil { - basefee = *params.BaseFee - } - - circSupply := DefaultCirculatingSupply - if params.CircSupply != nil { - circSupply = *params.CircSupply + if params.Rand == nil { + params.Rand = NewFixedRand() } // dummy state manager; only to reference the GetNetworkVersion method, @@ -172,13 +172,13 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP vmOpts := &vm.VMOpts{ StateBase: params.Preroot, Epoch: params.Epoch, - Rand: &testRand{}, // TODO always succeeds; need more flexibility. Bstore: bs, Syscalls: mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), // TODO always succeeds; need more flexibility. CircSupplyCalc: func(_ context.Context, _ abi.ChainEpoch, _ *state.StateTree) (abi.TokenAmount, error) { - return circSupply, nil + return params.CircSupply, nil }, - BaseFee: basefee, + Rand: params.Rand, + BaseFee: params.BaseFee, NtwkVersion: sm.GetNtwkVersion, } @@ -231,3 +231,22 @@ func toChainMsg(msg *types.Message) (ret types.ChainMsg) { } return ret } + +// BaseFeeOrDefault converts a basefee as passed in a test vector (go *big.Int +// type) to an abi.TokenAmount, or if nil it returns the DefaultBaseFee. +func BaseFeeOrDefault(basefee *gobig.Int) abi.TokenAmount { + if basefee == nil { + return DefaultBaseFee + } + return big.NewFromGo(basefee) +} + +// CircSupplyOrDefault converts a circulating supply as passed in a test vector +// (go *big.Int type) to an abi.TokenAmount, or if nil it returns the +// DefaultCirculatingSupply. +func CircSupplyOrDefault(circSupply *gobig.Int) abi.TokenAmount { + if circSupply == nil { + return DefaultBaseFee + } + return big.NewFromGo(circSupply) +} diff --git a/conformance/rand_fixed.go b/conformance/rand_fixed.go new file mode 100644 index 000000000..d356b53d0 --- /dev/null +++ b/conformance/rand_fixed.go @@ -0,0 +1,28 @@ +package conformance + +import ( + "context" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/vm" +) + +type fixedRand struct{} + +var _ vm.Rand = (*fixedRand)(nil) + +// NewFixedRand creates a test vm.Rand that always returns fixed bytes value +// of utf-8 string 'i_am_random_____i_am_random_____'. +func NewFixedRand() vm.Rand { + return &fixedRand{} +} + +func (r *fixedRand) GetChainRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { + return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. +} + +func (r *fixedRand) GetBeaconRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { + return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. +} diff --git a/conformance/runner.go b/conformance/runner.go index 2db53b3e4..a240c9067 100644 --- a/conformance/runner.go +++ b/conformance/runner.go @@ -13,9 +13,7 @@ import ( "github.com/fatih/color" "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/filecoin-project/test-vectors/schema" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" @@ -24,6 +22,8 @@ import ( "github.com/ipfs/go-merkledag" "github.com/ipld/go-car" + "github.com/filecoin-project/test-vectors/schema" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/lib/blockstore" @@ -46,18 +46,6 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) { // Create a new Driver. driver := NewDriver(ctx, vector.Selector, DriverOpts{DisableVMFlush: true}) - var circSupply *abi.TokenAmount - if cs := vector.Pre.CircSupply; cs != nil { - ta := big.NewFromGo(cs) - circSupply = &ta - } - - var basefee *abi.TokenAmount - if bf := vector.Pre.BaseFee; bf != nil { - ta := big.NewFromGo(bf) - basefee = &ta - } - // Apply every message. for i, m := range vector.ApplyMessages { msg, err := types.DecodeMessage(m.Bytes) @@ -76,8 +64,8 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) { Preroot: root, Epoch: abi.ChainEpoch(epoch), Message: msg, - CircSupply: circSupply, - BaseFee: basefee, + BaseFee: BaseFeeOrDefault(vector.Pre.BaseFee), + CircSupply: CircSupplyOrDefault(vector.Pre.CircSupply), }) if err != nil { r.Fatalf("fatal failure when executing message: %s", err) diff --git a/conformance/stubs.go b/conformance/stubs.go index a7100892f..9307fdd65 100644 --- a/conformance/stubs.go +++ b/conformance/stubs.go @@ -6,28 +6,16 @@ import ( "github.com/filecoin-project/specs-actors/actors/runtime/proof" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/specs-actors/actors/runtime" cbor "github.com/ipfs/go-ipld-cbor" ) -type testRand struct{} - -var _ vm.Rand = (*testRand)(nil) - -func (r *testRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. -} - -func (r *testRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. -} - type testSyscalls struct { runtime.Syscalls } From 39bc816a79c76d22e401310f9cae3fc3d3725992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 7 Oct 2020 15:01:30 +0200 Subject: [PATCH 09/24] build: Env var to keep test address output --- build/params_testnet.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/params_testnet.go b/build/params_testnet.go index 49abd30c0..1369b2c70 100644 --- a/build/params_testnet.go +++ b/build/params_testnet.go @@ -5,6 +5,8 @@ package build import ( + "os" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -36,7 +38,9 @@ func init() { abi.RegisteredSealProof_StackedDrg64GiBV1, ) - SetAddressNetwork(address.Mainnet) + if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" { + SetAddressNetwork(address.Mainnet) + } Devnet = false } From 7cd1330acd9802ee5709b324ba584f3882d8d2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 7 Oct 2020 15:52:03 +0100 Subject: [PATCH 10/24] make vm.EnableGasTracing public. --- chain/vm/runtime.go | 9 +++++---- chain/vm/runtime_test.go | 20 ++++++++++++++++++++ chain/vm/vm.go | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 72dd413ed..c612e374f 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -28,6 +28,9 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +// EnableGasTracing, if true, outputs gas tracing in execution traces. +var EnableGasTracing = false + type Runtime struct { types.Message rt0.Syscalls @@ -459,7 +462,7 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { } func (rt *Runtime) finilizeGasTracing() { - if enableTracing { + if EnableGasTracing { if rt.lastGasCharge != nil { rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime) } @@ -491,11 +494,9 @@ func (rt *Runtime) chargeGasFunc(skip int) func(GasCharge) { } -var enableTracing = false - func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError { toUse := gas.Total() - if enableTracing { + if EnableGasTracing { var callers [10]uintptr cout := 0 //gruntime.Callers(2+skip, callers[:]) diff --git a/chain/vm/runtime_test.go b/chain/vm/runtime_test.go index c22a8b615..9fc87f7c5 100644 --- a/chain/vm/runtime_test.go +++ b/chain/vm/runtime_test.go @@ -45,3 +45,23 @@ func TestRuntimePutErrors(t *testing.T) { rt.StorePut(&NotAVeryGoodMarshaler{}) t.Error("expected panic") } + +func BenchmarkRuntime_CreateRuntimeChargeGas_TracingDisabled(b *testing.B) { + var ( + cst = cbor.NewCborStore(nil) + gch = newGasCharge("foo", 1000, 1000) + ) + + b.ResetTimer() + + EnableGasTracing = false + noop := func() bool { return EnableGasTracing } + for n := 0; n < b.N; n++ { + // flip the value and access it to make sure + // the compiler doesn't optimize away + EnableGasTracing = true + _ = noop() + EnableGasTracing = false + _ = (&Runtime{cst: cst}).chargeGasInternal(gch, 0) + } +} diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 44979454f..e2ccd33c7 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -227,7 +227,7 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, } rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac) - if enableTracing { + if EnableGasTracing { rt.lastGasChargeTime = start if parent != nil { rt.lastGasChargeTime = parent.lastGasChargeTime From e803cf151f1dd6df0dd30681a44665a07e7b4ef9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 6 Oct 2020 15:22:54 -0700 Subject: [PATCH 11/24] introduce separate state-tree versions Instead of versioning the state tree along with the actors, version it separately. This structure may not upgrade every time we update actors. --- chain/gen/genesis/genesis.go | 3 +- chain/state/statetree.go | 56 ++++++++++++++++++-------- chain/state/statetree_test.go | 13 +++--- chain/stmgr/forks.go | 2 +- chain/types/cbor_gen.go | 16 ++++---- chain/types/state.go | 17 ++++++-- gen/main.go | 2 +- lotuspond/front/src/chain/methods.json | 3 +- 8 files changed, 73 insertions(+), 39 deletions(-) diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index f532b9f5e..9f15ecaed 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -26,7 +26,6 @@ import ( adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -117,7 +116,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge return nil, nil, xerrors.Errorf("putting empty object: %w", err) } - state, err := state.NewStateTree(cst, actors.Version0) + state, err := state.NewStateTree(cst, types.StateTreeVersion0) if err != nil { return nil, nil, xerrors.Errorf("making new state tree: %w", err) } diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 3f9597420..e9b76ea77 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" cbg "github.com/whyrusleeping/cbor-gen" @@ -26,7 +27,7 @@ var log = logging.Logger("statetree") // StateTree stores actors state by their ID. type StateTree struct { root adt.Map - version actors.Version // TODO + version types.StateTreeVersion info cid.Cid Store cbor.IpldStore @@ -120,21 +121,41 @@ func (ss *stateSnaps) deleteActor(addr address.Address) { ss.layers[len(ss.layers)-1].actors[addr] = streeOp{Delete: true} } -func NewStateTree(cst cbor.IpldStore, version actors.Version) (*StateTree, error) { +// VersionForNetwork returns the state tree version for the given network +// version. +func VersionForNetwork(ver network.Version) types.StateTreeVersion { + if actors.VersionForNetwork(ver) == actors.Version0 { + return types.StateTreeVersion0 + } + return types.StateTreeVersion1 +} + +func adtForSTVersion(ver types.StateTreeVersion) actors.Version { + switch ver { + case types.StateTreeVersion0: + return actors.Version0 + case types.StateTreeVersion1: + return actors.Version2 + default: + panic("unhandled state tree version") + } +} + +func NewStateTree(cst cbor.IpldStore, ver types.StateTreeVersion) (*StateTree, error) { var info cid.Cid - switch version { - case actors.Version0: + switch ver { + case types.StateTreeVersion0: // info is undefined - case actors.Version2: + case types.StateTreeVersion1: var err error - info, err = cst.Put(context.TODO(), new(types.StateInfo)) + info, err = cst.Put(context.TODO(), new(types.StateInfo0)) if err != nil { return nil, err } default: - return nil, xerrors.Errorf("unsupported state tree version: %d", version) + return nil, xerrors.Errorf("unsupported state tree version: %d", ver) } - root, err := adt.NewMap(adt.WrapStore(context.TODO(), cst), version) + root, err := adt.NewMap(adt.WrapStore(context.TODO(), cst), adtForSTVersion(ver)) if err != nil { return nil, err } @@ -142,7 +163,7 @@ func NewStateTree(cst cbor.IpldStore, version actors.Version) (*StateTree, error return &StateTree{ root: root, info: info, - version: version, + version: ver, Store: cst, snaps: newStateSnaps(), }, nil @@ -154,13 +175,16 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { if err := cst.Get(context.TODO(), c, &root); err != nil { // We failed to decode as the new version, must be an old version. root.Actors = c - root.Version = actors.Version0 + root.Version = types.StateTreeVersion0 } switch root.Version { - case actors.Version0, actors.Version2: + case types.StateTreeVersion0, types.StateTreeVersion1: // Load the actual state-tree HAMT. - nd, err := adt.AsMap(adt.WrapStore(context.TODO(), cst), root.Actors, actors.Version(root.Version)) + nd, err := adt.AsMap( + adt.WrapStore(context.TODO(), cst), root.Actors, + adtForSTVersion(root.Version), + ) if err != nil { log.Errorf("loading hamt node %s failed: %s", c, err) return nil, err @@ -169,7 +193,7 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { return &StateTree{ root: nd, info: root.Info, - version: actors.Version(root.Version), + version: root.Version, Store: cst, snaps: newStateSnaps(), }, nil @@ -309,11 +333,11 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { return cid.Undef, xerrors.Errorf("failed to flush state-tree hamt: %w", err) } // If we're version 0, return a raw tree. - if st.version == actors.Version0 { + if st.version == types.StateTreeVersion0 { return root, nil } // Otherwise, return a versioned tree. - return st.Store.Put(ctx, &types.StateRoot{Version: uint64(st.version), Actors: root, Info: st.info}) + return st.Store.Put(ctx, &types.StateRoot{Version: st.version, Actors: root, Info: st.info}) } func (st *StateTree) Snapshot(ctx context.Context) error { @@ -400,7 +424,7 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error } // Version returns the version of the StateTree data structure in use. -func (st *StateTree) Version() actors.Version { +func (st *StateTree) Version() types.StateTreeVersion { return st.version } diff --git a/chain/state/statetree_test.go b/chain/state/statetree_test.go index 3b08a4b53..ed1fb1889 100644 --- a/chain/state/statetree_test.go +++ b/chain/state/statetree_test.go @@ -13,13 +13,12 @@ import ( "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" ) func BenchmarkStateTreeSet(b *testing.B) { cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) + st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion)) if err != nil { b.Fatal(err) } @@ -46,7 +45,7 @@ func BenchmarkStateTreeSet(b *testing.B) { func BenchmarkStateTreeSetFlush(b *testing.B) { cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) + st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion)) if err != nil { b.Fatal(err) } @@ -76,7 +75,7 @@ func BenchmarkStateTreeSetFlush(b *testing.B) { func BenchmarkStateTree10kGetActor(b *testing.B) { cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) + st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion)) if err != nil { b.Fatal(err) } @@ -118,7 +117,7 @@ func BenchmarkStateTree10kGetActor(b *testing.B) { func TestSetCache(t *testing.T) { cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) + st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion)) if err != nil { t.Fatal(err) } @@ -155,7 +154,7 @@ func TestSetCache(t *testing.T) { func TestSnapshots(t *testing.T) { ctx := context.Background() cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) + st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion)) if err != nil { t.Fatal(err) } @@ -239,7 +238,7 @@ func assertNotHas(t *testing.T, st *StateTree, addr address.Address) { func TestStateTreeConsistency(t *testing.T) { cst := cbor.NewMemCborStore() // TODO: ActorUpgrade: this test tests pre actors v2 - st, err := NewStateTree(cst, actors.VersionForNetwork(network.Version3)) + st, err := NewStateTree(cst, VersionForNetwork(network.Version3)) if err != nil { t.Fatal(err) } diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index d5d0dbf7e..fd6058127 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -494,7 +494,7 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, roo epoch := ts.Height() - 1 - info, err := store.Put(ctx, new(types.StateInfo)) + info, err := store.Put(ctx, new(types.StateInfo0)) if err != nil { return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) } diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index f95df33bc..d063ce8c9 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -1648,7 +1648,7 @@ func (t *StateRoot) MarshalCBOR(w io.Writer) error { scratch := make([]byte, 9) - // t.Version (uint64) (uint64) + // t.Version (types.StateTreeVersion) (uint64) if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Version)); err != nil { return err @@ -1687,7 +1687,7 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input had wrong number of fields") } - // t.Version (uint64) (uint64) + // t.Version (types.StateTreeVersion) (uint64) { @@ -1698,7 +1698,7 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error { if maj != cbg.MajUnsignedInt { return fmt.Errorf("wrong type for uint64 field") } - t.Version = uint64(extra) + t.Version = StateTreeVersion(extra) } // t.Actors (cid.Cid) (struct) @@ -1728,22 +1728,22 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufStateInfo = []byte{128} +var lengthBufStateInfo0 = []byte{128} -func (t *StateInfo) MarshalCBOR(w io.Writer) error { +func (t *StateInfo0) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufStateInfo); err != nil { + if _, err := w.Write(lengthBufStateInfo0); err != nil { return err } return nil } -func (t *StateInfo) UnmarshalCBOR(r io.Reader) error { - *t = StateInfo{} +func (t *StateInfo0) UnmarshalCBOR(r io.Reader) error { + *t = StateInfo0{} br := cbg.GetPeeker(r) scratch := make([]byte, 8) diff --git a/chain/types/state.go b/chain/types/state.go index b99eb19c2..a96883604 100644 --- a/chain/types/state.go +++ b/chain/types/state.go @@ -2,9 +2,20 @@ package types import "github.com/ipfs/go-cid" +// StateTreeVersion is the version of the state tree itself, independent of the +// network version or the actors version. +type StateTreeVersion uint64 + +const ( + // StateTreeVersion0 corresponds to actors < v2. + StateTreeVersion0 StateTreeVersion = iota + // StateTreeVersion1 corresponds to actors >= v2. + StateTreeVersion1 +) + type StateRoot struct { - // State root version. Versioned along with actors (for now). - Version uint64 + // State tree version. + Version StateTreeVersion // Actors tree. The structure depends on the state root version. Actors cid.Cid // Info. The structure depends on the state root version. @@ -12,4 +23,4 @@ type StateRoot struct { } // TODO: version this. -type StateInfo struct{} +type StateInfo0 struct{} diff --git a/gen/main.go b/gen/main.go index bcb43a8f0..d5874af2c 100644 --- a/gen/main.go +++ b/gen/main.go @@ -27,7 +27,7 @@ func main() { types.ExpTipSet{}, types.BeaconEntry{}, types.StateRoot{}, - types.StateInfo{}, + types.StateInfo0{}, ) if err != nil { fmt.Println(err) diff --git a/lotuspond/front/src/chain/methods.json b/lotuspond/front/src/chain/methods.json index 5e15b053b..b271bfae5 100644 --- a/lotuspond/front/src/chain/methods.json +++ b/lotuspond/front/src/chain/methods.json @@ -176,7 +176,8 @@ "CompactPartitions", "CompactSectorNumbers", "ConfirmUpdateWorkerKey", - "RepayDebt" + "RepayDebt", + "ChangeOwnerAddress" ], "fil/2/storagepower": [ "Send", From c17fa4bc35d3a7847fcc50a967fce468b56993f9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 6 Oct 2020 16:58:18 -0700 Subject: [PATCH 12/24] update test-vectors for StateManager constructor change --- extern/test-vectors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/test-vectors b/extern/test-vectors index 3a6e0b5e0..56f0676eb 160000 --- a/extern/test-vectors +++ b/extern/test-vectors @@ -1 +1 @@ -Subproject commit 3a6e0b5e069b1452ce1a032aa315354d645f3ec4 +Subproject commit 56f0676eb3be4b1e1dea892eea330614de755177 From cca17f607898615ded848d89a5de27c139413f90 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 6 Oct 2020 17:51:43 -0700 Subject: [PATCH 13/24] fix state tree version in v2 upgrade --- chain/actors/version.go | 4 ++-- chain/stmgr/forks.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/chain/actors/version.go b/chain/actors/version.go index 385ff592f..17af8b08b 100644 --- a/chain/actors/version.go +++ b/chain/actors/version.go @@ -9,8 +9,8 @@ import ( type Version int const ( - Version0 = 0 - Version2 = 2 + Version0 Version = 0 + Version2 Version = 2 ) // Converts a network version into an actors adt version. diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index fd6058127..52c764ba2 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -25,7 +25,6 @@ import ( states2 "github.com/filecoin-project/specs-actors/v2/actors/states" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" @@ -518,8 +517,7 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, roo } newRoot, err := store.Put(ctx, &types.StateRoot{ - // TODO: ActorUpgrade: should be state-tree specific, not just the actors version. - Version: actors.Version2, + Version: types.StateTreeVersion1, Actors: newHamtRoot, Info: info, }) From 767c346cf82dd2a41d72926dd2b406707f049a0c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 12:17:32 -0700 Subject: [PATCH 14/24] update test-vectors --- extern/test-vectors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/test-vectors b/extern/test-vectors index 56f0676eb..7471e2805 160000 --- a/extern/test-vectors +++ b/extern/test-vectors @@ -1 +1 @@ -Subproject commit 56f0676eb3be4b1e1dea892eea330614de755177 +Subproject commit 7471e2805fc3e459e4ee325775633e8ec76cb7c6 From fe912223bdfa000b008c143b21f2cca1c0ac7388 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 13:56:38 -0700 Subject: [PATCH 15/24] pass an explicit upgrade height to migrations The tipset height may not be the correct one, given null blocks. --- chain/stmgr/forks.go | 23 +++++++++++++---------- chain/stmgr/forks_test.go | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 52c764ba2..b897c86eb 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -33,7 +33,14 @@ import ( "github.com/filecoin-project/lotus/chain/vm" ) -type UpgradeFunc func(context.Context, *StateManager, ExecCallback, cid.Cid, *types.TipSet) (cid.Cid, error) +// UpgradeFunc is a migration function run ate very upgrade. +// +// - 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. +type UpgradeFunc func(ctx context.Context, sm *StateManager, cb ExecCallback, oldState cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (newState cid.Cid, err error) type Upgrade struct { Height abi.ChainEpoch @@ -108,7 +115,7 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig var err error f, ok := sm.stateMigrations[height] if ok { - retCid, err = f(ctx, sm, cb, root, ts) + retCid, err = f(ctx, sm, cb, root, height, ts) if err != nil { return cid.Undef, err } @@ -179,7 +186,7 @@ func doTransfer(cb ExecCallback, tree types.StateTree, from, to address.Address, return nil } -func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { +func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCallback, 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) @@ -431,11 +438,9 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal return tree.Flush(ctx) } -func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { +func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { store := sm.cs.Store(ctx) - epoch := ts.Height() - 1 - if build.UpgradeLiftoffHeight <= epoch { return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") } @@ -488,11 +493,9 @@ func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, roo return tree.Flush(ctx) } -func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { +func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { store := sm.cs.Store(ctx) - epoch := ts.Height() - 1 - info, err := store.Put(ctx, new(types.StateInfo0)) if err != nil { return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) @@ -539,7 +542,7 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, roo return newRoot, nil } -func UpgradeLiftoff(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { +func UpgradeLiftoff(ctx context.Context, sm *StateManager, cb ExecCallback, 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) diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index bb03f13b9..daa39a8d6 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -120,7 +120,7 @@ func TestForkHeightTriggers(t *testing.T) { Network: 1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback, - root cid.Cid, ts *types.TipSet) (cid.Cid, error) { + root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { cst := ipldcbor.NewCborStore(sm.ChainStore().Blockstore()) st, err := sm.StateTree(root) From dab1107f5b86f6f6dbef8e630b92fc9d0b4efac7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 14:35:07 -0700 Subject: [PATCH 16/24] avoid estimating gas and explicitly calling blocks on fork tipsets These tipsets can be slow. --- chain/stmgr/call.go | 39 +++++++++++++++++++++++++++++++++++++++ chain/stmgr/forks.go | 5 +++++ node/impl/full/gas.go | 13 ++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 15cbac53e..0497a20ed 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -2,6 +2,7 @@ package stmgr import ( "context" + "errors" "fmt" "github.com/filecoin-project/go-address" @@ -17,17 +18,37 @@ import ( "github.com/filecoin-project/lotus/chain/vm" ) +var ErrWouldFork = errors.New("refusing explicit call due to state fork at epoch") + 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() + // 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.hasStateFork(ctx, ts.Height()-1) { + var err error + ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) + } + } } bstate := ts.ParentState() bheight := ts.Height() + // If we have to run a 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 bheight-1 > 0 && sm.hasStateFork(ctx, bheight-1) { + return nil, ErrWouldFork + } + bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) @@ -106,6 +127,24 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri if ts == nil { ts = sm.cs.GetHeaviestTipSet() + + // Search back till we find a height with no fork, or we reach the beginning. + // We need the _previous_ height to have no fork, because we'll + // run the fork logic in `sm.TipSetState`. We need the _current_ + // height to have no fork, because we'll run it inside this + // function before executing the given message. + for ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { + var err error + ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) + } + } + } + + // When we're not at the genesis block, make sure we're at a migration height. + if ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { + return nil, ErrWouldFork } state, _, err := sm.TipSetState(ctx, ts) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index b897c86eb..69159f0fe 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -124,6 +124,11 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig return retCid, nil } +func (sm *StateManager) hasStateFork(ctx context.Context, height abi.ChainEpoch) bool { + _, ok := sm.stateMigrations[height] + return ok +} + func doTransfer(cb ExecCallback, tree types.StateTree, from, to address.Address, amt abi.TokenAmount) error { fromAct, err := tree.GetActor(from) if err != nil { diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index c912c7a8c..1b07aa950 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -160,7 +160,18 @@ func (a *GasAPI) GasEstimateGasLimit(ctx context.Context, msgIn *types.Message, priorMsgs = append(priorMsgs, m) } - res, err := a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts) + // Try calling until we find a height with no migration. + var res *api.InvocResult + for { + res, err = a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts) + if err != stmgr.ErrWouldFork { + break + } + ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) + if err != nil { + return -1, xerrors.Errorf("getting parent tipset: %w", err) + } + } if err != nil { return -1, xerrors.Errorf("CallWithGas failed: %w", err) } From 9b7b6146ebf6e3c18ecd421a0f148045d937325e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 15:26:08 -0700 Subject: [PATCH 17/24] construct the new vm with the state manager's vm constructor --- chain/stmgr/call.go | 4 ++-- chain/stmgr/utils.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 0497a20ed..14a4359c1 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -65,7 +65,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. BaseFee: types.NewInt(0), } - vmi, err := vm.NewVM(ctx, vmopt) + vmi, err := sm.newVM(ctx, vmopt) if err != nil { return nil, xerrors.Errorf("failed to set up vm: %w", err) } @@ -177,7 +177,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, } - vmi, err := vm.NewVM(ctx, vmopt) + vmi, err := sm.newVM(ctx, vmopt) if err != nil { return nil, xerrors.Errorf("failed to set up vm: %w", err) } diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 2c9c5ad94..c0f0c4d2f 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -387,7 +387,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, } - vmi, err := vm.NewVM(ctx, vmopt) + vmi, err := sm.newVM(ctx, vmopt) if err != nil { return cid.Undef, nil, err } From 1fea550ce5028997cbe7423b0e9347251dc5878f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 15:28:16 -0700 Subject: [PATCH 18/24] test that we refuse explicit calls at the migration epochs --- chain/stmgr/forks_test.go | 87 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index daa39a8d6..bd3e960d0 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -10,8 +10,9 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/cbor" "github.com/filecoin-project/specs-actors/actors/builtin" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" + init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" "github.com/filecoin-project/specs-actors/actors/runtime" + "github.com/stretchr/testify/require" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/actors" @@ -76,7 +77,7 @@ func (ta testActor) Exports() []interface{} { func (ta *testActor) Constructor(rt runtime.Runtime, params *abi.EmptyValue) *abi.EmptyValue { rt.ValidateImmediateCallerAcceptAny() rt.StateCreate(&testActorState{11}) - fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Receiver()) + //fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Receiver()) return abi.Empty } @@ -173,7 +174,7 @@ func TestForkHeightTriggers(t *testing.T) { var msgs []*types.SignedMessage - enc, err := actors.SerializeParams(&init_.ExecParams{CodeCID: (testActor{}).Code()}) + enc, err := actors.SerializeParams(&init0.ExecParams{CodeCID: (testActor{}).Code()}) if err != nil { t.Fatal(err) } @@ -233,3 +234,83 @@ func TestForkHeightTriggers(t *testing.T) { } } } + +func TestForkRefuseCall(t *testing.T) { + logging.SetAllLoggers(logging.LevelInfo) + + ctx := context.TODO() + + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + sm, err := NewStateManagerWithUpgradeSchedule( + cg.ChainStore(), UpgradeSchedule{{ + Network: 1, + Height: testForkHeight, + Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback, + root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + return root, nil + }}}) + if err != nil { + t.Fatal(err) + } + + inv := vm.NewActorRegistry() + inv.Register(nil, testActor{}) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) { + nvm, err := vm.NewVM(ctx, vmopt) + if err != nil { + return nil, err + } + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + enc, err := actors.SerializeParams(&init0.ExecParams{CodeCID: (testActor{}).Code()}) + if err != nil { + t.Fatal(err) + } + + m := &types.Message{ + From: cg.Banker(), + To: lotusinit.Address, + Method: builtin.MethodsInit.Exec, + Params: enc, + GasLimit: types.TestGasLimit, + Value: types.NewInt(0), + GasPremium: types.NewInt(0), + GasFeeCap: types.NewInt(0), + } + + for i := 0; i < 50; i++ { + ts, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + ret, err := sm.CallWithGas(ctx, m, nil, ts.TipSet.TipSet()) + switch ts.TipSet.TipSet().Height() { + case testForkHeight, testForkHeight + 1: + // If I had a fork, or I _will_ have a fork, it should fail. + require.Equal(t, ErrWouldFork, err) + default: + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + } + // Call just runs on the parent state for a tipset, so we only + // expect an error at the fork height. + ret, err = sm.Call(ctx, m, ts.TipSet.TipSet()) + switch ts.TipSet.TipSet().Height() { + case testForkHeight + 1: + require.Equal(t, ErrWouldFork, err) + default: + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + } + } +} From b0bea5f14558f49a676d471c5c612d97c925158b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 15:46:56 -0700 Subject: [PATCH 19/24] return an illegal actor error when we see an unsupported actor version As far as the chain is concerned, this actor does not exist. --- chain/vm/invoker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 1e9f04081..661e31178 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -70,12 +70,12 @@ func (ar *ActorRegistry) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.Meth log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Receiver()) return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "no code for actor %s(%d)(%s)", codeCid, method, hex.EncodeToString(params)) } + if err := act.predicate(rt, act.vmActor); err != nil { + return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "unsupported actor: %s", err) + } if method >= abi.MethodNum(len(act.methods)) || act.methods[method] == nil { return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "no method %d on actor", method) } - if err := act.predicate(rt, act.vmActor); err != nil { - return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "unsupported actor: %s", err) - } return act.methods[method](rt, params) } From a4e954197caf1c10ee67eb6487398640a96028b5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 16:03:42 -0700 Subject: [PATCH 20/24] retry StateCall at different height if we're at an expensive fork height --- node/impl/full/state.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 3a4390d03..0a5c856bd 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -307,12 +307,22 @@ func (a *StateAPI) StateMinerPower(ctx context.Context, addr address.Address, ts }, nil } -func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) { +func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) { ts, err := a.Chain.GetTipSetFromKey(tsk) if err != nil { return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) } - return a.StateManager.Call(ctx, msg, ts) + for { + res, err = a.StateManager.Call(ctx, msg, ts) + if err != stmgr.ErrWouldFork { + break + } + ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("getting parent tipset: %w", err) + } + } + return res, err } func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid.Cid) (*api.InvocResult, error) { From e8253d22c6600d8b03733bf1d32fb1b508101fa3 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 16:14:11 -0700 Subject: [PATCH 21/24] only forbid Call* at expensive forks --- chain/stmgr/call.go | 21 +++++++++++---------- chain/stmgr/forks.go | 6 ++++-- chain/stmgr/forks_test.go | 9 +++++---- chain/stmgr/stmgr.go | 23 ++++++++++++++++------- node/impl/full/gas.go | 2 +- node/impl/full/state.go | 2 +- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 14a4359c1..df3bfa357 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -18,7 +18,7 @@ import ( "github.com/filecoin-project/lotus/chain/vm" ) -var ErrWouldFork = errors.New("refusing explicit call due to state fork at epoch") +var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch") func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { ctx, span := trace.StartSpan(ctx, "statemanager.Call") @@ -29,7 +29,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. ts = sm.cs.GetHeaviestTipSet() // Search back till we find a height with no fork, or we reach the beginning. - for ts.Height() > 0 && sm.hasStateFork(ctx, ts.Height()-1) { + for ts.Height() > 0 && sm.hasExpensiveFork(ctx, ts.Height()-1) { var err error ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) if err != nil { @@ -41,14 +41,15 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. bstate := ts.ParentState() bheight := ts.Height() - // If we have to run a migration, and we're not at genesis, return an - // error because the migration will take too long. + // 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 bheight-1 > 0 && sm.hasStateFork(ctx, bheight-1) { - return nil, ErrWouldFork + if bheight-1 > 0 && sm.hasExpensiveFork(ctx, bheight-1) { + return nil, ErrExpensiveFork } + // Run the (not expensive) migration. bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts) if err != nil { return nil, fmt.Errorf("failed to handle fork: %w", err) @@ -133,7 +134,7 @@ 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.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { + 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()) if err != nil { @@ -142,9 +143,9 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri } } - // When we're not at the genesis block, make sure we're at a migration height. - if ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { - return nil, ErrWouldFork + // 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 } state, _, err := sm.TipSetState(ctx, ts) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 69159f0fe..6aa5e5d87 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -45,6 +45,7 @@ type UpgradeFunc func(ctx context.Context, sm *StateManager, cb ExecCallback, ol type Upgrade struct { Height abi.ChainEpoch Network network.Version + Expensive bool Migration UpgradeFunc } @@ -68,6 +69,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { }, { Height: build.UpgradeActorsV2Height, Network: network.Version4, + Expensive: true, Migration: UpgradeActorsV2, }, { Height: build.UpgradeLiftoffHeight, @@ -124,8 +126,8 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig return retCid, nil } -func (sm *StateManager) hasStateFork(ctx context.Context, height abi.ChainEpoch) bool { - _, ok := sm.stateMigrations[height] +func (sm *StateManager) hasExpensiveFork(ctx context.Context, 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 bd3e960d0..bf1c711e4 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -247,8 +247,9 @@ func TestForkRefuseCall(t *testing.T) { sm, err := NewStateManagerWithUpgradeSchedule( cg.ChainStore(), UpgradeSchedule{{ - Network: 1, - Height: testForkHeight, + Network: 1, + Expensive: true, + Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { return root, nil @@ -297,7 +298,7 @@ func TestForkRefuseCall(t *testing.T) { switch ts.TipSet.TipSet().Height() { case testForkHeight, testForkHeight + 1: // If I had a fork, or I _will_ have a fork, it should fail. - require.Equal(t, ErrWouldFork, err) + require.Equal(t, ErrExpensiveFork, err) default: require.NoError(t, err) require.True(t, ret.MsgRct.ExitCode.IsSuccess()) @@ -307,7 +308,7 @@ func TestForkRefuseCall(t *testing.T) { ret, err = sm.Call(ctx, m, ts.TipSet.TipSet()) switch ts.TipSet.TipSet().Height() { case testForkHeight + 1: - require.Equal(t, ErrWouldFork, err) + require.Equal(t, ErrExpensiveFork, err) default: require.NoError(t, err) require.True(t, ret.MsgRct.ExitCode.IsSuccess()) diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index ac01ebb61..d81cf1c72 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -53,6 +53,10 @@ type StateManager struct { // Maps chain epochs to upgrade functions. stateMigrations map[abi.ChainEpoch]UpgradeFunc + // A set of potentially expensive/time consuming upgrades. Explicit + // calls for, e.g., gas estimation fail against this epoch with + // ErrExpensiveFork. + expensiveUpgrades map[abi.ChainEpoch]struct{} stCache map[string][]cid.Cid compWait map[string]chan struct{} @@ -78,6 +82,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule } stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us)) + expensiveUpgrades := make(map[abi.ChainEpoch]struct{}, len(us)) var networkVersions []versionSpec lastVersion := network.Version0 if len(us) > 0 { @@ -87,6 +92,9 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule if upgrade.Migration != nil { stateMigrations[upgrade.Height] = upgrade.Migration } + if upgrade.Expensive { + expensiveUpgrades[upgrade.Height] = struct{}{} + } networkVersions = append(networkVersions, versionSpec{ networkVersion: lastVersion, atOrBelow: upgrade.Height, @@ -99,13 +107,14 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule } return &StateManager{ - networkVersions: networkVersions, - latestVersion: lastVersion, - stateMigrations: stateMigrations, - newVM: vm.NewVM, - cs: cs, - stCache: make(map[string][]cid.Cid), - compWait: make(map[string]chan struct{}), + networkVersions: networkVersions, + latestVersion: lastVersion, + stateMigrations: stateMigrations, + expensiveUpgrades: expensiveUpgrades, + newVM: vm.NewVM, + cs: cs, + stCache: make(map[string][]cid.Cid), + compWait: make(map[string]chan struct{}), }, nil } diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 1b07aa950..3580ca26d 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -164,7 +164,7 @@ func (a *GasAPI) GasEstimateGasLimit(ctx context.Context, msgIn *types.Message, var res *api.InvocResult for { res, err = a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts) - if err != stmgr.ErrWouldFork { + if err != stmgr.ErrExpensiveFork { break } ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 0a5c856bd..7d654985a 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -314,7 +314,7 @@ func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types. } for { res, err = a.StateManager.Call(ctx, msg, ts) - if err != stmgr.ErrWouldFork { + if err != stmgr.ErrExpensiveFork { break } ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) From d97eb10349398e8fae800bf11b2bfc046d96d57e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 7 Oct 2020 16:32:54 -0700 Subject: [PATCH 22/24] fix spelling nit --- chain/stmgr/forks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 6aa5e5d87..1888d1ee9 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -33,7 +33,7 @@ import ( "github.com/filecoin-project/lotus/chain/vm" ) -// UpgradeFunc is a migration function run ate very upgrade. +// UpgradeFunc is a migration function run at every upgrade. // // - 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. From d1555106a474bf75c2e5329df02579165ffcbff8 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 6 Oct 2020 17:47:03 -0400 Subject: [PATCH 23/24] Set actorsv2 upgrade epoch --- build/params_2k.go | 3 +- build/params_testground.go | 3 +- build/params_testnet.go | 5 +-- chain/actors/builtin/builtin.go | 1 + chain/stmgr/forks.go | 68 +++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/build/params_2k.go b/build/params_2k.go index f4a17f724..a9277831d 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -13,8 +13,9 @@ const BreezeGasTampingDuration = 0 const UpgradeSmokeHeight = -1 const UpgradeIgnitionHeight = -2 -const UpgradeLiftoffHeight = -3 +const UpgradeRefuelHeight = -3 const UpgradeActorsV2Height = 10 +const UpgradeLiftoffHeight = -4 var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/build/params_testground.go b/build/params_testground.go index bd064b3d6..6109cbc04 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -82,8 +82,9 @@ var ( UpgradeSmokeHeight abi.ChainEpoch = -1 UpgradeIgnitionHeight abi.ChainEpoch = -2 - UpgradeLiftoffHeight abi.ChainEpoch = -3 + UpgradeRefuelHeight abi.ChainEpoch = -3 UpgradeActorsV2Height abi.ChainEpoch = 10 + UpgradeLiftoffHeight abi.ChainEpoch = -4 DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/build/params_testnet.go b/build/params_testnet.go index 16de8ff75..aef3ff450 100644 --- a/build/params_testnet.go +++ b/build/params_testnet.go @@ -25,9 +25,8 @@ const BreezeGasTampingDuration = 120 const UpgradeSmokeHeight = 51000 const UpgradeIgnitionHeight = 94000 - -// TODO: Actual epoch needs to be filled in -const UpgradeActorsV2Height = 128888 +const UpgradeRefuelHeight = 130800 +const UpgradeActorsV2Height = 138720 // This signals our tentative epoch for mainnet launch. Can make it later, but not earlier. // Miners, clients, developers, custodians all need time to prepare. diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 7def78dcf..cb24a2c33 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -22,6 +22,7 @@ import ( var SystemActorAddr = builtin0.SystemActorAddr var BurntFundsActorAddr = builtin0.BurntFundsActorAddr var ReserveAddress = makeAddress("t090") +var RootVerifierAddress = makeAddress("t080") // TODO: Why does actors have 2 different versions of this? type SectorInfo = proof0.SectorInfo diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 1888d1ee9..989a94870 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "math" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -66,6 +68,10 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradeIgnitionHeight, Network: network.Version3, Migration: UpgradeIgnition, + }, { + Height: build.UpgradeRefuelHeight, + Network: network.Version3, + Migration: UpgradeRefuel, }, { Height: build.UpgradeActorsV2Height, Network: network.Version4, @@ -500,6 +506,36 @@ func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, roo return tree.Flush(ctx) } +func UpgradeRefuel(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + store := sm.cs.Store(ctx) + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + addr, err := address.NewFromString("t0122") + if err != nil { + return cid.Undef, xerrors.Errorf("getting address: %w", err) + } + + err = resetMultisigVesting(ctx, store, tree, addr, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting(ctx, store, tree, builtin.ReserveAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting(ctx, store, tree, builtin.RootVerifierAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + return tree.Flush(ctx) +} + func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { store := sm.cs.Store(ctx) @@ -707,6 +743,7 @@ func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, erro return addr, nil } +// TODO: After the Liftoff epoch, refactor this to use resetMultisigVesting func resetGenesisMsigs(ctx context.Context, sm *StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { gb, err := sm.cs.GetGenesis() if err != nil { @@ -756,3 +793,34 @@ func resetGenesisMsigs(ctx context.Context, sm *StateManager, store adt0.Store, return nil } + +func resetMultisigVesting(ctx context.Context, store adt0.Store, tree *state.StateTree, addr address.Address, startEpoch abi.ChainEpoch, duration abi.ChainEpoch, balance abi.TokenAmount) error { + act, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("getting actor: %w", err) + } + + if !builtin.IsMultisigActor(act.Code) { + return xerrors.Errorf("actor wasn't msig: %w", err) + } + + var msigState multisig0.State + if err := store.Get(ctx, act.Head, &msigState); err != nil { + return xerrors.Errorf("reading multisig state: %w", err) + } + + msigState.StartEpoch = startEpoch + msigState.UnlockDuration = duration + msigState.InitialBalance = balance + + act.Head, err = store.Put(ctx, &msigState) + if err != nil { + return xerrors.Errorf("writing new multisig state: %w", err) + } + + if err := tree.SetActor(addr, act); err != nil { + return xerrors.Errorf("setting multisig actor: %w", err) + } + + return nil +} From f9f54819d41d333e021b9d2c3b4d32c4d3dbd138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 8 Oct 2020 00:17:24 +0200 Subject: [PATCH 24/24] Env var to control v2 actor migration Env var to control v2 actor migration --- build/params_2k.go | 13 +++++++++++-- build/params_testnet.go | 8 +++++++- chain/stmgr/forks.go | 30 ++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/build/params_2k.go b/build/params_2k.go index a9277831d..c6538dc08 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -3,6 +3,9 @@ package build import ( + "math" + "os" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -14,8 +17,9 @@ const BreezeGasTampingDuration = 0 const UpgradeSmokeHeight = -1 const UpgradeIgnitionHeight = -2 const UpgradeRefuelHeight = -3 -const UpgradeActorsV2Height = 10 -const UpgradeLiftoffHeight = -4 + +var UpgradeActorsV2Height = abi.ChainEpoch(10) +var UpgradeLiftoffHeight = abi.ChainEpoch(-4) var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, @@ -26,6 +30,11 @@ func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) + if os.Getenv("LOTUS_DISABLE_V2_ACTOR_MIGRATION") == "1" { + UpgradeActorsV2Height = math.MaxInt64 + UpgradeLiftoffHeight = 11 + } + BuildType |= Build2k } diff --git a/build/params_testnet.go b/build/params_testnet.go index aef3ff450..fe70deaef 100644 --- a/build/params_testnet.go +++ b/build/params_testnet.go @@ -5,6 +5,7 @@ package build import ( + "math" "os" "github.com/filecoin-project/go-address" @@ -26,7 +27,8 @@ const UpgradeSmokeHeight = 51000 const UpgradeIgnitionHeight = 94000 const UpgradeRefuelHeight = 130800 -const UpgradeActorsV2Height = 138720 + +var UpgradeActorsV2Height = abi.ChainEpoch(138720) // This signals our tentative epoch for mainnet launch. Can make it later, but not earlier. // Miners, clients, developers, custodians all need time to prepare. @@ -44,6 +46,10 @@ func init() { SetAddressNetwork(address.Mainnet) } + if os.Getenv("LOTUS_DISABLE_V2_ACTOR_MIGRATION") == "1" { + UpgradeActorsV2Height = math.MaxInt64 + } + Devnet = false } diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 989a94870..cbbc351f9 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -56,7 +56,7 @@ type UpgradeSchedule []Upgrade func DefaultUpgradeSchedule() UpgradeSchedule { var us UpgradeSchedule - for _, u := range []Upgrade{{ + updates := []Upgrade{{ Height: build.UpgradeBreezeHeight, Network: network.Version1, Migration: UpgradeFaucetBurnRecovery, @@ -81,7 +81,33 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradeLiftoffHeight, Network: network.Version4, Migration: UpgradeLiftoff, - }} { + }} + + if build.UpgradeActorsV2Height == math.MaxInt64 { // disable actors upgrade + updates = []Upgrade{{ + Height: build.UpgradeBreezeHeight, + Network: network.Version1, + Migration: UpgradeFaucetBurnRecovery, + }, { + Height: build.UpgradeSmokeHeight, + Network: network.Version2, + Migration: nil, + }, { + Height: build.UpgradeIgnitionHeight, + Network: network.Version3, + Migration: UpgradeIgnition, + }, { + Height: build.UpgradeRefuelHeight, + Network: network.Version3, + Migration: UpgradeRefuel, + }, { + Height: build.UpgradeLiftoffHeight, + Network: network.Version3, + Migration: UpgradeLiftoff, + }} + } + + for _, u := range updates { if u.Height < 0 { // upgrade disabled continue