github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/core/roundstate_db.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"math/big"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/common/task"
    28  	"github.com/ethereum/go-ethereum/consensus/istanbul"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/rlp"
    31  	"github.com/syndtr/goleveldb/leveldb"
    32  	lvlerrors "github.com/syndtr/goleveldb/leveldb/errors"
    33  	"github.com/syndtr/goleveldb/leveldb/opt"
    34  	"github.com/syndtr/goleveldb/leveldb/storage"
    35  	"github.com/syndtr/goleveldb/leveldb/util"
    36  )
    37  
    38  const (
    39  	dbVersion    = 2
    40  	dbVersionKey = "version"  // Version of the database to flush if changes
    41  	lastViewKey  = "lastView" // Last View that we know of
    42  	rsKey        = "rs"       // Database Key Pefix for RoundState
    43  )
    44  
    45  type RoundStateDB interface {
    46  	GetLastView() (*istanbul.View, error)
    47  	// GetOldestValidView returns the oldest valid view that can be stored on the db
    48  	// it might or might not be present on the db
    49  	GetOldestValidView() (*istanbul.View, error)
    50  	GetRoundStateFor(view *istanbul.View) (RoundState, error)
    51  	UpdateLastRoundState(rs RoundState) error
    52  	Close() error
    53  }
    54  
    55  // RoundStateDBOptions are the options for a RoundStateDB instance
    56  type RoundStateDBOptions struct {
    57  	withGarbageCollector   bool
    58  	garbageCollectorPeriod time.Duration
    59  	sequencesToSave        uint64
    60  }
    61  
    62  type roundStateDBImpl struct {
    63  	db                   *leveldb.DB
    64  	stopGarbageCollector task.StopFn
    65  	opts                 RoundStateDBOptions
    66  	logger               log.Logger
    67  }
    68  
    69  var defaultRoundStateDBOptions = RoundStateDBOptions{
    70  	withGarbageCollector:   true,
    71  	sequencesToSave:        100,
    72  	garbageCollectorPeriod: 2 * time.Minute,
    73  }
    74  
    75  func coerceOptions(opts *RoundStateDBOptions) RoundStateDBOptions {
    76  	if opts == nil {
    77  		return defaultRoundStateDBOptions
    78  	}
    79  
    80  	options := *opts
    81  	if options.sequencesToSave == 0 {
    82  		options.sequencesToSave = defaultRoundStateDBOptions.sequencesToSave
    83  	}
    84  	if options.withGarbageCollector && options.garbageCollectorPeriod == 0 {
    85  		options.garbageCollectorPeriod = defaultRoundStateDBOptions.garbageCollectorPeriod
    86  	}
    87  	return options
    88  }
    89  
    90  func newRoundStateDB(path string, opts *RoundStateDBOptions) (RoundStateDB, error) {
    91  	logger := log.New("func", "newRoundStateDB", "type", "roundStateDB", "rsdb_path", path)
    92  
    93  	logger.Info("Open roundstate db")
    94  	var db *leveldb.DB
    95  	var err error
    96  	if path == "" {
    97  		db, err = newMemoryDB()
    98  	} else {
    99  		db, err = newPersistentDB(path)
   100  	}
   101  
   102  	if err != nil {
   103  		logger.Error("Failed to open roundstate db", "err", err)
   104  		return nil, err
   105  	}
   106  
   107  	rsdb := &roundStateDBImpl{
   108  		db:     db,
   109  		opts:   coerceOptions(opts),
   110  		logger: logger,
   111  	}
   112  
   113  	if rsdb.opts.withGarbageCollector {
   114  		rsdb.stopGarbageCollector = task.RunTaskRepeateadly(rsdb.garbageCollectEntries, rsdb.opts.garbageCollectorPeriod)
   115  	}
   116  
   117  	return rsdb, nil
   118  }
   119  
   120  // newMemoryDB creates a new in-memory node database without a persistent backend.
   121  func newMemoryDB() (*leveldb.DB, error) {
   122  	db, err := leveldb.Open(storage.NewMemStorage(), nil)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return db, nil
   127  }
   128  
   129  // newPersistentNodeDB creates/opens a leveldb backed persistent node database,
   130  // also flushing its contents in case of a version mismatch.
   131  func newPersistentDB(path string) (*leveldb.DB, error) {
   132  	opts := &opt.Options{OpenFilesCacheCapacity: 5}
   133  	db, err := leveldb.OpenFile(path, opts)
   134  	if _, iscorrupted := err.(*lvlerrors.ErrCorrupted); iscorrupted {
   135  		db, err = leveldb.RecoverFile(path, nil)
   136  	}
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	// The nodes contained in the cache correspond to a certain protocol version.
   141  	// Flush all nodes if the version doesn't match.
   142  	currentVer := make([]byte, binary.MaxVarintLen64)
   143  	currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))]
   144  
   145  	blob, err := db.Get([]byte(dbVersionKey), nil)
   146  	switch err {
   147  	case leveldb.ErrNotFound:
   148  		// Version not found (i.e. empty cache), insert it
   149  		if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil {
   150  			db.Close()
   151  			return nil, err
   152  		}
   153  
   154  	case nil:
   155  		// Version present, flush if different
   156  		if !bytes.Equal(blob, currentVer) {
   157  			db.Close()
   158  			if err = os.RemoveAll(path); err != nil {
   159  				return nil, err
   160  			}
   161  			return newPersistentDB(path)
   162  		}
   163  	}
   164  	return db, nil
   165  }
   166  
   167  // storeRoundState will store the currentRoundState in a Map<view, roundState> schema.
   168  func (rsdb *roundStateDBImpl) UpdateLastRoundState(rs RoundState) error {
   169  	// We store the roundState for each view; since we'll need this
   170  	// information to allow the node to have evidence to show that
   171  	// a validator did a "valid" double signing
   172  	logger := rsdb.logger.New("func", "UpdateLastRoundState")
   173  	viewKey := view2Key(rs.View())
   174  
   175  	entryBytes, err := rlp.EncodeToBytes(rs)
   176  	if err != nil {
   177  		logger.Error("Failed to save roundState", "reason", "rlp encoding", "err", err)
   178  		return err
   179  	}
   180  
   181  	batch := new(leveldb.Batch)
   182  	batch.Put([]byte(lastViewKey), viewKey)
   183  	batch.Put(viewKey, entryBytes)
   184  
   185  	err = rsdb.db.Write(batch, nil)
   186  	if err != nil {
   187  		logger.Error("Failed to save roundState", "reason", "levelDB write", "err", err, "func")
   188  	}
   189  
   190  	return err
   191  }
   192  
   193  func (rsdb *roundStateDBImpl) GetLastView() (*istanbul.View, error) {
   194  	rawEntry, err := rsdb.db.Get([]byte(lastViewKey), nil)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return key2View(rawEntry), nil
   200  }
   201  
   202  func (rsdb *roundStateDBImpl) GetOldestValidView() (*istanbul.View, error) {
   203  	lastView, err := rsdb.GetLastView()
   204  	// If nothing stored all views are valid
   205  	if err == leveldb.ErrNotFound {
   206  		return &istanbul.View{Sequence: common.Big0, Round: common.Big0}, nil
   207  	} else if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	oldestValidSequence := new(big.Int).Sub(lastView.Sequence, new(big.Int).SetUint64(rsdb.opts.sequencesToSave))
   212  	if oldestValidSequence.Cmp(common.Big0) < 0 {
   213  		oldestValidSequence = common.Big0
   214  	}
   215  
   216  	return &istanbul.View{Sequence: oldestValidSequence, Round: common.Big0}, nil
   217  }
   218  
   219  func (rsdb *roundStateDBImpl) GetRoundStateFor(view *istanbul.View) (RoundState, error) {
   220  	viewKey := view2Key(view)
   221  	rawEntry, err := rsdb.db.Get(viewKey, nil)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	var entry roundStateImpl
   227  	if err = rlp.DecodeBytes(rawEntry, &entry); err != nil {
   228  		return nil, err
   229  	}
   230  	return &entry, nil
   231  }
   232  
   233  func (rsdb *roundStateDBImpl) Close() error {
   234  	if rsdb.opts.withGarbageCollector {
   235  		rsdb.stopGarbageCollector()
   236  	}
   237  	return rsdb.db.Close()
   238  }
   239  
   240  func (rsdb *roundStateDBImpl) garbageCollectEntries() {
   241  	logger := rsdb.logger.New("func", "garbageCollectEntries")
   242  
   243  	oldestValidView, err := rsdb.GetOldestValidView()
   244  	if err != nil {
   245  		logger.Error("Aborting RoundStateDB GarbageCollect: Failed to fetch oldestValidView", "err", err)
   246  		return
   247  	}
   248  
   249  	logger.Debug("Pruning entries from old views", "oldestValidView", oldestValidView)
   250  	count, err := rsdb.deleteEntriesOlderThan(oldestValidView)
   251  	if err != nil {
   252  		logger.Error("Aborting RoundStateDB GarbageCollect: Failed to remove entries", "entries_removed", count, "err", err)
   253  		return
   254  	}
   255  
   256  	logger.Debug("Finished RoundStateDB GarbageCollect", "removed_entries", count)
   257  }
   258  
   259  func (rsdb *roundStateDBImpl) deleteEntriesOlderThan(lastView *istanbul.View) (int, error) {
   260  	fromViewKey := view2Key(&istanbul.View{Sequence: common.Big0, Round: common.Big0})
   261  	toViewKey := view2Key(lastView)
   262  
   263  	iter := rsdb.db.NewIterator(&util.Range{Start: fromViewKey, Limit: toViewKey}, nil)
   264  	defer iter.Release()
   265  
   266  	counter := 0
   267  	for iter.Next() {
   268  		rawKey := iter.Key()
   269  		err := rsdb.db.Delete(rawKey, nil)
   270  		if err != nil {
   271  			return counter, err
   272  		}
   273  		counter++
   274  	}
   275  	return counter, nil
   276  }
   277  
   278  // view2Key will encode a view in binary format
   279  // so that the binary format maintains the sort order for the view
   280  func view2Key(view *istanbul.View) []byte {
   281  	// leveldb sorts entries by key
   282  	// keys are sorted with their binary representation, so we need a binary representation
   283  	// that mantains the key order
   284  	// The key format is [ prefix . BigEndian(Sequence) . BigEndian(Round)]
   285  	// We use BigEndian so to maintain order in binary format
   286  	// And we want to sort by (seq, round); since seq had higher precedence than round
   287  	prefix := []byte(rsKey)
   288  	buff := make([]byte, len(prefix)+16)
   289  
   290  	copy(buff, prefix)
   291  	// TODO (mcortesi) Support Seq/Round bigger than 64bits
   292  	binary.BigEndian.PutUint64(buff[len(prefix):], view.Sequence.Uint64())
   293  	binary.BigEndian.PutUint64(buff[len(prefix)+8:], view.Round.Uint64())
   294  
   295  	return buff
   296  }
   297  
   298  func key2View(key []byte) *istanbul.View {
   299  	prefixLen := len([]byte(rsKey))
   300  	seq := binary.BigEndian.Uint64(key[prefixLen : prefixLen+8])
   301  	round := binary.BigEndian.Uint64(key[prefixLen+8:])
   302  	return &istanbul.View{
   303  		Sequence: new(big.Int).SetUint64(seq),
   304  		Round:    new(big.Int).SetUint64(round),
   305  	}
   306  }