github.com/prysmaticlabs/prysm@v1.4.4/validator/db/kv/db.go (about)

     1  // Package kv defines a persistent backend for the validator service.
     2  package kv
     3  
     4  import (
     5  	"context"
     6  	"os"
     7  	"path/filepath"
     8  	"time"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	prombolt "github.com/prysmaticlabs/prombbolt"
    13  	"github.com/prysmaticlabs/prysm/shared/abool"
    14  	"github.com/prysmaticlabs/prysm/shared/event"
    15  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    16  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    17  	"github.com/prysmaticlabs/prysm/shared/params"
    18  	bolt "go.etcd.io/bbolt"
    19  )
    20  
    21  const (
    22  	// Number of attestation records we can hold in memory
    23  	// before we flush them to the database. Roughly corresponds
    24  	// to the max number of keys per validator client, but there is no
    25  	// detriment if there are more keys than this capacity, as attestations
    26  	// for those keys will simply be flushed at the next flush interval.
    27  	attestationBatchCapacity = 2048
    28  	// Time interval after which we flush attestation records to the database
    29  	// from a batch kept in memory for slashing protection.
    30  	attestationBatchWriteInterval = time.Millisecond * 100
    31  )
    32  
    33  // ProtectionDbFileName Validator slashing protection db file name.
    34  var (
    35  	ProtectionDbFileName = "validator.db"
    36  )
    37  
    38  // blockedBuckets represents the buckets that we want to restrict
    39  // from our metrics fetching for performance reasons. For a detailed
    40  // summary, it can be read in https://github.com/prysmaticlabs/prysm/issues/8274.
    41  var blockedBuckets = [][]byte{
    42  	deprecatedAttestationHistoryBucket,
    43  	lowestSignedSourceBucket,
    44  	lowestSignedTargetBucket,
    45  	lowestSignedProposalsBucket,
    46  	highestSignedProposalsBucket,
    47  	pubKeysBucket,
    48  	attestationSigningRootsBucket,
    49  	attestationSourceEpochsBucket,
    50  	attestationTargetEpochsBucket,
    51  }
    52  
    53  // Config represents store's config object.
    54  type Config struct {
    55  	PubKeys         [][48]byte
    56  	InitialMMapSize int
    57  }
    58  
    59  // Store defines an implementation of the Prysm Database interface
    60  // using BoltDB as the underlying persistent kv-store for Ethereum consensus nodes.
    61  type Store struct {
    62  	db                                 *bolt.DB
    63  	databasePath                       string
    64  	batchedAttestations                *QueuedAttestationRecords
    65  	batchedAttestationsChan            chan *AttestationRecord
    66  	batchAttestationsFlushedFeed       *event.Feed
    67  	batchedAttestationsFlushInProgress abool.AtomicBool
    68  }
    69  
    70  // Close closes the underlying boltdb database.
    71  func (s *Store) Close() error {
    72  	prometheus.Unregister(createBoltCollector(s.db))
    73  	return s.db.Close()
    74  }
    75  
    76  func (s *Store) update(fn func(*bolt.Tx) error) error {
    77  	return s.db.Update(fn)
    78  }
    79  func (s *Store) view(fn func(*bolt.Tx) error) error {
    80  	return s.db.View(fn)
    81  }
    82  
    83  // ClearDB removes any previously stored data at the configured data directory.
    84  func (s *Store) ClearDB() error {
    85  	if _, err := os.Stat(s.databasePath); os.IsNotExist(err) {
    86  		return nil
    87  	}
    88  	prometheus.Unregister(createBoltCollector(s.db))
    89  	return os.Remove(filepath.Join(s.databasePath, ProtectionDbFileName))
    90  }
    91  
    92  // DatabasePath at which this database writes files.
    93  func (s *Store) DatabasePath() string {
    94  	return s.databasePath
    95  }
    96  
    97  func createBuckets(tx *bolt.Tx, buckets ...[]byte) error {
    98  	for _, bucket := range buckets {
    99  		if _, err := tx.CreateBucketIfNotExists(bucket); err != nil {
   100  			return err
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  // NewKVStore initializes a new boltDB key-value store at the directory
   107  // path specified, creates the kv-buckets based on the schema, and stores
   108  // an open connection db object as a property of the Store struct.
   109  func NewKVStore(ctx context.Context, dirPath string, config *Config) (*Store, error) {
   110  	hasDir, err := fileutil.HasDir(dirPath)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	if !hasDir {
   115  		if err := fileutil.MkdirAll(dirPath); err != nil {
   116  			return nil, err
   117  		}
   118  	}
   119  	datafile := filepath.Join(dirPath, ProtectionDbFileName)
   120  	boltDB, err := bolt.Open(datafile, params.BeaconIoConfig().ReadWritePermissions, &bolt.Options{
   121  		Timeout:         params.BeaconIoConfig().BoltTimeout,
   122  		InitialMmapSize: config.InitialMMapSize,
   123  	})
   124  	if err != nil {
   125  		if errors.Is(err, bolt.ErrTimeout) {
   126  			return nil, errors.New("cannot obtain database lock, database may be in use by another process")
   127  		}
   128  		return nil, err
   129  	}
   130  
   131  	kv := &Store{
   132  		db:                           boltDB,
   133  		databasePath:                 dirPath,
   134  		batchedAttestations:          NewQueuedAttestationRecords(),
   135  		batchedAttestationsChan:      make(chan *AttestationRecord, attestationBatchCapacity),
   136  		batchAttestationsFlushedFeed: new(event.Feed),
   137  	}
   138  
   139  	if err := kv.db.Update(func(tx *bolt.Tx) error {
   140  		return createBuckets(
   141  			tx,
   142  			genesisInfoBucket,
   143  			deprecatedAttestationHistoryBucket,
   144  			historicProposalsBucket,
   145  			lowestSignedSourceBucket,
   146  			lowestSignedTargetBucket,
   147  			lowestSignedProposalsBucket,
   148  			highestSignedProposalsBucket,
   149  			slashablePublicKeysBucket,
   150  			pubKeysBucket,
   151  			migrationsBucket,
   152  			graffitiBucket,
   153  		)
   154  	}); err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	// Initialize the required public keys into the DB to ensure they're not empty.
   159  	if config != nil {
   160  		if err := kv.UpdatePublicKeysBuckets(config.PubKeys); err != nil {
   161  			return nil, err
   162  		}
   163  	}
   164  
   165  	if featureconfig.Get().EnableSlashingProtectionPruning {
   166  		// Prune attesting records older than the current weak subjectivity period.
   167  		if err := kv.PruneAttestations(ctx); err != nil {
   168  			return nil, errors.Wrap(err, "could not prune old attestations from DB")
   169  		}
   170  	}
   171  
   172  	// Batch save attestation records for slashing protection at timed
   173  	// intervals to our database.
   174  	go kv.batchAttestationWrites(ctx)
   175  
   176  	return kv, prometheus.Register(createBoltCollector(kv.db))
   177  }
   178  
   179  // UpdatePublicKeysBuckets for a specified list of keys.
   180  func (s *Store) UpdatePublicKeysBuckets(pubKeys [][48]byte) error {
   181  	return s.update(func(tx *bolt.Tx) error {
   182  		bucket := tx.Bucket(historicProposalsBucket)
   183  		for _, pubKey := range pubKeys {
   184  			if _, err := bucket.CreateBucketIfNotExists(pubKey[:]); err != nil {
   185  				return errors.Wrap(err, "failed to create proposal history bucket")
   186  			}
   187  		}
   188  		return nil
   189  	})
   190  }
   191  
   192  // Size returns the db size in bytes.
   193  func (s *Store) Size() (int64, error) {
   194  	var size int64
   195  	err := s.db.View(func(tx *bolt.Tx) error {
   196  		size = tx.Size()
   197  		return nil
   198  	})
   199  	return size, err
   200  }
   201  
   202  // createBoltCollector returns a prometheus collector specifically configured for boltdb.
   203  func createBoltCollector(db *bolt.DB) prometheus.Collector {
   204  	return prombolt.New("boltDB", db, blockedBuckets...)
   205  }