diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 5e3083d74..2cfb62451 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -100,6 +100,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon "getpeers": (*Server).getPeers, "getrawmempool": (*Server).getRawMempool, "getrawtransaction": (*Server).getrawtransaction, + "getstateroot": (*Server).getStateRoot, "getstorage": (*Server).getStorage, "gettransactionheight": (*Server).getTransactionHeight, "getunclaimedgas": (*Server).getUnclaimedGas, @@ -773,6 +774,28 @@ func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160 return result, nil } +func (s *Server) getStateRoot(ps request.Params) (interface{}, *response.Error) { + p := ps.Value(0) + if p == nil { + return nil, response.NewRPCError("Invalid parameter.", "", nil) + } + var rt *state.MPTRootState + var h util.Uint256 + height, err := p.GetInt() + if err == nil { + rt, err = s.chain.GetStateRoot(uint32(height)) + } else if h, err = p.GetUint256(); err == nil { + hdr, err := s.chain.GetHeader(h) + if err == nil { + rt, err = s.chain.GetStateRoot(hdr.Index) + } + } + if err != nil { + return nil, response.NewRPCError("Unknown state root.", "", err) + } + return rt, nil +} + func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) { id, rErr := s.contractIDFromParam(ps.Value(0)) if rErr == response.ErrUnknown { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 249719dfd..1ca90dd57 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -285,6 +285,18 @@ var rpcTestCases = map[string][]rpcTestCase{ check: checkNep5Transfers, }, }, + "getstateroot": { + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid hash", + params: `["0x1234567890"]`, + fail: true, + }, + }, "getstorage": { { name: "positive", @@ -985,6 +997,23 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.Equal(t, b.Hash(), res.Hash) }) }) + t.Run("getstateroot", func(t *testing.T) { + testRoot := func(t *testing.T, p string) { + rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [%s]}`, p) + body := doRPCCall(rpc, httpSrv.URL, t) + rawRes := checkErrGetResult(t, body, false) + + res := new(state.MPTRootState) + require.NoError(t, json.Unmarshal(rawRes, res)) + require.NotEqual(t, util.Uint256{}, res.Root) // be sure this test uses valid height + + expected, err := e.chain.GetStateRoot(5) + require.NoError(t, err) + require.Equal(t, expected, res) + } + t.Run("ByHeight", func(t *testing.T) { testRoot(t, strconv.FormatInt(5, 10)) }) + t.Run("ByHash", func(t *testing.T) { testRoot(t, `"`+chain.GetHeaderHash(5).StringLE()+`"`) }) + }) t.Run("getrawtransaction", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0))