diff --git a/pkg/rpc/response/result/mpt.go b/pkg/rpc/response/result/mpt.go index 4473d7ea3..10ef7e8c3 100644 --- a/pkg/rpc/response/result/mpt.go +++ b/pkg/rpc/response/result/mpt.go @@ -1,8 +1,10 @@ package result import ( + "bytes" "encoding/hex" "encoding/json" + "errors" "github.com/nspcc-dev/neo-go/pkg/io" ) @@ -25,6 +27,12 @@ type GetProof struct { Success bool `json:"success"` } +// VerifyProof is a result of verifyproof RPC. +// nil Value is considered invalid. +type VerifyProof struct { + Value []byte +} + // MarshalJSON implements json.Marshaler. func (p *ProofWithKey) MarshalJSON() ([]byte, error) { w := io.NewBufBinWriter() @@ -79,3 +87,36 @@ func (p *ProofWithKey) FromString(s string) error { p.DecodeBinary(r) return r.Err } + +// MarshalJSON implements json.Marshaler. +func (p *VerifyProof) MarshalJSON() ([]byte, error) { + if p.Value == nil { + return []byte(`"invalid"`), nil + } + return []byte(`{"value":"` + hex.EncodeToString(p.Value) + `"}`), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (p *VerifyProof) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, []byte(`"invalid"`)) { + p.Value = nil + return nil + } + var m map[string]string + if err := json.Unmarshal(data, &m); err != nil { + return err + } + if len(m) != 1 { + return errors.New("must have single key") + } + v, ok := m["value"] + if !ok { + return errors.New("invalid json") + } + b, err := hex.DecodeString(v) + if err != nil { + return err + } + p.Value = b + return nil +} diff --git a/pkg/rpc/response/result/mpt_test.go b/pkg/rpc/response/result/mpt_test.go index 3a3497aee..22e0c021c 100644 --- a/pkg/rpc/response/result/mpt_test.go +++ b/pkg/rpc/response/result/mpt_test.go @@ -55,3 +55,14 @@ func TestProofWithKey_EncodeString(t *testing.T) { require.NoError(t, actual.FromString(expected.String())) require.Equal(t, expected, &actual) } + +func TestVerifyProof_MarshalJSON(t *testing.T) { + t.Run("Good", func(t *testing.T) { + vp := &VerifyProof{random.Bytes(100)} + testserdes.MarshalUnmarshalJSON(t, vp, new(VerifyProof)) + }) + t.Run("NoValue", func(t *testing.T) { + vp := new(VerifyProof) + testserdes.MarshalUnmarshalJSON(t, vp, &VerifyProof{[]byte{1, 2, 3}}) + }) +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index cc1d178bf..6912c4e24 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -112,6 +112,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon "sendrawtransaction": (*Server).sendrawtransaction, "submitblock": (*Server).submitBlock, "validateaddress": (*Server).validateAddress, + "verifyproof": (*Server).verifyProof, } var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){ @@ -716,6 +717,33 @@ func (s *Server) getProof(ps request.Params) (interface{}, *response.Error) { }, nil } +func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) { + root, err := ps.Value(0).GetUint256() + if err != nil { + return nil, response.ErrInvalidParams + } + proofStr, err := ps.Value(1).GetString() + if err != nil { + return nil, response.ErrInvalidParams + } + var p result.ProofWithKey + if err := p.FromString(proofStr); err != nil { + return nil, response.ErrInvalidParams + } + vp := new(result.VerifyProof) + val, ok := mpt.VerifyProof(root, p.Key, p.Proof) + if ok { + var si state.StorageItem + r := io.NewBinReaderFromBuf(val[1:]) + si.DecodeBinary(r) + if r.Err != nil { + return nil, response.NewInternalServerError("invalid item in trie", r.Err) + } + vp.Value = si.Value + } + return vp, nil +} + func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) { height := s.chain.BlockHeight() return &result.StateHeight{ diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index bb6d8cc11..e53687baf 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -16,6 +16,7 @@ import ( "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/mpt" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -978,6 +979,33 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) } + t.Run("getproof", func(t *testing.T) { + r, err := chain.GetStateRoot(205) + require.NoError(t, err) + + rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getproof", "params": ["%s", "%s", "%x"]}`, + r.Root.StringLE(), testContractHash, []byte("testkey")) + fmt.Println(rpc) + body := doRPCCall(rpc, httpSrv.URL, t) + fmt.Println(string(body)) + rawRes := checkErrGetResult(t, body, false) + res := new(result.GetProof) + require.NoError(t, json.Unmarshal(rawRes, res)) + require.True(t, res.Success) + h, _ := hex.DecodeString(testContractHash) + skey := append(h, []byte("testkey")...) + require.Equal(t, mpt.ToNeoStorageKey(skey), res.Result.Key) + require.True(t, len(res.Result.Proof) > 0) + + rpc = fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "verifyproof", "params": ["%s", "%s"]}`, + r.Root.StringLE(), res.Result.String()) + body = doRPCCall(rpc, httpSrv.URL, t) + rawRes = checkErrGetResult(t, body, false) + vp := new(result.VerifyProof) + require.NoError(t, json.Unmarshal(rawRes, vp)) + require.Equal(t, []byte("testvalue"), vp.Value) + }) + 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)