@ -2,16 +2,10 @@ 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"
"github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto"
ptypes "github.com/tendermint/tendermint/proto/tendermint/types"
@ -26,325 +20,42 @@ const (
var _ types . Application = ( * PersistentKVStoreApplication ) ( nil )
type PersistentKVStoreApplication struct {
app * Application
// validator set
ValUpdates [ ] types . ValidatorUpdate
valAddrToPubKeyMap map [ string ] cryptoproto . PublicKey
logger log . Logger
* Application
}
func NewPersistentKVStoreApplication ( logger log . Logger , dbDir string ) * PersistentKVStoreApplication {
name := "kvstore"
db , err := dbm . NewGoLevelDB ( name , dbDir )
db , err := dbm . NewGoLevelDB ( "kvstore" , dbDir )
if err != nil {
panic ( err )
}
state := loadState ( db )
return & PersistentKVStoreApplication {
app : & Application { state : state } ,
valAddrToPubKeyMap : make ( map [ string ] cryptoproto . PublicKey ) ,
logger : logger ,
}
}
func ( app * PersistentKVStoreApplication ) Close ( ) error {
return app . app . state . db . Close ( )
}
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 ) HandleTx ( tx [ ] byte ) * types . ResponseDeliverTx {
// if it starts with "val:", update the validator set
// format is "val:pubkey!power"
if isValidatorTx ( tx ) {
// update validators in the merkle tree
// and in app.ValUpdates
return app . execValidatorTx ( tx )
}
if isPrepareTx ( tx ) {
return app . execPrepareTx ( tx )
}
// otherwise, update the key-value store
return app . app . HandleTx ( tx )
}
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 )
Application : & Application {
valAddrToPubKeyMap : make ( map [ string ] cryptoproto . PublicKey ) ,
state : loadState ( db ) ,
logger : logger ,
} ,
}
}
// 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
// Execute transactions
// Update the validator set
func ( app * PersistentKVStoreApplication ) FinalizeBlock ( req types . RequestFinalizeBlock ) types . ResponseFinalizeBlock {
// reset valset changes
app . ValUpdates = make ( [ ] types . ValidatorUpdate , 0 )
// Punish validators who committed equivocation.
for _ , ev := range req . ByzantineValidators {
if ev . Type == types . EvidenceType_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 )
}
}
}
respTxs := make ( [ ] * types . ResponseDeliverTx , len ( req . Txs ) )
for i , tx := range req . Txs {
respTxs [ i ] = app . HandleTx ( tx )
}
return types . ResponseFinalizeBlock { Txs : respTxs , 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 {
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 {
func ( app * PersistentKVStoreApplication ) ApplySnapshotChunk ( req types . RequestApplySnapshotChunk ) types . ResponseApplySnapshotChunk {
return types . ResponseApplySnapshotChunk { Result : types . ResponseApplySnapshotChunk_ABORT }
}
func ( app * PersistentKVStoreApplication ) ExtendVote (
req types . RequestExtendVote ) types . ResponseExtendVote {
return types . ResponseExtendVote {
VoteExtension : ConstructVoteExtension ( req . Vote . ValidatorAddress ) ,
}
}
func ( app * PersistentKVStoreApplication ) VerifyVoteExtension (
req types . RequestVerifyVoteExtension ) types . ResponseVerifyVoteExtension {
return types . RespondVerifyVoteExtension (
app . verifyExtension ( req . Vote . ValidatorAddress , req . Vote . VoteExtension ) )
}
func ( app * PersistentKVStoreApplication ) PrepareProposal (
req types . RequestPrepareProposal ) types . ResponsePrepareProposal {
return types . ResponsePrepareProposal { BlockData : app . substPrepareTx ( req . BlockData ) }
}
func ( app * PersistentKVStoreApplication ) ProcessProposal (
req types . RequestProcessProposal ) types . ResponseProcessProposal {
for _ , tx := range req . Txs {
if len ( tx ) == 0 {
return types . ResponseProcessProposal { Result : types . ResponseProcessProposal_REJECT }
}
}
return types . ResponseProcessProposal { Result : 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 cryptoproto . PublicKey , power int64 ) [ ] byte {
pk , err := encoding . PubKeyFromProto ( pubkey )
if err != nil {
panic ( err )
}
pubStr := base64 . StdEncoding . EncodeToString ( pk . Bytes ( ) )
return [ ] byte ( fmt . Sprintf ( "val:%s!%d" , pubStr , power ) )
func ( app * PersistentKVStoreApplication ) ExtendVote ( req types . RequestExtendVote ) types . ResponseExtendVote {
return types . ResponseExtendVote { VoteExtension : ConstructVoteExtension ( req . Vote . ValidatorAddress ) }
}
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 := encoding . 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 }
func ( app * PersistentKVStoreApplication ) VerifyVoteExtension ( req types . RequestVerifyVoteExtension ) types . ResponseVerifyVoteExtension {
return types . RespondVerifyVoteExtension ( app . verifyExtension ( req . Vote . ValidatorAddress , req . Vote . VoteExtension ) )
}
// -----------------------------
const PreparePrefix = "prepare"
func isPrepareTx ( tx [ ] byte ) bool {
return strings . HasPrefix ( string ( tx ) , 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 subst all the preparetx in the blockdata
// to null string(could be any arbitrary string).
func ( app * PersistentKVStoreApplication ) substPrepareTx ( blockData [ ] [ ] byte ) [ ] [ ] byte {
// TODO: this mechanism will change with the current spec of PrepareProposal
// We now have a special type for marking a tx as changed
for i , tx := range blockData {
if isPrepareTx ( tx ) {
blockData [ i ] = make ( [ ] byte , len ( tx ) )
}
}
return blockData
}
func ConstructVoteExtension ( valAddr [ ] byte ) * ptypes . VoteExtension {
return & ptypes . VoteExtension {
AppDataToSign : valAddr ,