github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/slasherkv/pruning_test.go (about)

     1  package slasherkv
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	types "github.com/prysmaticlabs/eth2-types"
     9  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    10  	slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
    11  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    12  	"github.com/prysmaticlabs/prysm/shared/params"
    13  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    14  	logTest "github.com/sirupsen/logrus/hooks/test"
    15  	bolt "go.etcd.io/bbolt"
    16  )
    17  
    18  func TestStore_PruneProposals(t *testing.T) {
    19  	ctx := context.Background()
    20  
    21  	// If the current slot is less than the history length we track in slasher, there
    22  	// is nothing to prune yet, so we expect exiting early.
    23  	t.Run("current_epoch_less_than_history_length", func(t *testing.T) {
    24  		hook := logTest.NewGlobal()
    25  		beaconDB := setupDB(t)
    26  		epochPruningIncrements := types.Epoch(100)
    27  		currentEpoch := types.Epoch(1)
    28  		historyLength := types.Epoch(2)
    29  		err := beaconDB.PruneProposals(ctx, currentEpoch, epochPruningIncrements, historyLength)
    30  		require.NoError(t, err)
    31  		require.LogsContain(t, hook, "Current epoch 1 < history length 2, nothing to prune")
    32  	})
    33  
    34  	// If the lowest stored epoch in the database is >= the end epoch of the pruning process,
    35  	// there is nothing to prune, so we also expect exiting early.
    36  	t.Run("lowest_stored_epoch_greater_than_pruning_limit_epoch", func(t *testing.T) {
    37  		hook := logTest.NewGlobal()
    38  		beaconDB := setupDB(t)
    39  		epochPruningIncrements := types.Epoch(100)
    40  
    41  		// With a current epoch of 20 and a history length of 10, we should be pruning
    42  		// everything before epoch (20 - 10) = 10.
    43  		currentEpoch := types.Epoch(20)
    44  		historyLength := types.Epoch(10)
    45  
    46  		pruningLimitEpoch := currentEpoch - historyLength
    47  		lowestStoredSlot, err := helpers.StartSlot(pruningLimitEpoch)
    48  		require.NoError(t, err)
    49  
    50  		err = beaconDB.db.Update(func(tx *bolt.Tx) error {
    51  			bkt := tx.Bucket(proposalRecordsBucket)
    52  			key, err := keyForValidatorProposal(&slashertypes.SignedBlockHeaderWrapper{
    53  				SignedBeaconBlockHeader: &ethpb.SignedBeaconBlockHeader{
    54  					Header: &ethpb.BeaconBlockHeader{
    55  						Slot:          lowestStoredSlot,
    56  						ProposerIndex: 0,
    57  					},
    58  				},
    59  			})
    60  			if err != nil {
    61  				return err
    62  			}
    63  			return bkt.Put(key, []byte("hi"))
    64  		})
    65  		require.NoError(t, err)
    66  
    67  		err = beaconDB.PruneProposals(ctx, currentEpoch, epochPruningIncrements, historyLength)
    68  		require.NoError(t, err)
    69  		expectedLog := fmt.Sprintf(
    70  			"Lowest slot %d is >= pruning slot %d, nothing to prune", lowestStoredSlot, lowestStoredSlot,
    71  		)
    72  		require.LogsContain(t, hook, expectedLog)
    73  	})
    74  
    75  	// Prune in increments until the cursor reaches the pruning limit epoch, and we expect
    76  	// that all the proposals written are deleted from the database, while those above the
    77  	// pruning limit epoch are kept intact.
    78  	t.Run("prune_in_full_increments_and_verify_deletions", func(t *testing.T) {
    79  		hook := logTest.NewGlobal()
    80  		beaconDB := setupDB(t)
    81  
    82  		config := params.BeaconConfig()
    83  		copyConfig := config.Copy()
    84  		copyConfig.SlotsPerEpoch = 2
    85  		params.OverrideBeaconConfig(copyConfig)
    86  		defer params.OverrideBeaconConfig(config)
    87  
    88  		epochPruningIncrements := types.Epoch(2)
    89  		historyLength := types.Epoch(10)
    90  		currentEpoch := types.Epoch(20)
    91  		pruningLimitEpoch := currentEpoch - historyLength
    92  
    93  		// We create proposals from genesis to the current epoch, with 2 proposals
    94  		// at each slot to ensure the entire pruning logic works correctly.
    95  		slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
    96  		proposals := make([]*slashertypes.SignedBlockHeaderWrapper, 0, uint64(currentEpoch)*uint64(slotsPerEpoch)*2)
    97  		for i := types.Epoch(0); i < currentEpoch; i++ {
    98  			startSlot, err := helpers.StartSlot(i)
    99  			require.NoError(t, err)
   100  			endSlot, err := helpers.StartSlot(i + 1)
   101  			require.NoError(t, err)
   102  			for j := startSlot; j < endSlot; j++ {
   103  				prop1 := createProposalWrapper(t, j, 0 /* proposer index */, []byte{0})
   104  				prop2 := createProposalWrapper(t, j, 2 /* proposer index */, []byte{1})
   105  				proposals = append(proposals, prop1, prop2)
   106  			}
   107  		}
   108  
   109  		require.NoError(t, beaconDB.SaveBlockProposals(ctx, proposals))
   110  
   111  		// We expect pruning completes without an issue and properly logs progress.
   112  		err := beaconDB.PruneProposals(ctx, currentEpoch, epochPruningIncrements, historyLength)
   113  		require.NoError(t, err)
   114  
   115  		for i := types.Epoch(0); i < pruningLimitEpoch; i++ {
   116  			wantedLog := fmt.Sprintf("Pruned %d/%d epochs", i, pruningLimitEpoch-1)
   117  			require.LogsContain(t, hook, wantedLog)
   118  		}
   119  
   120  		// Everything before epoch 10 should be deleted.
   121  		for i := types.Epoch(0); i < pruningLimitEpoch; i++ {
   122  			err = beaconDB.db.View(func(tx *bolt.Tx) error {
   123  				bkt := tx.Bucket(proposalRecordsBucket)
   124  				startSlot, err := helpers.StartSlot(i)
   125  				require.NoError(t, err)
   126  				endSlot, err := helpers.StartSlot(i + 1)
   127  				require.NoError(t, err)
   128  				for j := startSlot; j < endSlot; j++ {
   129  					prop1 := createProposalWrapper(t, j, 0 /* proposer index */, []byte{0})
   130  					prop1Key, err := keyForValidatorProposal(prop1)
   131  					if err != nil {
   132  						return err
   133  					}
   134  					prop2 := createProposalWrapper(t, j, 2 /* proposer index */, []byte{1})
   135  					prop2Key, err := keyForValidatorProposal(prop2)
   136  					if err != nil {
   137  						return err
   138  					}
   139  					if bkt.Get(prop1Key) != nil {
   140  						return fmt.Errorf("proposal still exists for epoch %d, validator 0", j)
   141  					}
   142  					if bkt.Get(prop2Key) != nil {
   143  						return fmt.Errorf("proposal still exists for slot %d, validator 1", j)
   144  					}
   145  				}
   146  				return nil
   147  			})
   148  			require.NoError(t, err)
   149  		}
   150  	})
   151  }
   152  
   153  func TestStore_PruneAttestations_OK(t *testing.T) {
   154  	ctx := context.Background()
   155  
   156  	// If the current slot is less than the history length we track in slasher, there
   157  	// is nothing to prune yet, so we expect exiting early.
   158  	t.Run("current_epoch_less_than_history_length", func(t *testing.T) {
   159  		hook := logTest.NewGlobal()
   160  		beaconDB := setupDB(t)
   161  		epochPruningIncrements := types.Epoch(100)
   162  		currentEpoch := types.Epoch(1)
   163  		historyLength := types.Epoch(2)
   164  		err := beaconDB.PruneAttestations(ctx, currentEpoch, epochPruningIncrements, historyLength)
   165  		require.NoError(t, err)
   166  		require.LogsContain(t, hook, "Current epoch 1 < history length 2, nothing to prune")
   167  	})
   168  
   169  	// If the lowest stored epoch in the database is >= the end epoch of the pruning process,
   170  	// there is nothing to prune, so we also expect exiting early.
   171  	t.Run("lowest_stored_epoch_greater_than_pruning_limit_epoch", func(t *testing.T) {
   172  		hook := logTest.NewGlobal()
   173  		beaconDB := setupDB(t)
   174  		epochPruningIncrements := types.Epoch(100)
   175  
   176  		// With a current epoch of 20 and a history length of 10, we should be pruning
   177  		// everything before epoch (20 - 10) = 10.
   178  		currentEpoch := types.Epoch(20)
   179  		historyLength := types.Epoch(10)
   180  
   181  		pruningLimitEpoch := currentEpoch - historyLength
   182  		lowestStoredEpoch := pruningLimitEpoch
   183  
   184  		err := beaconDB.db.Update(func(tx *bolt.Tx) error {
   185  			bkt := tx.Bucket(attestationDataRootsBucket)
   186  			encIdx := encodeValidatorIndex(types.ValidatorIndex(0))
   187  			encodedTargetEpoch := encodeTargetEpoch(lowestStoredEpoch)
   188  			key := append(encodedTargetEpoch, encIdx...)
   189  			return bkt.Put(key, []byte("hi"))
   190  		})
   191  		require.NoError(t, err)
   192  
   193  		err = beaconDB.PruneAttestations(ctx, currentEpoch, epochPruningIncrements, historyLength)
   194  		require.NoError(t, err)
   195  		expectedLog := fmt.Sprintf(
   196  			"Lowest epoch %d is >= pruning epoch %d, nothing to prune", lowestStoredEpoch, lowestStoredEpoch,
   197  		)
   198  		require.LogsContain(t, hook, expectedLog)
   199  	})
   200  
   201  	// Prune in increments until the cursor reaches the pruning limit epoch, and we expect
   202  	// that all the attestations written are deleted from the database, while those above the
   203  	// pruning limit epoch are kept intact.
   204  	t.Run("prune_in_full_increments_and_verify_deletions", func(t *testing.T) {
   205  		hook := logTest.NewGlobal()
   206  		beaconDB := setupDB(t)
   207  
   208  		config := params.BeaconConfig()
   209  		copyConfig := config.Copy()
   210  		copyConfig.SlotsPerEpoch = 2
   211  		params.OverrideBeaconConfig(copyConfig)
   212  		defer params.OverrideBeaconConfig(config)
   213  
   214  		epochPruningIncrements := types.Epoch(2)
   215  		historyLength := types.Epoch(10)
   216  		currentEpoch := types.Epoch(20)
   217  		pruningLimitEpoch := currentEpoch - historyLength
   218  
   219  		// We create attestations from genesis to the current epoch, with 2 attestations
   220  		// at each slot to ensure the entire pruning logic works correctly.
   221  		slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
   222  		attestations := make([]*slashertypes.IndexedAttestationWrapper, 0, uint64(currentEpoch)*uint64(slotsPerEpoch)*2)
   223  		for i := types.Epoch(0); i < currentEpoch; i++ {
   224  			startSlot, err := helpers.StartSlot(i)
   225  			require.NoError(t, err)
   226  			endSlot, err := helpers.StartSlot(i + 1)
   227  			require.NoError(t, err)
   228  			for j := startSlot; j < endSlot; j++ {
   229  				attester1 := uint64(j + 10)
   230  				attester2 := uint64(j + 11)
   231  				target := i
   232  				var source types.Epoch
   233  				if i > 0 {
   234  					source = target - 1
   235  				}
   236  				att1 := createAttestationWrapper(source, target, []uint64{attester1}, []byte{0})
   237  				att2 := createAttestationWrapper(source, target, []uint64{attester2}, []byte{1})
   238  				attestations = append(attestations, att1, att2)
   239  			}
   240  		}
   241  
   242  		require.NoError(t, beaconDB.SaveAttestationRecordsForValidators(ctx, attestations))
   243  
   244  		// We expect pruning completes without an issue and properly logs progress.
   245  		err := beaconDB.PruneAttestations(ctx, currentEpoch, epochPruningIncrements, historyLength)
   246  		require.NoError(t, err)
   247  
   248  		for i := types.Epoch(0); i < pruningLimitEpoch; i++ {
   249  			wantedLog := fmt.Sprintf("Pruned %d/%d epochs", i, pruningLimitEpoch-1)
   250  			require.LogsContain(t, hook, wantedLog)
   251  		}
   252  
   253  		// Everything before epoch 10 should be deleted.
   254  		for i := types.Epoch(0); i < pruningLimitEpoch; i++ {
   255  			err = beaconDB.db.View(func(tx *bolt.Tx) error {
   256  				bkt := tx.Bucket(attestationDataRootsBucket)
   257  				startSlot, err := helpers.StartSlot(i)
   258  				require.NoError(t, err)
   259  				endSlot, err := helpers.StartSlot(i + 1)
   260  				require.NoError(t, err)
   261  				for j := startSlot; j < endSlot; j++ {
   262  					attester1 := types.ValidatorIndex(j + 10)
   263  					attester2 := types.ValidatorIndex(j + 11)
   264  					key1 := append(encodeTargetEpoch(i), encodeValidatorIndex(attester1)...)
   265  					key2 := append(encodeTargetEpoch(i), encodeValidatorIndex(attester2)...)
   266  					if bkt.Get(key1) != nil {
   267  						return fmt.Errorf("still exists for epoch %d, validator %d", i, attester1)
   268  					}
   269  					if bkt.Get(key2) != nil {
   270  						return fmt.Errorf("still exists for slot %d, validator %d", i, attester2)
   271  					}
   272  				}
   273  				return nil
   274  			})
   275  			require.NoError(t, err)
   276  		}
   277  	})
   278  }