@ -0,0 +1,4 @@ | |||||
# Dummy | |||||
DEPRECATED. See KVStore | |||||
@ -0,0 +1,36 @@ | |||||
package dummy | |||||
import ( | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
// RandVal creates one random validator, with a key derived | |||||
// from the input value | |||||
func RandVal(i int) types.Validator { | |||||
pubkey := cmn.RandBytes(33) | |||||
power := cmn.RandUint16() + 1 | |||||
return types.Validator{pubkey, int64(power)} | |||||
} | |||||
// RandVals returns a list of cnt validators for initializing | |||||
// the application. Note that the keys are deterministically | |||||
// derived from the index in the array, while the power is | |||||
// random (Change this if not desired) | |||||
func RandVals(cnt int) []types.Validator { | |||||
res := make([]types.Validator, cnt) | |||||
for i := 0; i < cnt; i++ { | |||||
res[i] = RandVal(i) | |||||
} | |||||
return res | |||||
} | |||||
// InitDummy initializes the dummy app with some data, | |||||
// which allows tests to pass and is fine as long as you | |||||
// don't make any tx that modify the validator state | |||||
func InitDummy(app *PersistentDummyApplication) { | |||||
app.InitChain(types.RequestInitChain{ | |||||
Validators: RandVals(1), | |||||
AppStateBytes: []byte("[]"), | |||||
}) | |||||
} |
@ -0,0 +1,126 @@ | |||||
package dummy | |||||
import ( | |||||
"bytes" | |||||
"encoding/binary" | |||||
"encoding/json" | |||||
"fmt" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
dbm "github.com/tendermint/tmlibs/db" | |||||
) | |||||
var ( | |||||
stateKey = []byte("stateKey") | |||||
kvPairPrefixKey = []byte("kvPairKey:") | |||||
) | |||||
type State struct { | |||||
db dbm.DB | |||||
Size int64 `json:"size"` | |||||
Height int64 `json:"height"` | |||||
AppHash []byte `json:"app_hash"` | |||||
} | |||||
func loadState(db dbm.DB) State { | |||||
stateBytes := db.Get(stateKey) | |||||
var state State | |||||
if len(stateBytes) != 0 { | |||||
err := json.Unmarshal(stateBytes, &state) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
} | |||||
state.db = db | |||||
return state | |||||
} | |||||
func saveState(state State) { | |||||
stateBytes, err := json.Marshal(state) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
state.db.Set(stateKey, stateBytes) | |||||
} | |||||
func prefixKey(key []byte) []byte { | |||||
return append(kvPairPrefixKey, key...) | |||||
} | |||||
//--------------------------------------------------- | |||||
var _ types.Application = (*DummyApplication)(nil) | |||||
type DummyApplication struct { | |||||
types.BaseApplication | |||||
state State | |||||
} | |||||
func NewDummyApplication() *DummyApplication { | |||||
state := loadState(dbm.NewMemDB()) | |||||
return &DummyApplication{state: state} | |||||
} | |||||
func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { | |||||
return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size)} | |||||
} | |||||
// tx is either "key=value" or just arbitrary bytes | |||||
func (app *DummyApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { | |||||
var key, value []byte | |||||
parts := bytes.Split(tx, []byte("=")) | |||||
if len(parts) == 2 { | |||||
key, value = parts[0], parts[1] | |||||
} else { | |||||
key, value = tx, tx | |||||
} | |||||
app.state.db.Set(prefixKey(key), value) | |||||
app.state.Size += 1 | |||||
tags := []cmn.KVPair{ | |||||
{[]byte("app.creator"), []byte("jae")}, | |||||
{[]byte("app.key"), key}, | |||||
} | |||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags} | |||||
} | |||||
func (app *DummyApplication) CheckTx(tx []byte) types.ResponseCheckTx { | |||||
return types.ResponseCheckTx{Code: code.CodeTypeOK} | |||||
} | |||||
func (app *DummyApplication) Commit() types.ResponseCommit { | |||||
// Using a memdb - just return the big endian size of the db | |||||
appHash := make([]byte, 8) | |||||
binary.PutVarint(appHash, app.state.Size) | |||||
app.state.AppHash = appHash | |||||
app.state.Height += 1 | |||||
saveState(app.state) | |||||
return types.ResponseCommit{Data: appHash} | |||||
} | |||||
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { | |||||
if reqQuery.Prove { | |||||
value := app.state.db.Get(prefixKey(reqQuery.Data)) | |||||
resQuery.Index = -1 // TODO make Proof return index | |||||
resQuery.Key = reqQuery.Data | |||||
resQuery.Value = value | |||||
if value != nil { | |||||
resQuery.Log = "exists" | |||||
} else { | |||||
resQuery.Log = "does not exist" | |||||
} | |||||
return | |||||
} else { | |||||
value := app.state.db.Get(prefixKey(reqQuery.Data)) | |||||
resQuery.Value = value | |||||
if value != nil { | |||||
resQuery.Log = "exists" | |||||
} else { | |||||
resQuery.Log = "does not exist" | |||||
} | |||||
return | |||||
} | |||||
} |
@ -0,0 +1,310 @@ | |||||
package dummy | |||||
import ( | |||||
"bytes" | |||||
"io/ioutil" | |||||
"sort" | |||||
"testing" | |||||
"github.com/stretchr/testify/require" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
"github.com/tendermint/tmlibs/log" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/example/code" | |||||
abciserver "github.com/tendermint/abci/server" | |||||
"github.com/tendermint/abci/types" | |||||
) | |||||
func testDummy(t *testing.T, app types.Application, tx []byte, key, value string) { | |||||
ar := app.DeliverTx(tx) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// repeating tx doesn't raise error | |||||
ar = app.DeliverTx(tx) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// make sure query is fine | |||||
resQuery := app.Query(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
}) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
// make sure proof is fine | |||||
resQuery = app.Query(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
Prove: true, | |||||
}) | |||||
require.EqualValues(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
} | |||||
func TestDummyKV(t *testing.T) { | |||||
dummy := NewDummyApplication() | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testDummy(t, dummy, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testDummy(t, dummy, tx, key, value) | |||||
} | |||||
func TestPersistentDummyKV(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
dummy := NewPersistentDummyApplication(dir) | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testDummy(t, dummy, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testDummy(t, dummy, tx, key, value) | |||||
} | |||||
func TestPersistentDummyInfo(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
dummy := NewPersistentDummyApplication(dir) | |||||
InitDummy(dummy) | |||||
height := int64(0) | |||||
resInfo := dummy.Info(types.RequestInfo{}) | |||||
if resInfo.LastBlockHeight != height { | |||||
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) | |||||
} | |||||
// make and apply block | |||||
height = int64(1) | |||||
hash := []byte("foo") | |||||
header := types.Header{ | |||||
Height: int64(height), | |||||
} | |||||
dummy.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil}) | |||||
dummy.EndBlock(types.RequestEndBlock{header.Height}) | |||||
dummy.Commit() | |||||
resInfo = dummy.Info(types.RequestInfo{}) | |||||
if resInfo.LastBlockHeight != height { | |||||
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) | |||||
} | |||||
} | |||||
// add a validator, remove a validator, update a validator | |||||
func TestValUpdates(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
dummy := NewPersistentDummyApplication(dir) | |||||
// init with some validators | |||||
total := 10 | |||||
nInit := 5 | |||||
vals := RandVals(total) | |||||
// iniitalize with the first nInit | |||||
dummy.InitChain(types.RequestInitChain{ | |||||
Validators: vals[:nInit], | |||||
}) | |||||
vals1, vals2 := vals[:nInit], dummy.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
var v1, v2, v3 types.Validator | |||||
// add some validators | |||||
v1, v2 = vals[nInit], vals[nInit+1] | |||||
diff := []types.Validator{v1, v2} | |||||
tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power) | |||||
makeApplyBlock(t, dummy, 1, diff, tx1, tx2) | |||||
vals1, vals2 = vals[:nInit+2], dummy.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
// remove some validators | |||||
v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit] | |||||
v1.Power = 0 | |||||
v2.Power = 0 | |||||
v3.Power = 0 | |||||
diff = []types.Validator{v1, v2, v3} | |||||
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power) | |||||
tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power) | |||||
makeApplyBlock(t, dummy, 2, diff, tx1, tx2, tx3) | |||||
vals1 = append(vals[:nInit-2], vals[nInit+1]) | |||||
vals2 = dummy.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
// update some validators | |||||
v1 = vals[0] | |||||
if v1.Power == 5 { | |||||
v1.Power = 6 | |||||
} else { | |||||
v1.Power = 5 | |||||
} | |||||
diff = []types.Validator{v1} | |||||
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
makeApplyBlock(t, dummy, 3, diff, tx1) | |||||
vals1 = append([]types.Validator{v1}, vals1[1:]...) | |||||
vals2 = dummy.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
} | |||||
func makeApplyBlock(t *testing.T, dummy types.Application, heightInt int, diff []types.Validator, txs ...[]byte) { | |||||
// make and apply block | |||||
height := int64(heightInt) | |||||
hash := []byte("foo") | |||||
header := types.Header{ | |||||
Height: height, | |||||
} | |||||
dummy.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil}) | |||||
for _, tx := range txs { | |||||
if r := dummy.DeliverTx(tx); r.IsErr() { | |||||
t.Fatal(r) | |||||
} | |||||
} | |||||
resEndBlock := dummy.EndBlock(types.RequestEndBlock{header.Height}) | |||||
dummy.Commit() | |||||
valsEqual(t, diff, resEndBlock.ValidatorUpdates) | |||||
} | |||||
// order doesn't matter | |||||
func valsEqual(t *testing.T, vals1, vals2 []types.Validator) { | |||||
if len(vals1) != len(vals2) { | |||||
t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1)) | |||||
} | |||||
sort.Sort(types.Validators(vals1)) | |||||
sort.Sort(types.Validators(vals2)) | |||||
for i, v1 := range vals1 { | |||||
v2 := vals2[i] | |||||
if !bytes.Equal(v1.PubKey, v2.PubKey) || | |||||
v1.Power != v2.Power { | |||||
t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power) | |||||
} | |||||
} | |||||
} | |||||
func makeSocketClientServer(app types.Application, name string) (abcicli.Client, cmn.Service, error) { | |||||
// Start the listener | |||||
socket := cmn.Fmt("unix://%s.sock", name) | |||||
logger := log.TestingLogger() | |||||
server := abciserver.NewSocketServer(socket, app) | |||||
server.SetLogger(logger.With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
// Connect to the socket | |||||
client := abcicli.NewSocketClient(socket, false) | |||||
client.SetLogger(logger.With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
server.Stop() | |||||
return nil, nil, err | |||||
} | |||||
return client, server, nil | |||||
} | |||||
func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, cmn.Service, error) { | |||||
// Start the listener | |||||
socket := cmn.Fmt("unix://%s.sock", name) | |||||
logger := log.TestingLogger() | |||||
gapp := types.NewGRPCApplication(app) | |||||
server := abciserver.NewGRPCServer(socket, gapp) | |||||
server.SetLogger(logger.With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
client := abcicli.NewGRPCClient(socket, true) | |||||
client.SetLogger(logger.With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
server.Stop() | |||||
return nil, nil, err | |||||
} | |||||
return client, server, nil | |||||
} | |||||
func TestClientServer(t *testing.T) { | |||||
// set up socket app | |||||
dummy := NewDummyApplication() | |||||
client, server, err := makeSocketClientServer(dummy, "dummy-socket") | |||||
require.Nil(t, err) | |||||
defer server.Stop() | |||||
defer client.Stop() | |||||
runClientTests(t, client) | |||||
// set up grpc app | |||||
dummy = NewDummyApplication() | |||||
gclient, gserver, err := makeGRPCClientServer(dummy, "dummy-grpc") | |||||
require.Nil(t, err) | |||||
defer gserver.Stop() | |||||
defer gclient.Stop() | |||||
runClientTests(t, gclient) | |||||
} | |||||
func runClientTests(t *testing.T, client abcicli.Client) { | |||||
// run some tests.... | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testClient(t, client, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testClient(t, client, tx, key, value) | |||||
} | |||||
func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) { | |||||
ar, err := app.DeliverTxSync(tx) | |||||
require.NoError(t, err) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// repeating tx doesn't raise error | |||||
ar, err = app.DeliverTxSync(tx) | |||||
require.NoError(t, err) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// make sure query is fine | |||||
resQuery, err := app.QuerySync(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
}) | |||||
require.Nil(t, err) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
// make sure proof is fine | |||||
resQuery, err = app.QuerySync(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
Prove: true, | |||||
}) | |||||
require.Nil(t, err) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
} |
@ -0,0 +1,205 @@ | |||||
package dummy | |||||
import ( | |||||
"bytes" | |||||
"encoding/hex" | |||||
"fmt" | |||||
"strconv" | |||||
"strings" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
dbm "github.com/tendermint/tmlibs/db" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
const ( | |||||
ValidatorSetChangePrefix string = "val:" | |||||
) | |||||
//----------------------------------------- | |||||
var _ types.Application = (*PersistentDummyApplication)(nil) | |||||
type PersistentDummyApplication struct { | |||||
app *DummyApplication | |||||
// validator set | |||||
ValUpdates []types.Validator | |||||
logger log.Logger | |||||
} | |||||
func NewPersistentDummyApplication(dbDir string) *PersistentDummyApplication { | |||||
name := "dummy" | |||||
db, err := dbm.NewGoLevelDB(name, dbDir) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
state := loadState(db) | |||||
return &PersistentDummyApplication{ | |||||
app: &DummyApplication{state: state}, | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
} | |||||
func (app *PersistentDummyApplication) SetLogger(l log.Logger) { | |||||
app.logger = l | |||||
} | |||||
func (app *PersistentDummyApplication) 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 | |||||
} | |||||
func (app *PersistentDummyApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption { | |||||
return app.app.SetOption(req) | |||||
} | |||||
// tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes | |||||
func (app *PersistentDummyApplication) DeliverTx(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) | |||||
} | |||||
// otherwise, update the key-value store | |||||
return app.app.DeliverTx(tx) | |||||
} | |||||
func (app *PersistentDummyApplication) CheckTx(tx []byte) types.ResponseCheckTx { | |||||
return app.app.CheckTx(tx) | |||||
} | |||||
// Commit will panic if InitChain was not called | |||||
func (app *PersistentDummyApplication) Commit() types.ResponseCommit { | |||||
return app.app.Commit() | |||||
} | |||||
func (app *PersistentDummyApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery { | |||||
return app.app.Query(reqQuery) | |||||
} | |||||
// Save the validators in the merkle tree | |||||
func (app *PersistentDummyApplication) 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 *PersistentDummyApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { | |||||
// reset valset changes | |||||
app.ValUpdates = make([]types.Validator, 0) | |||||
return types.ResponseBeginBlock{} | |||||
} | |||||
// Update the validator set | |||||
func (app *PersistentDummyApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { | |||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates} | |||||
} | |||||
//--------------------------------------------- | |||||
// update validators | |||||
func (app *PersistentDummyApplication) Validators() (validators []types.Validator) { | |||||
itr := app.app.state.db.Iterator(nil, nil) | |||||
for ; itr.Valid(); itr.Next() { | |||||
if isValidatorTx(itr.Key()) { | |||||
validator := new(types.Validator) | |||||
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
validators = append(validators, *validator) | |||||
} | |||||
} | |||||
return | |||||
} | |||||
func MakeValSetChangeTx(pubkey []byte, power int64) []byte { | |||||
return []byte(cmn.Fmt("val:%X/%d", pubkey, power)) | |||||
} | |||||
func isValidatorTx(tx []byte) bool { | |||||
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix) | |||||
} | |||||
// format is "val:pubkey1/power1,addr2/power2,addr3/power3"tx | |||||
func (app *PersistentDummyApplication) 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, ensuring its go-crypto encoded | |||||
pubkey, err := hex.DecodeString(pubkeyS) | |||||
if err != nil { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Pubkey (%s) is invalid hex", pubkeyS)} | |||||
} | |||||
/*_, err = crypto.PubKeyFromBytes(pubkey) | |||||
if err != nil { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Pubkey (%X) is invalid go-crypto encoded", pubkey)} | |||||
}*/ | |||||
// 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.Validator{pubkey, power}) | |||||
} | |||||
// add, update, or remove a validator | |||||
func (app *PersistentDummyApplication) updateValidator(v types.Validator) types.ResponseDeliverTx { | |||||
key := []byte("val:" + string(v.PubKey)) | |||||
if v.Power == 0 { | |||||
// remove validator | |||||
if !app.app.state.db.Has(key) { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeUnauthorized, | |||||
Log: fmt.Sprintf("Cannot remove non-existent validator %X", key)} | |||||
} | |||||
app.app.state.db.Delete(key) | |||||
} 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)} | |||||
} | |||||
app.app.state.db.Set(key, value.Bytes()) | |||||
} | |||||
// we only update the changes array if we successfully updated the tree | |||||
app.ValUpdates = append(app.ValUpdates, v) | |||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK} | |||||
} |