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

     1  package miner_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/filecoin-project/go-bitfield"
    12  	"github.com/filecoin-project/go-state-types/abi"
    13  	"github.com/filecoin-project/go-state-types/exitcode"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    18  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/miner"
    19  	"github.com/filecoin-project/specs-actors/v4/actors/util/adt"
    20  	"github.com/filecoin-project/specs-actors/v4/support/ipld"
    21  )
    22  
    23  func TestPartitions(t *testing.T) {
    24  	sectors := []*miner.SectorOnChainInfo{
    25  		testSector(2, 1, 50, 60, 1000),
    26  		testSector(3, 2, 51, 61, 1001),
    27  		testSector(7, 3, 52, 62, 1002),
    28  		testSector(8, 4, 53, 63, 1003),
    29  		testSector(11, 5, 54, 64, 1004),
    30  		testSector(13, 6, 55, 65, 1005),
    31  	}
    32  	sectorSize := abi.SectorSize(32 << 30)
    33  
    34  	quantSpec := miner.NewQuantSpec(4, 1)
    35  
    36  	setupUnproven := func(t *testing.T) (adt.Store, *miner.Partition) {
    37  		store := ipld.NewADTStore(context.Background())
    38  		partition := emptyPartition(t, store)
    39  
    40  		power, err := partition.AddSectors(store, false, sectors, sectorSize, quantSpec)
    41  		require.NoError(t, err)
    42  		expectedPower := miner.PowerForSectors(sectorSize, sectors)
    43  		assert.True(t, expectedPower.Equals(power))
    44  
    45  		return store, partition
    46  	}
    47  
    48  	setup := func(t *testing.T) (adt.Store, *miner.Partition) {
    49  		store, partition := setupUnproven(t)
    50  
    51  		power := partition.ActivateUnproven()
    52  
    53  		expectedPower := miner.PowerForSectors(sectorSize, sectors)
    54  		assert.True(t, expectedPower.Equals(power))
    55  
    56  		return store, partition
    57  	}
    58  
    59  	t.Run("adds sectors then activates unproven", func(t *testing.T) {
    60  		_, partition := setupUnproven(t)
    61  
    62  		power := partition.ActivateUnproven()
    63  		expectedPower := miner.PowerForSectors(sectorSize, sectors)
    64  		assert.True(t, expectedPower.Equals(power))
    65  	})
    66  
    67  	t.Run("adds sectors and reports sector stats", func(t *testing.T) {
    68  		store, partition := setup(t)
    69  
    70  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(), bf(), bf(), bf())
    71  
    72  		// assert sectors have been arranged into 3 groups
    73  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
    74  			{expiration: 5, sectors: bf(1, 2)},
    75  			{expiration: 9, sectors: bf(3, 4)},
    76  			{expiration: 13, sectors: bf(5, 6)},
    77  		})
    78  	})
    79  
    80  	t.Run("doesn't add sectors twice", func(t *testing.T) {
    81  		store, partition := setup(t)
    82  
    83  		_, err := partition.AddSectors(store, false, sectors[:1], sectorSize, quantSpec)
    84  		require.EqualError(t, err, "not all added sectors are new")
    85  	})
    86  
    87  	for _, proven := range []bool{true, false} {
    88  		t.Run(fmt.Sprintf("adds faults (proven:%v)", proven), func(t *testing.T) {
    89  			store, partition := setupUnproven(t)
    90  			if proven {
    91  				_ = partition.ActivateUnproven()
    92  			}
    93  			sectorArr := sectorsArr(t, store, sectors)
    94  
    95  			faultSet := bf(4, 5)
    96  			_, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
    97  			require.NoError(t, err)
    98  
    99  			expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, faultSet))
   100  			expectedPowerDelta := miner.NewPowerPairZero()
   101  			if proven {
   102  				expectedPowerDelta = expectedFaultyPower.Neg()
   103  			}
   104  			assert.True(t, expectedFaultyPower.Equals(newFaultyPower))
   105  			assert.True(t, powerDelta.Equals(expectedPowerDelta))
   106  
   107  			sectorNos := bf(1, 2, 3, 4, 5, 6)
   108  			unprovenSet := bf(1, 2, 3, 6) // faults are no longer "unproven", just faulty.
   109  			if proven {
   110  				unprovenSet = bf()
   111  			}
   112  
   113  			assertPartitionState(
   114  				t, store, partition, quantSpec, sectorSize, sectors,
   115  				sectorNos, faultSet, bf(), bf(), unprovenSet,
   116  			)
   117  
   118  			// moves faulty sectors after expiration to earlier group
   119  			assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   120  				{expiration: 5, sectors: bf(1, 2)},
   121  				{expiration: 9, sectors: bf(3, 4, 5)},
   122  				{expiration: 13, sectors: bf(6)},
   123  			})
   124  		})
   125  	}
   126  
   127  	t.Run("re-adding faults is a no-op", func(t *testing.T) {
   128  		store, partition := setup(t)
   129  		sectorArr := sectorsArr(t, store, sectors)
   130  
   131  		faultSet := bf(4, 5)
   132  		_, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   133  		require.NoError(t, err)
   134  
   135  		expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, faultSet))
   136  		assert.True(t, expectedFaultyPower.Equals(newFaultyPower))
   137  		assert.True(t, powerDelta.Equals(expectedFaultyPower.Neg()))
   138  
   139  		faultSet = bf(5, 6)
   140  		newFaults, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(3), sectorSize, quantSpec)
   141  		require.NoError(t, err)
   142  		assertBitfieldEquals(t, newFaults, 6)
   143  		expectedFaultyPower = miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(6)))
   144  		assert.True(t, expectedFaultyPower.Equals(newFaultyPower))
   145  		assert.True(t, powerDelta.Equals(expectedFaultyPower.Neg()))
   146  
   147  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(), bf(), bf())
   148  
   149  		// moves newly-faulty sector
   150  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   151  			{expiration: 5, sectors: bf(1, 2, 6)},
   152  			{expiration: 9, sectors: bf(3, 4, 5)},
   153  		})
   154  	})
   155  
   156  	t.Run("fails to add faults for missing sectors", func(t *testing.T) {
   157  		store, partition := setup(t)
   158  		sectorArr := sectorsArr(t, store, sectors)
   159  
   160  		faultSet := bf(99)
   161  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   162  		require.Error(t, err)
   163  		assert.Contains(t, err.Error(), "not all sectors are assigned to the partition")
   164  	})
   165  
   166  	t.Run("adds recoveries", func(t *testing.T) {
   167  		store, partition := setup(t)
   168  		sectorArr := sectorsArr(t, store, sectors)
   169  
   170  		// make 4, 5 and 6 faulty
   171  		faultSet := bf(4, 5, 6)
   172  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   173  		require.NoError(t, err)
   174  
   175  		// add 4 and 5 as recoveries
   176  		recoverSet := bf(4, 5)
   177  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   178  		require.NoError(t, err)
   179  
   180  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4, 5), bf(), bf())
   181  	})
   182  
   183  	t.Run("remove recoveries", func(t *testing.T) {
   184  		store, partition := setup(t)
   185  		sectorArr := sectorsArr(t, store, sectors)
   186  
   187  		// make 4, 5 and 6 faulty
   188  		faultSet := bf(4, 5, 6)
   189  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   190  		require.NoError(t, err)
   191  
   192  		// add 4 and 5 as recoveries
   193  		recoverSet := bf(4, 5)
   194  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   195  		require.NoError(t, err)
   196  
   197  		// declaring no faults doesn't do anything.
   198  		newFaults, _, _, err := partition.RecordFaults(store, sectorArr, bf(), abi.ChainEpoch(7), sectorSize, quantSpec)
   199  		require.NoError(t, err)
   200  		assertBitfieldEmpty(t, newFaults) // no new faults.
   201  
   202  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4, 5), bf(), bf())
   203  
   204  		// removing sector 5 alters recovery set and recovery power
   205  		newFaults, _, _, err = partition.RecordFaults(store, sectorArr, bf(5), abi.ChainEpoch(10), sectorSize, quantSpec)
   206  		require.NoError(t, err)
   207  		assertBitfieldEmpty(t, newFaults) // these faults aren't new.
   208  
   209  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4), bf(), bf())
   210  	})
   211  
   212  	t.Run("recovers faults", func(t *testing.T) {
   213  		store, partition := setup(t)
   214  		sectorArr := sectorsArr(t, store, sectors)
   215  
   216  		// make 4, 5 and 6 faulty
   217  		faultSet := bf(4, 5, 6)
   218  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   219  		require.NoError(t, err)
   220  
   221  		// add 4 and 5 as recoveries
   222  		recoverSet := bf(4, 5)
   223  		recoveryPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, recoverSet))
   224  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   225  		require.NoError(t, err)
   226  
   227  		// mark recoveries as recovered recover sectors
   228  		recoveredPower, err := partition.RecoverFaults(store, sectorArr, sectorSize, quantSpec)
   229  		require.NoError(t, err)
   230  
   231  		// recovered power should equal power of recovery sectors
   232  		assert.True(t, recoveryPower.Equals(recoveredPower))
   233  
   234  		// state should be as if recovered sectors were never faults
   235  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(6), bf(), bf(), bf())
   236  
   237  		// restores recovered expirations to original state (unrecovered sector 6 still expires early)
   238  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   239  			{expiration: 5, sectors: bf(1, 2)},
   240  			{expiration: 9, sectors: bf(3, 4, 6)},
   241  			{expiration: 13, sectors: bf(5)},
   242  		})
   243  	})
   244  
   245  	t.Run("faulty power recovered exactly once", func(t *testing.T) {
   246  		store, partition := setup(t)
   247  		sectorArr := sectorsArr(t, store, sectors)
   248  
   249  		// make 4, 5 and 6 faulty
   250  		faultSet := bf(4, 5, 6)
   251  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   252  		require.NoError(t, err)
   253  
   254  		// add 3, 4 and 5 as recoveries. 3 is not faulty so it's skipped
   255  		recoverSet := bf(3, 4, 5)
   256  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   257  		require.NoError(t, err)
   258  
   259  		recoveringPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, faultSet))
   260  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, faultSet)
   261  		require.NoError(t, err)
   262  		assert.True(t, partition.RecoveringPower.Equals(recoveringPower))
   263  
   264  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4, 5, 6), bf(), bf())
   265  	})
   266  
   267  	t.Run("missing sectors are not recovered", func(t *testing.T) {
   268  		store, partition := setup(t)
   269  		sectorArr := sectorsArr(t, store, sectors)
   270  
   271  		// try to add 99 as a recovery but it's not in the partition
   272  		err := partition.DeclareFaultsRecovered(sectorArr, sectorSize, bf(99))
   273  		require.Error(t, err)
   274  		assert.Contains(t, err.Error(), "not all sectors are assigned to the partition")
   275  	})
   276  
   277  	t.Run("reschedules expirations", func(t *testing.T) {
   278  		store, partition := setup(t)
   279  
   280  		unprovenSector := testSector(13, 7, 55, 65, 1006)
   281  		allSectors := append(sectors[:len(sectors):len(sectors)], unprovenSector)
   282  		sectorArr := sectorsArr(t, store, allSectors)
   283  
   284  		// Mark sector 2 faulty, we should skip it when rescheduling
   285  		faultSet := bf(2)
   286  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   287  		require.NoError(t, err)
   288  
   289  		// Add an unproven sector. We _should_ reschedule the expiration.
   290  		// This is fine as we don't allow actually _expiring_ sectors
   291  		// while there are unproven sectors.
   292  		power, err := partition.AddSectors(
   293  			store, false,
   294  			[]*miner.SectorOnChainInfo{unprovenSector},
   295  			sectorSize, quantSpec,
   296  		)
   297  		require.NoError(t, err)
   298  		expectedPower := miner.PowerForSectors(sectorSize, []*miner.SectorOnChainInfo{unprovenSector})
   299  		assert.True(t, expectedPower.Equals(power))
   300  
   301  		// reschedule
   302  		replaced, err := partition.RescheduleExpirations(store, sectorArr, 18, bf(2, 4, 6, 7), sectorSize, quantSpec)
   303  		require.NoError(t, err)
   304  
   305  		// Assert we returned the sector infos of the replaced sectors
   306  		assert.Len(t, replaced, 3)
   307  		sort.Slice(replaced, func(i, j int) bool {
   308  			return replaced[i].SectorNumber < replaced[j].SectorNumber
   309  		})
   310  		assert.Equal(t, abi.SectorNumber(4), replaced[0].SectorNumber)
   311  		assert.Equal(t, abi.SectorNumber(6), replaced[1].SectorNumber)
   312  		assert.Equal(t, abi.SectorNumber(7), replaced[2].SectorNumber)
   313  
   314  		// We need to change the actual sector infos so our queue validation works.
   315  		rescheduled := rescheduleSectors(t, 18, allSectors, bf(4, 6, 7))
   316  
   317  		// partition power and sector categorization should remain the same
   318  		assertPartitionState(t, store, partition, quantSpec, sectorSize, rescheduled, bf(1, 2, 3, 4, 5, 6, 7), bf(2), bf(), bf(), bf(7))
   319  
   320  		// sectors should move to new expiration group
   321  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   322  			{expiration: 5, sectors: bf(1, 2)},
   323  			{expiration: 9, sectors: bf(3)},
   324  			{expiration: 13, sectors: bf(5)},
   325  			{expiration: 21, sectors: bf(4, 6, 7)},
   326  		})
   327  	})
   328  
   329  	t.Run("replace sectors", func(t *testing.T) {
   330  		store, partition := setup(t)
   331  
   332  		// remove 3 sectors starting with 2
   333  		oldSectors := sectors[1:4]
   334  		oldSectorPower := miner.PowerForSectors(sectorSize, oldSectors)
   335  		oldSectorPledge := int64(1001 + 1002 + 1003)
   336  
   337  		// replace 1 and add 2 new sectors
   338  		newSectors := []*miner.SectorOnChainInfo{
   339  			testSector(10, 2, 150, 260, 3000),
   340  			testSector(10, 7, 151, 261, 3001),
   341  			testSector(18, 8, 152, 262, 3002),
   342  		}
   343  		newSectorPower := miner.PowerForSectors(sectorSize, newSectors)
   344  		newSectorPledge := int64(3000 + 3001 + 3002)
   345  
   346  		powerDelta, pledgeDelta, err := partition.ReplaceSectors(store, oldSectors, newSectors, sectorSize, quantSpec)
   347  		require.NoError(t, err)
   348  
   349  		expectedPowerDelta := newSectorPower.Sub(oldSectorPower)
   350  		assert.True(t, expectedPowerDelta.Equals(powerDelta))
   351  		assert.Equal(t, abi.NewTokenAmount(newSectorPledge-oldSectorPledge), pledgeDelta)
   352  
   353  		// partition state should contain new sectors and not old sectors
   354  		allSectors := append(newSectors, sectors[:1]...)
   355  		allSectors = append(allSectors, sectors[4:]...)
   356  		assertPartitionState(t, store, partition, quantSpec, sectorSize, allSectors, bf(1, 2, 5, 6, 7, 8), bf(), bf(), bf(), bf())
   357  
   358  		// sector 2 should be moved, 3 and 4 should be removed, and 7 and 8 added
   359  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   360  			{expiration: 5, sectors: bf(1)},
   361  			{expiration: 13, sectors: bf(2, 5, 6, 7)},
   362  			{expiration: 21, sectors: bf(8)},
   363  		})
   364  	})
   365  
   366  	t.Run("replace sectors errors when attempting to replace inactive sector", func(t *testing.T) {
   367  		store, partition := setup(t)
   368  		sectorArr := sectorsArr(t, store, sectors)
   369  
   370  		// fault sector 2
   371  		faultSet := bf(2)
   372  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   373  		require.NoError(t, err)
   374  
   375  		// remove 3 sectors starting with 2
   376  		oldSectors := sectors[1:4]
   377  
   378  		// replace sector 2
   379  		newSectors := []*miner.SectorOnChainInfo{
   380  			testSector(10, 2, 150, 260, 3000),
   381  		}
   382  
   383  		_, _, err = partition.ReplaceSectors(store, oldSectors, newSectors, sectorSize, quantSpec)
   384  		require.Error(t, err)
   385  		assert.Contains(t, err.Error(), "refusing to replace inactive sectors")
   386  	})
   387  
   388  	t.Run("replace sectors errors when attempting to unproven sector", func(t *testing.T) {
   389  		store, partition := setupUnproven(t)
   390  
   391  		// remove 3 sectors starting with 2
   392  		oldSectors := sectors[1:4]
   393  
   394  		// replace sector 2
   395  		newSectors := []*miner.SectorOnChainInfo{
   396  			testSector(10, 2, 150, 260, 3000),
   397  		}
   398  
   399  		_, _, err := partition.ReplaceSectors(store, oldSectors, newSectors, sectorSize, quantSpec)
   400  		require.Error(t, err)
   401  		assert.Contains(t, err.Error(), "refusing to replace inactive sectors")
   402  	})
   403  
   404  	t.Run("terminate sectors", func(t *testing.T) {
   405  		store, partition := setup(t)
   406  
   407  		unprovenSector := testSector(13, 7, 55, 65, 1006)
   408  		allSectors := append(sectors[:len(sectors):len(sectors)], unprovenSector)
   409  		sectorArr := sectorsArr(t, store, allSectors)
   410  
   411  		// Add an unproven sector.
   412  		power, err := partition.AddSectors(
   413  			store, false,
   414  			[]*miner.SectorOnChainInfo{unprovenSector},
   415  			sectorSize, quantSpec,
   416  		)
   417  		require.NoError(t, err)
   418  		expectedPower := miner.PowerForSectors(sectorSize, []*miner.SectorOnChainInfo{unprovenSector})
   419  		assert.True(t, expectedPower.Equals(power))
   420  
   421  		// fault sector 3, 4, 5 and 6
   422  		faultSet := bf(3, 4, 5, 6)
   423  		_, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   424  		require.NoError(t, err)
   425  
   426  		// mark 4and 5 as a recoveries
   427  		recoverSet := bf(4, 5)
   428  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   429  		require.NoError(t, err)
   430  
   431  		// now terminate 1, 3, 5, and 7
   432  		terminations := bf(1, 3, 5, 7)
   433  		terminationEpoch := abi.ChainEpoch(3)
   434  		removed, err := partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   435  		require.NoError(t, err)
   436  
   437  		expectedActivePower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(1)))
   438  		assert.True(t, expectedActivePower.Equals(removed.ActivePower))
   439  		expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(3, 5)))
   440  		assert.True(t, expectedFaultyPower.Equals(removed.FaultyPower))
   441  
   442  		// expect partition state to no longer reflect power and pledge from terminated sectors and terminations to contain new sectors
   443  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6, 7), bf(4, 6), bf(4), terminations, bf())
   444  
   445  		// sectors should move to new expiration group
   446  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   447  			{expiration: 5, sectors: bf(2)},
   448  			{expiration: 9, sectors: bf(4, 6)},
   449  		})
   450  
   451  		// sectors should be added to early termination bitfield queue
   452  		queue, err := miner.LoadBitfieldQueue(store, partition.EarlyTerminated, miner.NoQuantization, miner.PartitionEarlyTerminationArrayAmtBitwidth)
   453  		require.NoError(t, err)
   454  
   455  		ExpectBQ().
   456  			Add(terminationEpoch, 1, 3, 5, 7).
   457  			Equals(t, queue)
   458  	})
   459  
   460  	t.Run("terminate non-existent sectors", func(t *testing.T) {
   461  		store, partition := setup(t)
   462  		sectorArr := sectorsArr(t, store, sectors)
   463  
   464  		terminations := bf(99)
   465  		terminationEpoch := abi.ChainEpoch(3)
   466  		_, err := partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   467  		require.EqualError(t, err, "can only terminate live sectors")
   468  	})
   469  
   470  	t.Run("terminate already terminated sector", func(t *testing.T) {
   471  		store, partition := setup(t)
   472  		sectorArr := sectorsArr(t, store, sectors)
   473  
   474  		terminations := bf(1)
   475  		terminationEpoch := abi.ChainEpoch(3)
   476  
   477  		// First termination works.
   478  		removed, err := partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   479  		require.NoError(t, err)
   480  		expectedActivePower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(1)))
   481  		assert.True(t, expectedActivePower.Equals(removed.ActivePower))
   482  		assert.True(t, removed.FaultyPower.Equals(miner.NewPowerPairZero()))
   483  		count, err := removed.Count()
   484  		require.NoError(t, err)
   485  		assert.EqualValues(t, 1, count)
   486  
   487  		// Second termination fails
   488  		_, err = partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   489  		require.EqualError(t, err, "can only terminate live sectors")
   490  	})
   491  
   492  	t.Run("mark terminated sectors as faulty", func(t *testing.T) {
   493  		store, partition := setup(t)
   494  		sectorArr := sectorsArr(t, store, sectors)
   495  
   496  		terminations := bf(1)
   497  		terminationEpoch := abi.ChainEpoch(3)
   498  
   499  		// Termination works.
   500  		_, err := partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   501  		require.NoError(t, err)
   502  
   503  		// Fault declaration for terminated sectors fails.
   504  		newFaults, _, _, err := partition.RecordFaults(store, sectorArr, terminations, abi.ChainEpoch(5), sectorSize, quantSpec)
   505  		require.NoError(t, err)
   506  		empty, err := newFaults.IsEmpty()
   507  		require.NoError(t, err)
   508  		require.True(t, empty)
   509  	})
   510  
   511  	t.Run("pop expiring sectors", func(t *testing.T) {
   512  		store, partition := setup(t)
   513  		sectorArr := sectorsArr(t, store, sectors)
   514  
   515  		// add one fault with an early termination
   516  		faultSet := bf(4)
   517  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(2), sectorSize, quantSpec)
   518  		require.NoError(t, err)
   519  
   520  		// pop first expiration set
   521  		expireEpoch := abi.ChainEpoch(5)
   522  		expset, err := partition.PopExpiredSectors(store, expireEpoch, quantSpec)
   523  		require.NoError(t, err)
   524  
   525  		assertBitfieldEquals(t, expset.OnTimeSectors, 1, 2)
   526  		assertBitfieldEquals(t, expset.EarlySectors, 4)
   527  		assert.Equal(t, abi.NewTokenAmount(1000+1001), expset.OnTimePledge)
   528  
   529  		// active power only contains power from non-faulty sectors
   530  		assert.True(t, expset.ActivePower.Equals(miner.PowerForSectors(sectorSize, sectors[:2])))
   531  
   532  		// faulty power comes from early termination
   533  		assert.True(t, expset.FaultyPower.Equals(miner.PowerForSectors(sectorSize, sectors[3:4])))
   534  
   535  		// expect sectors to be moved to terminations
   536  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(), bf(), bf(1, 2, 4), bf())
   537  
   538  		// sectors should move to new expiration group
   539  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   540  			{expiration: 9, sectors: bf(3)},
   541  			{expiration: 13, sectors: bf(5, 6)},
   542  		})
   543  
   544  		// sectors should be added to early termination bitfield queue
   545  		queue, err := miner.LoadBitfieldQueue(store, partition.EarlyTerminated, miner.NoQuantization, miner.PartitionEarlyTerminationArrayAmtBitwidth)
   546  		require.NoError(t, err)
   547  
   548  		// only early termination appears in bitfield queue
   549  		ExpectBQ().
   550  			Add(expireEpoch, 4).
   551  			Equals(t, queue)
   552  	})
   553  
   554  	t.Run("pop expiring sectors errors if a recovery exists", func(t *testing.T) {
   555  		store, partition := setup(t)
   556  		sectorArr := sectorsArr(t, store, sectors)
   557  
   558  		_, _, _, err := partition.RecordFaults(store, sectorArr, bf(5), abi.ChainEpoch(2), sectorSize, quantSpec)
   559  		require.NoError(t, err)
   560  
   561  		// add a recovery
   562  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, bf(5))
   563  		require.NoError(t, err)
   564  
   565  		// pop first expiration set
   566  		expireEpoch := abi.ChainEpoch(5)
   567  		_, err = partition.PopExpiredSectors(store, expireEpoch, quantSpec)
   568  		require.Error(t, err)
   569  		assert.Contains(t, err.Error(), "unexpected recoveries while processing expirations")
   570  	})
   571  
   572  	t.Run("pop expiring sectors errors if a unproven sectors exist", func(t *testing.T) {
   573  		store, partition := setupUnproven(t)
   574  
   575  		// pop first expiration set
   576  		expireEpoch := abi.ChainEpoch(5)
   577  		_, err := partition.PopExpiredSectors(store, expireEpoch, quantSpec)
   578  		require.Error(t, err)
   579  		assert.Contains(t, err.Error(), "cannot pop expired sectors from a partition with unproven sectors")
   580  	})
   581  
   582  	t.Run("records missing PoSt", func(t *testing.T) {
   583  		store, partition := setup(t)
   584  
   585  		unprovenSector := testSector(13, 7, 55, 65, 1006)
   586  		allSectors := append(sectors[:len(sectors):len(sectors)], unprovenSector)
   587  		sectorArr := sectorsArr(t, store, allSectors)
   588  
   589  		// Add an unproven sector.
   590  		power, err := partition.AddSectors(
   591  			store, false,
   592  			[]*miner.SectorOnChainInfo{unprovenSector},
   593  			sectorSize, quantSpec,
   594  		)
   595  		require.NoError(t, err)
   596  		expectedPower := miner.PowerForSectors(sectorSize, []*miner.SectorOnChainInfo{unprovenSector})
   597  		assert.True(t, expectedPower.Equals(power))
   598  
   599  		// make 4, 5 and 6 faulty
   600  		faultSet := bf(4, 5, 6)
   601  		_, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   602  		require.NoError(t, err)
   603  
   604  		// add 4 and 5 as recoveries
   605  		recoverSet := bf(4, 5)
   606  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   607  		require.NoError(t, err)
   608  
   609  		// record entire partition as faulted
   610  		powerDelta, penalizedPower, newFaultyPower, err := partition.RecordMissedPost(store, abi.ChainEpoch(6), quantSpec)
   611  		require.NoError(t, err)
   612  
   613  		expectedNewFaultPower := miner.PowerForSectors(sectorSize, append(allSectors[:3:3], allSectors[6]))
   614  		assert.True(t, expectedNewFaultPower.Equals(newFaultyPower))
   615  
   616  		// 6 has always been faulty, so we shouldn't be penalized for it (except ongoing).
   617  		expectedPenalizedPower := miner.PowerForSectors(sectorSize, allSectors).
   618  			Sub(miner.PowerForSector(sectorSize, allSectors[5]))
   619  		assert.True(t, expectedPenalizedPower.Equals(penalizedPower))
   620  
   621  		// We should lose power for sectors 1-3.
   622  		expectedPowerDelta := miner.PowerForSectors(sectorSize, allSectors[:3]).Neg()
   623  		assert.True(t, expectedPowerDelta.Equals(powerDelta))
   624  
   625  		// everything is now faulty
   626  		assertPartitionState(t, store, partition, quantSpec, sectorSize, allSectors, bf(1, 2, 3, 4, 5, 6, 7), bf(1, 2, 3, 4, 5, 6, 7), bf(), bf(), bf())
   627  
   628  		// everything not in first expiration group is now in second because fault expiration quantized to 9
   629  		assertPartitionExpirationQueue(t, store, partition, quantSpec, []expectExpirationGroup{
   630  			{expiration: 5, sectors: bf(1, 2)},
   631  			{expiration: 9, sectors: bf(3, 4, 5, 6, 7)},
   632  		})
   633  	})
   634  
   635  	t.Run("pops early terminations", func(t *testing.T) {
   636  		store, partition := setup(t)
   637  		sectorArr := sectorsArr(t, store, sectors)
   638  
   639  		// fault sector 3, 4, 5 and 6
   640  		faultSet := bf(3, 4, 5, 6)
   641  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   642  		require.NoError(t, err)
   643  
   644  		// mark 4and 5 as a recoveries
   645  		recoverSet := bf(4, 5)
   646  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   647  		require.NoError(t, err)
   648  
   649  		// now terminate 1, 3 and 5
   650  		terminations := bf(1, 3, 5)
   651  		terminationEpoch := abi.ChainEpoch(3)
   652  		_, err = partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   653  		require.NoError(t, err)
   654  
   655  		// pop first termination
   656  		result, hasMore, err := partition.PopEarlyTerminations(store, 1)
   657  		require.NoError(t, err)
   658  
   659  		// expect first sector to be in early terminations
   660  		assertBitfieldEquals(t, result.Sectors[terminationEpoch], 1)
   661  
   662  		// expect more results
   663  		assert.True(t, hasMore)
   664  
   665  		// expect terminations to still contain 3 and 5
   666  		queue, err := miner.LoadBitfieldQueue(store, partition.EarlyTerminated, miner.NoQuantization, miner.PartitionEarlyTerminationArrayAmtBitwidth)
   667  		require.NoError(t, err)
   668  
   669  		// only early termination appears in bitfield queue
   670  		ExpectBQ().
   671  			Add(terminationEpoch, 3, 5).
   672  			Equals(t, queue)
   673  
   674  		// pop the rest
   675  		result, hasMore, err = partition.PopEarlyTerminations(store, 5)
   676  		require.NoError(t, err)
   677  
   678  		// expect 3 and 5
   679  		assertBitfieldEquals(t, result.Sectors[terminationEpoch], 3, 5)
   680  
   681  		// expect no more results
   682  		assert.False(t, hasMore)
   683  
   684  		// expect early terminations to be empty
   685  		queue, err = miner.LoadBitfieldQueue(store, partition.EarlyTerminated, miner.NoQuantization, miner.PartitionEarlyTerminationArrayAmtBitwidth)
   686  		require.NoError(t, err)
   687  		ExpectBQ().Equals(t, queue)
   688  	})
   689  
   690  	t.Run("test max sectors", func(t *testing.T) {
   691  		store := ipld.NewADTStore(context.Background())
   692  		partition := emptyPartition(t, store)
   693  
   694  		proofType := abi.RegisteredSealProof_StackedDrg32GiBV1_1
   695  		sectorSize, err := proofType.SectorSize()
   696  		require.NoError(t, err)
   697  		partitionSectors, err := builtin.SealProofWindowPoStPartitionSectors(proofType)
   698  		require.NoError(t, err)
   699  
   700  		manySectors := make([]*miner.SectorOnChainInfo, partitionSectors)
   701  		ids := make([]uint64, partitionSectors)
   702  		for i := range manySectors {
   703  			id := uint64((i + 1) << 50)
   704  			ids[i] = id
   705  			manySectors[i] = testSector(int64(i+1), int64(id), 50, 60, 1000)
   706  		}
   707  		sectorNos := bf(ids...)
   708  
   709  		power, err := partition.AddSectors(store, false, manySectors, sectorSize, miner.NoQuantization)
   710  		require.NoError(t, err)
   711  		expectedPower := miner.PowerForSectors(sectorSize, manySectors)
   712  		assert.True(t, expectedPower.Equals(power))
   713  
   714  		assertPartitionState(
   715  			t, store, partition,
   716  			miner.NoQuantization, sectorSize, manySectors,
   717  			sectorNos, bf(), bf(), bf(), sectorNos,
   718  		)
   719  
   720  		// Make sure we can still encode and decode.
   721  		var buf bytes.Buffer
   722  		err = partition.MarshalCBOR(&buf)
   723  		require.NoError(t, err)
   724  
   725  		var newPartition miner.Partition
   726  		err = newPartition.UnmarshalCBOR(&buf)
   727  		require.NoError(t, err)
   728  
   729  		assertPartitionState(
   730  			t, store, &newPartition,
   731  			miner.NoQuantization, sectorSize, manySectors,
   732  			sectorNos, bf(), bf(), bf(), sectorNos,
   733  		)
   734  
   735  	})
   736  }
   737  
   738  func TestRecordSkippedFaults(t *testing.T) {
   739  	sectors := []*miner.SectorOnChainInfo{
   740  		testSector(2, 1, 50, 60, 1000),
   741  		testSector(3, 2, 51, 61, 1001),
   742  		testSector(7, 3, 52, 62, 1002),
   743  		testSector(8, 4, 53, 63, 1003),
   744  		testSector(11, 5, 54, 64, 1004),
   745  		testSector(13, 6, 55, 65, 1005),
   746  	}
   747  	sectorSize := abi.SectorSize(32 << 30)
   748  
   749  	quantSpec := miner.NewQuantSpec(4, 1)
   750  	exp := abi.ChainEpoch(100)
   751  
   752  	setup := func(t *testing.T) (adt.Store, *miner.Partition) {
   753  		store := ipld.NewADTStore(context.Background())
   754  		partition := emptyPartition(t, store)
   755  
   756  		power, err := partition.AddSectors(store, true, sectors, sectorSize, quantSpec)
   757  		require.NoError(t, err)
   758  		expectedPower := miner.PowerForSectors(sectorSize, sectors)
   759  		assert.True(t, expectedPower.Equals(power))
   760  
   761  		return store, partition
   762  	}
   763  
   764  	t.Run("fail if ALL declared sectors are NOT in the partition", func(t *testing.T) {
   765  		store, partition := setup(t)
   766  		sectorArr := sectorsArr(t, store, sectors)
   767  
   768  		skipped := bitfield.NewFromSet([]uint64{1, 100})
   769  
   770  		powerDelta, newFaulty, retractedRecovery, newFaults, err := partition.RecordSkippedFaults(
   771  			store, sectorArr, sectorSize, quantSpec, exp, skipped,
   772  		)
   773  		require.Error(t, err)
   774  		require.EqualValues(t, exitcode.ErrIllegalArgument, exitcode.Unwrap(err, exitcode.Ok))
   775  		require.EqualValues(t, miner.NewPowerPairZero(), newFaulty)
   776  		require.EqualValues(t, miner.NewPowerPairZero(), retractedRecovery)
   777  		require.EqualValues(t, miner.NewPowerPairZero(), powerDelta)
   778  		require.False(t, newFaults)
   779  	})
   780  
   781  	t.Run("already faulty and terminated sectors are ignored", func(t *testing.T) {
   782  		store, partition := setup(t)
   783  		sectorArr := sectorsArr(t, store, sectors)
   784  
   785  		// terminate 1 AND 2
   786  		terminations := bf(1, 2)
   787  		terminationEpoch := abi.ChainEpoch(3)
   788  		_, err := partition.TerminateSectors(store, sectorArr, terminationEpoch, terminations, sectorSize, quantSpec)
   789  		require.NoError(t, err)
   790  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(), bf(), terminations, bf())
   791  
   792  		// declare 4 & 5 as faulty
   793  		faultSet := bf(4, 5)
   794  		_, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   795  		require.NoError(t, err)
   796  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), faultSet, bf(), terminations, bf())
   797  
   798  		// record skipped faults such that some of them are already faulty/terminated
   799  		skipped := bitfield.NewFromSet([]uint64{1, 2, 3, 4, 5})
   800  		powerDelta, newFaultPower, retractedPower, newFaults, err := partition.RecordSkippedFaults(store, sectorArr, sectorSize, quantSpec, exp, skipped)
   801  		require.NoError(t, err)
   802  		require.EqualValues(t, miner.NewPowerPairZero(), retractedPower)
   803  		expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(3)))
   804  		require.EqualValues(t, expectedFaultyPower, newFaultPower)
   805  		require.EqualValues(t, powerDelta, newFaultPower.Neg())
   806  		require.True(t, newFaults)
   807  
   808  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(3, 4, 5), bf(), bf(1, 2), bf())
   809  	})
   810  
   811  	t.Run("recoveries are retracted without being marked as new faulty power", func(t *testing.T) {
   812  		store, partition := setup(t)
   813  		sectorArr := sectorsArr(t, store, sectors)
   814  
   815  		// make 4, 5 and 6 faulty
   816  		faultSet := bf(4, 5, 6)
   817  		_, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec)
   818  		require.NoError(t, err)
   819  
   820  		// add 4 and 5 as recoveries
   821  		recoverSet := bf(4, 5)
   822  		err = partition.DeclareFaultsRecovered(sectorArr, sectorSize, recoverSet)
   823  		require.NoError(t, err)
   824  
   825  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4, 5), bf(), bf())
   826  
   827  		// record skipped faults such that some of them have been marked as recovered
   828  		skipped := bitfield.NewFromSet([]uint64{1, 4, 5})
   829  		powerDelta, newFaultPower, recoveryPower, newFaults, err := partition.RecordSkippedFaults(store, sectorArr, sectorSize, quantSpec, exp, skipped)
   830  		require.NoError(t, err)
   831  		require.True(t, newFaults)
   832  
   833  		// only 1 is marked for fault power as 4 & 5 are recovering
   834  		expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(1)))
   835  		require.EqualValues(t, expectedFaultyPower, newFaultPower)
   836  		require.EqualValues(t, expectedFaultyPower.Neg(), powerDelta)
   837  
   838  		// 4 & 5 are marked for recovery power
   839  		expectedRecoveryPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(4, 5)))
   840  		require.EqualValues(t, expectedRecoveryPower, recoveryPower)
   841  
   842  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(1, 4, 5, 6), bf(), bf(), bf())
   843  	})
   844  
   845  	t.Run("successful when skipped fault set is empty", func(t *testing.T) {
   846  		store, partition := setup(t)
   847  		sectorArr := sectorsArr(t, store, sectors)
   848  
   849  		powerDelta, newFaultPower, recoveryPower, newFaults, err := partition.RecordSkippedFaults(store, sectorArr, sectorSize, quantSpec, exp, bf())
   850  		require.NoError(t, err)
   851  		require.EqualValues(t, miner.NewPowerPairZero(), newFaultPower)
   852  		require.EqualValues(t, miner.NewPowerPairZero(), recoveryPower)
   853  		require.EqualValues(t, miner.NewPowerPairZero(), powerDelta)
   854  		require.False(t, newFaults)
   855  
   856  		assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(), bf(), bf(), bf())
   857  	})
   858  }
   859  
   860  type expectExpirationGroup struct {
   861  	expiration abi.ChainEpoch
   862  	sectors    bitfield.BitField
   863  }
   864  
   865  func assertPartitionExpirationQueue(t *testing.T, store adt.Store, partition *miner.Partition, quant miner.QuantSpec, groups []expectExpirationGroup) {
   866  	queue, err := miner.LoadExpirationQueue(store, partition.ExpirationsEpochs, quant, miner.PartitionExpirationAmtBitwidth)
   867  	require.NoError(t, err)
   868  
   869  	for _, group := range groups {
   870  		requireNoExpirationGroupsBefore(t, group.expiration, queue)
   871  		set, err := queue.PopUntil(group.expiration)
   872  		require.NoError(t, err)
   873  
   874  		// we pnly care whether the sectors are in the queue or not. ExpirationQueue tests can deal with early or on time.
   875  		allSectors, err := bitfield.MergeBitFields(set.OnTimeSectors, set.EarlySectors)
   876  		require.NoError(t, err)
   877  		assertBitfieldsEqual(t, group.sectors, allSectors)
   878  	}
   879  }
   880  
   881  func assertPartitionState(t *testing.T,
   882  	store adt.Store,
   883  	partition *miner.Partition,
   884  	quant miner.QuantSpec,
   885  	sectorSize abi.SectorSize,
   886  	sectors []*miner.SectorOnChainInfo,
   887  	allSectorIds bitfield.BitField,
   888  	faults bitfield.BitField,
   889  	recovering bitfield.BitField,
   890  	terminations bitfield.BitField,
   891  	unproven bitfield.BitField,
   892  ) {
   893  
   894  	assertBitfieldsEqual(t, faults, partition.Faults)
   895  	assertBitfieldsEqual(t, recovering, partition.Recoveries)
   896  	assertBitfieldsEqual(t, terminations, partition.Terminated)
   897  	assertBitfieldsEqual(t, unproven, partition.Unproven)
   898  	assertBitfieldsEqual(t, allSectorIds, partition.Sectors)
   899  
   900  	msgs := &builtin.MessageAccumulator{}
   901  	_ = miner.CheckPartitionStateInvariants(partition, store, quant, sectorSize, sectorsAsMap(sectors), msgs)
   902  	assert.True(t, msgs.IsEmpty(), strings.Join(msgs.Messages(), "\n"))
   903  }
   904  
   905  func bf(secNos ...uint64) bitfield.BitField {
   906  	return bitfield.NewFromSet(secNos)
   907  }
   908  
   909  func selectSectors(t *testing.T, sectors []*miner.SectorOnChainInfo, field bitfield.BitField) []*miner.SectorOnChainInfo {
   910  	toInclude, err := field.AllMap(miner.AddressedSectorsMax)
   911  	require.NoError(t, err)
   912  
   913  	included := []*miner.SectorOnChainInfo{}
   914  	for _, s := range sectors {
   915  		if !toInclude[uint64(s.SectorNumber)] {
   916  			continue
   917  		}
   918  		included = append(included, s)
   919  		delete(toInclude, uint64(s.SectorNumber))
   920  	}
   921  	assert.Empty(t, toInclude, "expected additional sectors")
   922  	return included
   923  }
   924  
   925  func emptyPartition(t *testing.T, store adt.Store) *miner.Partition {
   926  	p, err := miner.ConstructPartition(store)
   927  	require.NoError(t, err)
   928  	return p
   929  }
   930  
   931  func rescheduleSectors(t *testing.T, target abi.ChainEpoch, sectors []*miner.SectorOnChainInfo, filter bitfield.BitField) []*miner.SectorOnChainInfo {
   932  	toReschedule, err := filter.AllMap(miner.AddressedSectorsMax)
   933  	require.NoError(t, err)
   934  	output := make([]*miner.SectorOnChainInfo, len(sectors))
   935  	for i, sector := range sectors {
   936  		cpy := *sector
   937  		if toReschedule[uint64(cpy.SectorNumber)] {
   938  			cpy.Expiration = target
   939  		}
   940  		output[i] = &cpy
   941  	}
   942  	return output
   943  }
   944  
   945  func sectorsAsMap(sectors []*miner.SectorOnChainInfo) map[abi.SectorNumber]*miner.SectorOnChainInfo {
   946  	m := map[abi.SectorNumber]*miner.SectorOnChainInfo{}
   947  	for _, s := range sectors {
   948  		m[s.SectorNumber] = s
   949  	}
   950  	return m
   951  }