diff --git a/CHANGELOG.md b/CHANGELOG.md index 6789f720eb926532663df8772cd6c7b1f6e6df4e..fd017681bf46c3065c2c6219b59ee2fe9fe5aa98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,50 @@ # Hyperledger Burrow Changelog +## Version 0.19.0 +This is a major (pre-1.0.0) release that brings upgrades, safety improvements, cloud configuration, and GRPC endpoints to Burrow. + +#### Breaking changes +In addition to breaking changes associated with Tendermint (see their changelog): +- State checkpointing logic has changed which has we load based on blockchain +- Event format has changed over rpc/V0 see execution/events/ package +- On-disk keys format has change from monax-keys to be more standard burrow keys +- Address format has been changed (by Tendermint and we have followed suite) - conversion is possible but simpler to regenerated keys + +#### Features +- Tendermint 0.20.0 +- Implemented EVM opcodes: REVERT, INVALID, SHL, SAR, SHR, RETURNDATACOPY, RETURNDATASIZE +- Add config templating with burrow configure --config-template-in --config-out +- Add config templates for kubernetes +- Integrate monax-keys as internal (default) or standalone keys service, key gen exposed over CLI +- Use GRPC for keys +- Add GRPC service for Transactor and Events +- Store ExecutionEvent by height and index in merkle tree state +- Add historical query for all time with GetEvents +- Add streaming GRPC service for ExecutionEvents with query language over tags +- Add metadata to ExecutionEvents +- Add BlockExplorer CLI for forensics +- Expose reason for REVERT +- Add last_block_info healthcheck endpoint to rpc/TM +- +#### Improvements +- Implement checkpointing when saving application and blockchain state in commit - interrupted commit rolls burrow back to last block whereon it can catch up using Tendermint +- Maintain separate read-only tree in state so that long-running RPC request cannot block writes +- Improve state safety +- Improved input account server-side-signing +- Increase subscription reap time on rpc/V0 to 20 seconds +- Reorganise CLI +- Improve internal serialisation +- Refactor and modularise execution logic + +#### Bug fixes +- Fix address generation from bytes mismatch + + + +## Version 0.18.1 +This is a minor release including: +- Introduce InputAccount param for RPC/v0 for integration in JS libs +- Resolve some issues with RPC/tm tests swallowing timeouts and not dealing with reordered events + ## Version 0.18.0 This is an extremely large release in terms of lines of code changed addressing several years of technical debt. Despite this efforts were made to maintain external interfaces as much as possible and an extended period of stabilisation has taken place on develop. diff --git a/Makefile b/Makefile index 1d51945e3ed46a87ef36d4cf84055a41e5d68dd3..c7b33d452dcecc99acc5741ef1b0b5e66c21ee77 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ install_db: build_db # build burrow-client .PHONY: build_client -build_client: commit_hash protobuf +build_client: commit_hash go build -ldflags "-extldflags '-static' \ -X github.com/hyperledger/burrow/project.commit=$(shell cat commit_hash.txt)" \ -o ${REPO}/bin/burrow-client ./client/cmd/burrow-client diff --git a/NOTES.md b/NOTES.md index 22d88dc460f8fb6488e47a85e62704b89225ec1f..01b0bdadcf5980aac68eb80cf7e4604c45f18df4 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,47 +1,39 @@ -This is an extremely large release in terms of lines of code changed addressing several years of technical debt. Despite this efforts were made to maintain external interfaces as much as possible and an extended period of stabilisation has taken place on develop. +This is a major (pre-1.0.0) release that brings upgrades, safety improvements, cloud configuration, and GRPC endpoints to Burrow. -A major strand of work has been in condensing previous Monax tooling spread across multiple repos into just two. The Hyperledger Burrow repo and [Bosmarmot](http://github.com/monax/bosmarmot). Burrow is now able to generate chains (replacing 'monax chains make') with 'burrow spec' and 'burrow configure'. Our 'EPM' contract deployment and testing tool, our javascript libraries, compilers, and monax-keys are avaiable in Bosmarmot (the former in the 'bos' tool). Work is underway to pull monax-keys into the Burrow project, and we will continue to make Burrow as self-contained as possible. +#### Breaking changes +In addition to breaking changes associated with Tendermint (see their changelog): +- State checkpointing logic has changed which has we load based on blockchain +- Event format has changed over rpc/V0 see execution/events/ package +- On-disk keys format has change from monax-keys to be more standard burrow keys +- Address format has been changed (by Tendermint and we have followed suite) - conversion is possible but simpler to regenerated keys #### Features -- Substantial support for latest EVM and solidity 0.4.21+ (missing some opcodes that will be added shortly - see known issues) -- Tendermint 0.18.0 -- All signing through monax-keys KeyClient connection (preparation for HSM and GPG based signing daemon) -- Address-based signing (Burrow acts as delegate when you send transact, transactAndHold, send, sendAndHold, and transactNameReg a parameter including input_account (hex address) instead of priv_key. -- Provide sequential signing when using transact family methods (above) - allowing 100s Tx per second with the same input account -- Genesis making, config making, and key generation through 'burrow spec' and 'burrow configure' -- Logging configuration language and text/template for output -- Improved CLI UX and framework (mow.cli) -- Improved configuration - - -#### Internal Improvements -- Refactored execution and provide interfaces for executor -- Segregate EVM and blockchain state to act as better library -- Panic recovery on TX execution -- Stricter interface boundaries and immutability of core objects by default -- Replace broken BlockCache with universal StateCache that doesn't write directly to DB -- All dependencies upgraded, notably: tendermint/IAVL 0.7.0 -- Use Go dep instead of glide -- PubSub event hub with query language -- Heavily optimised logging -- PPROF profiling server option -- Additional tests in multiple packages including v0 RPC and concurrency-focussed test -- Use Tendermint verifier for PrivValidator -- Use monax/relic for project history -- Run bosmarmot integration tests in CI -- Update documentation -- Numerous maintainability, naming, and aesthetic code improvements +- Tendermint 0.20.0 +- Implemented EVM opcodes: REVERT, INVALID, SHL, SAR, SHR, RETURNDATACOPY, RETURNDATASIZE +- Add config templating with burrow configure --config-template-in --config-out +- Add config templates for kubernetes +- Integrate monax-keys as internal (default) or standalone keys service, key gen exposed over CLI +- Use GRPC for keys +- Add GRPC service for Transactor and Events +- Store ExecutionEvent by height and index in merkle tree state +- Add historical query for all time with GetEvents +- Add streaming GRPC service for ExecutionEvents with query language over tags +- Add metadata to ExecutionEvents +- Add BlockExplorer CLI for forensics +- Expose reason for REVERT +- Add last_block_info healthcheck endpoint to rpc/TM +- +#### Improvements +- Implement checkpointing when saving application and blockchain state in commit - interrupted commit rolls burrow back to last block whereon it can catch up using Tendermint +- Maintain separate read-only tree in state so that long-running RPC request cannot block writes +- Improve state safety +- Improved input account server-side-signing +- Increase subscription reap time on rpc/V0 to 20 seconds +- Reorganise CLI +- Improve internal serialisation +- Refactor and modularise execution logic #### Bug fixes -- Fix memory leak in BlockCache -- Fix CPU usage in BlockCache -- Fix SIGNEXTEND for negative numbers -- Fix multiple execution level panics -- Make Transactor work during tendermint recheck +- Fix address generation from bytes mismatch -#### Known issues -- Documentation rot - some effort has been made to update documentation to represent the current state but in some places it has slipped help can be found (and would be welcomed) on: [Hyperledger Burrow Chat](https://chat.hyperledger.org/channel/burrow) -- Missing support for: RETURNDATACOPY and RETURNDATASIZE https://github.com/hyperledger/burrow/issues/705 (coming very soon) -- Missing support for: INVALID https://github.com/hyperledger/burrow/issues/705 (coming very soon) -- Missing support for: REVERT https://github.com/hyperledger/burrow/issues/600 (coming very soon) diff --git a/execution/events/event.go b/execution/events/event.go index e5f7f5edb3773ba0608164d216bd1ebda9b8d93f..5830a1298cbd7894c5cf209fd07880f9eb3570b2 100644 --- a/execution/events/event.go +++ b/execution/events/event.go @@ -39,6 +39,27 @@ func (ev *Event) Key() Key { return ev.Header.Key() } +// Performs a shallow copy of Event +func (ev *Event) Copy() *Event { + h := *ev.Header + evCopy := Event{ + Header: &h, + } + if ev.Tx != nil { + tx := *ev.Tx + evCopy.Tx = &tx + } + if ev.Call != nil { + call := *ev.Call + evCopy.Call = &call + } + if ev.Log != nil { + log := *ev.Log + evCopy.Log = &log + } + return &evCopy +} + func (ev *Event) Encode() ([]byte, error) { return cdc.MarshalBinary(ev) } diff --git a/execution/state.go b/execution/state.go index 07409c436ab2cdfb3fc03cb7123e316c2213368f..8418c836fdadca65c923fd81b71010451c573e86 100644 --- a/execution/state.go +++ b/execution/state.go @@ -321,6 +321,12 @@ func (ws *writeState) Publish(ctx context.Context, msg interface{}, tags event.T ws.state.eventKeyHighWatermark, exeEvent) } ws.state.eventKeyHighWatermark = key + if exeEvent.Tx != nil { + // Don't serialise the tx (for now) we should normalise and store against tx hash + exeEvent = exeEvent.Copy() + // The header still contains the tx hash + exeEvent.Tx.Tx = nil + } bs, err := exeEvent.Encode() if err != nil { return err diff --git a/project/history.go b/project/history.go index f740a0a6f49563535470f9bb63f844881d3b3f0b..54faa09739f656351df317b9f11ae5db24257ef4 100644 --- a/project/history.go +++ b/project/history.go @@ -28,6 +28,46 @@ func FullVersion() string { // To cut a new release add a release to the front of this slice then run the // release tagging script: ./scripts/tag_release.sh var History relic.ImmutableHistory = relic.NewHistory("Hyperledger Burrow").MustDeclareReleases( + "0.19.0", + `This is a major (pre-1.0.0) release that brings upgrades, safety improvements, cloud configuration, and GRPC endpoints to Burrow. + +#### Breaking changes +In addition to breaking changes associated with Tendermint (see their changelog): +- State checkpointing logic has changed which has we load based on blockchain +- Event format has changed over rpc/V0 see execution/events/ package +- On-disk keys format has change from monax-keys to be more standard burrow keys +- Address format has been changed (by Tendermint and we have followed suite) - conversion is possible but simpler to regenerated keys + +#### Features +- Tendermint 0.20.0 +- Implemented EVM opcodes: REVERT, INVALID, SHL, SAR, SHR, RETURNDATACOPY, RETURNDATASIZE +- Add config templating with burrow configure --config-template-in --config-out +- Add config templates for kubernetes +- Integrate monax-keys as internal (default) or standalone keys service, key gen exposed over CLI +- Use GRPC for keys +- Add GRPC service for Transactor and Events +- Store ExecutionEvent by height and index in merkle tree state +- Add historical query for all time with GetEvents +- Add streaming GRPC service for ExecutionEvents with query language over tags +- Add metadata to ExecutionEvents +- Add BlockExplorer CLI for forensics +- Expose reason for REVERT +- Add last_block_info healthcheck endpoint to rpc/TM +- +#### Improvements +- Implement checkpointing when saving application and blockchain state in commit - interrupted commit rolls burrow back to last block whereon it can catch up using Tendermint +- Maintain separate read-only tree in state so that long-running RPC request cannot block writes +- Improve state safety +- Improved input account server-side-signing +- Increase subscription reap time on rpc/V0 to 20 seconds +- Reorganise CLI +- Improve internal serialisation +- Refactor and modularise execution logic + +#### Bug fixes +- Fix address generation from bytes mismatch + +`, "0.18.1", `This is a minor release including: - Introduce InputAccount param for RPC/v0 for integration in JS libs diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index 99941c88be1d16fecd584cd774427633e293a84f..1d21c21a1298eaaeac9a7fb4354856e486ba3a3e 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -187,22 +187,22 @@ func testWithHTTPClient(t *testing.T, cl client.HTTPClient) { val := "acbd" got, err := echoViaHTTP(cl, val) require.Nil(t, err) - assert.Equal(t, got, val) + assert.Equal(t, val, got) val2 := randBytes(t) got2, err := echoBytesViaHTTP(cl, val2) require.Nil(t, err) - assert.Equal(t, got2, val2) + assert.Equal(t, val2, got2) val3 := cmn.HexBytes(randBytes(t)) got3, err := echoDataBytesViaHTTP(cl, val3) require.Nil(t, err) - assert.Equal(t, got3, val3) + assert.Equal(t, val3, got3) val4 := rand.Intn(10000) got4, err := echoIntViaHTTP(cl, val4) require.Nil(t, err) - assert.Equal(t, got4, val4) + assert.Equal(t, val4, got4) } func echoViaWS(cl *client.WSClient, val string) (string, error) { diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 31892c0fcb44ea49622b27ac112593a1afae9571..506425b725eaa372428e9fb5f165a93ac80aa6d7 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -311,37 +311,40 @@ func jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { } func nonJSONToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) { - isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) - isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") expectingString := ty.Kind() == reflect.String - expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8 + expectingBytes := (ty.Kind() == reflect.Slice || ty.Kind() == reflect.Array) && ty.Elem().Kind() == reflect.Uint8 - if isHexString { - if !expectingString && !expectingByteSlice { - err := errors.Errorf("Got a hex string arg, but expected '%s'", - ty.Kind().String()) - return reflect.ValueOf(nil), err, false - } + isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) - var value []byte - value, err := hex.DecodeString(arg[2:]) - if err != nil { - return reflect.ValueOf(nil), err, false - } - if ty.Kind() == reflect.String { - return reflect.ValueOf(string(value)), nil, true - } - return reflect.ValueOf([]byte(value)), nil, true + // Throw quoted strings at JSON parser later... because it always has... + if expectingString && !isQuotedString { + return reflect.ValueOf(arg), nil, true } - if isQuotedString && expectingByteSlice { - v := reflect.New(reflect.TypeOf("")) - err := json.Unmarshal([]byte(arg), v.Interface()) + if expectingBytes { + if isQuotedString { + rv := reflect.New(ty) + err := json.Unmarshal([]byte(arg), rv.Interface()) + if err != nil { + return reflect.ValueOf(nil), err, false + } + return rv.Elem(), nil, true + } + if strings.HasPrefix(strings.ToLower(arg), "0x") { + arg = arg[2:] + } + var value []byte + value, err := hex.DecodeString(arg) if err != nil { return reflect.ValueOf(nil), err, false } - v = v.Elem() - return reflect.ValueOf([]byte(v.String())), nil, true + if ty.Kind() == reflect.Array { + // Gives us an empty array of the right type + rv := reflect.New(ty).Elem() + reflect.Copy(rv, reflect.ValueOf(value)) + return rv, nil, true + } + return reflect.ValueOf(value), nil, true } return reflect.ValueOf(nil), nil, false diff --git a/rpc/lib/server/handlers_test.go b/rpc/lib/server/handlers_test.go index be66d56ceb08b7d7a9cf15f1dd89673b44fbe792..1c7ce385771752e4b4e8abdeec6815061b8114d9 100644 --- a/rpc/lib/server/handlers_test.go +++ b/rpc/lib/server/handlers_test.go @@ -37,8 +37,8 @@ func TestRPCParams(t *testing.T) { wantErr string }{ // bad - {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found"}, - {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found"}, + {`{"jsonrpc": "2.0", "id": "0"}`, "Method Not Found"}, + {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method Not Found"}, {`{"method": "c", "id": "0", "params": a}`, "invalid character"}, {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1"}, {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "of type int"}, @@ -56,7 +56,6 @@ func TestRPCParams(t *testing.T) { mux.ServeHTTP(rec, req) res := rec.Result() // Always expecting back a JSONRPCResponse - assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) blob, err := ioutil.ReadAll(res.Body) if err != nil { t.Errorf("#%d: err reading body: %v", i, err) @@ -66,7 +65,7 @@ func TestRPCParams(t *testing.T) { recv := new(types.RPCResponse) assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) - + assert.Equal(t, recv.Error.HTTPStatusCode(), res.StatusCode, "#%d: status should match error code", i) if tt.wantErr == "" { assert.Nil(t, recv.Error, "#%d: not expecting an error", i) } else { diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index e0734304feadf7c8f7f0f741642a8481c786d7d1..e080ff269f843260b44bf41f2999cebc82156211 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -46,7 +46,7 @@ func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) { panic(err) } w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(res.Error.HTTPStatusCode()) w.Write(jsonBytes) // nolint: errcheck, gas } diff --git a/rpc/lib/types/error_codes.go b/rpc/lib/types/error_codes.go new file mode 100644 index 0000000000000000000000000000000000000000..4ef91dd26ab3e31350c3045452b23dd920161059 --- /dev/null +++ b/rpc/lib/types/error_codes.go @@ -0,0 +1,52 @@ +package types + +import ( + "net/http" + "strconv" +) + +// From JSONRPC 2.0 spec +type RPCErrorCode int + +const ( + RPCErrorCodeParseError RPCErrorCode = -32700 + RPCErrorCodeInvalidRequest RPCErrorCode = -32600 + RPCErrorCodeMethodNotFound RPCErrorCode = -32601 + RPCErrorCodeInvalidParams RPCErrorCode = -32602 + RPCErrorCodeInternalError RPCErrorCode = -32603 + RPCErrorCodeServerError RPCErrorCode = -32000 +) + +func (code RPCErrorCode) String() string { + switch code { + case RPCErrorCodeParseError: + return "Parse Error" + case RPCErrorCodeInvalidRequest: + return "Parse Error" + case RPCErrorCodeMethodNotFound: + return "Method Not Found" + case RPCErrorCodeInvalidParams: + return "Invalid Params" + case RPCErrorCodeInternalError: + return "Internal Error" + case RPCErrorCodeServerError: + return "Server Error" + default: + return strconv.FormatInt(int64(code), 10) + } +} + +func (code RPCErrorCode) HTTPStatusCode() int { + switch code { + case RPCErrorCodeInvalidRequest: + return http.StatusBadRequest + case RPCErrorCodeMethodNotFound: + return http.StatusMethodNotAllowed + default: + return http.StatusInternalServerError + } +} + +func (code RPCErrorCode) Error() string { + return code.String() +} diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go index 42a7a219540c8305f51a940820d0e3870f54eceb..8ff3e6c65f0d29c1789ae845d895bf3b3c1dc2e2 100644 --- a/rpc/lib/types/types.go +++ b/rpc/lib/types/types.go @@ -71,9 +71,9 @@ func ArrayToRequest(id string, method string, params []interface{}) (RPCRequest, // RESPONSE type RPCError struct { - Code int `json:"code"` - Message string `json:"message"` - Data string `json:"data,omitempty"` + Code RPCErrorCode `json:"code"` + Message string `json:"message"` + Data string `json:"data,omitempty"` } func (err RPCError) Error() string { @@ -84,6 +84,13 @@ func (err RPCError) Error() string { return fmt.Sprintf(baseFormat, err.Code, err.Message) } +func (err *RPCError) HTTPStatusCode() int { + if err == nil { + return 200 + } + return err.Code.HTTPStatusCode() +} + type RPCResponse struct { JSONRPC string `json:"jsonrpc"` ID string `json:"id"` @@ -106,11 +113,11 @@ func NewRPCSuccessResponse(id string, res interface{}) RPCResponse { return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg} } -func NewRPCErrorResponse(id string, code int, msg string, data string) RPCResponse { +func NewRPCErrorResponse(id string, code RPCErrorCode, data string) RPCResponse { return RPCResponse{ JSONRPC: "2.0", ID: id, - Error: &RPCError{Code: code, Message: msg, Data: data}, + Error: &RPCError{Code: code, Message: code.String(), Data: data}, } } @@ -122,27 +129,27 @@ func (resp RPCResponse) String() string { } func RPCParseError(id string, err error) RPCResponse { - return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error()) + return NewRPCErrorResponse(id, RPCErrorCodeParseError, err.Error()) } func RPCInvalidRequestError(id string, err error) RPCResponse { - return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error()) + return NewRPCErrorResponse(id, RPCErrorCodeInvalidRequest, err.Error()) } func RPCMethodNotFoundError(id string) RPCResponse { - return NewRPCErrorResponse(id, -32601, "Method not found", "") + return NewRPCErrorResponse(id, RPCErrorCodeMethodNotFound, "") } func RPCInvalidParamsError(id string, err error) RPCResponse { - return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error()) + return NewRPCErrorResponse(id, RPCErrorCodeInvalidParams, err.Error()) } func RPCInternalError(id string, err error) RPCResponse { - return NewRPCErrorResponse(id, -32603, "Internal error", err.Error()) + return NewRPCErrorResponse(id, RPCErrorCodeInternalError, err.Error()) } func RPCServerError(id string, err error) RPCResponse { - return NewRPCErrorResponse(id, -32000, "Server error", err.Error()) + return NewRPCErrorResponse(id, RPCErrorCodeServerError, err.Error()) } //---------------------------------------- diff --git a/rpc/lib/types/types_test.go b/rpc/lib/types/types_test.go index 1227bbd149a8c6996d4acf6800d1566fd7ba53cc..dd0e0316d5250623f31fde9fc6f3623c26ee30f1 100644 --- a/rpc/lib/types/types_test.go +++ b/rpc/lib/types/types_test.go @@ -23,12 +23,12 @@ func TestResponses(t *testing.T) { d := RPCParseError("1", errors.New("Hello world")) e, _ := json.Marshal(d) - f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}` + f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse Error","data":"Hello world"}}` assert.Equal(string(f), string(e)) g := RPCMethodNotFoundError("2") h, _ := json.Marshal(g) - i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method not found"}}` + i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method Not Found"}}` assert.Equal(string(h), string(i)) } diff --git a/rpc/result.go b/rpc/result.go index 3159690e33135a908f76daa1e93402f2228f9e6a..44a61c1d7186c87c06b77555f731352c9b66fc8c 100644 --- a/rpc/result.go +++ b/rpc/result.go @@ -18,6 +18,8 @@ import ( "encoding/json" "fmt" + "time" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" @@ -120,6 +122,12 @@ type ResultStatus struct { NodeVersion string } +type ResultLastBlockInfo struct { + LastBlockHeight uint64 + LastBlockTime time.Time + LastBlockHash binary.HexBytes +} + type ResultChainId struct { ChainName string ChainId string diff --git a/rpc/result_test.go b/rpc/result_test.go index 62ea5b510fd0c4404108091e5ae8b16460194b9d..ade8a61574da9b417e4b8ac8e3c4b3587f030a8e 100644 --- a/rpc/result_test.go +++ b/rpc/result_test.go @@ -18,7 +18,12 @@ import ( "encoding/json" "testing" + "time" + + "fmt" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/txs" @@ -166,3 +171,15 @@ func TestResultDumpConsensusState(t *testing.T) { require.NoError(t, err) assert.Equal(t, string(bs), string(bsOut)) } + +func TestResultLastBlockInfo(t *testing.T) { + res := &ResultLastBlockInfo{ + LastBlockTime: time.Now(), + LastBlockHash: binary.HexBytes{3, 4, 5, 6}, + LastBlockHeight: 2343, + } + bs, err := json.Marshal(res) + require.NoError(t, err) + fmt.Println(string(bs)) + +} diff --git a/rpc/service.go b/rpc/service.go index 81afee6098b3b6b0f40eb08b48f2421400e84a6a..0863a65a4e4678ecd2d9aff64a25ad37d2a10422 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -18,6 +18,10 @@ import ( "context" "fmt" + "time" + + "encoding/json" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" @@ -418,3 +422,33 @@ func (s *Service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error PrivateAccount: acm.AsConcretePrivateAccount(privateAccount), }, nil } + +func (s *Service) LastBlockInfo(blockWithin string) (*ResultLastBlockInfo, error) { + res := &ResultLastBlockInfo{ + LastBlockHeight: s.blockchain.LastBlockHeight(), + LastBlockHash: s.blockchain.LastBlockHash(), + LastBlockTime: s.blockchain.LastBlockTime(), + } + if blockWithin == "" { + return res, nil + } + duration, err := time.ParseDuration(blockWithin) + if err != nil { + return nil, fmt.Errorf("could not parse blockWithin duration to determine whether to throw error: %v", err) + } + // Take neg abs in case caller is counting backwards (not we add later) + if duration > 0 { + duration = -duration + } + blockTimeThreshold := time.Now().Add(duration) + if res.LastBlockTime.After(blockTimeThreshold) { + // We've created blocks recently enough + return res, nil + } + resJSON, err := json.Marshal(res) + if err != nil { + resJSON = []byte("<error: could not marshal last block info>") + } + return nil, fmt.Errorf("no block committed within the last %s (cutoff: %s), last block info: %s", + blockWithin, blockTimeThreshold.Format(time.RFC3339), string(resJSON)) +} diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 6e892df8e645175fda3f3e5dd93f4fd57e963a6f..017cf29c9bf88c4114f472d2825bc611532cdd14 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -55,6 +55,9 @@ const ( // Private keys and signing GeneratePrivateAccount = "unsafe/gen_priv_account" SignTx = "unsafe/sign_tx" + + // Health check + LastBlockInfo = "last_block_info" ) const SubscriptionTimeout = 5 * time.Second @@ -175,6 +178,7 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server. // Private account GeneratePrivateAccount: server.NewRPCFunc(service.GeneratePrivateAccount, ""), + LastBlockInfo: server.NewRPCFunc(service.LastBlockInfo, "block_within"), } } diff --git a/rpc/v0/json_service.go b/rpc/v0/json_service.go index bb4facea92691dd0acc07499673a955f310bf7dd..41595de660ede4e892f287403ca9a76098c4c4f3 100644 --- a/rpc/v0/json_service.go +++ b/rpc/v0/json_service.go @@ -132,8 +132,7 @@ func (js *JSONService) Process(r *http.Request, w http.ResponseWriter) { if handler, ok := js.defaultHandlers[mName]; ok { js.logger.TraceMsg("Request received", "id", req.Id, - "method", req.Method, - "params", string(req.Params)) + "method", req.Method) resp, errCode, err := handler(req, w) if err != nil { js.writeError(err.Error(), req.Id, errCode, w) diff --git a/scripts/deps/bos.sh b/scripts/deps/bos.sh index 8adb3069550a8f745519ff95274dd962af560f93..778e5fa97dd2503625b46b991661844b90b10087 100755 --- a/scripts/deps/bos.sh +++ b/scripts/deps/bos.sh @@ -2,5 +2,4 @@ # The git revision of Bosmarmot/bos we will build and install into ./bin/ for integration tests -echo "ca5fda25977a5e445561aff2078ef398e9962cc0" - +echo "a8162471f2dfd1cae183f2aa2614fdd3dc1d5155" \ No newline at end of file