github.com/evdatsion/aphelion-dpos-bft@v0.32.1/lite/dbprovider.go (about)

     1  package lite
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  
     8  	amino "github.com/evdatsion/go-amino"
     9  	cryptoAmino "github.com/evdatsion/aphelion-dpos-bft/crypto/encoding/amino"
    10  	dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db"
    11  	log "github.com/evdatsion/aphelion-dpos-bft/libs/log"
    12  	lerr "github.com/evdatsion/aphelion-dpos-bft/lite/errors"
    13  	"github.com/evdatsion/aphelion-dpos-bft/types"
    14  )
    15  
    16  var _ PersistentProvider = (*DBProvider)(nil)
    17  
    18  // DBProvider stores commits and validator sets in a DB.
    19  type DBProvider struct {
    20  	logger log.Logger
    21  	label  string
    22  	db     dbm.DB
    23  	cdc    *amino.Codec
    24  	limit  int
    25  }
    26  
    27  func NewDBProvider(label string, db dbm.DB) *DBProvider {
    28  
    29  	// NOTE: when debugging, this type of construction might be useful.
    30  	//db = dbm.NewDebugDB("db provider "+cmn.RandStr(4), db)
    31  
    32  	cdc := amino.NewCodec()
    33  	cryptoAmino.RegisterAmino(cdc)
    34  	dbp := &DBProvider{
    35  		logger: log.NewNopLogger(),
    36  		label:  label,
    37  		db:     db,
    38  		cdc:    cdc,
    39  	}
    40  	return dbp
    41  }
    42  
    43  func (dbp *DBProvider) SetLogger(logger log.Logger) {
    44  	dbp.logger = logger.With("label", dbp.label)
    45  }
    46  
    47  func (dbp *DBProvider) SetLimit(limit int) *DBProvider {
    48  	dbp.limit = limit
    49  	return dbp
    50  }
    51  
    52  // Implements PersistentProvider.
    53  func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error {
    54  
    55  	dbp.logger.Info("DBProvider.SaveFullCommit()...", "fc", fc)
    56  	batch := dbp.db.NewBatch()
    57  	defer batch.Close()
    58  
    59  	// Save the fc.validators.
    60  	// We might be overwriting what we already have, but
    61  	// it makes the logic easier for now.
    62  	vsKey := validatorSetKey(fc.ChainID(), fc.Height())
    63  	vsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.Validators)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	batch.Set(vsKey, vsBz)
    68  
    69  	// Save the fc.NextValidators.
    70  	nvsKey := validatorSetKey(fc.ChainID(), fc.Height()+1)
    71  	nvsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.NextValidators)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	batch.Set(nvsKey, nvsBz)
    76  
    77  	// Save the fc.SignedHeader
    78  	shKey := signedHeaderKey(fc.ChainID(), fc.Height())
    79  	shBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.SignedHeader)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	batch.Set(shKey, shBz)
    84  
    85  	// And write sync.
    86  	batch.WriteSync()
    87  
    88  	// Garbage collect.
    89  	// TODO: optimize later.
    90  	if dbp.limit > 0 {
    91  		dbp.deleteAfterN(fc.ChainID(), dbp.limit)
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // Implements Provider.
    98  func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (
    99  	FullCommit, error) {
   100  
   101  	dbp.logger.Info("DBProvider.LatestFullCommit()...",
   102  		"chainID", chainID, "minHeight", minHeight, "maxHeight", maxHeight)
   103  
   104  	if minHeight <= 0 {
   105  		minHeight = 1
   106  	}
   107  	if maxHeight == 0 {
   108  		maxHeight = 1<<63 - 1
   109  	}
   110  
   111  	itr := dbp.db.ReverseIterator(
   112  		signedHeaderKey(chainID, minHeight),
   113  		append(signedHeaderKey(chainID, maxHeight), byte(0x00)),
   114  	)
   115  	defer itr.Close()
   116  
   117  	for itr.Valid() {
   118  		key := itr.Key()
   119  		_, _, ok := parseSignedHeaderKey(key)
   120  		if !ok {
   121  			// Skip over other keys.
   122  			itr.Next()
   123  			continue
   124  		} else {
   125  			// Found the latest full commit signed header.
   126  			shBz := itr.Value()
   127  			sh := types.SignedHeader{}
   128  			err := dbp.cdc.UnmarshalBinaryLengthPrefixed(shBz, &sh)
   129  			if err != nil {
   130  				return FullCommit{}, err
   131  			} else {
   132  				lfc, err := dbp.fillFullCommit(sh)
   133  				if err == nil {
   134  					dbp.logger.Info("DBProvider.LatestFullCommit() found latest.", "height", lfc.Height())
   135  					return lfc, nil
   136  				} else {
   137  					dbp.logger.Error("DBProvider.LatestFullCommit() got error", "lfc", lfc)
   138  					dbp.logger.Error(fmt.Sprintf("%+v", err))
   139  					return lfc, err
   140  				}
   141  			}
   142  		}
   143  	}
   144  	return FullCommit{}, lerr.ErrCommitNotFound()
   145  }
   146  
   147  func (dbp *DBProvider) ValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
   148  	return dbp.getValidatorSet(chainID, height)
   149  }
   150  
   151  func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
   152  	vsBz := dbp.db.Get(validatorSetKey(chainID, height))
   153  	if vsBz == nil {
   154  		err = lerr.ErrUnknownValidators(chainID, height)
   155  		return
   156  	}
   157  	err = dbp.cdc.UnmarshalBinaryLengthPrefixed(vsBz, &valset)
   158  	if err != nil {
   159  		return
   160  	}
   161  
   162  	// To test deep equality.  This makes it easier to test for e.g. valset
   163  	// equivalence using assert.Equal (tests for deep equality) in our tests,
   164  	// which also tests for unexported/private field equivalence.
   165  	valset.TotalVotingPower()
   166  
   167  	return
   168  }
   169  
   170  func (dbp *DBProvider) fillFullCommit(sh types.SignedHeader) (FullCommit, error) {
   171  	var chainID = sh.ChainID
   172  	var height = sh.Height
   173  	var valset, nextValset *types.ValidatorSet
   174  	// Load the validator set.
   175  	valset, err := dbp.getValidatorSet(chainID, height)
   176  	if err != nil {
   177  		return FullCommit{}, err
   178  	}
   179  	// Load the next validator set.
   180  	nextValset, err = dbp.getValidatorSet(chainID, height+1)
   181  	if err != nil {
   182  		return FullCommit{}, err
   183  	}
   184  	// Return filled FullCommit.
   185  	return FullCommit{
   186  		SignedHeader:   sh,
   187  		Validators:     valset,
   188  		NextValidators: nextValset,
   189  	}, nil
   190  }
   191  
   192  func (dbp *DBProvider) deleteAfterN(chainID string, after int) error {
   193  
   194  	dbp.logger.Info("DBProvider.deleteAfterN()...", "chainID", chainID, "after", after)
   195  
   196  	itr := dbp.db.ReverseIterator(
   197  		signedHeaderKey(chainID, 1),
   198  		append(signedHeaderKey(chainID, 1<<63-1), byte(0x00)),
   199  	)
   200  	defer itr.Close()
   201  
   202  	var lastHeight int64 = 1<<63 - 1
   203  	var numSeen = 0
   204  	var numDeleted = 0
   205  
   206  	for itr.Valid() {
   207  		key := itr.Key()
   208  		_, height, ok := parseChainKeyPrefix(key)
   209  		if !ok {
   210  			return fmt.Errorf("unexpected key %v", key)
   211  		} else {
   212  			if height < lastHeight {
   213  				lastHeight = height
   214  				numSeen += 1
   215  			}
   216  			if numSeen > after {
   217  				dbp.db.Delete(key)
   218  				numDeleted += 1
   219  			}
   220  		}
   221  		itr.Next()
   222  	}
   223  
   224  	dbp.logger.Info(fmt.Sprintf("DBProvider.deleteAfterN() deleted %v items", numDeleted))
   225  	return nil
   226  }
   227  
   228  //----------------------------------------
   229  // key encoding
   230  
   231  func signedHeaderKey(chainID string, height int64) []byte {
   232  	return []byte(fmt.Sprintf("%s/%010d/sh", chainID, height))
   233  }
   234  
   235  func validatorSetKey(chainID string, height int64) []byte {
   236  	return []byte(fmt.Sprintf("%s/%010d/vs", chainID, height))
   237  }
   238  
   239  //----------------------------------------
   240  // key parsing
   241  
   242  var keyPattern = regexp.MustCompile(`^([^/]+)/([0-9]*)/(.*)$`)
   243  
   244  func parseKey(key []byte) (chainID string, height int64, part string, ok bool) {
   245  	submatch := keyPattern.FindSubmatch(key)
   246  	if submatch == nil {
   247  		return "", 0, "", false
   248  	}
   249  	chainID = string(submatch[1])
   250  	heightStr := string(submatch[2])
   251  	heightInt, err := strconv.Atoi(heightStr)
   252  	if err != nil {
   253  		return "", 0, "", false
   254  	}
   255  	height = int64(heightInt)
   256  	part = string(submatch[3])
   257  	ok = true // good!
   258  	return
   259  }
   260  
   261  func parseSignedHeaderKey(key []byte) (chainID string, height int64, ok bool) {
   262  	var part string
   263  	chainID, height, part, ok = parseKey(key)
   264  	if part != "sh" {
   265  		return "", 0, false
   266  	}
   267  	return
   268  }
   269  
   270  func parseChainKeyPrefix(key []byte) (chainID string, height int64, ok bool) {
   271  	chainID, height, _, ok = parseKey(key)
   272  	return
   273  }