github.com/evdatsion/aphelion-dpos-bft@v0.32.1/abci/example/kvstore/persistent_kvstore.go (about)

     1  package kvstore
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/evdatsion/aphelion-dpos-bft/abci/example/code"
    11  	"github.com/evdatsion/aphelion-dpos-bft/abci/types"
    12  	dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db"
    13  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    14  )
    15  
    16  const (
    17  	ValidatorSetChangePrefix string = "val:"
    18  )
    19  
    20  //-----------------------------------------
    21  
    22  var _ types.Application = (*PersistentKVStoreApplication)(nil)
    23  
    24  type PersistentKVStoreApplication struct {
    25  	app *KVStoreApplication
    26  
    27  	// validator set
    28  	ValUpdates []types.ValidatorUpdate
    29  
    30  	logger log.Logger
    31  }
    32  
    33  func NewPersistentKVStoreApplication(dbDir string) *PersistentKVStoreApplication {
    34  	name := "kvstore"
    35  	db, err := dbm.NewGoLevelDB(name, dbDir)
    36  	if err != nil {
    37  		panic(err)
    38  	}
    39  
    40  	state := loadState(db)
    41  
    42  	return &PersistentKVStoreApplication{
    43  		app:    &KVStoreApplication{state: state},
    44  		logger: log.NewNopLogger(),
    45  	}
    46  }
    47  
    48  func (app *PersistentKVStoreApplication) SetLogger(l log.Logger) {
    49  	app.logger = l
    50  }
    51  
    52  func (app *PersistentKVStoreApplication) Info(req types.RequestInfo) types.ResponseInfo {
    53  	res := app.app.Info(req)
    54  	res.LastBlockHeight = app.app.state.Height
    55  	res.LastBlockAppHash = app.app.state.AppHash
    56  	return res
    57  }
    58  
    59  func (app *PersistentKVStoreApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption {
    60  	return app.app.SetOption(req)
    61  }
    62  
    63  // tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
    64  func (app *PersistentKVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
    65  	// if it starts with "val:", update the validator set
    66  	// format is "val:pubkey!power"
    67  	if isValidatorTx(req.Tx) {
    68  		// update validators in the merkle tree
    69  		// and in app.ValUpdates
    70  		return app.execValidatorTx(req.Tx)
    71  	}
    72  
    73  	// otherwise, update the key-value store
    74  	return app.app.DeliverTx(req)
    75  }
    76  
    77  func (app *PersistentKVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
    78  	return app.app.CheckTx(req)
    79  }
    80  
    81  // Commit will panic if InitChain was not called
    82  func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
    83  	return app.app.Commit()
    84  }
    85  
    86  func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery {
    87  	return app.app.Query(reqQuery)
    88  }
    89  
    90  // Save the validators in the merkle tree
    91  func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
    92  	for _, v := range req.Validators {
    93  		r := app.updateValidator(v)
    94  		if r.IsErr() {
    95  			app.logger.Error("Error updating validators", "r", r)
    96  		}
    97  	}
    98  	return types.ResponseInitChain{}
    99  }
   100  
   101  // Track the block hash and header information
   102  func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
   103  	// reset valset changes
   104  	app.ValUpdates = make([]types.ValidatorUpdate, 0)
   105  	return types.ResponseBeginBlock{}
   106  }
   107  
   108  // Update the validator set
   109  func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
   110  	return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
   111  }
   112  
   113  //---------------------------------------------
   114  // update validators
   115  
   116  func (app *PersistentKVStoreApplication) Validators() (validators []types.ValidatorUpdate) {
   117  	itr := app.app.state.db.Iterator(nil, nil)
   118  	for ; itr.Valid(); itr.Next() {
   119  		if isValidatorTx(itr.Key()) {
   120  			validator := new(types.ValidatorUpdate)
   121  			err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
   122  			if err != nil {
   123  				panic(err)
   124  			}
   125  			validators = append(validators, *validator)
   126  		}
   127  	}
   128  	return
   129  }
   130  
   131  func MakeValSetChangeTx(pubkey types.PubKey, power int64) []byte {
   132  	pubStr := base64.StdEncoding.EncodeToString(pubkey.Data)
   133  	return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
   134  }
   135  
   136  func isValidatorTx(tx []byte) bool {
   137  	return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
   138  }
   139  
   140  // format is "val:pubkey!power"
   141  // pubkey is a base64-encoded 32-byte ed25519 key
   142  func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {
   143  	tx = tx[len(ValidatorSetChangePrefix):]
   144  
   145  	//get the pubkey and power
   146  	pubKeyAndPower := strings.Split(string(tx), "!")
   147  	if len(pubKeyAndPower) != 2 {
   148  		return types.ResponseDeliverTx{
   149  			Code: code.CodeTypeEncodingError,
   150  			Log:  fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
   151  	}
   152  	pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
   153  
   154  	// decode the pubkey
   155  	pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
   156  	if err != nil {
   157  		return types.ResponseDeliverTx{
   158  			Code: code.CodeTypeEncodingError,
   159  			Log:  fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
   160  	}
   161  
   162  	// decode the power
   163  	power, err := strconv.ParseInt(powerS, 10, 64)
   164  	if err != nil {
   165  		return types.ResponseDeliverTx{
   166  			Code: code.CodeTypeEncodingError,
   167  			Log:  fmt.Sprintf("Power (%s) is not an int", powerS)}
   168  	}
   169  
   170  	// update
   171  	return app.updateValidator(types.Ed25519ValidatorUpdate(pubkey, int64(power)))
   172  }
   173  
   174  // add, update, or remove a validator
   175  func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
   176  	key := []byte("val:" + string(v.PubKey.Data))
   177  	if v.Power == 0 {
   178  		// remove validator
   179  		if !app.app.state.db.Has(key) {
   180  			pubStr := base64.StdEncoding.EncodeToString(v.PubKey.Data)
   181  			return types.ResponseDeliverTx{
   182  				Code: code.CodeTypeUnauthorized,
   183  				Log:  fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
   184  		}
   185  		app.app.state.db.Delete(key)
   186  	} else {
   187  		// add or update validator
   188  		value := bytes.NewBuffer(make([]byte, 0))
   189  		if err := types.WriteMessage(&v, value); err != nil {
   190  			return types.ResponseDeliverTx{
   191  				Code: code.CodeTypeEncodingError,
   192  				Log:  fmt.Sprintf("Error encoding validator: %v", err)}
   193  		}
   194  		app.app.state.db.Set(key, value.Bytes())
   195  	}
   196  
   197  	// we only update the changes array if we successfully updated the tree
   198  	app.ValUpdates = append(app.ValUpdates, v)
   199  
   200  	return types.ResponseDeliverTx{Code: code.CodeTypeOK}
   201  }