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: ðpb.SignedBeaconBlockHeader{ 54 Header: ðpb.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 }