github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/slasherkv/pruning.go (about) 1 package slasherkv 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 8 fssz "github.com/ferranbt/fastssz" 9 types "github.com/prysmaticlabs/eth2-types" 10 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 11 bolt "go.etcd.io/bbolt" 12 ) 13 14 // PruneProposals prunes all proposal data older than currentEpoch - historyLength in 15 // specified epoch increments. BoltDB cannot handle long-running transactions, so we instead 16 // use a cursor-based mechanism to prune at pruningEpochIncrements by opening individual bolt 17 // transactions for each pruning iteration. 18 func (s *Store) PruneProposals( 19 ctx context.Context, currentEpoch, pruningEpochIncrements, historyLength types.Epoch, 20 ) error { 21 if currentEpoch < historyLength { 22 log.Debugf("Current epoch %d < history length %d, nothing to prune", currentEpoch, historyLength) 23 return nil 24 } 25 // We can prune everything less than the current epoch - history length. 26 endEpoch := currentEpoch - historyLength 27 endPruneSlot, err := helpers.StartSlot(endEpoch) 28 if err != nil { 29 return err 30 } 31 encodedEndPruneSlot := fssz.MarshalUint64([]byte{}, uint64(endPruneSlot)) 32 33 // We retrieve the lowest stored slot in the proposals bucket. 34 var lowestSlot types.Slot 35 if err = s.db.View(func(tx *bolt.Tx) error { 36 proposalBkt := tx.Bucket(proposalRecordsBucket) 37 c := proposalBkt.Cursor() 38 k, _ := c.First() 39 lowestSlot = slotFromProposalKey(k) 40 return nil 41 }); err != nil { 42 return err 43 } 44 45 // If the lowest slot is greater than or equal to the end pruning slot, 46 // there is nothing to prune, so we return early. 47 if lowestSlot >= endPruneSlot { 48 log.Debugf("Lowest slot %d is >= pruning slot %d, nothing to prune", lowestSlot, endPruneSlot) 49 return nil 50 } 51 52 // We prune in increments of `pruningEpochIncrements` at a time to prevent 53 // a long-running bolt transaction which overwhelms CPU and memory. 54 // While we still have epochs to prune based on a cursor, we continue the pruning process. 55 epochAtCursor := helpers.SlotToEpoch(lowestSlot) 56 for epochAtCursor < endEpoch { 57 // Each pruning iteration involves a unique bolt transaction. Given pruning can be 58 // a very expensive process which puts pressure on the database, we perform 59 // the process in a batch-based method using a cursor to proceed to the next batch. 60 log.Debugf("Pruned %d/%d epochs worth of proposals", epochAtCursor, endEpoch-1) 61 if err = s.db.Update(func(tx *bolt.Tx) error { 62 proposalBkt := tx.Bucket(proposalRecordsBucket) 63 c := proposalBkt.Cursor() 64 65 var lastPrunedEpoch, epochsPruned types.Epoch 66 // We begin a pruning iteration at starting from the first item in the bucket. 67 for k, _ := c.First(); k != nil; k, _ = c.Next() { 68 // We check the slot from the current key in the database. 69 // If we have hit a slot that is greater than the end slot of the pruning process, 70 // we then completely exit the process as we are done. 71 if !uint64PrefixLessThan(k, encodedEndPruneSlot) { 72 epochAtCursor = endEpoch 73 return nil 74 } 75 epochAtCursor = helpers.SlotToEpoch(slotFromProposalKey(k)) 76 77 // Proposals in the database look like this: 78 // (slot ++ validatorIndex) => encode(proposal) 79 // so it is possible we have a few adjacent objects that have the same slot, such as 80 // (slot = 3 ++ validatorIndex = 0) => ... 81 // (slot = 3 ++ validatorIndex = 1) => ... 82 // (slot = 3 ++ validatorIndex = 2) => ... 83 // so we only mark an epoch as pruned if the epoch of the current object 84 // under the cursor has changed. 85 if err := proposalBkt.Delete(k); err != nil { 86 return err 87 } 88 if epochAtCursor == 0 { 89 epochsPruned = 1 90 } else if epochAtCursor > lastPrunedEpoch { 91 epochsPruned++ 92 lastPrunedEpoch = epochAtCursor 93 } 94 slasherProposalsPrunedTotal.Inc() 95 96 // If we have pruned N epochs in this pruning iteration, 97 // we exit from the bolt transaction. 98 if epochsPruned >= pruningEpochIncrements { 99 return nil 100 } 101 } 102 return nil 103 }); err != nil { 104 return err 105 } 106 } 107 return nil 108 } 109 110 // PruneAttestations prunes all proposal data older than historyLength. 111 func (s *Store) PruneAttestations( 112 ctx context.Context, currentEpoch, pruningEpochIncrements, historyLength types.Epoch, 113 ) error { 114 if currentEpoch < historyLength { 115 log.Debugf("Current epoch %d < history length %d, nothing to prune", currentEpoch, historyLength) 116 return nil 117 } 118 // We can prune everything less than the current epoch - history length. 119 endPruneEpoch := currentEpoch - historyLength 120 encodedEndPruneEpoch := fssz.MarshalUint64([]byte{}, uint64(endPruneEpoch)) 121 122 // We retrieve the lowest stored epoch in the proposals bucket. 123 var lowestEpoch types.Epoch 124 if err := s.db.View(func(tx *bolt.Tx) error { 125 bkt := tx.Bucket(attestationDataRootsBucket) 126 c := bkt.Cursor() 127 k, _ := c.First() 128 lowestEpoch = types.Epoch(binary.LittleEndian.Uint64(k)) 129 return nil 130 }); err != nil { 131 return err 132 } 133 134 // If the lowest slot is greater than or equal to the end pruning slot, 135 // there is nothing to prune, so we return early. 136 if lowestEpoch >= endPruneEpoch { 137 log.Debugf("Lowest epoch %d is >= pruning epoch %d, nothing to prune", lowestEpoch, endPruneEpoch) 138 return nil 139 } 140 141 // We prune in increments of `pruningEpochIncrements` at a time to prevent 142 // a long-running bolt transaction which overwhelms CPU and memory. 143 // While we still have epochs to prune based on a cursor, we continue the pruning process. 144 epochAtCursor := lowestEpoch 145 for epochAtCursor < endPruneEpoch { 146 // Each pruning iteration involves a unique bolt transaction. Given pruning can be 147 // a very expensive process which puts pressure on the database, we perform 148 // the process in a batch-based method using a cursor to proceed to the next batch. 149 log.Debugf("Pruned %d/%d epochs worth of attestations", epochAtCursor, endPruneEpoch-1) 150 if err := s.db.Update(func(tx *bolt.Tx) error { 151 rootsBkt := tx.Bucket(attestationDataRootsBucket) 152 attsBkt := tx.Bucket(attestationRecordsBucket) 153 c := rootsBkt.Cursor() 154 155 var lastPrunedEpoch, epochsPruned types.Epoch 156 // We begin a pruning iteration at starting from the first item in the bucket. 157 for k, v := c.First(); k != nil; k, v = c.Next() { 158 // We check the epoch from the current key in the database. 159 // If we have hit an epoch that is greater than the end epoch of the pruning process, 160 // we then completely exit the process as we are done. 161 if !uint64PrefixLessThan(k, encodedEndPruneEpoch) { 162 epochAtCursor = endPruneEpoch 163 return nil 164 } 165 166 epochAtCursor = types.Epoch(binary.LittleEndian.Uint64(k)) 167 168 // Attestation in the database look like this: 169 // (target_epoch ++ _) => encode(attestation) 170 // so it is possible we have a few adjacent objects that have the same slot, such as 171 // (target_epoch = 3 ++ _) => encode(attestation) 172 // so we only mark an epoch as pruned if the epoch of the current object 173 // under the cursor has changed. 174 if err := rootsBkt.Delete(k); err != nil { 175 return err 176 } 177 if err := attsBkt.Delete(v); err != nil { 178 return err 179 } 180 if epochAtCursor == 0 { 181 epochsPruned = 1 182 } else if epochAtCursor > lastPrunedEpoch { 183 epochsPruned++ 184 lastPrunedEpoch = epochAtCursor 185 } 186 slasherAttestationsPrunedTotal.Inc() 187 188 // If we have pruned N epochs in this pruning iteration, 189 // we exit from the bolt transaction. 190 if epochsPruned >= pruningEpochIncrements { 191 return nil 192 } 193 } 194 return nil 195 }); err != nil { 196 return err 197 } 198 } 199 return nil 200 } 201 202 func slotFromProposalKey(key []byte) types.Slot { 203 return types.Slot(binary.LittleEndian.Uint64(key[:8])) 204 } 205 206 func uint64PrefixLessThan(key, lessThan []byte) bool { 207 enc := key[:8] 208 return bytes.Compare(enc, lessThan) < 0 209 }