github.com/okex/exchain@v1.8.0/libs/tendermint/lite/dbprovider.go (about)

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