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 }