package kvstore import ( "bytes" "encoding/binary" "encoding/json" "fmt" dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/abci/example/code" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/kv" "github.com/tendermint/tendermint/version" ) var ( stateKey = []byte("stateKey") kvPairPrefixKey = []byte("kvPairKey:") ProtocolVersion version.Protocol = 0x1 ) 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 { var state State state.db = db stateBytes, err := db.Get(stateKey) if err != nil { panic(err) } if len(stateBytes) == 0 { return state } err = json.Unmarshal(stateBytes, &state) if err != nil { panic(err) } 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 = (*Application)(nil) type Application struct { types.BaseApplication state State RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight) } func NewApplication() *Application { state := loadState(dbm.NewMemDB()) return &Application{state: state} } func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { return types.ResponseInfo{ Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), Version: version.ABCIVersion, AppVersion: ProtocolVersion.Uint64(), LastBlockHeight: app.state.Height, LastBlockAppHash: app.state.AppHash, } } // tx is either "key=value" or just arbitrary bytes func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { var key, value []byte parts := bytes.Split(req.Tx, []byte("=")) if len(parts) == 2 { key, value = parts[0], parts[1] } else { key, value = req.Tx, req.Tx } app.state.db.Set(prefixKey(key), value) app.state.Size++ events := []types.Event{ { Type: "app", Attributes: []kv.Pair{ {Key: []byte("creator"), Value: []byte("Cosmoshi Netowoko")}, {Key: []byte("key"), Value: key}, }, }, } return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events} } func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} } func (app *Application) 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++ saveState(app.state) resp := types.ResponseCommit{Data: appHash} if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks { resp.RetainHeight = app.state.Height - app.RetainBlocks + 1 } return resp } // Returns an associated value or nil if missing. func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { if reqQuery.Prove { value, err := app.state.db.Get(prefixKey(reqQuery.Data)) if err != nil { panic(err) } if value == nil { resQuery.Log = "does not exist" } else { resQuery.Log = "exists" } resQuery.Index = -1 // TODO make Proof return index resQuery.Key = reqQuery.Data resQuery.Value = value resQuery.Height = app.state.Height return } resQuery.Key = reqQuery.Data value, err := app.state.db.Get(prefixKey(reqQuery.Data)) if err != nil { panic(err) } if value == nil { resQuery.Log = "does not exist" } else { resQuery.Log = "exists" } resQuery.Value = value resQuery.Height = app.state.Height return resQuery }