2021-04-23 17:06:13 +00:00
|
|
|
package slasherkv
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/binary"
|
|
|
|
|
2022-06-28 13:03:24 +00:00
|
|
|
fssz "github.com/prysmaticlabs/fastssz"
|
2022-04-29 14:32:11 +00:00
|
|
|
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
|
2021-10-01 20:17:57 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/time/slots"
|
2021-04-23 17:06:13 +00:00
|
|
|
bolt "go.etcd.io/bbolt"
|
|
|
|
)
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// PruneAttestationsAtEpoch deletes all attestations from the slasher DB with target epoch
|
|
|
|
// less than or equal to the specified epoch.
|
|
|
|
func (s *Store) PruneAttestationsAtEpoch(
|
2021-12-01 18:56:07 +00:00
|
|
|
_ context.Context, maxEpoch types.Epoch,
|
2021-08-05 21:27:05 +00:00
|
|
|
) (numPruned uint, err error) {
|
2021-04-23 17:06:13 +00:00
|
|
|
// We can prune everything less than the current epoch - history length.
|
2021-08-05 21:27:05 +00:00
|
|
|
encodedEndPruneEpoch := fssz.MarshalUint64([]byte{}, uint64(maxEpoch))
|
2021-04-23 17:06:13 +00:00
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// We retrieve the lowest stored epoch in the attestations bucket.
|
|
|
|
var lowestEpoch types.Epoch
|
|
|
|
var hasData bool
|
2021-04-23 17:06:13 +00:00
|
|
|
if err = s.db.View(func(tx *bolt.Tx) error {
|
2021-08-05 21:27:05 +00:00
|
|
|
bkt := tx.Bucket(attestationDataRootsBucket)
|
|
|
|
c := bkt.Cursor()
|
2021-04-23 17:06:13 +00:00
|
|
|
k, _ := c.First()
|
2021-08-05 21:27:05 +00:00
|
|
|
if k == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
hasData = true
|
|
|
|
lowestEpoch = types.Epoch(binary.LittleEndian.Uint64(k))
|
2021-04-23 17:06:13 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
2021-08-05 21:27:05 +00:00
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// If there is no data stored, just exit early.
|
|
|
|
if !hasData {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the lowest epoch is greater than the end pruning epoch,
|
2021-04-23 17:06:13 +00:00
|
|
|
// there is nothing to prune, so we return early.
|
2021-08-05 21:27:05 +00:00
|
|
|
if lowestEpoch > maxEpoch {
|
|
|
|
log.Debugf("Lowest epoch %d is > pruning epoch %d, nothing to prune", lowestEpoch, maxEpoch)
|
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
if err = s.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
|
|
|
|
attRecordsBkt := tx.Bucket(attestationRecordsBucket)
|
|
|
|
c := signingRootsBkt.Cursor()
|
|
|
|
|
|
|
|
// We begin a pruning iteration starting from the first item in the bucket.
|
|
|
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
|
|
// We check the epoch from the current key in the database.
|
|
|
|
// If we have hit an epoch that is greater than the end epoch of the pruning process,
|
|
|
|
// we then completely exit the process as we are done.
|
|
|
|
if uint64PrefixGreaterThan(k, encodedEndPruneEpoch) {
|
|
|
|
return nil
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
|
|
|
|
// Attestation in the database look like this:
|
|
|
|
// (target_epoch ++ _) => encode(attestation)
|
|
|
|
// so it is possible we have a few adjacent objects that have the same slot, such as
|
|
|
|
// (target_epoch = 3 ++ _) => encode(attestation)
|
|
|
|
if err := signingRootsBkt.Delete(k); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := attRecordsBkt.Delete(v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
slasherAttestationsPrunedTotal.Inc()
|
|
|
|
numPruned++
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// PruneProposalsAtEpoch deletes all proposals from the slasher DB with epoch
|
|
|
|
// less than or equal to the specified epoch.
|
|
|
|
func (s *Store) PruneProposalsAtEpoch(
|
2021-12-06 21:45:38 +00:00
|
|
|
ctx context.Context, maxEpoch types.Epoch,
|
2021-08-05 21:27:05 +00:00
|
|
|
) (numPruned uint, err error) {
|
|
|
|
var endPruneSlot types.Slot
|
2021-10-01 20:17:57 +00:00
|
|
|
endPruneSlot, err = slots.EpochEnd(maxEpoch)
|
2021-08-05 21:27:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
encodedEndPruneSlot := fssz.MarshalUint64([]byte{}, uint64(endPruneSlot))
|
2021-04-23 17:06:13 +00:00
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// We retrieve the lowest stored slot in the proposals bucket.
|
|
|
|
var lowestSlot types.Slot
|
|
|
|
var hasData bool
|
|
|
|
if err = s.db.View(func(tx *bolt.Tx) error {
|
|
|
|
proposalBkt := tx.Bucket(proposalRecordsBucket)
|
|
|
|
c := proposalBkt.Cursor()
|
2021-04-23 17:06:13 +00:00
|
|
|
k, _ := c.First()
|
2021-08-05 21:27:05 +00:00
|
|
|
if k == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
hasData = true
|
|
|
|
lowestSlot = slotFromProposalKey(k)
|
2021-04-23 17:06:13 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
2021-08-05 21:27:05 +00:00
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
// If there is no data stored, just exit early.
|
|
|
|
if !hasData {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the lowest slot is greater than the end pruning slot,
|
2021-04-23 17:06:13 +00:00
|
|
|
// there is nothing to prune, so we return early.
|
2021-08-05 21:27:05 +00:00
|
|
|
if lowestSlot > endPruneSlot {
|
|
|
|
log.Debugf("Lowest slot %d is > pruning slot %d, nothing to prune", lowestSlot, endPruneSlot)
|
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
if err = s.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
proposalBkt := tx.Bucket(proposalRecordsBucket)
|
|
|
|
c := proposalBkt.Cursor()
|
|
|
|
// We begin a pruning iteration starting from the first item in the bucket.
|
|
|
|
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
2021-12-06 21:45:38 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
// We check the slot from the current key in the database.
|
|
|
|
// If we have hit a slot that is greater than the end slot of the pruning process,
|
|
|
|
// we then completely exit the process as we are done.
|
|
|
|
if uint64PrefixGreaterThan(k, encodedEndPruneSlot) {
|
|
|
|
return nil
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
// Proposals in the database look like this:
|
|
|
|
// (slot ++ validatorIndex) => encode(proposal)
|
|
|
|
// so it is possible we have a few adjacent objects that have the same slot, such as
|
|
|
|
// (slot = 3 ++ validatorIndex = 0) => ...
|
|
|
|
// (slot = 3 ++ validatorIndex = 1) => ...
|
|
|
|
// (slot = 3 ++ validatorIndex = 2) => ...
|
|
|
|
if err := proposalBkt.Delete(k); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
slasherProposalsPrunedTotal.Inc()
|
|
|
|
numPruned++
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
2021-08-05 21:27:05 +00:00
|
|
|
return
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func slotFromProposalKey(key []byte) types.Slot {
|
|
|
|
return types.Slot(binary.LittleEndian.Uint64(key[:8]))
|
|
|
|
}
|
|
|
|
|
2021-08-05 21:27:05 +00:00
|
|
|
func uint64PrefixGreaterThan(key, lessThan []byte) bool {
|
2021-04-23 17:06:13 +00:00
|
|
|
enc := key[:8]
|
2021-08-05 21:27:05 +00:00
|
|
|
return bytes.Compare(enc, lessThan) > 0
|
2021-04-23 17:06:13 +00:00
|
|
|
}
|