mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-26 05:17:22 +00:00
29513c804c
* bytesutil * gaz Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
579 lines
20 KiB
Go
579 lines
20 KiB
Go
package kv
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/monitoring/tracing"
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/slashings"
|
|
bolt "go.etcd.io/bbolt"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// SlashingKind used for helpful information upon detection.
|
|
type SlashingKind int
|
|
|
|
// AttestationRecord which can be represented by these simple values
|
|
// for manipulation by database methods.
|
|
type AttestationRecord struct {
|
|
PubKey [48]byte
|
|
Source types.Epoch
|
|
Target types.Epoch
|
|
SigningRoot [32]byte
|
|
}
|
|
|
|
// NewQueuedAttestationRecords constructor allocates the underlying slice and
|
|
// required attributes for managing pending attestation records.
|
|
func NewQueuedAttestationRecords() *QueuedAttestationRecords {
|
|
return &QueuedAttestationRecords{
|
|
records: make([]*AttestationRecord, 0, attestationBatchCapacity),
|
|
}
|
|
}
|
|
|
|
// QueuedAttestationRecords is a thread-safe struct for managing a queue of
|
|
// attestation records to save to validator database.
|
|
type QueuedAttestationRecords struct {
|
|
records []*AttestationRecord
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// Append a new attestation record to the queue.
|
|
func (p *QueuedAttestationRecords) Append(ar *AttestationRecord) {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
p.records = append(p.records, ar)
|
|
}
|
|
|
|
// Flush all records. This method returns the current pending records and resets
|
|
// the pending records slice.
|
|
func (p *QueuedAttestationRecords) Flush() []*AttestationRecord {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
recs := p.records
|
|
p.records = make([]*AttestationRecord, 0, attestationBatchCapacity)
|
|
return recs
|
|
}
|
|
|
|
// Len returns the current length of records.
|
|
func (p *QueuedAttestationRecords) Len() int {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
return len(p.records)
|
|
}
|
|
|
|
// A wrapper over an error received from a background routine
|
|
// saving batched attestations for slashing protection.
|
|
// This wrapper allows us to send this response over event feeds,
|
|
// as our event feed does not allow sending `nil` values to
|
|
// subscribers.
|
|
type saveAttestationsResponse struct {
|
|
err error
|
|
}
|
|
|
|
// Enums representing the types of slashable events for attesters.
|
|
const (
|
|
NotSlashable SlashingKind = iota
|
|
DoubleVote
|
|
SurroundingVote
|
|
SurroundedVote
|
|
)
|
|
|
|
var (
|
|
doubleVoteMessage = "double vote found, existing attestation at target epoch %d with conflicting signing root %#x"
|
|
surroundingVoteMessage = "attestation with (source %d, target %d) surrounds another with (source %d, target %d)"
|
|
surroundedVoteMessage = "attestation with (source %d, target %d) is surrounded by another with (source %d, target %d)"
|
|
)
|
|
|
|
// AttestationHistoryForPubKey retrieves a list of attestation records for data
|
|
// we have stored in the database for the given validator public key.
|
|
func (s *Store) AttestationHistoryForPubKey(ctx context.Context, pubKey [48]byte) ([]*AttestationRecord, error) {
|
|
records := make([]*AttestationRecord, 0)
|
|
ctx, span := trace.StartSpan(ctx, "Validator.AttestationHistoryForPubKey")
|
|
defer span.End()
|
|
err := s.view(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(pubKeysBucket)
|
|
pkBucket := bucket.Bucket(pubKey[:])
|
|
if pkBucket == nil {
|
|
return nil
|
|
}
|
|
signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket)
|
|
sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket)
|
|
|
|
return sourceEpochsBucket.ForEach(func(sourceBytes, targetEpochsList []byte) error {
|
|
targetEpochs := make([]types.Epoch, 0)
|
|
for i := 0; i < len(targetEpochsList); i += 8 {
|
|
epoch := bytesutil.BytesToEpochBigEndian(targetEpochsList[i : i+8])
|
|
targetEpochs = append(targetEpochs, epoch)
|
|
}
|
|
sourceEpoch := bytesutil.BytesToEpochBigEndian(sourceBytes)
|
|
for _, targetEpoch := range targetEpochs {
|
|
record := &AttestationRecord{
|
|
PubKey: pubKey,
|
|
Source: sourceEpoch,
|
|
Target: targetEpoch,
|
|
}
|
|
signingRoot := signingRootsBucket.Get(bytesutil.EpochToBytesBigEndian(targetEpoch))
|
|
if signingRoot != nil {
|
|
copy(record.SigningRoot[:], signingRoot)
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
return records, err
|
|
}
|
|
|
|
// CheckSlashableAttestation verifies an incoming attestation is
|
|
// not a double vote for a validator public key nor a surround vote.
|
|
func (s *Store) CheckSlashableAttestation(
|
|
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
|
) (SlashingKind, error) {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.CheckSlashableAttestation")
|
|
defer span.End()
|
|
var slashKind SlashingKind
|
|
err := s.view(func(tx *bolt.Tx) error {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
bucket := tx.Bucket(pubKeysBucket)
|
|
pkBucket := bucket.Bucket(pubKey[:])
|
|
if pkBucket == nil {
|
|
return nil
|
|
}
|
|
|
|
// First we check for double votes.
|
|
signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket)
|
|
if signingRootsBucket != nil {
|
|
targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.Data.Target.Epoch)
|
|
existingSigningRoot := signingRootsBucket.Get(targetEpochBytes)
|
|
if existingSigningRoot != nil {
|
|
var existing [32]byte
|
|
copy(existing[:], existingSigningRoot)
|
|
if slashings.SigningRootsDiffer(existing, signingRoot) {
|
|
slashKind = DoubleVote
|
|
return fmt.Errorf(doubleVoteMessage, att.Data.Target.Epoch, existingSigningRoot)
|
|
}
|
|
}
|
|
}
|
|
|
|
sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket)
|
|
targetEpochsBucket := pkBucket.Bucket(attestationTargetEpochsBucket)
|
|
if sourceEpochsBucket == nil {
|
|
return nil
|
|
}
|
|
|
|
// Is this attestation surrounding any other?
|
|
var err error
|
|
slashKind, err = s.checkSurroundingVote(sourceEpochsBucket, att)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if targetEpochsBucket == nil {
|
|
return nil
|
|
}
|
|
|
|
// Is this attestation surrounded by any other?
|
|
slashKind, err = s.checkSurroundedVote(targetEpochsBucket, att)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
tracing.AnnotateError(span, err)
|
|
return slashKind, err
|
|
}
|
|
|
|
// Iterate from the back of the bucket since we are looking for target_epoch > att.target_epoch
|
|
func (s *Store) checkSurroundedVote(
|
|
targetEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation,
|
|
) (SlashingKind, error) {
|
|
c := targetEpochsBucket.Cursor()
|
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
|
existingTargetEpoch := bytesutil.BytesToEpochBigEndian(k)
|
|
if existingTargetEpoch <= att.Data.Target.Epoch {
|
|
break
|
|
}
|
|
|
|
// There can be multiple source epochs attested per target epoch.
|
|
attestedSourceEpochs := make([]types.Epoch, 0, len(v)/8)
|
|
for i := 0; i < len(v); i += 8 {
|
|
sourceEpoch := bytesutil.BytesToEpochBigEndian(v[i : i+8])
|
|
attestedSourceEpochs = append(attestedSourceEpochs, sourceEpoch)
|
|
}
|
|
|
|
for _, existingSourceEpoch := range attestedSourceEpochs {
|
|
existingAtt := ðpb.IndexedAttestation{
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{Epoch: existingSourceEpoch},
|
|
Target: ðpb.Checkpoint{Epoch: existingTargetEpoch},
|
|
},
|
|
}
|
|
surrounded := slashings.IsSurround(existingAtt, att)
|
|
if surrounded {
|
|
return SurroundedVote, fmt.Errorf(
|
|
surroundedVoteMessage,
|
|
att.Data.Source.Epoch,
|
|
att.Data.Target.Epoch,
|
|
existingSourceEpoch,
|
|
existingTargetEpoch,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return NotSlashable, nil
|
|
}
|
|
|
|
// Iterate from the back of the bucket since we are looking for source_epoch > att.source_epoch
|
|
func (s *Store) checkSurroundingVote(
|
|
sourceEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation,
|
|
) (SlashingKind, error) {
|
|
c := sourceEpochsBucket.Cursor()
|
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
|
existingSourceEpoch := bytesutil.BytesToEpochBigEndian(k)
|
|
if existingSourceEpoch <= att.Data.Source.Epoch {
|
|
break
|
|
}
|
|
|
|
// There can be multiple target epochs attested per source epoch.
|
|
attestedTargetEpochs := make([]types.Epoch, 0, len(v)/8)
|
|
for i := 0; i < len(v); i += 8 {
|
|
targetEpoch := bytesutil.BytesToEpochBigEndian(v[i : i+8])
|
|
attestedTargetEpochs = append(attestedTargetEpochs, targetEpoch)
|
|
}
|
|
|
|
for _, existingTargetEpoch := range attestedTargetEpochs {
|
|
existingAtt := ðpb.IndexedAttestation{
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{Epoch: existingSourceEpoch},
|
|
Target: ðpb.Checkpoint{Epoch: existingTargetEpoch},
|
|
},
|
|
}
|
|
surrounding := slashings.IsSurround(att, existingAtt)
|
|
if surrounding {
|
|
return SurroundingVote, fmt.Errorf(
|
|
surroundingVoteMessage,
|
|
att.Data.Source.Epoch,
|
|
att.Data.Target.Epoch,
|
|
existingSourceEpoch,
|
|
existingTargetEpoch,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return NotSlashable, nil
|
|
}
|
|
|
|
// SaveAttestationsForPubKey stores a batch of attestations all at once.
|
|
func (s *Store) SaveAttestationsForPubKey(
|
|
ctx context.Context, pubKey [48]byte, signingRoots [][32]byte, atts []*ethpb.IndexedAttestation,
|
|
) error {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationsForPubKey")
|
|
defer span.End()
|
|
if len(signingRoots) != len(atts) {
|
|
return fmt.Errorf(
|
|
"number of signing roots %d does not match number of attestations %d",
|
|
len(signingRoots),
|
|
len(atts),
|
|
)
|
|
}
|
|
records := make([]*AttestationRecord, len(atts))
|
|
for i, a := range atts {
|
|
records[i] = &AttestationRecord{
|
|
PubKey: pubKey,
|
|
Source: a.Data.Source.Epoch,
|
|
Target: a.Data.Target.Epoch,
|
|
SigningRoot: signingRoots[i],
|
|
}
|
|
}
|
|
return s.saveAttestationRecords(ctx, records)
|
|
}
|
|
|
|
// SaveAttestationForPubKey saves an attestation for a validator public
|
|
// key for local validator slashing protection.
|
|
func (s *Store) SaveAttestationForPubKey(
|
|
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
|
) error {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationForPubKey")
|
|
defer span.End()
|
|
s.batchedAttestationsChan <- &AttestationRecord{
|
|
PubKey: pubKey,
|
|
Source: att.Data.Source.Epoch,
|
|
Target: att.Data.Target.Epoch,
|
|
SigningRoot: signingRoot,
|
|
}
|
|
// Subscribe to be notified when the attestation record queued
|
|
// for saving to the DB is indeed saved. If an error occurred
|
|
// during the process of saving the attestation record, the sender
|
|
// will give us that error. We use a buffered channel
|
|
// to prevent blocking the sender from notifying us of the result.
|
|
responseChan := make(chan saveAttestationsResponse, 1)
|
|
defer close(responseChan)
|
|
sub := s.batchAttestationsFlushedFeed.Subscribe(responseChan)
|
|
defer sub.Unsubscribe()
|
|
res := <-responseChan
|
|
return res.err
|
|
}
|
|
|
|
// Meant to run as a background routine, this function checks whether:
|
|
// (a) we have reached a max capacity of batched attestations in the Store or
|
|
// (b) attestationBatchWriteInterval has passed
|
|
// Based on whichever comes first, this function then proceeds
|
|
// to flush the attestations to the DB all at once in a single boltDB
|
|
// transaction for efficiency. Then, batched attestations slice is emptied out.
|
|
func (s *Store) batchAttestationWrites(ctx context.Context) {
|
|
ticker := time.NewTicker(attestationBatchWriteInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case v := <-s.batchedAttestationsChan:
|
|
s.batchedAttestations.Append(v)
|
|
if numRecords := s.batchedAttestations.Len(); numRecords >= attestationBatchCapacity {
|
|
log.WithField("numRecords", numRecords).Debug(
|
|
"Reached max capacity of batched attestation records, flushing to DB",
|
|
)
|
|
if s.batchedAttestationsFlushInProgress.IsNotSet() {
|
|
s.flushAttestationRecords(ctx, s.batchedAttestations.Flush())
|
|
}
|
|
}
|
|
case <-ticker.C:
|
|
if numRecords := s.batchedAttestations.Len(); numRecords > 0 {
|
|
log.WithField("numRecords", numRecords).Debug(
|
|
"Batched attestation records write interval reached, flushing to DB",
|
|
)
|
|
if s.batchedAttestationsFlushInProgress.IsNotSet() {
|
|
s.flushAttestationRecords(ctx, s.batchedAttestations.Flush())
|
|
}
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flushes a list of batched attestations to the database
|
|
// and resets the list of batched attestations for future writes.
|
|
// This function notifies all subscribers for flushed attestations
|
|
// of the result of the save operation.
|
|
func (s *Store) flushAttestationRecords(ctx context.Context, records []*AttestationRecord) {
|
|
if s.batchedAttestationsFlushInProgress.IsSet() {
|
|
// This should never happen. This method should not be called when a flush is already in
|
|
// progress. If you are seeing this log, check the atomic bool before calling this method.
|
|
log.Error("Attempted to flush attestation records when already in progress")
|
|
return
|
|
}
|
|
s.batchedAttestationsFlushInProgress.Set()
|
|
defer s.batchedAttestationsFlushInProgress.UnSet()
|
|
|
|
start := time.Now()
|
|
err := s.saveAttestationRecords(ctx, records)
|
|
// If there was any error, retry the records since the TX would have been reverted.
|
|
if err == nil {
|
|
log.WithField("duration", time.Since(start)).Debug("Successfully flushed batched attestations to DB")
|
|
} else {
|
|
// This should never happen.
|
|
log.WithError(err).Error("Failed to batch save attestation records, retrying in queue")
|
|
for _, ar := range records {
|
|
s.batchedAttestations.Append(ar)
|
|
}
|
|
}
|
|
// Forward the error, if any, to all subscribers via an event feed.
|
|
// We use a struct wrapper around the error as the event feed
|
|
// cannot handle sending a raw `nil` in case there is no error.
|
|
s.batchAttestationsFlushedFeed.Send(saveAttestationsResponse{
|
|
err: err,
|
|
})
|
|
}
|
|
|
|
// Saves a list of attestation records to the database in a single boltDB
|
|
// transaction to minimize write lock contention compared to doing them
|
|
// all in individual, isolated boltDB transactions.
|
|
func (s *Store) saveAttestationRecords(ctx context.Context, atts []*AttestationRecord) error {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.saveAttestationRecords")
|
|
defer span.End()
|
|
return s.update(func(tx *bolt.Tx) error {
|
|
// Initialize buckets for the lowest target and source epochs.
|
|
lowestSourceBucket, err := tx.CreateBucketIfNotExists(lowestSignedSourceBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lowestTargetBucket, err := tx.CreateBucketIfNotExists(lowestSignedTargetBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bucket := tx.Bucket(pubKeysBucket)
|
|
for _, att := range atts {
|
|
pkBucket, err := bucket.CreateBucketIfNotExists(att.PubKey[:])
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not create public key bucket")
|
|
}
|
|
sourceEpochBytes := bytesutil.EpochToBytesBigEndian(att.Source)
|
|
targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.Target)
|
|
|
|
signingRootsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSigningRootsBucket)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not create signing roots bucket")
|
|
}
|
|
if err := signingRootsBucket.Put(targetEpochBytes, att.SigningRoot[:]); err != nil {
|
|
return errors.Wrapf(err, "could not save signing signing root for epoch %d", att.Target)
|
|
}
|
|
sourceEpochsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSourceEpochsBucket)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not create source epochs bucket")
|
|
}
|
|
|
|
// There can be multiple attested target epochs per source epoch.
|
|
// If a previous list exists, we append to that list with the incoming target epoch.
|
|
// Otherwise, we initialize it using the incoming target epoch.
|
|
var existingAttestedTargetsBytes []byte
|
|
if existing := sourceEpochsBucket.Get(sourceEpochBytes); existing != nil {
|
|
existingAttestedTargetsBytes = append(existing, targetEpochBytes...)
|
|
} else {
|
|
existingAttestedTargetsBytes = targetEpochBytes
|
|
}
|
|
|
|
if err := sourceEpochsBucket.Put(sourceEpochBytes, existingAttestedTargetsBytes); err != nil {
|
|
return errors.Wrapf(err, "could not save source epoch %d for epoch %d", att.Source, att.Target)
|
|
}
|
|
|
|
targetEpochsBucket, err := pkBucket.CreateBucketIfNotExists(attestationTargetEpochsBucket)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not create target epochs bucket")
|
|
}
|
|
var existingAttestedSourceBytes []byte
|
|
if existing := targetEpochsBucket.Get(targetEpochBytes); existing != nil {
|
|
existingAttestedSourceBytes = append(existing, sourceEpochBytes...)
|
|
} else {
|
|
existingAttestedSourceBytes = sourceEpochBytes
|
|
}
|
|
|
|
if err := targetEpochsBucket.Put(targetEpochBytes, existingAttestedSourceBytes); err != nil {
|
|
return errors.Wrapf(err, "could not save target epoch %d for epoch %d", att.Target, att.Source)
|
|
}
|
|
|
|
// If the incoming source epoch is lower than the lowest signed source epoch, override.
|
|
lowestSignedSourceBytes := lowestSourceBucket.Get(att.PubKey[:])
|
|
var lowestSignedSourceEpoch types.Epoch
|
|
if len(lowestSignedSourceBytes) >= 8 {
|
|
lowestSignedSourceEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedSourceBytes)
|
|
}
|
|
if len(lowestSignedSourceBytes) == 0 || att.Source < lowestSignedSourceEpoch {
|
|
if err := lowestSourceBucket.Put(
|
|
att.PubKey[:], bytesutil.EpochToBytesBigEndian(att.Source),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If the incoming target epoch is lower than the lowest signed target epoch, override.
|
|
lowestSignedTargetBytes := lowestTargetBucket.Get(att.PubKey[:])
|
|
var lowestSignedTargetEpoch types.Epoch
|
|
if len(lowestSignedTargetBytes) >= 8 {
|
|
lowestSignedTargetEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedTargetBytes)
|
|
}
|
|
if len(lowestSignedTargetBytes) == 0 || att.Target < lowestSignedTargetEpoch {
|
|
if err := lowestTargetBucket.Put(
|
|
att.PubKey[:], bytesutil.EpochToBytesBigEndian(att.Target),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// AttestedPublicKeys retrieves all public keys that have attested.
|
|
func (s *Store) AttestedPublicKeys(ctx context.Context) ([][48]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.AttestedPublicKeys")
|
|
defer span.End()
|
|
var err error
|
|
attestedPublicKeys := make([][48]byte, 0)
|
|
err = s.view(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(pubKeysBucket)
|
|
return bucket.ForEach(func(pubKey []byte, _ []byte) error {
|
|
var pk [48]byte
|
|
copy(pk[:], pubKey)
|
|
attestedPublicKeys = append(attestedPublicKeys, pk)
|
|
return nil
|
|
})
|
|
})
|
|
return attestedPublicKeys, err
|
|
}
|
|
|
|
// SigningRootAtTargetEpoch checks for an existing signing root at a specified
|
|
// target epoch for a given validator public key.
|
|
func (s *Store) SigningRootAtTargetEpoch(ctx context.Context, pubKey [48]byte, target types.Epoch) ([32]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.SigningRootAtTargetEpoch")
|
|
defer span.End()
|
|
var signingRoot [32]byte
|
|
err := s.view(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(pubKeysBucket)
|
|
pkBucket := bucket.Bucket(pubKey[:])
|
|
if pkBucket == nil {
|
|
return nil
|
|
}
|
|
signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket)
|
|
if signingRootsBucket == nil {
|
|
return nil
|
|
}
|
|
sr := signingRootsBucket.Get(bytesutil.EpochToBytesBigEndian(target))
|
|
copy(signingRoot[:], sr)
|
|
return nil
|
|
})
|
|
return signingRoot, err
|
|
}
|
|
|
|
// LowestSignedSourceEpoch returns the lowest signed source epoch for a validator public key.
|
|
// If no data exists, returning 0 is a sensible default.
|
|
func (s *Store) LowestSignedSourceEpoch(ctx context.Context, publicKey [48]byte) (types.Epoch, bool, error) {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedSourceEpoch")
|
|
defer span.End()
|
|
|
|
var err error
|
|
var lowestSignedSourceEpoch types.Epoch
|
|
var exists bool
|
|
err = s.view(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(lowestSignedSourceBucket)
|
|
lowestSignedSourceBytes := bucket.Get(publicKey[:])
|
|
// 8 because bytesutil.BytesToEpochBigEndian will return 0 if input is less than 8 bytes.
|
|
if len(lowestSignedSourceBytes) < 8 {
|
|
return nil
|
|
}
|
|
exists = true
|
|
lowestSignedSourceEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedSourceBytes)
|
|
return nil
|
|
})
|
|
return lowestSignedSourceEpoch, exists, err
|
|
}
|
|
|
|
// LowestSignedTargetEpoch returns the lowest signed target epoch for a validator public key.
|
|
// If no data exists, returning 0 is a sensible default.
|
|
func (s *Store) LowestSignedTargetEpoch(ctx context.Context, publicKey [48]byte) (types.Epoch, bool, error) {
|
|
ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedTargetEpoch")
|
|
defer span.End()
|
|
|
|
var err error
|
|
var lowestSignedTargetEpoch types.Epoch
|
|
var exists bool
|
|
err = s.view(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(lowestSignedTargetBucket)
|
|
lowestSignedTargetBytes := bucket.Get(publicKey[:])
|
|
// 8 because bytesutil.BytesToEpochBigEndian will return 0 if input is less than 8 bytes.
|
|
if len(lowestSignedTargetBytes) < 8 {
|
|
return nil
|
|
}
|
|
exists = true
|
|
lowestSignedTargetEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedTargetBytes)
|
|
return nil
|
|
})
|
|
return lowestSignedTargetEpoch, exists, err
|
|
}
|