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

     1  package miner
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/filecoin-project/go-bitfield"
     8  	"github.com/filecoin-project/go-state-types/abi"
     9  	"github.com/filecoin-project/go-state-types/big"
    10  	"github.com/ipfs/go-cid"
    11  	"golang.org/x/xerrors"
    12  
    13  	"github.com/filecoin-project/specs-actors/v4/actors/util"
    14  	"github.com/filecoin-project/specs-actors/v4/actors/util/adt"
    15  )
    16  
    17  // ExpirationSet is a collection of sector numbers that are expiring, either due to
    18  // expected "on-time" expiration at the end of their life, or unexpected "early" termination
    19  // due to being faulty for too long consecutively.
    20  // Note that there is not a direct correspondence between on-time sectors and active power;
    21  // a sector may be faulty but expiring on-time if it faults just prior to expected termination.
    22  // Early sectors are always faulty, and active power always represents on-time sectors.
    23  type ExpirationSet struct {
    24  	OnTimeSectors bitfield.BitField // Sectors expiring "on time" at the end of their committed life
    25  	EarlySectors  bitfield.BitField // Sectors expiring "early" due to being faulty for too long
    26  	OnTimePledge  abi.TokenAmount   // Pledge total for the on-time sectors
    27  	ActivePower   PowerPair         // Power that is currently active (not faulty)
    28  	FaultyPower   PowerPair         // Power that is currently faulty
    29  }
    30  
    31  func NewExpirationSetEmpty() *ExpirationSet {
    32  	return NewExpirationSet(bitfield.New(), bitfield.New(), big.Zero(), NewPowerPairZero(), NewPowerPairZero())
    33  }
    34  
    35  func NewExpirationSet(onTimeSectors, earlySectors bitfield.BitField, onTimePledge abi.TokenAmount, activePower, faultyPower PowerPair) *ExpirationSet {
    36  	return &ExpirationSet{
    37  		OnTimeSectors: onTimeSectors,
    38  		EarlySectors:  earlySectors,
    39  		OnTimePledge:  onTimePledge,
    40  		ActivePower:   activePower,
    41  		FaultyPower:   faultyPower,
    42  	}
    43  }
    44  
    45  // Adds sectors and power to the expiration set in place.
    46  func (es *ExpirationSet) Add(onTimeSectors, earlySectors bitfield.BitField, onTimePledge abi.TokenAmount, activePower, faultyPower PowerPair) error {
    47  	var err error
    48  	if es.OnTimeSectors, err = bitfield.MergeBitFields(es.OnTimeSectors, onTimeSectors); err != nil {
    49  		return err
    50  	}
    51  	if es.EarlySectors, err = bitfield.MergeBitFields(es.EarlySectors, earlySectors); err != nil {
    52  		return err
    53  	}
    54  	es.OnTimePledge = big.Add(es.OnTimePledge, onTimePledge)
    55  	es.ActivePower = es.ActivePower.Add(activePower)
    56  	es.FaultyPower = es.FaultyPower.Add(faultyPower)
    57  	return es.ValidateState()
    58  }
    59  
    60  // Removes sectors and power from the expiration set in place.
    61  func (es *ExpirationSet) Remove(onTimeSectors, earlySectors bitfield.BitField, onTimePledge abi.TokenAmount, activePower, faultyPower PowerPair) error {
    62  	// Check for sector intersection. This could be cheaper with a combined intersection/difference method used below.
    63  	if found, err := util.BitFieldContainsAll(es.OnTimeSectors, onTimeSectors); err != nil {
    64  		return err
    65  	} else if !found {
    66  		return xerrors.Errorf("removing on-time sectors %v not contained in %v", onTimeSectors, es.OnTimeSectors)
    67  	}
    68  	if found, err := util.BitFieldContainsAll(es.EarlySectors, earlySectors); err != nil {
    69  		return err
    70  	} else if !found {
    71  		return xerrors.Errorf("removing early sectors %v not contained in %v", earlySectors, es.EarlySectors)
    72  	}
    73  
    74  	var err error
    75  	if es.OnTimeSectors, err = bitfield.SubtractBitField(es.OnTimeSectors, onTimeSectors); err != nil {
    76  		return err
    77  	}
    78  	if es.EarlySectors, err = bitfield.SubtractBitField(es.EarlySectors, earlySectors); err != nil {
    79  		return err
    80  	}
    81  	es.OnTimePledge = big.Sub(es.OnTimePledge, onTimePledge)
    82  	es.ActivePower = es.ActivePower.Sub(activePower)
    83  	es.FaultyPower = es.FaultyPower.Sub(faultyPower)
    84  	// Check underflow.
    85  	if es.OnTimePledge.LessThan(big.Zero()) {
    86  		return xerrors.Errorf("expiration set pledge underflow: %v", es)
    87  	}
    88  	if es.ActivePower.QA.LessThan(big.Zero()) || es.FaultyPower.QA.LessThan(big.Zero()) {
    89  		return xerrors.Errorf("expiration set power underflow: %v", es)
    90  	}
    91  	return es.ValidateState()
    92  }
    93  
    94  // A set is empty if it has no sectors.
    95  // The power and pledge are not checked, but expected to be zero.
    96  func (es *ExpirationSet) IsEmpty() (empty bool, err error) {
    97  	if empty, err = es.OnTimeSectors.IsEmpty(); err != nil {
    98  		return false, err
    99  	} else if empty {
   100  		if empty, err = es.EarlySectors.IsEmpty(); err != nil {
   101  			return false, err
   102  		}
   103  		return empty, nil
   104  	} else {
   105  		return false, nil
   106  	}
   107  }
   108  
   109  // Counts all sectors in the expiration set.
   110  func (es *ExpirationSet) Count() (count uint64, err error) {
   111  	onTime, err := es.OnTimeSectors.Count()
   112  	if err != nil {
   113  		return 0, err
   114  	}
   115  
   116  	early, err := es.EarlySectors.Count()
   117  	if err != nil {
   118  		return 0, err
   119  	}
   120  
   121  	return onTime + early, nil
   122  }
   123  
   124  // validates a set of assertions that must hold for expiration sets
   125  func (es *ExpirationSet) ValidateState() error {
   126  	if es.OnTimePledge.LessThan(big.Zero()) {
   127  		return xerrors.Errorf("ESet left with negative pledge: %+v", es)
   128  	}
   129  
   130  	if es.ActivePower.Raw.LessThan(big.Zero()) {
   131  		return xerrors.Errorf("ESet left with negative raw active power: %+v", es)
   132  	}
   133  
   134  	if es.ActivePower.QA.LessThan(big.Zero()) {
   135  		return xerrors.Errorf("ESet left with negative qa active power: %+v", es)
   136  	}
   137  
   138  	if es.FaultyPower.Raw.LessThan(big.Zero()) {
   139  		return xerrors.Errorf("ESet left with negative raw faulty power: %+v", es)
   140  	}
   141  
   142  	if es.FaultyPower.QA.LessThan(big.Zero()) {
   143  		return xerrors.Errorf("ESet left with negative qa faulty power: %+v", es)
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  // A queue of expiration sets by epoch, representing the on-time or early termination epoch for a collection of sectors.
   150  // Wraps an AMT[ChainEpoch]*ExpirationSet.
   151  // Keys in the queue are quantized (upwards), modulo some offset, to reduce the cardinality of keys.
   152  type ExpirationQueue struct {
   153  	*adt.Array
   154  	quant QuantSpec
   155  }
   156  
   157  // An internal limit on the cardinality of a bitfield in a queue entry.
   158  // This must be at least large enough to support the maximum number of sectors in a partition.
   159  // It would be a bit better to derive this number from an enumeration over all partition sizes.
   160  const entrySectorsMax = 10_000
   161  
   162  // Loads a queue root.
   163  // Epochs provided to subsequent method calls will be quantized upwards to quanta mod offsetSeed before being
   164  // written to/read from queue entries.
   165  func LoadExpirationQueue(store adt.Store, root cid.Cid, quant QuantSpec, bitwidth int) (ExpirationQueue, error) {
   166  	arr, err := adt.AsArray(store, root, bitwidth)
   167  	if err != nil {
   168  		return ExpirationQueue{}, xerrors.Errorf("failed to load epoch queue %v: %w", root, err)
   169  	}
   170  	return ExpirationQueue{arr, quant}, nil
   171  }
   172  
   173  // Adds a collection of sectors to their on-time target expiration entries (quantized).
   174  // The sectors are assumed to be active (non-faulty).
   175  // Returns the sector numbers, power, and pledge added.
   176  func (q ExpirationQueue) AddActiveSectors(sectors []*SectorOnChainInfo, ssize abi.SectorSize) (bitfield.BitField, PowerPair, abi.TokenAmount, error) {
   177  	totalPower := NewPowerPairZero()
   178  	totalPledge := big.Zero()
   179  	var totalSectors []bitfield.BitField
   180  	noEarlySectors := bitfield.New()
   181  	noFaultyPower := NewPowerPairZero()
   182  	for _, group := range groupNewSectorsByDeclaredExpiration(ssize, sectors, q.quant) {
   183  		snos := bitfield.NewFromSet(group.sectors)
   184  		if err := q.add(group.epoch, snos, noEarlySectors, group.power, noFaultyPower, group.pledge); err != nil {
   185  			return bitfield.BitField{}, NewPowerPairZero(), big.Zero(), xerrors.Errorf("failed to record new sector expirations: %w", err)
   186  		}
   187  		totalSectors = append(totalSectors, snos)
   188  		totalPower = totalPower.Add(group.power)
   189  		totalPledge = big.Add(totalPledge, group.pledge)
   190  	}
   191  	snos, err := bitfield.MultiMerge(totalSectors...)
   192  	if err != nil {
   193  		return bitfield.BitField{}, NewPowerPairZero(), big.Zero(), err
   194  	}
   195  	return snos, totalPower, totalPledge, nil
   196  }
   197  
   198  // Reschedules some sectors to a new (quantized) expiration epoch.
   199  // The sectors being rescheduled are assumed to be not faulty, and hence are removed from and re-scheduled for on-time
   200  // rather than early expiration.
   201  // The sectors' power and pledge are assumed not to change, despite the new expiration.
   202  func (q ExpirationQueue) RescheduleExpirations(newExpiration abi.ChainEpoch, sectors []*SectorOnChainInfo, ssize abi.SectorSize) error {
   203  	if len(sectors) == 0 {
   204  		return nil
   205  	}
   206  
   207  	snos, power, pledge, err := q.removeActiveSectors(sectors, ssize)
   208  	if err != nil {
   209  		return xerrors.Errorf("failed to remove sector expirations: %w", err)
   210  	}
   211  	if err = q.add(newExpiration, snos, bitfield.New(), power, NewPowerPairZero(), pledge); err != nil {
   212  		return xerrors.Errorf("failed to record new sector expirations: %w", err)
   213  	}
   214  	return nil
   215  }
   216  
   217  // Re-schedules sectors to expire at an early expiration epoch (quantized), if they wouldn't expire before then anyway.
   218  // The sectors must not be currently faulty, so must be registered as expiring on-time rather than early.
   219  // The pledge for the now-early sectors is removed from the queue.
   220  // Returns the total power represented by the sectors.
   221  func (q ExpirationQueue) RescheduleAsFaults(newExpiration abi.ChainEpoch, sectors []*SectorOnChainInfo, ssize abi.SectorSize) (PowerPair, error) {
   222  	var sectorsTotal []uint64
   223  	expiringPower := NewPowerPairZero()
   224  	rescheduledPower := NewPowerPairZero()
   225  
   226  	// Group sectors by their target expiration, then remove from existing queue entries according to those groups.
   227  	groups, err := q.findSectorsByExpiration(ssize, sectors)
   228  	if err != nil {
   229  		return NewPowerPairZero(), err
   230  	}
   231  
   232  	for _, group := range groups {
   233  		var err error
   234  		if group.epoch <= q.quant.QuantizeUp(newExpiration) {
   235  			// Don't reschedule sectors that are already due to expire on-time before the fault-driven expiration,
   236  			// but do represent their power as now faulty.
   237  			// Their pledge remains as "on-time".
   238  			group.expirationSet.ActivePower = group.expirationSet.ActivePower.Sub(group.power)
   239  			group.expirationSet.FaultyPower = group.expirationSet.FaultyPower.Add(group.power)
   240  			expiringPower = expiringPower.Add(group.power)
   241  		} else {
   242  			// Remove sectors from on-time expiry and active power.
   243  			sectorsBf := bitfield.NewFromSet(group.sectors)
   244  			if group.expirationSet.OnTimeSectors, err = bitfield.SubtractBitField(group.expirationSet.OnTimeSectors, sectorsBf); err != nil {
   245  				return NewPowerPairZero(), err
   246  			}
   247  			group.expirationSet.OnTimePledge = big.Sub(group.expirationSet.OnTimePledge, group.pledge)
   248  			group.expirationSet.ActivePower = group.expirationSet.ActivePower.Sub(group.power)
   249  
   250  			// Accumulate the sectors and power removed.
   251  			sectorsTotal = append(sectorsTotal, group.sectors...)
   252  			rescheduledPower = rescheduledPower.Add(group.power)
   253  		}
   254  		if err = q.mustUpdateOrDelete(group.epoch, group.expirationSet); err != nil {
   255  			return NewPowerPairZero(), err
   256  		}
   257  
   258  		if err = group.expirationSet.ValidateState(); err != nil {
   259  			return NewPowerPairZero(), err
   260  		}
   261  	}
   262  
   263  	if len(sectorsTotal) > 0 {
   264  		// Add sectors to new expiration as early-terminating and faulty.
   265  		earlySectors := bitfield.NewFromSet(sectorsTotal)
   266  		noOnTimeSectors := bitfield.New()
   267  		noOnTimePledge := abi.NewTokenAmount(0)
   268  		noActivePower := NewPowerPairZero()
   269  		if err := q.add(newExpiration, noOnTimeSectors, earlySectors, noActivePower, rescheduledPower, noOnTimePledge); err != nil {
   270  			return NewPowerPairZero(), err
   271  		}
   272  	}
   273  
   274  	return rescheduledPower.Add(expiringPower), nil
   275  }
   276  
   277  // Re-schedules *all* sectors to expire at an early expiration epoch, if they wouldn't expire before then anyway.
   278  func (q ExpirationQueue) RescheduleAllAsFaults(faultExpiration abi.ChainEpoch) error {
   279  	var rescheduledEpochs []uint64
   280  	var rescheduledSectors []bitfield.BitField
   281  	rescheduledPower := NewPowerPairZero()
   282  
   283  	var es ExpirationSet
   284  	if err := q.Array.ForEach(&es, func(e int64) error {
   285  		epoch := abi.ChainEpoch(e)
   286  		if epoch <= q.quant.QuantizeUp(faultExpiration) {
   287  			// Regardless of whether the sectors were expiring on-time or early, all the power is now faulty.
   288  			// Pledge is still on-time.
   289  			es.FaultyPower = es.FaultyPower.Add(es.ActivePower)
   290  			es.ActivePower = NewPowerPairZero()
   291  			if err := q.mustUpdate(epoch, &es); err != nil {
   292  				return err
   293  			}
   294  		} else {
   295  			rescheduledEpochs = append(rescheduledEpochs, uint64(epoch))
   296  			// sanity check to make sure we're not trying to re-schedule already faulty sectors.
   297  			if isEmpty, err := es.EarlySectors.IsEmpty(); err != nil {
   298  				return xerrors.Errorf("failed to determine if epoch had early expirations: %w", err)
   299  			} else if !isEmpty {
   300  				return xerrors.Errorf("attempted to re-schedule early expirations to an even earlier epoch")
   301  			}
   302  			rescheduledSectors = append(rescheduledSectors, es.OnTimeSectors)
   303  			rescheduledPower = rescheduledPower.Add(es.ActivePower)
   304  			rescheduledPower = rescheduledPower.Add(es.FaultyPower)
   305  		}
   306  
   307  		if err := es.ValidateState(); err != nil {
   308  			return err
   309  		}
   310  
   311  		return nil
   312  	}); err != nil {
   313  		return err
   314  	}
   315  
   316  	// If we didn't reschedule anything, we're done.
   317  	if len(rescheduledEpochs) == 0 {
   318  		return nil
   319  	}
   320  
   321  	// Add rescheduled sectors to new expiration as early-terminating and faulty.
   322  	allRescheduled, err := bitfield.MultiMerge(rescheduledSectors...)
   323  	if err != nil {
   324  		return xerrors.Errorf("failed to merge rescheduled sectors: %w", err)
   325  	}
   326  	noOnTimeSectors := bitfield.New()
   327  	noActivePower := NewPowerPairZero()
   328  	noOnTimePledge := abi.NewTokenAmount(0)
   329  	if err = q.add(faultExpiration, noOnTimeSectors, allRescheduled, noActivePower, rescheduledPower, noOnTimePledge); err != nil {
   330  		return err
   331  	}
   332  
   333  	// Trim the rescheduled epochs from the queue.
   334  	if err = q.BatchDelete(rescheduledEpochs, true); err != nil {
   335  		return err
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  // Removes sectors from any queue entries in which they appear that are earlier then their scheduled expiration epoch,
   342  // and schedules them at their expected termination epoch.
   343  // Pledge for the sectors is re-added as on-time.
   344  // Power for the sectors is changed from faulty to active (whether rescheduled or not).
   345  // Returns the newly-recovered power. Fails if any sectors are not found in the queue.
   346  func (q ExpirationQueue) RescheduleRecovered(sectors []*SectorOnChainInfo, ssize abi.SectorSize) (PowerPair, error) {
   347  	remaining := make(map[abi.SectorNumber]struct{}, len(sectors))
   348  	for _, s := range sectors {
   349  		remaining[s.SectorNumber] = struct{}{}
   350  	}
   351  
   352  	// Traverse the expiration queue once to find each recovering sector and remove it from early/faulty there.
   353  	// We expect this to find all recovering sectors within the first FaultMaxAge/WPoStProvingPeriod entries
   354  	// (i.e. 14 for 14-day faults), but if something has gone wrong it's safer not to fail if that's not met.
   355  	var sectorsRescheduled []*SectorOnChainInfo
   356  	recoveredPower := NewPowerPairZero()
   357  	if err := q.traverseMutate(func(epoch abi.ChainEpoch, es *ExpirationSet) (changed, keepGoing bool, err error) {
   358  		onTimeSectors, err := es.OnTimeSectors.AllMap(entrySectorsMax)
   359  		if err != nil {
   360  			return false, false, err
   361  		}
   362  		earlySectors, err := es.EarlySectors.AllMap(entrySectorsMax)
   363  		if err != nil {
   364  			return false, false, err
   365  		}
   366  
   367  		// This loop could alternatively be done by constructing bitfields and intersecting them, but it's not
   368  		// clear that would be much faster (O(max(N, M)) vs O(N+M)).
   369  		// If faults are correlated, the first queue entry likely has them all anyway.
   370  		// The length of sectors has a maximum of one partition size.
   371  		for _, sector := range sectors {
   372  			sno := uint64(sector.SectorNumber)
   373  			power := PowerForSector(ssize, sector)
   374  			var found bool
   375  			if _, found = onTimeSectors[sno]; found {
   376  				// If the sector expires on-time at this epoch, leave it here but change faulty power to active.
   377  				// The pledge is already part of the on-time pledge at this entry.
   378  				es.FaultyPower = es.FaultyPower.Sub(power)
   379  				es.ActivePower = es.ActivePower.Add(power)
   380  			} else if _, found = earlySectors[sno]; found {
   381  				// If the sector expires early at this epoch, remove it for re-scheduling.
   382  				// It's not part of the on-time pledge number here.
   383  				es.EarlySectors.Unset(sno)
   384  				es.FaultyPower = es.FaultyPower.Sub(power)
   385  				sectorsRescheduled = append(sectorsRescheduled, sector)
   386  			}
   387  			if found {
   388  				recoveredPower = recoveredPower.Add(power)
   389  				delete(remaining, sector.SectorNumber)
   390  				changed = true
   391  			}
   392  		}
   393  
   394  		if err = es.ValidateState(); err != nil {
   395  			return false, false, err
   396  		}
   397  
   398  		return changed, len(remaining) > 0, nil
   399  	}); err != nil {
   400  		return NewPowerPairZero(), err
   401  	}
   402  	if len(remaining) > 0 {
   403  		return NewPowerPairZero(), xerrors.Errorf("sectors not found in expiration queue: %v", remaining)
   404  	}
   405  
   406  	// Re-schedule the removed sectors to their target expiration.
   407  	if _, _, _, err := q.AddActiveSectors(sectorsRescheduled, ssize); err != nil {
   408  		return NewPowerPairZero(), err
   409  	}
   410  	return recoveredPower, nil
   411  }
   412  
   413  // Removes some sectors and adds some others.
   414  // The sectors being replaced must not be faulty, so must be scheduled for on-time rather than early expiration.
   415  // The sectors added are assumed to be not faulty.
   416  // Returns the old a new sector number bitfields, and delta to power and pledge, new minus old.
   417  func (q ExpirationQueue) ReplaceSectors(oldSectors, newSectors []*SectorOnChainInfo, ssize abi.SectorSize) (bitfield.BitField, bitfield.BitField, PowerPair, abi.TokenAmount, error) {
   418  	oldSnos, oldPower, oldPledge, err := q.removeActiveSectors(oldSectors, ssize)
   419  	if err != nil {
   420  		return bitfield.BitField{}, bitfield.BitField{}, NewPowerPairZero(), big.Zero(), xerrors.Errorf("failed to remove replaced sectors: %w", err)
   421  	}
   422  	newSnos, newPower, newPledge, err := q.AddActiveSectors(newSectors, ssize)
   423  	if err != nil {
   424  		return bitfield.BitField{}, bitfield.BitField{}, NewPowerPairZero(), big.Zero(), xerrors.Errorf("failed to add replacement sectors: %w", err)
   425  	}
   426  	return oldSnos, newSnos, newPower.Sub(oldPower), big.Sub(newPledge, oldPledge), nil
   427  }
   428  
   429  // Remove some sectors from the queue.
   430  // The sectors may be active or faulty, and scheduled either for on-time or early termination.
   431  // Returns the aggregate of removed sectors and power, and recovering power.
   432  // Fails if any sectors are not found in the queue.
   433  func (q ExpirationQueue) RemoveSectors(sectors []*SectorOnChainInfo, faults bitfield.BitField, recovering bitfield.BitField,
   434  	ssize abi.SectorSize) (*ExpirationSet, PowerPair, error) {
   435  	remaining := make(map[abi.SectorNumber]struct{}, len(sectors))
   436  	for _, s := range sectors {
   437  		remaining[s.SectorNumber] = struct{}{}
   438  	}
   439  	faultsMap, err := faults.AllMap(AddressedSectorsMax)
   440  	if err != nil {
   441  		return nil, NewPowerPairZero(), xerrors.Errorf("failed to expand faults: %w", err)
   442  	}
   443  	recoveringMap, err := recovering.AllMap(AddressedSectorsMax)
   444  	if err != nil {
   445  		return nil, NewPowerPairZero(), xerrors.Errorf("failed to expand recoveries: %w", err)
   446  	}
   447  
   448  	// results
   449  	removed := NewExpirationSetEmpty()
   450  	recoveringPower := NewPowerPairZero()
   451  
   452  	// Split into faulty and non-faulty. We process non-faulty sectors first
   453  	// because they always expire on-time so we know where to find them.
   454  	var (
   455  		nonFaultySectors []*SectorOnChainInfo
   456  		faultySectors    []*SectorOnChainInfo
   457  	)
   458  	for _, sector := range sectors {
   459  		if _, found := faultsMap[uint64(sector.SectorNumber)]; found {
   460  			faultySectors = append(faultySectors, sector)
   461  			continue
   462  		}
   463  		nonFaultySectors = append(nonFaultySectors, sector)
   464  		// remove them from "remaining", we're going to process them below.
   465  		delete(remaining, sector.SectorNumber)
   466  	}
   467  
   468  	// Remove non-faulty sectors.
   469  	removed.OnTimeSectors, removed.ActivePower, removed.OnTimePledge, err = q.removeActiveSectors(nonFaultySectors, ssize)
   470  	if err != nil {
   471  		return nil, NewPowerPairZero(), xerrors.Errorf("failed to remove on-time recoveries: %w", err)
   472  	}
   473  
   474  	// Finally, remove faulty sectors (on time and not). These sectors can
   475  	// only appear within the first 14 days (fault max age). Given that this
   476  	// queue is quantized, we should be able to stop traversing the queue
   477  	// after 14 entries.
   478  	if err = q.traverseMutate(func(epoch abi.ChainEpoch, es *ExpirationSet) (changed, keepGoing bool, err error) {
   479  		onTimeSectors, err := es.OnTimeSectors.AllMap(entrySectorsMax)
   480  		if err != nil {
   481  			return false, false, err
   482  		}
   483  		earlySectors, err := es.EarlySectors.AllMap(entrySectorsMax)
   484  		if err != nil {
   485  			return false, false, err
   486  		}
   487  
   488  		// This loop could alternatively be done by constructing bitfields and intersecting them, but it's not
   489  		// clear that would be much faster (O(max(N, M)) vs O(N+M)).
   490  		// The length of sectors has a maximum of one partition size.
   491  		for _, sector := range faultySectors {
   492  			sno := uint64(sector.SectorNumber)
   493  			var found bool
   494  			if _, found = onTimeSectors[sno]; found {
   495  				es.OnTimeSectors.Unset(sno)
   496  				removed.OnTimeSectors.Set(sno)
   497  				es.OnTimePledge = big.Sub(es.OnTimePledge, sector.InitialPledge)
   498  				removed.OnTimePledge = big.Add(removed.OnTimePledge, sector.InitialPledge)
   499  			} else if _, found = earlySectors[sno]; found {
   500  				es.EarlySectors.Unset(sno)
   501  				removed.EarlySectors.Set(sno)
   502  			}
   503  			if found {
   504  				power := PowerForSector(ssize, sector)
   505  				if _, f := faultsMap[sno]; f {
   506  					es.FaultyPower = es.FaultyPower.Sub(power)
   507  					removed.FaultyPower = removed.FaultyPower.Add(power)
   508  				} else {
   509  					es.ActivePower = es.ActivePower.Sub(power)
   510  					removed.ActivePower = removed.ActivePower.Add(power)
   511  				}
   512  				if _, r := recoveringMap[sno]; r {
   513  					recoveringPower = recoveringPower.Add(power)
   514  				}
   515  				delete(remaining, sector.SectorNumber)
   516  				changed = true
   517  			}
   518  		}
   519  
   520  		if err = es.ValidateState(); err != nil {
   521  			return false, false, err
   522  		}
   523  
   524  		return changed, len(remaining) > 0, nil
   525  	}); err != nil {
   526  		return nil, recoveringPower, err
   527  	}
   528  	if len(remaining) > 0 {
   529  		return NewExpirationSetEmpty(), NewPowerPairZero(), xerrors.Errorf("sectors not found in expiration queue: %v", remaining)
   530  	}
   531  
   532  	return removed, recoveringPower, nil
   533  }
   534  
   535  // Removes and aggregates entries from the queue up to and including some epoch.
   536  func (q ExpirationQueue) PopUntil(until abi.ChainEpoch) (*ExpirationSet, error) {
   537  	var onTimeSectors []bitfield.BitField
   538  	var earlySectors []bitfield.BitField
   539  	activePower := NewPowerPairZero()
   540  	faultyPower := NewPowerPairZero()
   541  	onTimePledge := big.Zero()
   542  
   543  	var poppedKeys []uint64
   544  	var thisValue ExpirationSet
   545  	stopErr := fmt.Errorf("stop")
   546  	if err := q.Array.ForEach(&thisValue, func(i int64) error {
   547  		if abi.ChainEpoch(i) > until {
   548  			return stopErr
   549  		}
   550  		poppedKeys = append(poppedKeys, uint64(i))
   551  		onTimeSectors = append(onTimeSectors, thisValue.OnTimeSectors)
   552  		earlySectors = append(earlySectors, thisValue.EarlySectors)
   553  		activePower = activePower.Add(thisValue.ActivePower)
   554  		faultyPower = faultyPower.Add(thisValue.FaultyPower)
   555  		onTimePledge = big.Add(onTimePledge, thisValue.OnTimePledge)
   556  		return nil
   557  	}); err != nil && err != stopErr {
   558  		return nil, err
   559  	}
   560  
   561  	if err := q.Array.BatchDelete(poppedKeys, true); err != nil {
   562  		return nil, err
   563  	}
   564  
   565  	allOnTime, err := bitfield.MultiMerge(onTimeSectors...)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	allEarly, err := bitfield.MultiMerge(earlySectors...)
   570  	if err != nil {
   571  		return nil, err
   572  	}
   573  	return NewExpirationSet(allOnTime, allEarly, onTimePledge, activePower, faultyPower), nil
   574  }
   575  
   576  func (q ExpirationQueue) add(rawEpoch abi.ChainEpoch, onTimeSectors, earlySectors bitfield.BitField, activePower, faultyPower PowerPair,
   577  	pledge abi.TokenAmount) error {
   578  	epoch := q.quant.QuantizeUp(rawEpoch)
   579  	es, err := q.mayGet(epoch)
   580  	if err != nil {
   581  		return err
   582  	}
   583  
   584  	if err = es.Add(onTimeSectors, earlySectors, pledge, activePower, faultyPower); err != nil {
   585  		return xerrors.Errorf("failed to add expiration values for epoch %v: %w", epoch, err)
   586  	}
   587  
   588  	return q.mustUpdate(epoch, es)
   589  }
   590  
   591  func (q ExpirationQueue) remove(rawEpoch abi.ChainEpoch, onTimeSectors, earlySectors bitfield.BitField, activePower, faultyPower PowerPair,
   592  	pledge abi.TokenAmount) error {
   593  	epoch := q.quant.QuantizeUp(rawEpoch)
   594  	var es ExpirationSet
   595  	if found, err := q.Array.Get(uint64(epoch), &es); err != nil {
   596  		return xerrors.Errorf("failed to lookup queue epoch %v: %w", epoch, err)
   597  	} else if !found {
   598  		return xerrors.Errorf("missing expected expiration set at epoch %v", epoch)
   599  	}
   600  
   601  	if err := es.Remove(onTimeSectors, earlySectors, pledge, activePower, faultyPower); err != nil {
   602  		return xerrors.Errorf("failed to remove expiration values for queue epoch %v: %w", epoch, err)
   603  	}
   604  
   605  	return q.mustUpdateOrDelete(epoch, &es)
   606  }
   607  
   608  func (q ExpirationQueue) removeActiveSectors(sectors []*SectorOnChainInfo, ssize abi.SectorSize) (bitfield.BitField, PowerPair, abi.TokenAmount, error) {
   609  	removedSnos := bitfield.New()
   610  	removedPower := NewPowerPairZero()
   611  	removedPledge := big.Zero()
   612  	noEarlySectors := bitfield.New()
   613  	noFaultyPower := NewPowerPairZero()
   614  
   615  	// Group sectors by their expiration, then remove from existing queue entries according to those groups.
   616  	groups, err := q.findSectorsByExpiration(ssize, sectors)
   617  	if err != nil {
   618  		return bitfield.BitField{}, NewPowerPairZero(), big.Zero(), err
   619  	}
   620  
   621  	for _, group := range groups {
   622  		sectorsBf := bitfield.NewFromSet(group.sectors)
   623  		if err := q.remove(group.epoch, sectorsBf, noEarlySectors, group.power, noFaultyPower, group.pledge); err != nil {
   624  			return bitfield.BitField{}, NewPowerPairZero(), big.Zero(), err
   625  		}
   626  		for _, n := range group.sectors {
   627  			removedSnos.Set(n)
   628  		}
   629  		removedPower = removedPower.Add(group.power)
   630  		removedPledge = big.Add(removedPledge, group.pledge)
   631  	}
   632  	return removedSnos, removedPower, removedPledge, nil
   633  }
   634  
   635  // Traverses the entire queue with a callback function that may mutate entries.
   636  // Iff the function returns that it changed an entry, the new entry will be re-written in the queue. Any changed
   637  // entries that become empty are removed after iteration completes.
   638  func (q ExpirationQueue) traverseMutate(f func(epoch abi.ChainEpoch, es *ExpirationSet) (changed, keepGoing bool, err error)) error {
   639  	var es ExpirationSet
   640  	var epochsEmptied []uint64
   641  	errStop := fmt.Errorf("stop")
   642  	if err := q.Array.ForEach(&es, func(epoch int64) error {
   643  		changed, keepGoing, err := f(abi.ChainEpoch(epoch), &es)
   644  		if err != nil {
   645  			return err
   646  		} else if changed {
   647  			if emptied, err := es.IsEmpty(); err != nil {
   648  				return err
   649  			} else if emptied {
   650  				epochsEmptied = append(epochsEmptied, uint64(epoch))
   651  			} else if err = q.mustUpdate(abi.ChainEpoch(epoch), &es); err != nil {
   652  				return err
   653  			}
   654  		}
   655  
   656  		if !keepGoing {
   657  			return errStop
   658  		}
   659  		return nil
   660  	}); err != nil && err != errStop {
   661  		return err
   662  	}
   663  	if err := q.Array.BatchDelete(epochsEmptied, true); err != nil {
   664  		return err
   665  	}
   666  	return nil
   667  }
   668  
   669  func (q ExpirationQueue) traverse(f func(epoch abi.ChainEpoch, es *ExpirationSet) (keepGoing bool, err error)) error {
   670  	return q.traverseMutate(func(epoch abi.ChainEpoch, es *ExpirationSet) (bool, bool, error) {
   671  		keepGoing, err := f(epoch, es)
   672  		return false, keepGoing, err
   673  	})
   674  }
   675  
   676  func (q ExpirationQueue) mayGet(key abi.ChainEpoch) (*ExpirationSet, error) {
   677  	es := NewExpirationSetEmpty()
   678  	if _, err := q.Array.Get(uint64(key), es); err != nil {
   679  		return nil, xerrors.Errorf("failed to lookup queue epoch %v: %w", key, err)
   680  	}
   681  	return es, nil
   682  }
   683  
   684  func (q ExpirationQueue) mustUpdate(epoch abi.ChainEpoch, es *ExpirationSet) error {
   685  	if err := q.Array.Set(uint64(epoch), es); err != nil {
   686  		return xerrors.Errorf("failed to set queue epoch %v: %w", epoch, err)
   687  	}
   688  	return nil
   689  }
   690  
   691  // Since this might delete the node, it's not safe for use inside an iteration.
   692  func (q ExpirationQueue) mustUpdateOrDelete(epoch abi.ChainEpoch, es *ExpirationSet) error {
   693  	if empty, err := es.IsEmpty(); err != nil {
   694  		return err
   695  	} else if empty {
   696  		if err = q.Array.Delete(uint64(epoch)); err != nil {
   697  			return xerrors.Errorf("failed to delete queue epoch %d: %w", epoch, err)
   698  		}
   699  	} else if err = q.Array.Set(uint64(epoch), es); err != nil {
   700  		return xerrors.Errorf("failed to set queue epoch %v: %w", epoch, err)
   701  	}
   702  	return nil
   703  }
   704  
   705  type sectorEpochSet struct {
   706  	epoch   abi.ChainEpoch
   707  	sectors []uint64
   708  	power   PowerPair
   709  	pledge  abi.TokenAmount
   710  }
   711  
   712  type sectorExpirationSet struct {
   713  	sectorEpochSet
   714  	expirationSet *ExpirationSet
   715  }
   716  
   717  // Takes a slice of sector infos and returns sector info sets grouped and
   718  // sorted by expiration epoch, quantized.
   719  //
   720  // Note: While the result is sorted by epoch, the order of per-epoch sectors is maintained.
   721  func groupNewSectorsByDeclaredExpiration(sectorSize abi.SectorSize, sectors []*SectorOnChainInfo, quant QuantSpec) []sectorEpochSet {
   722  	sectorsByExpiration := make(map[abi.ChainEpoch][]*SectorOnChainInfo)
   723  
   724  	for _, sector := range sectors {
   725  		qExpiration := quant.QuantizeUp(sector.Expiration)
   726  		sectorsByExpiration[qExpiration] = append(sectorsByExpiration[qExpiration], sector)
   727  	}
   728  
   729  	sectorEpochSets := make([]sectorEpochSet, 0, len(sectorsByExpiration))
   730  
   731  	// This map iteration is non-deterministic but safe because we sort by epoch below.
   732  	for expiration, epochSectors := range sectorsByExpiration { //nolint:nomaprange // result is subsequently sorted
   733  		sectorNumbers := make([]uint64, len(epochSectors))
   734  		totalPower := NewPowerPairZero()
   735  		totalPledge := big.Zero()
   736  		for i, sector := range epochSectors {
   737  			sectorNumbers[i] = uint64(sector.SectorNumber)
   738  			totalPower = totalPower.Add(PowerForSector(sectorSize, sector))
   739  			totalPledge = big.Add(totalPledge, sector.InitialPledge)
   740  		}
   741  		sectorEpochSets = append(sectorEpochSets, sectorEpochSet{
   742  			epoch:   expiration,
   743  			sectors: sectorNumbers,
   744  			power:   totalPower,
   745  			pledge:  totalPledge,
   746  		})
   747  	}
   748  
   749  	sort.Slice(sectorEpochSets, func(i, j int) bool {
   750  		return sectorEpochSets[i].epoch < sectorEpochSets[j].epoch
   751  	})
   752  	return sectorEpochSets
   753  }
   754  
   755  // Groups sectors into sets based on their Expiration field.
   756  // If sectors are not found in the expiration set corresponding to their expiration field
   757  // (i.e. they have been rescheduled) traverse expiration sets to for groups where these
   758  // sectors actually expire.
   759  // Groups will be returned in expiration order, earliest first.
   760  func (q *ExpirationQueue) findSectorsByExpiration(sectorSize abi.SectorSize, sectors []*SectorOnChainInfo) ([]sectorExpirationSet, error) {
   761  	declaredExpirations := make(map[abi.ChainEpoch]bool, len(sectors))
   762  	sectorsByNumber := make(map[uint64]*SectorOnChainInfo, len(sectors))
   763  	allRemaining := make(map[uint64]struct{})
   764  	expirationGroups := make([]sectorExpirationSet, 0, len(declaredExpirations))
   765  
   766  	for _, sector := range sectors {
   767  		qExpiration := q.quant.QuantizeUp(sector.Expiration)
   768  		declaredExpirations[qExpiration] = true
   769  		allRemaining[uint64(sector.SectorNumber)] = struct{}{}
   770  		sectorsByNumber[uint64(sector.SectorNumber)] = sector
   771  	}
   772  
   773  	// Traverse expiration sets first by expected expirations. This will find all groups if no sectors have been rescheduled.
   774  	// This map iteration is non-deterministic but safe because we sort by epoch below.
   775  	for expiration := range declaredExpirations { //nolint:nomaprange // result is subsequently sorted
   776  		es, err := q.mayGet(expiration)
   777  		if err != nil {
   778  			return nil, err
   779  		}
   780  
   781  		// create group from overlap
   782  		var group sectorExpirationSet
   783  		group, allRemaining, err = groupExpirationSet(sectorSize, sectorsByNumber, allRemaining, es, expiration)
   784  		if err != nil {
   785  			return nil, err
   786  		}
   787  		if len(group.sectors) > 0 {
   788  			expirationGroups = append(expirationGroups, group)
   789  		}
   790  	}
   791  
   792  	// If sectors remain, traverse next in epoch order. Remaining sectors should be rescheduled to expire soon, so
   793  	// this traversal should exit early.
   794  	if len(allRemaining) > 0 {
   795  		err := q.traverse(func(epoch abi.ChainEpoch, es *ExpirationSet) (bool, error) {
   796  			// If this set's epoch is one of our declared epochs, we've already processed it in the loop above,
   797  			// so skip processing here. Sectors rescheduled to this epoch would have been included in the earlier processing.
   798  			if _, found := declaredExpirations[epoch]; found {
   799  				return true, nil
   800  			}
   801  
   802  			// Sector should not be found in EarlyExpirations which holds faults. An implicit assumption
   803  			// of grouping is that it only returns sectors with active power. ExpirationQueue should not
   804  			// provide operations that allow this to happen.
   805  			if err := assertNoEarlySectors(allRemaining, es); err != nil {
   806  				return true, err
   807  			}
   808  
   809  			var group sectorExpirationSet
   810  			var err error
   811  			group, allRemaining, err = groupExpirationSet(sectorSize, sectorsByNumber, allRemaining, es, epoch)
   812  			if err != nil {
   813  				return false, err
   814  			}
   815  			if len(group.sectors) > 0 {
   816  				expirationGroups = append(expirationGroups, group)
   817  			}
   818  
   819  			return len(allRemaining) > 0, nil
   820  		})
   821  		if err != nil {
   822  			return nil, err
   823  		}
   824  	}
   825  
   826  	if len(allRemaining) > 0 {
   827  		return nil, xerrors.New("some sectors not found in expiration queue")
   828  	}
   829  
   830  	// sort groups, earliest first.
   831  	sort.Slice(expirationGroups, func(i, j int) bool {
   832  		return expirationGroups[i].epoch < expirationGroups[j].epoch
   833  	})
   834  	return expirationGroups, nil
   835  }
   836  
   837  // Takes a slice of sector infos a bitfield of sector numbers and returns a single group for all bitfield sectors
   838  // Also returns a bitfield containing sectors not found in expiration set.
   839  // This method mutates includeSet by removing sector numbers of sectors found in expiration set.
   840  func groupExpirationSet(sectorSize abi.SectorSize, sectors map[uint64]*SectorOnChainInfo,
   841  	includeSet map[uint64]struct{}, es *ExpirationSet, expiration abi.ChainEpoch,
   842  ) (sectorExpirationSet, map[uint64]struct{}, error) {
   843  	var sectorNumbers []uint64
   844  	totalPower := NewPowerPairZero()
   845  	totalPledge := big.Zero()
   846  	err := es.OnTimeSectors.ForEach(func(u uint64) error {
   847  		if _, found := includeSet[u]; found {
   848  			sector := sectors[u]
   849  			sectorNumbers = append(sectorNumbers, u)
   850  			totalPower = totalPower.Add(PowerForSector(sectorSize, sector))
   851  			totalPledge = big.Add(totalPledge, sector.InitialPledge)
   852  			delete(includeSet, u)
   853  		}
   854  		return nil
   855  	})
   856  	if err != nil {
   857  		return sectorExpirationSet{}, nil, err
   858  	}
   859  
   860  	return sectorExpirationSet{
   861  		sectorEpochSet: sectorEpochSet{
   862  			epoch:   expiration,
   863  			sectors: sectorNumbers,
   864  			power:   totalPower,
   865  			pledge:  totalPledge,
   866  		},
   867  		expirationSet: es,
   868  	}, includeSet, nil
   869  }
   870  
   871  // assertNoEarlySectors checks for an invalid overlap between a bitfield an a set's early sectors.
   872  func assertNoEarlySectors(set map[uint64]struct{}, es *ExpirationSet) error {
   873  	return es.EarlySectors.ForEach(func(u uint64) error {
   874  		if _, found := set[u]; found {
   875  			return xerrors.Errorf("Invalid attempt to group sector %d with an early expiration", u)
   876  		}
   877  		return nil
   878  	})
   879  }