mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-26 01:44:16 +00:00
53999f9e78
* [cherrypicked] abci++: only include meaningful header fields in data passed-through to application (#8216)
* make proto-gen
* Fix kvstore tests
* Small changes to abci protobufs taken from v0.36.x
* make proto-gen (again)
* [Partial cherrypick] Restore `Commit` to the ABCI++ spec, and other late modifications (backport #8796) (#8936)
* Restore `Commit` to the ABCI++ spec, and other late modifications (#8796)
* Added VoteExtensionsEnableHeight
* Fix reference to `modified`
* Removed old pseudo-code, now included in spec
* Removed markdown warnings in abci++_basic_concepts_002_draft.md
* Restored `Commit` in the Methods section
* Addressed remaining markdown warnings
* Revisited intro and basic concepts section
* Extra pass at all spec sections to recover Commit, and other ABCI++ spec modifications
* Fixed links
* make proto-gen
* Remove _primes_ from spec notation
* Update proto/tendermint/abci/types.proto
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
* Update spec/abci++/abci++_tmint_expected_behavior_002_draft.md
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
* Addressed @cmwaters' comments
* Addressed @angbrav's and @mpoke's comments on spec
* make proto-gen
* Fix MD anchor reference
* Clarify throughout the spec when `ProcessProposal` and `VerifyVoteExtension` are called
* Update spec/abci++/abci++_app_requirements_002_draft.md
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
* Update spec/abci++/abci++_app_requirements_002_draft.md
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
* Update spec/abci++/abci++_app_requirements_002_draft.md
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
* Update spec/abci++/abci++_basic_concepts_002_draft.md
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
* Update spec/abci++/abci++_basic_concepts_002_draft.md
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
* Update spec/abci++/abci++_basic_concepts_002_draft.md
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
* Update spec/abci++/abci++_methods_002_draft.md
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
* Update spec/abci++/abci++_tmint_expected_behavior_002_draft.md
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
* Addresed comments
* Renamed 'draft' files
* Adatped links to new filenames
* Fixed links and minor cosmetic changes
* Renamed 'byzantine_validators' to 'misbehavior' in ABCI++ spec and protobufs
* make proto-gen
* Renamed 'byzantine_validators' to 'misbehavior' in the code
* Fixed link
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_basic_concepts.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Update spec/abci++/abci++_methods.md
Co-authored-by: Daniel <daniel.cason@usi.ch>
* Addressed @cason's comments
* Clarified conditions for `ProcessProposal` call at proposer
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
Co-authored-by: Daniel <daniel.cason@usi.ch>
(cherry picked from commit 331860c2a8)
* Fixed merge conflicts
Co-authored-by: Sergio Mena <sergio@informal.systems>
* make proto-gen (and again)
* make build
* fix UTs
Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
349 lines
10 KiB
Go
349 lines
10 KiB
Go
package kvstore
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/abci/example/code"
|
|
"github.com/tendermint/tendermint/abci/types"
|
|
cryptoenc "github.com/tendermint/tendermint/crypto/encoding"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
pc "github.com/tendermint/tendermint/proto/tendermint/crypto"
|
|
)
|
|
|
|
const (
|
|
ValidatorSetChangePrefix string = "val:"
|
|
)
|
|
|
|
//-----------------------------------------
|
|
|
|
var _ types.Application = (*PersistentKVStoreApplication)(nil)
|
|
|
|
type PersistentKVStoreApplication struct {
|
|
app *Application
|
|
|
|
// validator set
|
|
ValUpdates []types.ValidatorUpdate
|
|
|
|
valAddrToPubKeyMap map[string]pc.PublicKey
|
|
|
|
logger log.Logger
|
|
}
|
|
|
|
func NewPersistentKVStoreApplication(dbDir string) *PersistentKVStoreApplication {
|
|
name := "kvstore"
|
|
db, err := dbm.NewGoLevelDB(name, dbDir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
state := loadState(db)
|
|
|
|
return &PersistentKVStoreApplication{
|
|
app: &Application{state: state},
|
|
valAddrToPubKeyMap: make(map[string]pc.PublicKey),
|
|
logger: log.NewNopLogger(),
|
|
}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) SetLogger(l log.Logger) {
|
|
app.logger = l
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) Info(req types.RequestInfo) types.ResponseInfo {
|
|
res := app.app.Info(req)
|
|
res.LastBlockHeight = app.app.state.Height
|
|
res.LastBlockAppHash = app.app.state.AppHash
|
|
return res
|
|
}
|
|
|
|
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
|
|
func (app *PersistentKVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
|
|
// if it starts with "val:", update the validator set
|
|
// format is "val:pubkey!power"
|
|
if isValidatorTx(req.Tx) {
|
|
// update validators in the merkle tree
|
|
// and in app.ValUpdates
|
|
return app.execValidatorTx(req.Tx)
|
|
}
|
|
|
|
if isPrepareTx(req.Tx) {
|
|
return app.execPrepareTx(req.Tx)
|
|
}
|
|
|
|
// otherwise, update the key-value store
|
|
return app.app.DeliverTx(req)
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
|
|
return app.app.CheckTx(req)
|
|
}
|
|
|
|
// Commit will panic if InitChain was not called
|
|
func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
|
|
return app.app.Commit()
|
|
}
|
|
|
|
// When path=/val and data={validator address}, returns the validator update (types.ValidatorUpdate) varint encoded.
|
|
// For any other path, returns an associated value or nil if missing.
|
|
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
|
switch reqQuery.Path {
|
|
case "/val":
|
|
key := []byte("val:" + string(reqQuery.Data))
|
|
value, err := app.app.state.db.Get(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
resQuery.Key = reqQuery.Data
|
|
resQuery.Value = value
|
|
return
|
|
default:
|
|
return app.app.Query(reqQuery)
|
|
}
|
|
}
|
|
|
|
// Save the validators in the merkle tree
|
|
func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
|
|
for _, v := range req.Validators {
|
|
r := app.updateValidator(v)
|
|
if r.IsErr() {
|
|
app.logger.Error("Error updating validators", "r", r)
|
|
}
|
|
}
|
|
return types.ResponseInitChain{}
|
|
}
|
|
|
|
// Track the block hash and header information
|
|
func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
|
|
// reset valset changes
|
|
app.ValUpdates = make([]types.ValidatorUpdate, 0)
|
|
|
|
// Punish validators who committed equivocation.
|
|
for _, ev := range req.ByzantineValidators {
|
|
if ev.Type == types.MisbehaviorType_DUPLICATE_VOTE {
|
|
addr := string(ev.Validator.Address)
|
|
if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok {
|
|
app.updateValidator(types.ValidatorUpdate{
|
|
PubKey: pubKey,
|
|
Power: ev.Validator.Power - 1,
|
|
})
|
|
app.logger.Info("Decreased val power by 1 because of the equivocation",
|
|
"val", addr)
|
|
} else {
|
|
app.logger.Error("Wanted to punish val, but can't find it",
|
|
"val", addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return types.ResponseBeginBlock{}
|
|
}
|
|
|
|
// Update the validator set
|
|
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
|
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) ListSnapshots(
|
|
req types.RequestListSnapshots) types.ResponseListSnapshots {
|
|
return types.ResponseListSnapshots{}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) LoadSnapshotChunk(
|
|
req types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk {
|
|
return types.ResponseLoadSnapshotChunk{}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) OfferSnapshot(
|
|
req types.RequestOfferSnapshot) types.ResponseOfferSnapshot {
|
|
return types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) ApplySnapshotChunk(
|
|
req types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk {
|
|
return types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) PrepareProposal(
|
|
req types.RequestPrepareProposal) types.ResponsePrepareProposal {
|
|
return types.ResponsePrepareProposal{TxRecords: app.substPrepareTx(req.Txs, req.MaxTxBytes)}
|
|
}
|
|
|
|
func (app *PersistentKVStoreApplication) ProcessProposal(
|
|
req types.RequestProcessProposal) types.ResponseProcessProposal {
|
|
for _, tx := range req.Txs {
|
|
if len(tx) == 0 {
|
|
return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}
|
|
}
|
|
}
|
|
return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// update validators
|
|
|
|
func (app *PersistentKVStoreApplication) Validators() (validators []types.ValidatorUpdate) {
|
|
itr, err := app.app.state.db.Iterator(nil, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for ; itr.Valid(); itr.Next() {
|
|
if isValidatorTx(itr.Key()) {
|
|
validator := new(types.ValidatorUpdate)
|
|
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
validators = append(validators, *validator)
|
|
}
|
|
}
|
|
if err = itr.Error(); err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func MakeValSetChangeTx(pubkey pc.PublicKey, power int64) []byte {
|
|
pk, err := cryptoenc.PubKeyFromProto(pubkey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pubStr := base64.StdEncoding.EncodeToString(pk.Bytes())
|
|
return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
|
|
}
|
|
|
|
func isValidatorTx(tx []byte) bool {
|
|
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
|
|
}
|
|
|
|
// format is "val:pubkey!power"
|
|
// pubkey is a base64-encoded 32-byte ed25519 key
|
|
func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {
|
|
tx = tx[len(ValidatorSetChangePrefix):]
|
|
|
|
// get the pubkey and power
|
|
pubKeyAndPower := strings.Split(string(tx), "!")
|
|
if len(pubKeyAndPower) != 2 {
|
|
return types.ResponseDeliverTx{
|
|
Code: code.CodeTypeEncodingError,
|
|
Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
|
|
}
|
|
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
|
|
|
|
// decode the pubkey
|
|
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
|
|
if err != nil {
|
|
return types.ResponseDeliverTx{
|
|
Code: code.CodeTypeEncodingError,
|
|
Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
|
|
}
|
|
|
|
// decode the power
|
|
power, err := strconv.ParseInt(powerS, 10, 64)
|
|
if err != nil {
|
|
return types.ResponseDeliverTx{
|
|
Code: code.CodeTypeEncodingError,
|
|
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
|
|
}
|
|
|
|
// update
|
|
return app.updateValidator(types.UpdateValidator(pubkey, power, ""))
|
|
}
|
|
|
|
// add, update, or remove a validator
|
|
func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
|
|
pubkey, err := cryptoenc.PubKeyFromProto(v.PubKey)
|
|
if err != nil {
|
|
panic(fmt.Errorf("can't decode public key: %w", err))
|
|
}
|
|
key := []byte("val:" + string(pubkey.Bytes()))
|
|
|
|
if v.Power == 0 {
|
|
// remove validator
|
|
hasKey, err := app.app.state.db.Has(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if !hasKey {
|
|
pubStr := base64.StdEncoding.EncodeToString(pubkey.Bytes())
|
|
return types.ResponseDeliverTx{
|
|
Code: code.CodeTypeUnauthorized,
|
|
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
|
|
}
|
|
if err = app.app.state.db.Delete(key); err != nil {
|
|
panic(err)
|
|
}
|
|
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
|
|
} else {
|
|
// add or update validator
|
|
value := bytes.NewBuffer(make([]byte, 0))
|
|
if err := types.WriteMessage(&v, value); err != nil {
|
|
return types.ResponseDeliverTx{
|
|
Code: code.CodeTypeEncodingError,
|
|
Log: fmt.Sprintf("Error encoding validator: %v", err)}
|
|
}
|
|
if err = app.app.state.db.Set(key, value.Bytes()); err != nil {
|
|
panic(err)
|
|
}
|
|
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
|
|
}
|
|
|
|
// we only update the changes array if we successfully updated the tree
|
|
app.ValUpdates = append(app.ValUpdates, v)
|
|
|
|
return types.ResponseDeliverTx{Code: code.CodeTypeOK}
|
|
}
|
|
|
|
// -----------------------------
|
|
|
|
const PreparePrefix = "prepare"
|
|
|
|
func isPrepareTx(tx []byte) bool {
|
|
return bytes.HasPrefix(tx, []byte(PreparePrefix))
|
|
}
|
|
|
|
// execPrepareTx is noop. tx data is considered as placeholder
|
|
// and is substitute at the PrepareProposal.
|
|
func (app *PersistentKVStoreApplication) execPrepareTx(tx []byte) types.ResponseDeliverTx {
|
|
// noop
|
|
return types.ResponseDeliverTx{}
|
|
}
|
|
|
|
// substPrepareTx substitutes all the transactions prefixed with 'prepare' in the
|
|
// proposal for transactions with the prefix stripped.
|
|
// It marks all of the original transactions as 'REMOVED' so that
|
|
// Tendermint will remove them from its mempool.
|
|
func (app *PersistentKVStoreApplication) substPrepareTx(blockData [][]byte, maxTxBytes int64) []*types.TxRecord {
|
|
trs := make([]*types.TxRecord, 0, len(blockData))
|
|
var removed []*types.TxRecord
|
|
var totalBytes int64
|
|
for _, tx := range blockData {
|
|
txMod := tx
|
|
action := types.TxRecord_UNMODIFIED
|
|
if isPrepareTx(tx) {
|
|
removed = append(removed, &types.TxRecord{
|
|
Tx: tx,
|
|
Action: types.TxRecord_REMOVED,
|
|
})
|
|
txMod = bytes.TrimPrefix(tx, []byte(PreparePrefix))
|
|
action = types.TxRecord_ADDED
|
|
}
|
|
totalBytes += int64(len(txMod))
|
|
if totalBytes > maxTxBytes {
|
|
break
|
|
}
|
|
trs = append(trs, &types.TxRecord{
|
|
Tx: txMod,
|
|
Action: action,
|
|
})
|
|
}
|
|
|
|
return append(trs, removed...)
|
|
}
|