github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/states/check.go (about)

     1  package states
     2  
     3  import (
     4  	"bytes"
     5  
     6  	addr "github.com/filecoin-project/go-address"
     7  	"github.com/filecoin-project/go-state-types/abi"
     8  	"github.com/filecoin-project/go-state-types/big"
     9  	"golang.org/x/xerrors"
    10  
    11  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/power"
    12  
    13  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    14  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/account"
    15  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/cron"
    16  	init_ "github.com/filecoin-project/specs-actors/v4/actors/builtin/init"
    17  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/market"
    18  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/miner"
    19  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig"
    20  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/paych"
    21  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/reward"
    22  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg"
    23  )
    24  
    25  // Within this code, Go errors are not expected, but are often converted to messages so that execution
    26  // can continue to find more errors rather than fail with no insight.
    27  // Only errors thar are particularly troublesome to recover from should propagate as Go errors.
    28  func CheckStateInvariants(tree *Tree, expectedBalanceTotal abi.TokenAmount, priorEpoch abi.ChainEpoch) (*builtin.MessageAccumulator, error) {
    29  	acc := &builtin.MessageAccumulator{}
    30  	totalFIl := big.Zero()
    31  	var initSummary *init_.StateSummary
    32  	var cronSummary *cron.StateSummary
    33  	var verifregSummary *verifreg.StateSummary
    34  	var marketSummary *market.StateSummary
    35  	var rewardSummary *reward.StateSummary
    36  	var accountSummaries []*account.StateSummary
    37  	var powerSummary *power.StateSummary
    38  	var paychSummaries []*paych.StateSummary
    39  	var multisigSummaries []*multisig.StateSummary
    40  	minerSummaries := make(map[addr.Address]*miner.StateSummary)
    41  
    42  	if err := tree.ForEach(func(key addr.Address, actor *Actor) error {
    43  		acc := acc.WithPrefix("%v ", key) // Intentional shadow
    44  		if key.Protocol() != addr.ID {
    45  			acc.Addf("unexpected address protocol in state tree root: %v", key)
    46  		}
    47  		totalFIl = big.Add(totalFIl, actor.Balance)
    48  
    49  		switch actor.Code {
    50  		case builtin.SystemActorCodeID:
    51  
    52  		case builtin.InitActorCodeID:
    53  			var st init_.State
    54  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    55  				return err
    56  			}
    57  			summary, msgs := init_.CheckStateInvariants(&st, tree.Store)
    58  			acc.WithPrefix("init: ").AddAll(msgs)
    59  			initSummary = summary
    60  		case builtin.CronActorCodeID:
    61  			var st cron.State
    62  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    63  				return err
    64  			}
    65  			summary, msgs := cron.CheckStateInvariants(&st, tree.Store)
    66  			acc.WithPrefix("cron: ").AddAll(msgs)
    67  			cronSummary = summary
    68  		case builtin.AccountActorCodeID:
    69  			var st account.State
    70  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    71  				return err
    72  			}
    73  			summary, msgs := account.CheckStateInvariants(&st, key)
    74  			acc.WithPrefix("account: ").AddAll(msgs)
    75  			accountSummaries = append(accountSummaries, summary)
    76  		case builtin.StoragePowerActorCodeID:
    77  			var st power.State
    78  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    79  				return err
    80  			}
    81  			summary, msgs := power.CheckStateInvariants(&st, tree.Store)
    82  			acc.WithPrefix("power: ").AddAll(msgs)
    83  			powerSummary = summary
    84  		case builtin.StorageMinerActorCodeID:
    85  			var st miner.State
    86  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    87  				return err
    88  			}
    89  			summary, msgs := miner.CheckStateInvariants(&st, tree.Store, actor.Balance)
    90  			acc.WithPrefix("miner: ").AddAll(msgs)
    91  			minerSummaries[key] = summary
    92  		case builtin.StorageMarketActorCodeID:
    93  			var st market.State
    94  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
    95  				return err
    96  			}
    97  			summary, msgs := market.CheckStateInvariants(&st, tree.Store, actor.Balance, priorEpoch)
    98  			acc.WithPrefix("market: ").AddAll(msgs)
    99  			marketSummary = summary
   100  		case builtin.PaymentChannelActorCodeID:
   101  			var st paych.State
   102  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
   103  				return err
   104  			}
   105  			summary, msgs := paych.CheckStateInvariants(&st, tree.Store, actor.Balance)
   106  			acc.WithPrefix("paych: ").AddAll(msgs)
   107  			paychSummaries = append(paychSummaries, summary)
   108  		case builtin.MultisigActorCodeID:
   109  			var st multisig.State
   110  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
   111  				return err
   112  			}
   113  			summary, msgs := multisig.CheckStateInvariants(&st, tree.Store)
   114  			acc.WithPrefix("multisig: ").AddAll(msgs)
   115  			multisigSummaries = append(multisigSummaries, summary)
   116  		case builtin.RewardActorCodeID:
   117  			var st reward.State
   118  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
   119  				return err
   120  			}
   121  			summary, msgs := reward.CheckStateInvariants(&st, tree.Store, priorEpoch, actor.Balance)
   122  			acc.WithPrefix("reward: ").AddAll(msgs)
   123  			rewardSummary = summary
   124  		case builtin.VerifiedRegistryActorCodeID:
   125  			var st verifreg.State
   126  			if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
   127  				return err
   128  			}
   129  			summary, msgs := verifreg.CheckStateInvariants(&st, tree.Store)
   130  			acc.WithPrefix("verifreg: ").AddAll(msgs)
   131  			verifregSummary = summary
   132  		default:
   133  			return xerrors.Errorf("unexpected actor code CID %v for address %v", actor.Code, key)
   134  		}
   135  		return nil
   136  	}); err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	//
   141  	// Perform cross-actor checks from state summaries here.
   142  	//
   143  
   144  	CheckMinersAgainstPower(acc, minerSummaries, powerSummary)
   145  	CheckDealStatesAgainstSectors(acc, minerSummaries, marketSummary)
   146  
   147  	_ = initSummary
   148  	_ = verifregSummary
   149  	_ = cronSummary
   150  	_ = marketSummary
   151  	_ = rewardSummary
   152  
   153  	if !totalFIl.Equals(expectedBalanceTotal) {
   154  		acc.Addf("total token balance is %v, expected %v", totalFIl, expectedBalanceTotal)
   155  	}
   156  
   157  	return acc, nil
   158  }
   159  
   160  func CheckMinersAgainstPower(acc *builtin.MessageAccumulator, minerSummaries map[addr.Address]*miner.StateSummary, powerSummary *power.StateSummary) {
   161  	for addr, minerSummary := range minerSummaries { // nolint:nomaprange
   162  		// check claim
   163  		claim, ok := powerSummary.Claims[addr]
   164  		acc.Require(ok, "miner %v has no power claim", addr)
   165  		if ok {
   166  			claimPower := miner.NewPowerPair(claim.RawBytePower, claim.QualityAdjPower)
   167  			acc.Require(minerSummary.ActivePower.Equals(claimPower),
   168  				"miner %v computed active power %v does not match claim %v", addr, minerSummary.ActivePower, claimPower)
   169  			acc.Require(minerSummary.WindowPoStProofType == claim.WindowPoStProofType,
   170  				"miner seal proof type %d does not match claim proof type %d", minerSummary.WindowPoStProofType, claim.WindowPoStProofType)
   171  		}
   172  
   173  		// check crons
   174  		crons, ok := powerSummary.Crons[addr]
   175  		if !ok { // with deferred and discontinued crons it is normal for a miner actor to have no cron events
   176  			continue
   177  		}
   178  
   179  		var payload miner.CronEventPayload
   180  		var provingPeriodCron *power.MinerCronEvent
   181  		for _, event := range crons {
   182  			err := payload.UnmarshalCBOR(bytes.NewReader(event.Payload))
   183  			acc.Require(err == nil, "miner %v registered cron at epoch %d with wrong or corrupt payload",
   184  				addr, event.Epoch)
   185  			acc.Require(payload.EventType == miner.CronEventProcessEarlyTerminations || payload.EventType == miner.CronEventProvingDeadline,
   186  				"miner %v has unexpected cron event type %v", addr, payload.EventType)
   187  
   188  			if payload.EventType == miner.CronEventProvingDeadline {
   189  				if provingPeriodCron != nil {
   190  					acc.Require(false, "miner %v has duplicate proving period crons at epoch %d and %d",
   191  						addr, provingPeriodCron.Epoch, event.Epoch)
   192  				}
   193  				provingPeriodCron = &event
   194  			}
   195  		}
   196  		hasProvingPeriodCron := provingPeriodCron != nil
   197  		acc.Require(hasProvingPeriodCron == minerSummary.DeadlineCronActive, "miner %v has invalid DeadlineCronActive (%t) for hasProvingPeriodCron status (%t)",
   198  			addr, minerSummary.DeadlineCronActive, hasProvingPeriodCron)
   199  
   200  		acc.Require(provingPeriodCron != nil, "miner %v has no proving period cron", addr)
   201  	}
   202  }
   203  
   204  func CheckDealStatesAgainstSectors(acc *builtin.MessageAccumulator, minerSummaries map[addr.Address]*miner.StateSummary, marketSummary *market.StateSummary) {
   205  	// Check that all active deals are included within a non-terminated sector.
   206  	// We cannot check that all deals referenced within a sector are in the market, because deals
   207  	// can be terminated independently of the sector in which they are included.
   208  	for dealID, deal := range marketSummary.Deals { // nolint:nomaprange
   209  		if deal.SectorStartEpoch == abi.ChainEpoch(-1) {
   210  			// deal hasn't been activated yet, make no assertions about sector state
   211  			continue
   212  		}
   213  
   214  		minerSummary, found := minerSummaries[deal.Provider]
   215  		if !found {
   216  			acc.Addf("provider %v for deal %d not found among miners", deal.Provider, dealID)
   217  			continue
   218  		}
   219  
   220  		sectorDeal, found := minerSummary.Deals[dealID]
   221  		if !found {
   222  			acc.Require(deal.SlashEpoch >= 0, "un-slashed deal %d not referenced in active sectors of miner %v", dealID, deal.Provider)
   223  			continue
   224  		}
   225  
   226  		acc.Require(deal.SectorStartEpoch == sectorDeal.SectorStart,
   227  			"deal state start %d does not match sector start %d for miner %v",
   228  			deal.SectorStartEpoch, sectorDeal.SectorStart, deal.Provider)
   229  
   230  		acc.Require(deal.SectorStartEpoch <= sectorDeal.SectorExpiration,
   231  			"deal state start %d activated after sector expiration %d for miner %v",
   232  			deal.SectorStartEpoch, sectorDeal.SectorExpiration, deal.Provider)
   233  
   234  		acc.Require(deal.LastUpdatedEpoch <= sectorDeal.SectorExpiration,
   235  			"deal state update at %d after sector expiration %d for miner %v",
   236  			deal.LastUpdatedEpoch, sectorDeal.SectorExpiration, deal.Provider)
   237  
   238  		acc.Require(deal.SlashEpoch <= sectorDeal.SectorExpiration,
   239  			"deal state slashed at %d after sector expiration %d for miner %v",
   240  			deal.SlashEpoch, sectorDeal.SectorExpiration, deal.Provider)
   241  	}
   242  }