github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/kv/kv.go (about)

     1  // Package kv defines a bolt-db, key-value store implementation
     2  // of the Database interface defined by a Prysm beacon node.
     3  package kv
     4  
     5  import (
     6  	"context"
     7  	"os"
     8  	"path"
     9  	"time"
    10  
    11  	"github.com/dgraph-io/ristretto"
    12  	"github.com/pkg/errors"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	prombolt "github.com/prysmaticlabs/prombbolt"
    15  	"github.com/prysmaticlabs/prysm/beacon-chain/db/iface"
    16  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    17  	"github.com/prysmaticlabs/prysm/shared/params"
    18  	bolt "go.etcd.io/bbolt"
    19  )
    20  
    21  var _ iface.Database = (*Store)(nil)
    22  
    23  const (
    24  	// VotesCacheSize with 1M validators will be 8MB.
    25  	VotesCacheSize = 1 << 23
    26  	// NumOfVotes specifies the vote cache size.
    27  	NumOfVotes = 1 << 20
    28  	// BeaconNodeDbDirName is the name of the directory containing the beacon node database.
    29  	BeaconNodeDbDirName = "beaconchaindata"
    30  	// DatabaseFileName is the name of the beacon node database.
    31  	DatabaseFileName = "beaconchain.db"
    32  
    33  	boltAllocSize = 8 * 1024 * 1024
    34  )
    35  
    36  // BlockCacheSize specifies 1000 slots worth of blocks cached, which
    37  // would be approximately 2MB
    38  var BlockCacheSize = int64(1 << 21)
    39  
    40  // blockedBuckets represents the buckets that we want to restrict
    41  // from our metrics fetching for performance reasons. For a detailed
    42  // summary, it can be read in https://github.com/prysmaticlabs/prysm/issues/8274.
    43  var blockedBuckets = [][]byte{
    44  	blocksBucket,
    45  	stateSummaryBucket,
    46  	blockParentRootIndicesBucket,
    47  	blockSlotIndicesBucket,
    48  	finalizedBlockRootsIndexBucket,
    49  }
    50  
    51  // Config for the bolt db kv store.
    52  type Config struct {
    53  	InitialMMapSize int
    54  }
    55  
    56  // Store defines an implementation of the Prysm Database interface
    57  // using BoltDB as the underlying persistent kv-store for Ethereum Beacon Nodes.
    58  type Store struct {
    59  	db                  *bolt.DB
    60  	databasePath        string
    61  	blockCache          *ristretto.Cache
    62  	validatorIndexCache *ristretto.Cache
    63  	stateSummaryCache   *stateSummaryCache
    64  	ctx                 context.Context
    65  }
    66  
    67  // KVStoreDatafilePath is the canonical construction of a full
    68  // database file path from the directory path, so that code outside
    69  // this package can find the full path in a consistent way.
    70  func KVStoreDatafilePath(dirPath string) string {
    71  	return path.Join(dirPath, DatabaseFileName)
    72  }
    73  
    74  // NewKVStore initializes a new boltDB key-value store at the directory
    75  // path specified, creates the kv-buckets based on the schema, and stores
    76  // an open connection db object as a property of the Store struct.
    77  func NewKVStore(ctx context.Context, dirPath string, config *Config) (*Store, error) {
    78  	hasDir, err := fileutil.HasDir(dirPath)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	if !hasDir {
    83  		if err := fileutil.MkdirAll(dirPath); err != nil {
    84  			return nil, err
    85  		}
    86  	}
    87  	datafile := KVStoreDatafilePath(dirPath)
    88  	boltDB, err := bolt.Open(
    89  		datafile,
    90  		params.BeaconIoConfig().ReadWritePermissions,
    91  		&bolt.Options{
    92  			Timeout:         1 * time.Second,
    93  			InitialMmapSize: config.InitialMMapSize,
    94  		},
    95  	)
    96  	if err != nil {
    97  		if errors.Is(err, bolt.ErrTimeout) {
    98  			return nil, errors.New("cannot obtain database lock, database may be in use by another process")
    99  		}
   100  		return nil, err
   101  	}
   102  	boltDB.AllocSize = boltAllocSize
   103  	blockCache, err := ristretto.NewCache(&ristretto.Config{
   104  		NumCounters: 1000,           // number of keys to track frequency of (1000).
   105  		MaxCost:     BlockCacheSize, // maximum cost of cache (1000 Blocks).
   106  		BufferItems: 64,             // number of keys per Get buffer.
   107  	})
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	validatorCache, err := ristretto.NewCache(&ristretto.Config{
   113  		NumCounters: NumOfVotes,     // number of keys to track frequency of (1M).
   114  		MaxCost:     VotesCacheSize, // maximum cost of cache (8MB).
   115  		BufferItems: 64,             // number of keys per Get buffer.
   116  	})
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	kv := &Store{
   122  		db:                  boltDB,
   123  		databasePath:        dirPath,
   124  		blockCache:          blockCache,
   125  		validatorIndexCache: validatorCache,
   126  		stateSummaryCache:   newStateSummaryCache(),
   127  		ctx:                 ctx,
   128  	}
   129  
   130  	if err := kv.db.Update(func(tx *bolt.Tx) error {
   131  		return createBuckets(
   132  			tx,
   133  			attestationsBucket,
   134  			blocksBucket,
   135  			stateBucket,
   136  			proposerSlashingsBucket,
   137  			attesterSlashingsBucket,
   138  			voluntaryExitsBucket,
   139  			chainMetadataBucket,
   140  			checkpointBucket,
   141  			powchainBucket,
   142  			stateSummaryBucket,
   143  			// Indices buckets.
   144  			attestationHeadBlockRootBucket,
   145  			attestationSourceRootIndicesBucket,
   146  			attestationSourceEpochIndicesBucket,
   147  			attestationTargetRootIndicesBucket,
   148  			attestationTargetEpochIndicesBucket,
   149  			blockSlotIndicesBucket,
   150  			stateSlotIndicesBucket,
   151  			blockParentRootIndicesBucket,
   152  			finalizedBlockRootsIndexBucket,
   153  			// State management service bucket.
   154  			newStateServiceCompatibleBucket,
   155  			// Migrations
   156  			migrationsBucket,
   157  		)
   158  	}); err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	err = prometheus.Register(createBoltCollector(kv.db))
   163  
   164  	return kv, err
   165  }
   166  
   167  // ClearDB removes the previously stored database in the data directory.
   168  func (s *Store) ClearDB() error {
   169  	if _, err := os.Stat(s.databasePath); os.IsNotExist(err) {
   170  		return nil
   171  	}
   172  	prometheus.Unregister(createBoltCollector(s.db))
   173  	if err := os.Remove(path.Join(s.databasePath, DatabaseFileName)); err != nil {
   174  		return errors.Wrap(err, "could not remove database file")
   175  	}
   176  	return nil
   177  }
   178  
   179  // Close closes the underlying BoltDB database.
   180  func (s *Store) Close() error {
   181  	prometheus.Unregister(createBoltCollector(s.db))
   182  
   183  	// Before DB closes, we should dump the cached state summary objects to DB.
   184  	if err := s.saveCachedStateSummariesDB(s.ctx); err != nil {
   185  		return err
   186  	}
   187  
   188  	return s.db.Close()
   189  }
   190  
   191  // DatabasePath at which this database writes files.
   192  func (s *Store) DatabasePath() string {
   193  	return s.databasePath
   194  }
   195  
   196  func createBuckets(tx *bolt.Tx, buckets ...[]byte) error {
   197  	for _, bucket := range buckets {
   198  		if _, err := tx.CreateBucketIfNotExists(bucket); err != nil {
   199  			return err
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  // createBoltCollector returns a prometheus collector specifically configured for boltdb.
   206  func createBoltCollector(db *bolt.DB) prometheus.Collector {
   207  	return prombolt.New("boltDB", db, blockedBuckets...)
   208  }