@ -2,14 +2,21 @@ package kvstore
import (
import (
"bytes"
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/binary"
"encoding/json"
"encoding/json"
"fmt"
"fmt"
"strconv"
"strings"
"sync"
dbm "github.com/tendermint/tm-db"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"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"
"github.com/tendermint/tendermint/version"
"github.com/tendermint/tendermint/version"
)
)
@ -65,17 +72,41 @@ var _ types.Application = (*Application)(nil)
type Application struct {
type Application struct {
types . BaseApplication
types . BaseApplication
mu sync . Mutex
state State
state State
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
logger log . Logger
// validator set
ValUpdates [ ] types . ValidatorUpdate
valAddrToPubKeyMap map [ string ] cryptoproto . PublicKey
}
}
func NewApplication ( ) * Application {
func NewApplication ( ) * Application {
state := loadState ( dbm . NewMemDB ( ) )
return & Application { state : state }
return & Application {
logger : log . NewNopLogger ( ) ,
state : loadState ( dbm . NewMemDB ( ) ) ,
valAddrToPubKeyMap : make ( map [ string ] cryptoproto . PublicKey ) ,
}
}
}
func ( app * Application ) Info ( req types . RequestInfo ) ( resInfo types . ResponseInfo ) {
func ( app * Application ) InitChain ( req types . RequestInitChain ) types . ResponseInitChain {
app . mu . Lock ( )
defer app . mu . Unlock ( )
for _ , v := range req . Validators {
r := app . updateValidator ( v )
if r . IsErr ( ) {
app . logger . Error ( "error updating validators" , "r" , r )
panic ( "problem updating validators" )
}
}
return types . ResponseInitChain { }
}
func ( app * Application ) Info ( req types . RequestInfo ) types . ResponseInfo {
app . mu . Lock ( )
defer app . mu . Unlock ( )
return types . ResponseInfo {
return types . ResponseInfo {
Data : fmt . Sprintf ( "{\"size\":%v}" , app . state . Size ) ,
Data : fmt . Sprintf ( "{\"size\":%v}" , app . state . Size ) ,
Version : version . ABCIVersion ,
Version : version . ABCIVersion ,
@ -85,8 +116,20 @@ func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo)
}
}
}
}
// tx is either "key=value" or just arbitrary bytes
func ( app * Application ) HandleTx ( tx [ ] byte ) * types . ResponseDeliverTx {
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
func ( app * Application ) 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 )
}
var key , value string
var key , value string
parts := bytes . Split ( tx , [ ] byte ( "=" ) )
parts := bytes . Split ( tx , [ ] byte ( "=" ) )
if len ( parts ) == 2 {
if len ( parts ) == 2 {
@ -116,19 +159,53 @@ func (app *Application) HandleTx(tx []byte) *types.ResponseDeliverTx {
return & types . ResponseDeliverTx { Code : code . CodeTypeOK , Events : events }
return & types . ResponseDeliverTx { Code : code . CodeTypeOK , Events : events }
}
}
func ( app * Application ) Close ( ) error {
app . mu . Lock ( )
defer app . mu . Unlock ( )
return app . state . db . Close ( )
}
func ( app * Application ) FinalizeBlock ( req types . RequestFinalizeBlock ) types . ResponseFinalizeBlock {
func ( app * Application ) FinalizeBlock ( req types . RequestFinalizeBlock ) types . ResponseFinalizeBlock {
txs := make ( [ ] * types . ResponseDeliverTx , len ( req . Txs ) )
app . mu . Lock ( )
defer app . mu . Unlock ( )
// 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 {
panic ( fmt . Errorf ( "wanted to punish val %q but can't find it" , addr ) )
}
}
}
respTxs := make ( [ ] * types . ResponseDeliverTx , len ( req . Txs ) )
for i , tx := range req . Txs {
for i , tx := range req . Txs {
txs [ i ] = app . HandleTx ( tx )
respT xs[ i ] = app . h andleTx( tx )
}
}
return types . ResponseFinalizeBlock { Txs : txs }
return types . ResponseFinalizeBlock { Txs : respTxs , ValidatorUpdates : app . ValUpdates }
}
}
func ( app * Application ) CheckTx ( req types . RequestCheckTx ) types . ResponseCheckTx {
func ( * Application ) CheckTx ( req types . RequestCheckTx ) types . ResponseCheckTx {
return types . ResponseCheckTx { Code : code . CodeTypeOK , GasWanted : 1 }
return types . ResponseCheckTx { Code : code . CodeTypeOK , GasWanted : 1 }
}
}
func ( app * Application ) Commit ( ) types . ResponseCommit {
func ( app * Application ) Commit ( ) types . ResponseCommit {
app . mu . Lock ( )
defer app . mu . Unlock ( )
// Using a memdb - just return the big endian size of the db
// Using a memdb - just return the big endian size of the db
appHash := make ( [ ] byte , 8 )
appHash := make ( [ ] byte , 8 )
binary . PutVarint ( appHash , app . state . Size )
binary . PutVarint ( appHash , app . state . Size )
@ -144,43 +221,225 @@ func (app *Application) Commit() types.ResponseCommit {
}
}
// Returns an associated value or nil if missing.
// Returns an associated value or nil if missing.
func ( app * Application ) Query ( reqQuery types . RequestQuery ) ( resQuery types . ResponseQuery ) {
func ( app * Application ) Query ( reqQuery types . RequestQuery ) types . ResponseQuery {
app . mu . Lock ( )
defer app . mu . Unlock ( )
if reqQuery . Path == "/val" {
key := [ ] byte ( "val:" + string ( reqQuery . Data ) )
value , err := app . state . db . Get ( key )
if err != nil {
panic ( err )
}
return types . ResponseQuery {
Key : reqQuery . Data ,
Value : value ,
}
}
if reqQuery . Prove {
if reqQuery . Prove {
value , err := app . state . db . Get ( prefixKey ( reqQuery . Data ) )
value , err := app . state . db . Get ( prefixKey ( reqQuery . Data ) )
if err != nil {
if err != nil {
panic ( err )
panic ( err )
}
}
resQuery := types . ResponseQuery {
Index : - 1 ,
Key : reqQuery . Data ,
Value : value ,
Height : app . state . Height ,
}
if value == nil {
if value == nil {
resQuery . Log = "does not exist"
resQuery . Log = "does not exist"
} else {
} else {
resQuery . Log = "exists"
resQuery . Log = "exists"
}
}
resQuery . Index = - 1 // TODO make Proof return index
resQuery . Key = reqQuery . Data
resQuery . Value = value
resQuery . Height = app . state . Height
return
return resQuery
}
}
resQuery . Key = reqQuery . Data
value , err := app . state . db . Get ( prefixKey ( reqQuery . Data ) )
value , err := app . state . db . Get ( prefixKey ( reqQuery . Data ) )
if err != nil {
if err != nil {
panic ( err )
panic ( err )
}
}
resQuery := types . ResponseQuery {
Key : reqQuery . Data ,
Value : value ,
Height : app . state . Height ,
}
if value == nil {
if value == nil {
resQuery . Log = "does not exist"
resQuery . Log = "does not exist"
} else {
} else {
resQuery . Log = "exists"
resQuery . Log = "exists"
}
}
resQuery . Value = value
resQuery . Height = app . state . Height
return resQuery
return resQuery
}
}
func ( app * Application ) PrepareProposal (
req types . RequestPrepareProposal ) types . ResponsePrepareProposal {
return types . ResponsePrepareProposal {
BlockData : req . BlockData }
func ( app * Application ) PrepareProposal ( req types . RequestPrepareProposal ) types . ResponsePrepareProposal {
app . mu . Lock ( )
defer app . mu . Unlock ( )
return types . ResponsePrepareProposal { BlockData : app . substPrepareTx ( req . BlockData ) }
}
func ( * Application ) 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 * Application ) Validators ( ) ( validators [ ] types . ValidatorUpdate ) {
app . mu . Lock ( )
defer app . mu . Unlock ( )
itr , err := 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 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 * Application ) 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 * Application ) 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 . 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 . 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 . 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 }
}
// -----------------------------
// prepare proposal machinery
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 * Application ) 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 * Application ) 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
}
}