github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/power/power_actor.go (about)

     1  package power
     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  	"github.com/filecoin-project/go-state-types/cbor"
    10  	"github.com/filecoin-project/go-state-types/exitcode"
    11  	rtt "github.com/filecoin-project/go-state-types/rt"
    12  	power0 "github.com/filecoin-project/specs-actors/actors/builtin/power"
    13  	"github.com/ipfs/go-cid"
    14  
    15  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    16  	initact "github.com/filecoin-project/specs-actors/v4/actors/builtin/init"
    17  	"github.com/filecoin-project/specs-actors/v4/actors/runtime"
    18  	"github.com/filecoin-project/specs-actors/v4/actors/runtime/proof"
    19  	"github.com/filecoin-project/specs-actors/v4/actors/util/adt"
    20  	"github.com/filecoin-project/specs-actors/v4/actors/util/smoothing"
    21  )
    22  
    23  type Runtime = runtime.Runtime
    24  
    25  type SectorTermination int64
    26  
    27  const (
    28  	ErrTooManyProveCommits = exitcode.FirstActorSpecificExitCode + iota
    29  )
    30  
    31  type Actor struct{}
    32  
    33  func (a Actor) Exports() []interface{} {
    34  	return []interface{}{
    35  		builtin.MethodConstructor: a.Constructor,
    36  		2:                         a.CreateMiner,
    37  		3:                         a.UpdateClaimedPower,
    38  		4:                         a.EnrollCronEvent,
    39  		5:                         a.OnEpochTickEnd,
    40  		6:                         a.UpdatePledgeTotal,
    41  		7:                         nil, // deprecated
    42  		8:                         a.SubmitPoRepForBulkVerify,
    43  		9:                         a.CurrentTotalPower,
    44  	}
    45  }
    46  
    47  func (a Actor) Code() cid.Cid {
    48  	return builtin.StoragePowerActorCodeID
    49  }
    50  
    51  func (a Actor) IsSingleton() bool {
    52  	return true
    53  }
    54  
    55  func (a Actor) State() cbor.Er {
    56  	return new(State)
    57  }
    58  
    59  var _ runtime.VMActor = Actor{}
    60  
    61  // Storage miner actor constructor params are defined here so the power actor can send them to the init actor
    62  // to instantiate miners.
    63  // Changed since v2:
    64  // - Seal proof type replaced with PoSt proof type
    65  type MinerConstructorParams struct {
    66  	OwnerAddr            addr.Address
    67  	WorkerAddr           addr.Address
    68  	ControlAddrs         []addr.Address
    69  	WindowPoStProofType  abi.RegisteredPoStProof
    70  	PeerId               abi.PeerID
    71  	Multiaddrs           []abi.Multiaddrs
    72  }
    73  
    74  ////////////////////////////////////////////////////////////////////////////////
    75  // Actor methods
    76  ////////////////////////////////////////////////////////////////////////////////
    77  
    78  func (a Actor) Constructor(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue {
    79  	rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
    80  
    81  	st, err := ConstructState(adt.AsStore(rt))
    82  	builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to construct state")
    83  	rt.StateCreate(st)
    84  	return nil
    85  }
    86  
    87  // Changed since v2:
    88  // - Seal proof type replaced with PoSt proof types
    89  type CreateMinerParams struct {
    90  	Owner                addr.Address
    91  	Worker               addr.Address
    92  	WindowPoStProofType  abi.RegisteredPoStProof
    93  	Peer                 abi.PeerID
    94  	Multiaddrs           []abi.Multiaddrs
    95  }
    96  
    97  //type CreateMinerReturn struct {
    98  //	IDAddress     addr.Address // The canonical ID-based address for the actor.
    99  //	RobustAddress addr.Address // A more expensive but re-org-safe address for the newly created actor.
   100  //}
   101  type CreateMinerReturn = power0.CreateMinerReturn
   102  
   103  func (a Actor) CreateMiner(rt Runtime, params *CreateMinerParams) *CreateMinerReturn {
   104  	rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...)
   105  
   106  	ctorParams := MinerConstructorParams{
   107  		OwnerAddr:  params.Owner,
   108  		WorkerAddr: params.Worker,
   109  		WindowPoStProofType: params.WindowPoStProofType,
   110  		PeerId:        params.Peer,
   111  		Multiaddrs:    params.Multiaddrs,
   112  	}
   113  	ctorParamBuf := new(bytes.Buffer)
   114  	err := ctorParams.MarshalCBOR(ctorParamBuf)
   115  	builtin.RequireNoErr(rt, err, exitcode.ErrSerialization, "failed to serialize miner constructor params %v", ctorParams)
   116  
   117  	var addresses initact.ExecReturn
   118  	code := rt.Send(
   119  		builtin.InitActorAddr,
   120  		builtin.MethodsInit.Exec,
   121  		&initact.ExecParams{
   122  			CodeCID:           builtin.StorageMinerActorCodeID,
   123  			ConstructorParams: ctorParamBuf.Bytes(),
   124  		},
   125  		rt.ValueReceived(), // Pass on any value to the new actor.
   126  		&addresses,
   127  	)
   128  	builtin.RequireSuccess(rt, code, "failed to init new actor")
   129  
   130  	var st State
   131  	rt.StateTransaction(&st, func() {
   132  		claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   133  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   134  
   135  		err = setClaim(claims, addresses.IDAddress, &Claim{params.WindowPoStProofType, abi.NewStoragePower(0), abi.NewStoragePower(0)})
   136  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to put power in claimed table while creating miner")
   137  
   138  		st.MinerCount += 1
   139  
   140  		// Ensure new claim updates all power stats
   141  		err = st.updateStatsForNewMiner(params.WindowPoStProofType)
   142  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed update power stats for new miner %v", addresses.IDAddress)
   143  
   144  		st.Claims, err = claims.Root()
   145  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims")
   146  	})
   147  	return &CreateMinerReturn{
   148  		IDAddress:     addresses.IDAddress,
   149  		RobustAddress: addresses.RobustAddress,
   150  	}
   151  }
   152  
   153  //type UpdateClaimedPowerParams struct {
   154  //	RawByteDelta         abi.StoragePower
   155  //	QualityAdjustedDelta abi.StoragePower
   156  //}
   157  type UpdateClaimedPowerParams = power0.UpdateClaimedPowerParams
   158  
   159  // Adds or removes claimed power for the calling actor.
   160  // May only be invoked by a miner actor.
   161  func (a Actor) UpdateClaimedPower(rt Runtime, params *UpdateClaimedPowerParams) *abi.EmptyValue {
   162  	rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID)
   163  	minerAddr := rt.Caller()
   164  	var st State
   165  	rt.StateTransaction(&st, func() {
   166  		claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   167  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   168  
   169  		err = st.addToClaim(claims, minerAddr, params.RawByteDelta, params.QualityAdjustedDelta)
   170  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update power raw %s, qa %s", params.RawByteDelta, params.QualityAdjustedDelta)
   171  
   172  		st.Claims, err = claims.Root()
   173  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims")
   174  	})
   175  	return nil
   176  }
   177  
   178  //type EnrollCronEventParams struct {
   179  //	EventEpoch abi.ChainEpoch
   180  //	Payload    []byte
   181  //}
   182  type EnrollCronEventParams = power0.EnrollCronEventParams
   183  
   184  func (a Actor) EnrollCronEvent(rt Runtime, params *EnrollCronEventParams) *abi.EmptyValue {
   185  	rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID)
   186  	minerAddr := rt.Caller()
   187  	minerEvent := CronEvent{
   188  		MinerAddr:       minerAddr,
   189  		CallbackPayload: params.Payload,
   190  	}
   191  
   192  	// Ensure it is not possible to enter a large negative number which would cause problems in cron processing.
   193  	if params.EventEpoch < 0 {
   194  		rt.Abortf(exitcode.ErrIllegalArgument, "cron event epoch %d cannot be less than zero", params.EventEpoch)
   195  	}
   196  
   197  	var st State
   198  	rt.StateTransaction(&st, func() {
   199  		events, err := adt.AsMultimap(adt.AsStore(rt), st.CronEventQueue, CronQueueHamtBitwidth, CronQueueAmtBitwidth)
   200  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events")
   201  
   202  		err = st.appendCronEvent(events, params.EventEpoch, &minerEvent)
   203  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to enroll cron event")
   204  
   205  		st.CronEventQueue, err = events.Root()
   206  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush cron events")
   207  	})
   208  	return nil
   209  }
   210  
   211  // Called by Cron.
   212  func (a Actor) OnEpochTickEnd(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue {
   213  	rt.ValidateImmediateCallerIs(builtin.CronActorAddr)
   214  
   215  	a.processBatchProofVerifies(rt)
   216  	a.processDeferredCronEvents(rt)
   217  
   218  	var st State
   219  	rt.StateTransaction(&st, func() {
   220  		// update next epoch's power and pledge values
   221  		// this must come before the next epoch's rewards are calculated
   222  		// so that next epoch reward reflects power added this epoch
   223  		rawBytePower, qaPower := CurrentTotalPower(&st)
   224  		st.ThisEpochPledgeCollateral = st.TotalPledgeCollateral
   225  		st.ThisEpochQualityAdjPower = qaPower
   226  		st.ThisEpochRawBytePower = rawBytePower
   227  		// we can now assume delta is one since cron is invoked on every epoch.
   228  		st.updateSmoothedEstimate(abi.ChainEpoch(1))
   229  	})
   230  
   231  	// update network KPI in RewardActor
   232  	code := rt.Send(
   233  		builtin.RewardActorAddr,
   234  		builtin.MethodsReward.UpdateNetworkKPI,
   235  		&st.ThisEpochRawBytePower,
   236  		abi.NewTokenAmount(0),
   237  		&builtin.Discard{},
   238  	)
   239  	builtin.RequireSuccess(rt, code, "failed to update network KPI with Reward Actor")
   240  
   241  	return nil
   242  }
   243  
   244  func (a Actor) UpdatePledgeTotal(rt Runtime, pledgeDelta *abi.TokenAmount) *abi.EmptyValue {
   245  	rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID)
   246  	var st State
   247  	rt.StateTransaction(&st, func() {
   248  		validateMinerHasClaim(rt, st, rt.Caller())
   249  		st.addPledgeTotal(*pledgeDelta)
   250  		builtin.RequireState(rt, st.TotalPledgeCollateral.GreaterThanEqual(big.Zero()), "negative total pledge collateral %v", st.TotalPledgeCollateral)
   251  	})
   252  	return nil
   253  }
   254  
   255  // GasOnSubmitVerifySeal is amount of gas charged for SubmitPoRepForBulkVerify
   256  // This number is empirically determined
   257  const GasOnSubmitVerifySeal = 34721049
   258  
   259  func (a Actor) SubmitPoRepForBulkVerify(rt Runtime, sealInfo *proof.SealVerifyInfo) *abi.EmptyValue {
   260  	rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID)
   261  
   262  	minerAddr := rt.Caller()
   263  
   264  	var st State
   265  	rt.StateTransaction(&st, func() {
   266  		validateMinerHasClaim(rt, st, minerAddr)
   267  
   268  		store := adt.AsStore(rt)
   269  		var mmap *adt.Multimap
   270  		var err error
   271  		if st.ProofValidationBatch == nil {
   272  			mmap, err = adt.MakeEmptyMultimap(store, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth)
   273  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to create empty proof validation set")
   274  		} else {
   275  			mmap, err = adt.AsMultimap(adt.AsStore(rt), *st.ProofValidationBatch, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth)
   276  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof batch set")
   277  		}
   278  
   279  		arr, found, err := mmap.Get(abi.AddrKey(minerAddr))
   280  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get get seal verify infos at addr %s", minerAddr)
   281  		if found && arr.Length() >= MaxMinerProveCommitsPerEpoch {
   282  			rt.Abortf(ErrTooManyProveCommits, "miner %s attempting to prove commit over %d sectors in epoch", minerAddr, MaxMinerProveCommitsPerEpoch)
   283  		}
   284  
   285  		err = mmap.Add(abi.AddrKey(minerAddr), sealInfo)
   286  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to insert proof into batch")
   287  
   288  		mmrc, err := mmap.Root()
   289  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush proof batch")
   290  
   291  		rt.ChargeGas("OnSubmitVerifySeal", GasOnSubmitVerifySeal, 0)
   292  		st.ProofValidationBatch = &mmrc
   293  	})
   294  
   295  	return nil
   296  }
   297  
   298  // Changed since v0:
   299  // - QualityAdjPowerSmoothed is not a pointer
   300  type CurrentTotalPowerReturn struct {
   301  	RawBytePower            abi.StoragePower
   302  	QualityAdjPower         abi.StoragePower
   303  	PledgeCollateral        abi.TokenAmount
   304  	QualityAdjPowerSmoothed smoothing.FilterEstimate
   305  }
   306  
   307  // Returns the total power and pledge recorded by the power actor.
   308  // The returned values are frozen during the cron tick before this epoch
   309  // so that this method returns consistent values while processing all messages
   310  // of an epoch.
   311  func (a Actor) CurrentTotalPower(rt Runtime, _ *abi.EmptyValue) *CurrentTotalPowerReturn {
   312  	rt.ValidateImmediateCallerAcceptAny()
   313  	var st State
   314  	rt.StateReadonly(&st)
   315  
   316  	return &CurrentTotalPowerReturn{
   317  		RawBytePower:            st.ThisEpochRawBytePower,
   318  		QualityAdjPower:         st.ThisEpochQualityAdjPower,
   319  		PledgeCollateral:        st.ThisEpochPledgeCollateral,
   320  		QualityAdjPowerSmoothed: st.ThisEpochQAPowerSmoothed,
   321  	}
   322  }
   323  
   324  ////////////////////////////////////////////////////////////////////////////////
   325  // Method utility functions
   326  ////////////////////////////////////////////////////////////////////////////////
   327  
   328  func validateMinerHasClaim(rt Runtime, st State, minerAddr addr.Address) {
   329  	claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   330  	builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   331  
   332  	found, err := claims.Has(abi.AddrKey(minerAddr))
   333  	builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim")
   334  	if !found {
   335  		rt.Abortf(exitcode.ErrForbidden, "unknown miner %s forbidden to interact with power actor", minerAddr)
   336  	}
   337  }
   338  
   339  func (a Actor) processBatchProofVerifies(rt Runtime) {
   340  	var st State
   341  
   342  	var miners []addr.Address
   343  	verifies := make(map[addr.Address][]proof.SealVerifyInfo)
   344  
   345  	rt.StateTransaction(&st, func() {
   346  		store := adt.AsStore(rt)
   347  		if st.ProofValidationBatch == nil {
   348  			return
   349  		}
   350  		mmap, err := adt.AsMultimap(store, *st.ProofValidationBatch, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth)
   351  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs validation batch")
   352  
   353  		claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   354  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   355  
   356  		err = mmap.ForAll(func(k string, arr *adt.Array) error {
   357  			a, err := addr.NewFromBytes([]byte(k))
   358  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to parse address key")
   359  
   360  			// refuse to process proofs for miner with no claim
   361  			found, err := claims.Has(abi.AddrKey(a))
   362  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim")
   363  			if !found {
   364  				rt.Log(rtt.WARN, "skipping batch verifies for unknown miner %s", a)
   365  				return nil
   366  			}
   367  
   368  			miners = append(miners, a)
   369  
   370  			var infos []proof.SealVerifyInfo
   371  			var svi proof.SealVerifyInfo
   372  			err = arr.ForEach(&svi, func(i int64) error {
   373  				infos = append(infos, svi)
   374  				return nil
   375  			})
   376  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to iterate over proof verify array for miner %s", a)
   377  
   378  			verifies[a] = infos
   379  			return nil
   380  		})
   381  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to iterate proof batch")
   382  
   383  		st.ProofValidationBatch = nil
   384  	})
   385  
   386  	res, err := rt.BatchVerifySeals(verifies)
   387  	builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to batch verify")
   388  
   389  	for _, m := range miners {
   390  		vres, ok := res[m]
   391  		if !ok {
   392  			rt.Abortf(exitcode.ErrNotFound, "batch verify seals syscall implemented incorrectly")
   393  		}
   394  
   395  		verifs := verifies[m]
   396  
   397  		seen := map[abi.SectorNumber]struct{}{}
   398  		var successful []abi.SectorNumber
   399  		for i, r := range vres {
   400  			if r {
   401  				snum := verifs[i].SectorID.Number
   402  
   403  				if _, exists := seen[snum]; exists {
   404  					// filter-out duplicates
   405  					continue
   406  				}
   407  
   408  				seen[snum] = struct{}{}
   409  				successful = append(successful, snum)
   410  			}
   411  		}
   412  
   413  		if len(successful) > 0 {
   414  			// The exit code is explicitly ignored
   415  			_ = rt.Send(
   416  				m,
   417  				builtin.MethodsMiner.ConfirmSectorProofsValid,
   418  				&builtin.ConfirmSectorProofsParams{Sectors: successful},
   419  				abi.NewTokenAmount(0),
   420  				&builtin.Discard{},
   421  			)
   422  		}
   423  	}
   424  }
   425  
   426  func (a Actor) processDeferredCronEvents(rt Runtime) {
   427  	rtEpoch := rt.CurrEpoch()
   428  
   429  	var cronEvents []CronEvent
   430  	var st State
   431  	rt.StateTransaction(&st, func() {
   432  		events, err := adt.AsMultimap(adt.AsStore(rt), st.CronEventQueue, CronQueueHamtBitwidth, CronQueueAmtBitwidth)
   433  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events")
   434  
   435  		claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   436  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   437  
   438  		for epoch := st.FirstCronEpoch; epoch <= rtEpoch; epoch++ {
   439  			epochEvents, err := loadCronEvents(events, epoch)
   440  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events at %v", epoch)
   441  
   442  			for _, evt := range epochEvents {
   443  				// refuse to process proofs for miner with no claim
   444  				found, err := claims.Has(abi.AddrKey(evt.MinerAddr))
   445  				builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim")
   446  				if !found {
   447  					rt.Log(rtt.WARN, "skipping cron event for unknown miner %v", evt.MinerAddr)
   448  					continue
   449  				}
   450  				cronEvents = append(cronEvents, evt)
   451  			}
   452  
   453  			if len(epochEvents) > 0 {
   454  				err = events.RemoveAll(epochKey(epoch))
   455  				builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to clear cron events at %v", epoch)
   456  			}
   457  		}
   458  
   459  		st.FirstCronEpoch = rtEpoch + 1
   460  
   461  		st.CronEventQueue, err = events.Root()
   462  		builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush events")
   463  	})
   464  	failedMinerCrons := make([]addr.Address, 0)
   465  	for _, event := range cronEvents {
   466  		code := rt.Send(
   467  			event.MinerAddr,
   468  			builtin.MethodsMiner.OnDeferredCronEvent,
   469  			builtin.CBORBytes(event.CallbackPayload),
   470  			abi.NewTokenAmount(0),
   471  			&builtin.Discard{},
   472  		)
   473  		// If a callback fails, this actor continues to invoke other callbacks
   474  		// and persists state removing the failed event from the event queue. It won't be tried again.
   475  		// Failures are unexpected here but will result in removal of miner power
   476  		// A log message would really help here.
   477  		if code != exitcode.Ok {
   478  			rt.Log(rtt.WARN, "OnDeferredCronEvent failed for miner %s: exitcode %d", event.MinerAddr, code)
   479  			failedMinerCrons = append(failedMinerCrons, event.MinerAddr)
   480  		}
   481  	}
   482  
   483  	if len(failedMinerCrons) > 0 {
   484  		rt.StateTransaction(&st, func() {
   485  			claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth)
   486  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims")
   487  
   488  			// Remove miner claim and leave miner frozen
   489  			for _, minerAddr := range failedMinerCrons {
   490  				found, err := st.deleteClaim(claims, minerAddr)
   491  				if err != nil {
   492  					rt.Log(rtt.ERROR, "failed to delete claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err)
   493  					continue
   494  				} else if !found {
   495  					rt.Log(rtt.ERROR, "can't find claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err)
   496  					continue
   497  				}
   498  
   499  				// Decrement miner count to keep stats consistent.
   500  				st.MinerCount--
   501  			}
   502  
   503  			st.Claims, err = claims.Root()
   504  			builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims")
   505  		})
   506  	}
   507  }