rpc: implement verifyproof RPC

Test getproof and verifyproof together.
This commit is contained in:
Evgenii Stratonikov 2020-06-05 11:51:39 +03:00
parent e38e8aa48a
commit 8e60a65b55
4 changed files with 108 additions and 0 deletions

View file

@ -1,8 +1,10 @@
package result package result
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
) )
@ -25,6 +27,12 @@ type GetProof struct {
Success bool `json:"success"` 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. // MarshalJSON implements json.Marshaler.
func (p *ProofWithKey) MarshalJSON() ([]byte, error) { func (p *ProofWithKey) MarshalJSON() ([]byte, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
@ -79,3 +87,36 @@ func (p *ProofWithKey) FromString(s string) error {
p.DecodeBinary(r) p.DecodeBinary(r)
return r.Err 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
}

View file

@ -55,3 +55,14 @@ func TestProofWithKey_EncodeString(t *testing.T) {
require.NoError(t, actual.FromString(expected.String())) require.NoError(t, actual.FromString(expected.String()))
require.Equal(t, expected, &actual) 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}})
})
}

View file

@ -20,6 +20,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"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/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -114,6 +115,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
"sendrawtransaction": (*Server).sendrawtransaction, "sendrawtransaction": (*Server).sendrawtransaction,
"submitblock": (*Server).submitBlock, "submitblock": (*Server).submitBlock,
"validateaddress": (*Server).validateAddress, "validateaddress": (*Server).validateAddress,
"verifyproof": (*Server).verifyProof,
} }
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){ var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
@ -817,6 +819,36 @@ func (s *Server) getProof(ps request.Params) (interface{}, *response.Error) {
}, nil }, nil
} }
func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) {
if s.chain.GetConfig().KeepOnlyLatestState {
return nil, response.NewInvalidRequestError("'verifyproof' is not supported", errKeepOnlyLatestState)
}
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)
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) { func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) {
var height = s.chain.BlockHeight() var height = s.chain.BlockHeight()
var stateHeight uint32 var stateHeight uint32

View file

@ -1033,6 +1033,30 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.Equal(t, b.Hash(), res.Hash) require.Equal(t, b.Hash(), res.Hash)
}) })
}) })
t.Run("getproof", func(t *testing.T) {
r, err := chain.GetStateRoot(3)
require.NoError(t, err)
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getproof", "params": ["%s", "%s", "%x"]}`,
r.Root.StringLE(), testContractHash, []byte("testkey"))
body := doRPCCall(rpc, httpSrv.URL, t)
rawRes := checkErrGetResult(t, body, false)
res := new(result.GetProof)
require.NoError(t, json.Unmarshal(rawRes, res))
require.True(t, res.Success)
h, _ := util.Uint160DecodeStringLE(testContractHash)
skey := makeStorageKey(chain.GetContractState(h).ID, []byte("testkey"))
require.Equal(t, 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) { t.Run("getstateroot", func(t *testing.T) {
testRoot := func(t *testing.T, p string) { testRoot := func(t *testing.T, p string) {
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [%s]}`, p) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [%s]}`, p)