@ -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} | |||
} |