Merge pull request #988 from filecoin-project/fork/fix-no-power-eps

FORK: No power EPS Fix
This commit is contained in:
Łukasz Magiera 2019-12-19 22:53:35 +01:00 committed by GitHub
commit ef3795ae3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 438 additions and 13 deletions

View File

@ -187,7 +187,7 @@ jobs:
chmod +x $HOME/.bin/jq
- restore_cache:
name: restore go mod and cargo cache
key: v2-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.mod" }}
key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }}
- install-deps
- go/mod-download
- run:
@ -198,11 +198,9 @@ jobs:
- store_artifacts:
path: lotus-storage-miner
- save_cache:
name: save go mod and cargo cache
key: v2-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.mod" }}
name: save cargo cache
key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }}
paths:
- "~/go/src/github.com"
- "~/go/src/golang.org"
- "~/.rustup"
- "~/.cargo"

View File

@ -1,3 +1,4 @@
package build
const ForkCCM = 1750
const ForkNoPowerEPSUpdates = 16450

View File

@ -140,7 +140,10 @@ func (sma StorageMinerActor) Exports() []interface{} {
return []interface{}{
1: sma.StorageMinerConstructor,
2: sma.PreCommitSector,
3: sma.ProveCommitSector,
3: withUpdates(
update{start: 0, method: sma.ProveCommitSectorV0},
update{start: build.ForkNoPowerEPSUpdates, method: sma.ProveCommitSectorV1},
),
4: sma.SubmitFallbackPoSt,
//5: sma.SlashStorageFault,
//6: sma.GetCurrentProvingSet,
@ -155,7 +158,10 @@ func (sma StorageMinerActor) Exports() []interface{} {
//15: sma.ChangeWorker,
16: sma.IsSlashed,
17: sma.CheckMiner,
18: sma.DeclareFaults,
18: withUpdates(
update{start: 0, method: sma.DeclareFaultsV0},
update{start: build.ForkNoPowerEPSUpdates, method: sma.DeclareFaultsV1},
),
19: sma.SlashConsensusFault,
20: sma.SubmitElectionPoSt,
}
@ -238,7 +244,7 @@ func (sma StorageMinerActor) PreCommitSector(act *types.Actor, vmctx types.VMCon
}
if vmctx.Message().From != mi.Worker {
return nil, aerrors.New(1, "not authorized to commit sector for miner")
return nil, aerrors.New(1, "not authorized to precommit sector for miner")
}
// make sure the miner isnt trying to submit a pre-existing sector
@ -291,7 +297,7 @@ type SectorProveCommitInfo struct {
DealIDs []uint64
}
func (sma StorageMinerActor) ProveCommitSector(act *types.Actor, vmctx types.VMContext, params *SectorProveCommitInfo) ([]byte, ActorError) {
func (sma StorageMinerActor) ProveCommitSectorV0(act *types.Actor, vmctx types.VMContext, params *SectorProveCommitInfo) ([]byte, ActorError) {
ctx := vmctx.Context()
oldstate, self, err := loadState(vmctx)
if err != nil {
@ -394,6 +400,113 @@ func (sma StorageMinerActor) ProveCommitSector(act *types.Actor, vmctx types.VMC
return nil, aerrors.Wrapf(err, "calling ActivateStorageDeals failed")
}
func (sma StorageMinerActor) ProveCommitSectorV1(act *types.Actor, vmctx types.VMContext, params *SectorProveCommitInfo) ([]byte, ActorError) {
ctx := vmctx.Context()
oldstate, self, err := loadState(vmctx)
if err != nil {
return nil, err
}
mi, err := loadMinerInfo(vmctx, self)
if err != nil {
return nil, err
}
if vmctx.Message().From != mi.Worker {
return nil, aerrors.New(1, "not authorized to submit sector proof for miner")
}
us, ok := self.PreCommittedSectors[uintToStringKey(params.SectorID)]
if !ok {
return nil, aerrors.New(1, "no pre-commitment found for sector")
}
if us.ReceivedEpoch+build.InteractivePoRepDelay >= vmctx.BlockHeight() {
return nil, aerrors.New(2, "too early for proof submission")
}
delete(self.PreCommittedSectors, uintToStringKey(params.SectorID))
// TODO: ensure normalization to ID address
maddr := vmctx.Message().To
ticket, err := vmctx.GetRandomness(us.Info.SealEpoch - build.SealRandomnessLookback)
if err != nil {
return nil, aerrors.Wrap(err, "failed to get ticket randomness")
}
seed, err := vmctx.GetRandomness(us.ReceivedEpoch + build.InteractivePoRepDelay)
if err != nil {
return nil, aerrors.Wrap(err, "failed to get randomness for prove sector commitment")
}
enc, err := SerializeParams(&ComputeDataCommitmentParams{
DealIDs: params.DealIDs,
SectorSize: mi.SectorSize,
})
if err != nil {
return nil, aerrors.Wrap(err, "failed to serialize ComputeDataCommitmentParams")
}
commD, err := vmctx.Send(StorageMarketAddress, SMAMethods.ComputeDataCommitment, types.NewInt(0), enc)
if err != nil {
return nil, aerrors.Wrapf(err, "failed to compute data commitment (sector %d, deals: %v)", params.SectorID, params.DealIDs)
}
if ok, err := vmctx.Sys().ValidatePoRep(ctx, maddr, mi.SectorSize, commD, us.Info.CommR, ticket, params.Proof, seed, params.SectorID); err != nil {
return nil, err
} else if !ok {
return nil, aerrors.Newf(2, "porep proof was invalid (t:%x; s:%x(%d); p:%s)", ticket, seed, us.ReceivedEpoch+build.InteractivePoRepDelay, truncateHexPrint(params.Proof))
}
// Note: There must exist a unique index in the miner's sector set for each
// sector ID. The `faults`, `recovered`, and `done` parameters of the
// SubmitPoSt method express indices into this sector set.
nssroot, err := AddToSectorSet(ctx, types.WrapStorage(vmctx.Storage()), self.Sectors, params.SectorID, us.Info.CommR, commD)
if err != nil {
return nil, err
}
self.Sectors = nssroot
// if miner is not mining, start their proving period now
// Note: As written here, every miners first PoSt will only be over one sector.
// We could set up a 'grace period' for starting mining that would allow miners
// to submit several sectors for their first proving period. Alternatively, we
// could simply make the 'PreCommitSector' call take multiple sectors at a time.
//
// Note: Proving period is a function of sector size; small sectors take less
// time to prove than large sectors do. Sector size is selected when pledging.
pss, lerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingSet)
if lerr != nil {
return nil, aerrors.HandleExternalError(lerr, "could not load proving set node")
}
if pss.Count == 0 && !self.Active {
self.ProvingSet = self.Sectors
// TODO: probably want to wait until the miner is above a certain
// threshold before starting this
self.ElectionPeriodStart = vmctx.BlockHeight()
}
nstate, err := vmctx.Storage().Put(self)
if err != nil {
return nil, err
}
if err := vmctx.Storage().Commit(oldstate, nstate); err != nil {
return nil, err
}
activateParams, err := SerializeParams(&ActivateStorageDealsParams{
Deals: params.DealIDs,
})
if err != nil {
return nil, err
}
_, err = vmctx.Send(StorageMarketAddress, SMAMethods.ActivateStorageDeals, types.NewInt(0), activateParams)
return nil, aerrors.Wrapf(err, "calling ActivateStorageDeals failed")
}
func truncateHexPrint(b []byte) string {
s := fmt.Sprintf("%x", b)
if len(s) > 60 {
@ -808,7 +921,7 @@ type DeclareFaultsParams struct {
Faults types.BitField
}
func (sma StorageMinerActor) DeclareFaults(act *types.Actor, vmctx types.VMContext, params *DeclareFaultsParams) ([]byte, ActorError) {
func (sma StorageMinerActor) DeclareFaultsV0(act *types.Actor, vmctx types.VMContext, params *DeclareFaultsParams) ([]byte, ActorError) {
oldstate, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
@ -834,6 +947,41 @@ func (sma StorageMinerActor) DeclareFaults(act *types.Actor, vmctx types.VMConte
return nil, nil
}
func (sma StorageMinerActor) DeclareFaultsV1(act *types.Actor, vmctx types.VMContext, params *DeclareFaultsParams) ([]byte, ActorError) {
oldstate, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
}
mi, aerr := loadMinerInfo(vmctx, self)
if aerr != nil {
return nil, aerr
}
if vmctx.Message().From != mi.Worker {
return nil, aerrors.New(1, "not authorized to declare faults for miner")
}
nfaults, err := types.MergeBitFields(params.Faults, self.FaultSet)
if err != nil {
return nil, aerrors.Absorb(err, 1, "failed to merge bitfields")
}
self.FaultSet = nfaults
self.LastFaultSubmission = vmctx.BlockHeight()
nstate, aerr := vmctx.Storage().Put(self)
if aerr != nil {
return nil, aerr
}
if err := vmctx.Storage().Commit(oldstate, nstate); err != nil {
return nil, err
}
return nil, nil
}
type MinerSlashConsensusFault struct {
Slasher address.Address
AtHeight uint64
@ -910,8 +1058,14 @@ func (sma StorageMinerActor) SubmitElectionPoSt(act *types.Actor, vmctx types.VM
}
func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext) aerrors.ActorError {
// TODO: some sector upkeep stuff that is very haphazard and unclear in the spec
if vmctx.BlockHeight() >= build.ForkNoPowerEPSUpdates {
return onSuccessfulPoStV1(self, vmctx)
}
return onSuccessfulPoStV0(self, vmctx)
}
func onSuccessfulPoStV0(self *StorageMinerActorState, vmctx types.VMContext) aerrors.ActorError {
var mi MinerInfo
if err := vmctx.Storage().Get(self.Info, &mi); err != nil {
return err
@ -978,6 +1132,76 @@ func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext) aerro
return nil
}
func onSuccessfulPoStV1(self *StorageMinerActorState, vmctx types.VMContext) aerrors.ActorError {
// TODO: some sector upkeep stuff that is very haphazard and unclear in the spec
var mi MinerInfo
if err := vmctx.Storage().Get(self.Info, &mi); err != nil {
return err
}
pss, nerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingSet)
if nerr != nil {
return aerrors.HandleExternalError(nerr, "failed to load proving set")
}
faults, nerr := self.FaultSet.All()
if nerr != nil {
return aerrors.Absorb(nerr, 1, "invalid bitfield (fatal?)")
}
self.FaultSet = types.NewBitField()
oldPower := self.Power
newPower := types.BigMul(types.NewInt(pss.Count-uint64(len(faults))), types.NewInt(mi.SectorSize))
// If below the minimum size requirement, miners have zero power
if newPower.LessThan(types.NewInt(build.MinimumMinerPower)) {
newPower = types.NewInt(0)
}
self.Power = newPower
delta := types.BigSub(self.Power, oldPower)
if self.SlashedAt != 0 {
self.SlashedAt = 0
delta = self.Power
}
prevSlashingDeadline := self.ElectionPeriodStart + build.SlashablePowerDelay
if !self.Active && newPower.GreaterThan(types.NewInt(0)) {
self.Active = true
prevSlashingDeadline = 0
}
if !(oldPower.IsZero() && newPower.IsZero()) {
enc, err := SerializeParams(&UpdateStorageParams{
Delta: delta,
NextSlashDeadline: vmctx.BlockHeight() + build.SlashablePowerDelay,
PreviousSlashDeadline: prevSlashingDeadline,
})
if err != nil {
return err
}
_, err = vmctx.Send(StoragePowerAddress, SPAMethods.UpdateStorage, types.NewInt(0), enc)
if err != nil {
return aerrors.Wrap(err, "updating storage failed")
}
self.ElectionPeriodStart = vmctx.BlockHeight()
}
ncid, err := RemoveFromSectorSet(vmctx.Context(), vmctx.Storage(), self.Sectors, faults)
if err != nil {
return err
}
self.Sectors = ncid
self.ProvingSet = ncid
return nil
}
func slasherShare(total types.BigInt, elapsed uint64) types.BigInt {
// [int(pow(1.26, n) * 10) for n in range(30)]
fracs := []uint64{10, 12, 15, 20, 25, 31, 40, 50, 63, 80, 100, 127, 160, 201, 254, 320, 403, 508, 640, 807, 1017, 1281, 1614, 2034, 2563, 3230, 4070, 5128, 6462, 8142}

View File

@ -393,7 +393,7 @@ func addMinerToBucket(vmctx types.VMContext, buckets *amt.Root, nextBucket uint6
return aerrors.HandleExternalError(err, "getting proving bucket")
}
err = bhamt.Set(vmctx.Context(), string(vmctx.Message().From.Bytes()), cborNull)
err = bhamt.Set(vmctx.Context(), string(vmctx.Message().From.Bytes()), CborNull)
if err != nil {
return aerrors.HandleExternalError(err, "setting miner in proving bucket")
}
@ -768,7 +768,7 @@ func MinerSetRemove(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, ma
type cbgNull struct{}
var cborNull = &cbgNull{}
var CborNull = &cbgNull{}
func (cbgNull) MarshalCBOR(w io.Writer) error {
n, err := w.Write(cbg.CborNull)

46
chain/actors/forks.go Normal file
View File

@ -0,0 +1,46 @@
package actors
import (
"reflect"
"sort"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/types"
)
type update struct {
start uint64
method interface{}
}
func withUpdates(updates ...update) interface{} {
sort.Slice(updates, func(i, j int) bool { // so we iterate from newest below
return updates[i].start > updates[j].start
})
// <script type="application/javascript">
typ := reflect.TypeOf(updates[0].method)
out := reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) {
vmctx := args[1].Interface().(types.VMContext)
for _, u := range updates {
if u.start >= vmctx.BlockHeight() {
return reflect.ValueOf(u.method).Call(args)
}
}
return reflect.ValueOf(notFound(vmctx)).Call([]reflect.Value{args[1]})
})
return out.Interface()
// </script>
}
func notFound(vmctx types.VMContext) func() ([]byte, ActorError) {
return func() ([]byte, ActorError) {
return nil, aerrors.Fatal("no code for method %d at height %d", vmctx.Message().Method, vmctx.BlockHeight())
}
}

View File

@ -0,0 +1,122 @@
package stmgr
import (
"context"
"github.com/filecoin-project/go-amt-ipld"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
)
func (sm *StateManager) forkNoPowerEPS(ctx context.Context, pstate cid.Cid) (cid.Cid, error) {
cst := hamt.CSTFromBstore(sm.cs.Blockstore())
st, err := state.LoadStateTree(cst, pstate)
if err != nil {
return cid.Undef, xerrors.Errorf("loading parent state tree: %w", err)
}
if err := st.MutateActor(actors.StoragePowerAddress, func(spa *types.Actor) error {
var head actors.StoragePowerState
if err := cst.Get(ctx, spa.Head, &head); err != nil {
return xerrors.Errorf("reading StoragePower state: %w", err)
}
buckets, err := amt.LoadAMT(amt.WrapBlockstore(sm.cs.Blockstore()), head.ProvingBuckets)
if err != nil {
return xerrors.Errorf("opening proving buckets AMT: %w", err)
}
fixedBuckets := map[uint64]map[address.Address]struct{}{}
if err := buckets.ForEach(func(bucketId uint64, ent *typegen.Deferred) error {
var bcid cid.Cid
if err := cbor.DecodeInto(ent.Raw, &bcid); err != nil {
return xerrors.Errorf("decoding bucket cid: %w", err)
}
bucket, err := hamt.LoadNode(ctx, cst, bcid)
if err != nil {
return xerrors.Errorf("loading bucket hamt: %w", err)
}
return bucket.ForEach(ctx, func(abytes string, _ interface{}) error {
addr, err := address.NewFromBytes([]byte(abytes))
if err != nil {
return xerrors.Errorf("parsing address in proving bucket: %w", err)
}
// now find the correct bucket
miner, err := st.GetActor(addr)
if err != nil {
return xerrors.Errorf("getting miner %s: %w", addr, err)
}
var minerHead actors.StorageMinerActorState
if err := cst.Get(ctx, miner.Head, &minerHead); err != nil {
return xerrors.Errorf("reading miner %s state: %w", addr, err)
}
correctBucket := minerHead.ElectionPeriodStart % build.SlashablePowerDelay
if correctBucket != bucketId {
log.Warnf("miner %s was in wrong proving bucket %d, putting in %d (eps: %d)", addr, bucketId, correctBucket, minerHead.ElectionPeriodStart)
}
if _, ok := fixedBuckets[correctBucket]; !ok {
fixedBuckets[correctBucket] = map[address.Address]struct{}{}
}
fixedBuckets[correctBucket][addr] = struct{}{}
return nil
})
}); err != nil {
return err
}
// /////
// Write fixed buckets
fixed := amt.NewAMT(amt.WrapBlockstore(sm.cs.Blockstore()))
for bucketId, addrss := range fixedBuckets {
bucket := hamt.NewNode(cst)
for addr := range addrss {
if err := bucket.Set(ctx, string(addr.Bytes()), actors.CborNull); err != nil {
return xerrors.Errorf("setting address in bucket: %w", err)
}
}
if err := bucket.Flush(ctx); err != nil {
return xerrors.Errorf("flushing bucket amt: %w", err)
}
bcid, err := cst.Put(context.TODO(), bucket)
if err != nil {
return xerrors.Errorf("put bucket: %w", err)
}
if err := fixed.Set(bucketId, bcid); err != nil {
return xerrors.Errorf("set bucket: %w", err)
}
}
spa.Head, err = fixed.Flush()
if err != nil {
return xerrors.Errorf("flushing bucket amt: %w", err)
}
return nil
}); err != nil {
return cid.Undef, err
}
return st.Flush()
}

23
chain/stmgr/forks.go Normal file
View File

@ -0,0 +1,23 @@
package stmgr
import (
"context"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build"
)
func (sm *StateManager) handleStateForks(ctx context.Context, pstate cid.Cid, height, parentH uint64) (_ cid.Cid, err error) {
for i := parentH; i < height; i++ {
switch i {
case build.ForkNoPowerEPSUpdates:
pstate, err = sm.forkNoPowerEPS(ctx, pstate)
if err != nil {
return cid.Undef, xerrors.Errorf("executing state fork in epoch %d: %w", i, err)
}
}
}
return pstate, nil
}

View File

@ -120,6 +120,17 @@ func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.Bl
}
pstate := blks[0].ParentStateRoot
if len(blks[0].Parents) > 0 { // don't support forks on genesis
parent, err := sm.cs.GetBlock(blks[0].Parents[0])
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err)
}
pstate, err = sm.handleStateForks(ctx, blks[0].ParentStateRoot, blks[0].Height, parent.Height)
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err)
}
}
cids := make([]cid.Cid, len(blks))
for i, v := range blks {