diff --git a/client/grpc_client.go b/client/grpc_client.go index f7a80268..36589322 100644 --- a/client/grpc_client.go +++ b/client/grpc_client.go @@ -265,10 +265,10 @@ func (cli *grpcClient) FlushSync() error { func (cli *grpcClient) InfoSync() (types.Result, *types.TMSPInfo, *types.LastBlockInfo, *types.ConfigInfo) { reqres := cli.InfoAsync() if res := cli.checkErrGetResult(); res.IsErr() { - return res + return res, nil, nil, nil } resp := reqres.Response.GetInfo() - return types.NewResultOK([]byte(resp.Info), LOG), r.TmspInfo, r.LastBlock, r.Config + return types.NewResultOK([]byte(resp.Info), LOG), resp.TmspInfo, resp.LastBlock, resp.Config } func (cli *grpcClient) SetOptionSync(key string, value string) (res types.Result) { diff --git a/cmd/dummy/main.go b/cmd/dummy/main.go index 8efa69e6..8a1465c4 100644 --- a/cmd/dummy/main.go +++ b/cmd/dummy/main.go @@ -6,16 +6,26 @@ import ( . "github.com/tendermint/go-common" "github.com/tendermint/tmsp/example/dummy" "github.com/tendermint/tmsp/server" + "github.com/tendermint/tmsp/types" ) func main() { addrPtr := flag.String("addr", "tcp://0.0.0.0:46658", "Listen address") tmspPtr := flag.String("tmsp", "socket", "socket | grpc") + persistencePtr := flag.String("persist", "", "directory to use for a database") flag.Parse() + // Create the application - in memory or persisted to disk + var app types.Application + if *persistencePtr == "" { + app = dummy.NewDummyApplication() + } else { + app = dummy.NewPersistentDummyApplication(*persistencePtr) + } + // Start the listener - _, err := server.NewServer(*addrPtr, *tmspPtr, dummy.NewDummyApplication()) + _, err := server.NewServer(*addrPtr, *tmspPtr, app) if err != nil { Exit(err.Error()) } diff --git a/example/dummy/dummy.go b/example/dummy/dummy.go index 5bf52a78..0c6a9089 100644 --- a/example/dummy/dummy.go +++ b/example/dummy/dummy.go @@ -50,6 +50,7 @@ func (app *DummyApplication) Commit() types.Result { func (app *DummyApplication) Query(query []byte) types.Result { index, value, exists := app.state.Get(query) + resStr := Fmt("Index=%v value=%v exists=%v", index, string(value), exists) return types.NewResultOK([]byte(resStr), "") } diff --git a/example/dummy/log.go b/example/dummy/log.go new file mode 100644 index 00000000..8571fa01 --- /dev/null +++ b/example/dummy/log.go @@ -0,0 +1,7 @@ +package dummy + +import ( + "github.com/tendermint/go-logger" +) + +var log = logger.New("module", "dummy") diff --git a/example/dummy/persistent_dummy.go b/example/dummy/persistent_dummy.go new file mode 100644 index 00000000..60e677e9 --- /dev/null +++ b/example/dummy/persistent_dummy.go @@ -0,0 +1,117 @@ +package dummy + +import ( + "bytes" + + . "github.com/tendermint/go-common" + dbm "github.com/tendermint/go-db" + "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" + "github.com/tendermint/tmsp/types" +) + +//----------------------------------------- +// persist the last block info + +var lastBlockKey = []byte("lastblock") + +// Get the last block from the db +func LoadLastBlock(db dbm.DB) (lastBlock types.LastBlockInfo) { + buf := db.Get(lastBlockKey) + if len(buf) != 0 { + r, n, err := bytes.NewReader(buf), new(int), new(error) + wire.ReadBinaryPtr(&lastBlock, r, 0, n, err) + if *err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err)) + } + // TODO: ensure that buf is completely read. + } + + return lastBlock +} + +func SaveLastBlock(db dbm.DB, lastBlock types.LastBlockInfo) { + log.Notice("Saving block", "height", lastBlock.BlockHeight, "hash", lastBlock.BlockHash, "root", lastBlock.AppHash) + buf, n, err := new(bytes.Buffer), new(int), new(error) + wire.WriteBinary(lastBlock, buf, n, err) + if *err != nil { + // TODO + PanicCrisis(*err) + } + db.Set(lastBlockKey, buf.Bytes()) +} + +//----------------------------------------- + +type PersistentDummyApplication struct { + app *DummyApplication + db dbm.DB +} + +func NewPersistentDummyApplication(dbDir string) *PersistentDummyApplication { + db := dbm.NewDB("dummy", "leveldb", dbDir) + lastBlock := LoadLastBlock(db) + + stateTree := merkle.NewIAVLTree( + 0, + db, + ) + stateTree.Load(lastBlock.AppHash) + + log.Notice("Loaded state", "block", lastBlock.BlockHeight, "root", stateTree.Hash()) + + return &PersistentDummyApplication{ + app: &DummyApplication{state: stateTree}, + db: db, + } +} + +func (app *PersistentDummyApplication) Info() (string, *types.TMSPInfo, *types.LastBlockInfo, *types.ConfigInfo) { + s, _, _, _ := app.app.Info() + lastBlock := LoadLastBlock(app.db) + return s, nil, &lastBlock, nil +} + +func (app *PersistentDummyApplication) SetOption(key string, value string) (log string) { + return app.app.SetOption(key, value) +} + +// tx is either "key=value" or just arbitrary bytes +func (app *PersistentDummyApplication) AppendTx(tx []byte) types.Result { + return app.app.AppendTx(tx) +} + +func (app *PersistentDummyApplication) CheckTx(tx []byte) types.Result { + return app.app.CheckTx(tx) +} + +func (app *PersistentDummyApplication) Commit() types.Result { + // Save + hash := app.app.state.Save() + log.Info("Saved state", "root", hash) + return types.NewResultOK(hash, "") +} + +func (app *PersistentDummyApplication) Query(query []byte) types.Result { + return app.app.Query(query) +} + +func (app *PersistentDummyApplication) InitChain(validators []*types.Validator) { + return +} + +func (app *PersistentDummyApplication) BeginBlock(header *types.Header) { + // we commit the previous block state on BeginBlock because thats + // when we get the prev block hash and the app hash + lastBlock := types.LastBlockInfo{ + BlockHeight: header.Height - 1, + BlockHash: header.LastBlockHash, + AppHash: header.AppHash, + } + SaveLastBlock(app.db, lastBlock) +} + +func (app *PersistentDummyApplication) EndBlock(height uint64) (diffs []*types.Validator) { + return nil +}