From ca7dbea05baffb7363c0d7023d496d08c2109f7a Mon Sep 17 00:00:00 2001 From: JayT106 Date: Wed, 14 Apr 2021 08:05:27 -0400 Subject: [PATCH] Set cache control in the HTTP-RPC response header (#6265) --- CHANGELOG_PENDING.md | 3 +- light/proxy/routes.go | 48 ++++++++--------- rpc/core/routes.go | 56 ++++++++++---------- rpc/jsonrpc/doc.go | 2 +- rpc/jsonrpc/jsonrpc_test.go | 8 +-- rpc/jsonrpc/server/http_json_handler.go | 33 ++++++++++-- rpc/jsonrpc/server/http_json_handler_test.go | 35 +++++++++++- rpc/jsonrpc/server/http_server.go | 8 ++- rpc/jsonrpc/server/http_server_test.go | 4 +- rpc/jsonrpc/server/http_uri_handler.go | 2 +- rpc/jsonrpc/server/parse_test.go | 4 +- rpc/jsonrpc/server/rpc_func.go | 11 ++-- rpc/jsonrpc/test/main.go | 2 +- test/fuzz/rpc/jsonrpc/server/handler.go | 2 +- 14 files changed, 144 insertions(+), 74 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 11d246889..b398cf7a4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -79,7 +79,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [types] \#6120 use batch verification for verifying commits signatures. - If the key type supports the batch verification API it will try to batch verify. If the verification fails we will single verify each signature. - [privval/file] \#6185 Return error on `LoadFilePV`, `LoadFilePVEmptyState`. Allows for better programmatic control of Tendermint. -- [privval] /#6240 Add `context.Context` to privval interface. +- [privval] \#6240 Add `context.Context` to privval interface. +- [rpc] \#6265 set cache control in http-rpc response header (@JayT106) ### BUG FIXES diff --git a/light/proxy/routes.go b/light/proxy/routes.go index e28b23e0c..10d2ba94e 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -18,36 +18,36 @@ func RPCRoutes(c *lrpc.Client) map[string]*rpcserver.RPCFunc { "unsubscribe_all": rpcserver.NewWSRPCFunc(c.UnsubscribeAllWS, ""), // info API - "health": rpcserver.NewRPCFunc(makeHealthFunc(c), ""), - "status": rpcserver.NewRPCFunc(makeStatusFunc(c), ""), - "net_info": rpcserver.NewRPCFunc(makeNetInfoFunc(c), ""), - "blockchain": rpcserver.NewRPCFunc(makeBlockchainInfoFunc(c), "minHeight,maxHeight"), - "genesis": rpcserver.NewRPCFunc(makeGenesisFunc(c), ""), - "block": rpcserver.NewRPCFunc(makeBlockFunc(c), "height"), - "block_by_hash": rpcserver.NewRPCFunc(makeBlockByHashFunc(c), "hash"), - "block_results": rpcserver.NewRPCFunc(makeBlockResultsFunc(c), "height"), - "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"), - "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"), - "tx_search": rpcserver.NewRPCFunc(makeTxSearchFunc(c), "query,prove,page,per_page,order_by"), - "block_search": rpcserver.NewRPCFunc(makeBlockSearchFunc(c), "query,page,per_page,order_by"), - "validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height,page,per_page"), - "dump_consensus_state": rpcserver.NewRPCFunc(makeDumpConsensusStateFunc(c), ""), - "consensus_state": rpcserver.NewRPCFunc(makeConsensusStateFunc(c), ""), - "consensus_params": rpcserver.NewRPCFunc(makeConsensusParamsFunc(c), "height"), - "unconfirmed_txs": rpcserver.NewRPCFunc(makeUnconfirmedTxsFunc(c), "limit"), - "num_unconfirmed_txs": rpcserver.NewRPCFunc(makeNumUnconfirmedTxsFunc(c), ""), + "health": rpcserver.NewRPCFunc(makeHealthFunc(c), "", false), + "status": rpcserver.NewRPCFunc(makeStatusFunc(c), "", false), + "net_info": rpcserver.NewRPCFunc(makeNetInfoFunc(c), "", false), + "blockchain": rpcserver.NewRPCFunc(makeBlockchainInfoFunc(c), "minHeight,maxHeight", true), + "genesis": rpcserver.NewRPCFunc(makeGenesisFunc(c), "", true), + "block": rpcserver.NewRPCFunc(makeBlockFunc(c), "height", true), + "block_by_hash": rpcserver.NewRPCFunc(makeBlockByHashFunc(c), "hash", true), + "block_results": rpcserver.NewRPCFunc(makeBlockResultsFunc(c), "height", true), + "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height", true), + "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove", true), + "tx_search": rpcserver.NewRPCFunc(makeTxSearchFunc(c), "query,prove,page,per_page,order_by", false), + "block_search": rpcserver.NewRPCFunc(makeBlockSearchFunc(c), "query,page,per_page,order_by", false), + "validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height,page,per_page", true), + "dump_consensus_state": rpcserver.NewRPCFunc(makeDumpConsensusStateFunc(c), "", false), + "consensus_state": rpcserver.NewRPCFunc(makeConsensusStateFunc(c), "", false), + "consensus_params": rpcserver.NewRPCFunc(makeConsensusParamsFunc(c), "height", true), + "unconfirmed_txs": rpcserver.NewRPCFunc(makeUnconfirmedTxsFunc(c), "limit", false), + "num_unconfirmed_txs": rpcserver.NewRPCFunc(makeNumUnconfirmedTxsFunc(c), "", false), // tx broadcast API - "broadcast_tx_commit": rpcserver.NewRPCFunc(makeBroadcastTxCommitFunc(c), "tx"), - "broadcast_tx_sync": rpcserver.NewRPCFunc(makeBroadcastTxSyncFunc(c), "tx"), - "broadcast_tx_async": rpcserver.NewRPCFunc(makeBroadcastTxAsyncFunc(c), "tx"), + "broadcast_tx_commit": rpcserver.NewRPCFunc(makeBroadcastTxCommitFunc(c), "tx", false), + "broadcast_tx_sync": rpcserver.NewRPCFunc(makeBroadcastTxSyncFunc(c), "tx", false), + "broadcast_tx_async": rpcserver.NewRPCFunc(makeBroadcastTxAsyncFunc(c), "tx", false), // abci API - "abci_query": rpcserver.NewRPCFunc(makeABCIQueryFunc(c), "path,data,height,prove"), - "abci_info": rpcserver.NewRPCFunc(makeABCIInfoFunc(c), ""), + "abci_query": rpcserver.NewRPCFunc(makeABCIQueryFunc(c), "path,data,height,prove", false), + "abci_info": rpcserver.NewRPCFunc(makeABCIInfoFunc(c), "", true), // evidence API - "broadcast_evidence": rpcserver.NewRPCFunc(makeBroadcastEvidenceFunc(c), "evidence"), + "broadcast_evidence": rpcserver.NewRPCFunc(makeBroadcastEvidenceFunc(c), "evidence", false), } } diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 5583d6f29..d83a882a3 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -14,43 +14,43 @@ var Routes = map[string]*rpc.RPCFunc{ "unsubscribe_all": rpc.NewWSRPCFunc(UnsubscribeAll, ""), // info API - "health": rpc.NewRPCFunc(Health, ""), - "status": rpc.NewRPCFunc(Status, ""), - "net_info": rpc.NewRPCFunc(NetInfo, ""), - "blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"), - "genesis": rpc.NewRPCFunc(Genesis, ""), - "block": rpc.NewRPCFunc(Block, "height"), - "block_by_hash": rpc.NewRPCFunc(BlockByHash, "hash"), - "block_results": rpc.NewRPCFunc(BlockResults, "height"), - "commit": rpc.NewRPCFunc(Commit, "height"), - "check_tx": rpc.NewRPCFunc(CheckTx, "tx"), - "tx": rpc.NewRPCFunc(Tx, "hash,prove"), - "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by"), - "block_search": rpc.NewRPCFunc(BlockSearch, "query,page,per_page,order_by"), - "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), - "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), - "consensus_state": rpc.NewRPCFunc(ConsensusState, ""), - "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height"), - "unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, "limit"), - "num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""), + "health": rpc.NewRPCFunc(Health, "", false), + "status": rpc.NewRPCFunc(Status, "", false), + "net_info": rpc.NewRPCFunc(NetInfo, "", false), + "blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight", true), + "genesis": rpc.NewRPCFunc(Genesis, "", true), + "block": rpc.NewRPCFunc(Block, "height", true), + "block_by_hash": rpc.NewRPCFunc(BlockByHash, "hash", true), + "block_results": rpc.NewRPCFunc(BlockResults, "height", true), + "commit": rpc.NewRPCFunc(Commit, "height", true), + "check_tx": rpc.NewRPCFunc(CheckTx, "tx", true), + "tx": rpc.NewRPCFunc(Tx, "hash,prove", true), + "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by", false), + "block_search": rpc.NewRPCFunc(BlockSearch, "query,page,per_page,order_by", false), + "validators": rpc.NewRPCFunc(Validators, "height,page,per_page", true), + "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, "", false), + "consensus_state": rpc.NewRPCFunc(ConsensusState, "", false), + "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height", true), + "unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, "limit", false), + "num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, "", false), // tx broadcast API - "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), - "broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"), - "broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"), + "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx", false), + "broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx", false), + "broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx", false), // abci API - "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), - "abci_info": rpc.NewRPCFunc(ABCIInfo, ""), + "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove", false), + "abci_info": rpc.NewRPCFunc(ABCIInfo, "", true), // evidence API - "broadcast_evidence": rpc.NewRPCFunc(BroadcastEvidence, "evidence"), + "broadcast_evidence": rpc.NewRPCFunc(BroadcastEvidence, "evidence", false), } // AddUnsafeRoutes adds unsafe routes. func AddUnsafeRoutes() { // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") - Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent,unconditional,private") - Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") + Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds", false) + Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent,unconditional,private", false) + Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "", false) } diff --git a/rpc/jsonrpc/doc.go b/rpc/jsonrpc/doc.go index b014fe38d..813f055f5 100644 --- a/rpc/jsonrpc/doc.go +++ b/rpc/jsonrpc/doc.go @@ -55,7 +55,7 @@ // Define some routes // // var Routes = map[string]*rpcserver.RPCFunc{ -// "status": rpcserver.NewRPCFunc(Status, "arg"), +// "status": rpcserver.NewRPCFunc(Status, "arg", false), // } // // An rpc function: diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index ec12f85d7..c06f13b7e 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -59,11 +59,11 @@ type ResultEchoDataBytes struct { // Define some routes var Routes = map[string]*server.RPCFunc{ - "echo": server.NewRPCFunc(EchoResult, "arg"), + "echo": server.NewRPCFunc(EchoResult, "arg", false), "echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"), - "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"), - "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"), - "echo_int": server.NewRPCFunc(EchoIntResult, "arg"), + "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg", false), + "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg", false), + "echo_int": server.NewRPCFunc(EchoIntResult, "arg", false), } func EchoResult(ctx *types.Context, v string) (*ResultEcho, error) { diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 17cba3828..eb8a22eb4 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -57,6 +57,11 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han requests = []types.RPCRequest{request} } + // Set the default response cache to true unless + // 1. Any RPC request rrror. + // 2. Any RPC request doesn't allow to be cached. + // 3. Any RPC request has the height argument and the value is 0 (the default). + var c = true for _, request := range requests { request := request @@ -74,11 +79,13 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han responses, types.RPCInvalidRequestError(request.ID, fmt.Errorf("path %s is invalid", r.URL.Path)), ) + c = false continue } rpcFunc, ok := funcMap[request.Method] if !ok || rpcFunc.ws { responses = append(responses, types.RPCMethodNotFoundError(request.ID)) + c = false continue } ctx := &types.Context{JSONReq: &request, HTTPReq: r} @@ -90,9 +97,15 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han responses, types.RPCInvalidParamsError(request.ID, fmt.Errorf("error converting json params to arguments: %w", err)), ) + c = false continue } args = append(args, fnArgs...) + + } + + if hasDefaultHeight(request, args) { + c = false } returns := rpcFunc.f.Call(args) @@ -106,23 +119,28 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han // if this already of type RPC error then forward that error case *types.RPCError: responses = append(responses, types.NewRPCErrorResponse(request.ID, e.Code, e.Message, e.Data)) - + c = false default: // we need to unwrap the error and parse it accordingly switch errors.Unwrap(err) { // check if the error was due to an invald request case ctypes.ErrZeroOrNegativeHeight, ctypes.ErrZeroOrNegativePerPage, ctypes.ErrPageOutOfRange, ctypes.ErrInvalidRequest: responses = append(responses, types.RPCInvalidRequestError(request.ID, err)) - + c = false // lastly default all remaining errors as internal errors default: // includes ctypes.ErrHeightNotAvailable and ctypes.ErrHeightExceedsChainHead responses = append(responses, types.RPCInternalError(request.ID, err)) + c = false } } + + if c && !rpcFunc.cache { + c = false + } } if len(responses) > 0 { - if wErr := WriteRPCResponseHTTP(w, responses...); wErr != nil { + if wErr := WriteRPCResponseHTTP(w, c, responses...); wErr != nil { logger.Error("failed to write responses", "res", responses, "err", wErr) } } @@ -258,3 +276,12 @@ func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[st w.WriteHeader(200) w.Write(buf.Bytes()) // nolint: errcheck } + +func hasDefaultHeight(r types.RPCRequest, h []reflect.Value) bool { + switch r.Method { + case "block", "block_results", "commit", "consensus_params", "validators": + return len(h) < 2 || h[1].IsZero() + default: + return false + } +} diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index a5c14e59a..591277586 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -18,7 +18,8 @@ import ( func testMux() *http.ServeMux { funcMap := map[string]*RPCFunc{ - "c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), + "c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i", false), + "block": NewRPCFunc(func(ctx *types.Context, h int) (string, error) { return "block", nil }, "height", true), } mux := http.NewServeMux() buf := new(bytes.Buffer) @@ -227,3 +228,35 @@ func TestUnknownRPCPath(t *testing.T) { require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") res.Body.Close() } + +func TestRPCResponseCache(t *testing.T) { + mux := testMux() + body := strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["1"]}`) + req, _ := http.NewRequest("Get", "http://localhost/", body) + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + res := rec.Result() + + // Always expecting back a JSONRPCResponse + require.True(t, statusOK(res.StatusCode), "should always return 2XX") + require.Equal(t, "max-age=31536000", res.Header.Get("Cache-control")) + + _, err := ioutil.ReadAll(res.Body) + res.Body.Close() + require.Nil(t, err, "reading from the body should not give back an error") + + // send a request with default height. + body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["0"]}`) + req, _ = http.NewRequest("Get", "http://localhost/", body) + rec = httptest.NewRecorder() + mux.ServeHTTP(rec, req) + res = rec.Result() + + // Always expecting back a JSONRPCResponse + require.True(t, statusOK(res.StatusCode), "should always return 2XX") + require.Equal(t, "", res.Header.Get("Cache-control")) + + _, err = ioutil.ReadAll(res.Body) + res.Body.Close() + require.Nil(t, err, "reading from the body should not give back an error") +} diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index 86dbed4fa..c21c71c49 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -133,7 +133,8 @@ func WriteRPCResponseHTTPError( } // WriteRPCResponseHTTP marshals res as JSON (with indent) and writes it to w. -func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error { +// If the rpc response can be cached, add cache-control to the response header. +func WriteRPCResponseHTTP(w http.ResponseWriter, c bool, res ...types.RPCResponse) error { var v interface{} if len(res) == 1 { v = res[0] @@ -146,6 +147,9 @@ func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error return fmt.Errorf("json marshal: %w", err) } w.Header().Set("Content-Type", "application/json") + if c { + w.Header().Set("Cache-Control", "max-age=31536000") // expired after one year + } w.WriteHeader(200) _, err = w.Write(jsonBytes) return err @@ -186,7 +190,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler // If RPCResponse if res, ok := e.(types.RPCResponse); ok { - if wErr := WriteRPCResponseHTTP(rww, res); wErr != nil { + if wErr := WriteRPCResponseHTTP(rww, false, res); wErr != nil { logger.Error("failed to write response", "res", res, "err", wErr) } } else { diff --git a/rpc/jsonrpc/server/http_server_test.go b/rpc/jsonrpc/server/http_server_test.go index f4da2f971..e7c517cde 100644 --- a/rpc/jsonrpc/server/http_server_test.go +++ b/rpc/jsonrpc/server/http_server_test.go @@ -112,7 +112,7 @@ func TestWriteRPCResponseHTTP(t *testing.T) { // one argument w := httptest.NewRecorder() - err := WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) + err := WriteRPCResponseHTTP(w, true, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) require.NoError(t, err) resp := w.Result() body, err := ioutil.ReadAll(resp.Body) @@ -120,6 +120,7 @@ func TestWriteRPCResponseHTTP(t *testing.T) { require.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) + assert.Equal(t, "max-age=31536000", resp.Header.Get("Cache-control")) assert.Equal(t, `{ "jsonrpc": "2.0", "id": -1, @@ -131,6 +132,7 @@ func TestWriteRPCResponseHTTP(t *testing.T) { // multiple arguments w = httptest.NewRecorder() err = WriteRPCResponseHTTP(w, + false, types.NewRPCSuccessResponse(id, &sampleResult{"hello"}), types.NewRPCSuccessResponse(id, &sampleResult{"world"})) require.NoError(t, err) diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index cd03383f7..f5a2ecebc 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -61,7 +61,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit // if no error then return a success response case nil: res := types.NewRPCSuccessResponse(dummyID, result) - if wErr := WriteRPCResponseHTTP(w, res); wErr != nil { + if wErr := WriteRPCResponseHTTP(w, rpcFunc.cache, res); wErr != nil { logger.Error("failed to write response", "res", res, "err", wErr) } diff --git a/rpc/jsonrpc/server/parse_test.go b/rpc/jsonrpc/server/parse_test.go index 9f36adca7..86316f8e5 100644 --- a/rpc/jsonrpc/server/parse_test.go +++ b/rpc/jsonrpc/server/parse_test.go @@ -135,7 +135,7 @@ func TestParseJSONArray(t *testing.T) { func TestParseJSONRPC(t *testing.T) { demo := func(ctx *types.Context, height int, name string) {} - call := NewRPCFunc(demo, "height,name") + call := NewRPCFunc(demo, "height,name", false) cases := []struct { raw string @@ -172,7 +172,7 @@ func TestParseJSONRPC(t *testing.T) { func TestParseURI(t *testing.T) { demo := func(ctx *types.Context, height int, name string) {} - call := NewRPCFunc(demo, "height,name") + call := NewRPCFunc(demo, "height,name", false) cases := []struct { raw []string diff --git a/rpc/jsonrpc/server/rpc_func.go b/rpc/jsonrpc/server/rpc_func.go index 17e72cb10..24f3c8976 100644 --- a/rpc/jsonrpc/server/rpc_func.go +++ b/rpc/jsonrpc/server/rpc_func.go @@ -31,20 +31,22 @@ type RPCFunc struct { returns []reflect.Type // type of each return arg argNames []string // name of each argument ws bool // websocket only + cache bool // allow the RPC response can be cached by the proxy cache server } // NewRPCFunc wraps a function for introspection. // f is the function, args are comma separated argument names -func NewRPCFunc(f interface{}, args string) *RPCFunc { - return newRPCFunc(f, args, false) +// cache is a bool value to allow the client proxy server to cache the RPC results +func NewRPCFunc(f interface{}, args string, cache bool) *RPCFunc { + return newRPCFunc(f, args, false, cache) } // NewWSRPCFunc wraps a function for introspection and use in the websockets. func NewWSRPCFunc(f interface{}, args string) *RPCFunc { - return newRPCFunc(f, args, true) + return newRPCFunc(f, args, true, false) } -func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc { +func newRPCFunc(f interface{}, args string, ws bool, c bool) *RPCFunc { var argNames []string if args != "" { argNames = strings.Split(args, ",") @@ -55,6 +57,7 @@ func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc { returns: funcReturnTypes(f), argNames: argNames, ws: ws, + cache: c, } } diff --git a/rpc/jsonrpc/test/main.go b/rpc/jsonrpc/test/main.go index fe3ffb769..e36caa3b5 100644 --- a/rpc/jsonrpc/test/main.go +++ b/rpc/jsonrpc/test/main.go @@ -12,7 +12,7 @@ import ( ) var routes = map[string]*rpcserver.RPCFunc{ - "hello_world": rpcserver.NewRPCFunc(HelloWorld, "name,num"), + "hello_world": rpcserver.NewRPCFunc(HelloWorld, "name,num", false), } func HelloWorld(ctx *rpctypes.Context, name string, num int) (Result, error) { diff --git a/test/fuzz/rpc/jsonrpc/server/handler.go b/test/fuzz/rpc/jsonrpc/server/handler.go index 3779ed2b4..b93388dd1 100644 --- a/test/fuzz/rpc/jsonrpc/server/handler.go +++ b/test/fuzz/rpc/jsonrpc/server/handler.go @@ -14,7 +14,7 @@ import ( ) var rpcFuncMap = map[string]*rs.RPCFunc{ - "c": rs.NewRPCFunc(func(s string, i int) (string, int) { return "foo", 200 }, "s,i"), + "c": rs.NewRPCFunc(func(s string, i int) (string, int) { return "foo", 200 }, "s,i", false), } var mux *http.ServeMux