From f21e618be5dded28eb37f48b0fee5c0d58708280 Mon Sep 17 00:00:00 2001 From: ZhangTao1596 Date: Thu, 23 Mar 2023 17:03:22 +0800 Subject: [PATCH] rpcclient: add GetProof and VerifyProof (fix #2942) --- pkg/rpcclient/rpc.go | 25 ++++++++++++ pkg/rpcclient/rpc_test.go | 61 ++++++++++++++++++++++++++++++ pkg/services/rpcsrv/client_test.go | 30 +++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/pkg/rpcclient/rpc.go b/pkg/rpcclient/rpc.go index 0e7147baa..6b97c8895 100644 --- a/pkg/rpcclient/rpc.go +++ b/pkg/rpcclient/rpc.go @@ -447,6 +447,31 @@ func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.Transactio return resp, nil } +// GetProof returns existence proof of storage item state by the given stateroot +// historical contract hash and historical item key. +func (c *Client) GetProof(stateroot util.Uint256, historicalContractHash util.Uint160, historicalKey []byte) (*result.ProofWithKey, error) { + var ( + params = []interface{}{stateroot.StringLE(), historicalContractHash.StringLE(), historicalKey} + resp = &result.ProofWithKey{} + ) + if err := c.performRequest("getproof", params, resp); err != nil { + return nil, err + } + return resp, nil +} + +// VerifyProof returns value by the given stateroot and proof. +func (c *Client) VerifyProof(stateroot util.Uint256, proof *result.ProofWithKey) ([]byte, error) { + var ( + params = []interface{}{stateroot.StringLE(), proof.String()} + resp []byte + ) + if err := c.performRequest("verifyproof", params, &resp); err != nil { + return nil, err + } + return resp, nil +} + // GetState returns historical contract storage item state by the given stateroot, // historical contract hash and historical item key. func (c *Client) GetState(stateroot util.Uint256, historicalContractHash util.Uint160, historicalKey []byte) ([]byte, error) { diff --git a/pkg/rpcclient/rpc_test.go b/pkg/rpcclient/rpc_test.go index 815798f4b..9458e0830 100644 --- a/pkg/rpcclient/rpc_test.go +++ b/pkg/rpcclient/rpc_test.go @@ -25,6 +25,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" @@ -831,6 +832,66 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getproof": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + root, _ := util.Uint256DecodeStringLE("272002b11a6a39035c719defec3e4e6a8d1f4ae37a995b44734911413fcc2ba5") + cHash, _ := util.Uint160DecodeStringLE("cc5e4edd9f5f8dba8bb65734541df7a1c081c67b") + key := []byte{10} + return c.GetProof(root, cHash, key) + }, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":"Bfn///8KBiQBAQ8DogBnMdiiPTEW05A6bJPmQ2TNVpuca/nB1rJRdQX7R4SyAAQEBAQEBAQDHvo5Rc9v\u002BWSpfsnMXM75ku\u002BZjvbLJhWXn/lh6L\u002B1yB0EA4k\u002Bsx4f7IgmdHNm3wRMpj5kTU4l0gChSGppo5p5wZyWA2\u002BKSFn16W6tRrGSfJob\u002BgqJukLcNDk0DBFYW2wIS2/NAzkugdLfZRXHOLqq5XJr89ElzlqyXU1o9D87l9YOcXjGBAQEA7oDTOxuU4iMAKPuhn5eJjzsM56bQrx3uORa8LKm42oDBCkBBg8PDw8PDwN96s39UOSCwMJmMQZzNjfNAPCbRRyke1B4VRKqOZ0NHlIAA2woQ13XO4Ug2aQ/cW4WBricVcUVqobFUU0dnRPtfIHeAxuYERXsV6HwdGjW\u002BhtpM0FEkw/mllbH5pyhn\u002BBx4r8wBAQEBAQEBAQEBAQEBAQEJAEBCgPXvpMqBogTeGhXjtFY4Rsn9bY/PgNX0l4iYOHMzUBQQgQCAugD"}`, + result: func(c *Client) interface{} { + b, _ := base64.StdEncoding.DecodeString("Bfn///8KBiQBAQ8DogBnMdiiPTEW05A6bJPmQ2TNVpuca/nB1rJRdQX7R4SyAAQEBAQEBAQDHvo5Rc9v\u002BWSpfsnMXM75ku\u002BZjvbLJhWXn/lh6L\u002B1yB0EA4k\u002Bsx4f7IgmdHNm3wRMpj5kTU4l0gChSGppo5p5wZyWA2\u002BKSFn16W6tRrGSfJob\u002BgqJukLcNDk0DBFYW2wIS2/NAzkugdLfZRXHOLqq5XJr89ElzlqyXU1o9D87l9YOcXjGBAQEA7oDTOxuU4iMAKPuhn5eJjzsM56bQrx3uORa8LKm42oDBCkBBg8PDw8PDwN96s39UOSCwMJmMQZzNjfNAPCbRRyke1B4VRKqOZ0NHlIAA2woQ13XO4Ug2aQ/cW4WBricVcUVqobFUU0dnRPtfIHeAxuYERXsV6HwdGjW\u002BhtpM0FEkw/mllbH5pyhn\u002BBx4r8wBAQEBAQEBAQEBAQEBAQEJAEBCgPXvpMqBogTeGhXjtFY4Rsn9bY/PgNX0l4iYOHMzUBQQgQCAugD") + proof := &result.ProofWithKey{} + r := io.NewBinReaderFromBuf(b) + proof.DecodeBinary(r) + return proof + }, + }, + { + name: "not found", + invoke: func(c *Client) (interface{}, error) { + root, _ := util.Uint256DecodeStringLE("272002b11a6a39035c719defec3e4e6a8d1f4ae37a995b44734911413fcc2ba5") + cHash, _ := util.Uint160DecodeStringLE("cc5e4edd9f5f8dba8bb65734541df7a1c081c67b") + key := []byte{01} + return c.GetProof(root, cHash, key) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":"failed to get proof: item not found"}}`, + fails: true, + }, + }, + "verifyproof": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + root, _ := util.Uint256DecodeStringLE("272002b11a6a39035c719defec3e4e6a8d1f4ae37a995b44734911413fcc2ba5") + b, _ := base64.StdEncoding.DecodeString("Bfn///8KBiQBAQ8DogBnMdiiPTEW05A6bJPmQ2TNVpuca/nB1rJRdQX7R4SyAAQEBAQEBAQDHvo5Rc9v\u002BWSpfsnMXM75ku\u002BZjvbLJhWXn/lh6L\u002B1yB0EA4k\u002Bsx4f7IgmdHNm3wRMpj5kTU4l0gChSGppo5p5wZyWA2\u002BKSFn16W6tRrGSfJob\u002BgqJukLcNDk0DBFYW2wIS2/NAzkugdLfZRXHOLqq5XJr89ElzlqyXU1o9D87l9YOcXjGBAQEA7oDTOxuU4iMAKPuhn5eJjzsM56bQrx3uORa8LKm42oDBCkBBg8PDw8PDwN96s39UOSCwMJmMQZzNjfNAPCbRRyke1B4VRKqOZ0NHlIAA2woQ13XO4Ug2aQ/cW4WBricVcUVqobFUU0dnRPtfIHeAxuYERXsV6HwdGjW\u002BhtpM0FEkw/mllbH5pyhn\u002BBx4r8wBAQEBAQEBAQEBAQEBAQEJAEBCgPXvpMqBogTeGhXjtFY4Rsn9bY/PgNX0l4iYOHMzUBQQgQCAugD") + proof := &result.ProofWithKey{} + r := io.NewBinReaderFromBuf(b) + proof.DecodeBinary(r) + return c.VerifyProof(root, proof) + }, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":"6AM="}`, + result: func(c *Client) interface{} { + return bigint.ToPreallocatedBytes(big.NewInt(1000), nil) + }, + }, + { + name: "fail", + invoke: func(c *Client) (interface{}, error) { + root, _ := util.Uint256DecodeStringLE("272002b11a6a39035c719defec3e4e6a8d1f4ae37a995b44734911413fcc2ba5") + b, _ := base64.StdEncoding.DecodeString("Bfn///8KBiQBAQ8DogBnMdiiPTEW05A6bJPmQ2TNVpuca/nB1rJRdQX7R4SyAAQEBAQEBAQDHvo5Rc9v\u002BWSpfsnMXM75ku\u002BZjvbLJhWXn/lh6L\u002B1yB0EA4k\u002Bsx4f7IgmdHNm3wRMpj5kTU4l0gChSGppo5p5wZyWA2\u002BKSFn16W6tRrGSfJob\u002BgqJukLcNDk0DBFYW2wIS2/NAzkugdLfZRXHOLqq5XJr89ElzlqyXU1o9D87l9YOcXjGBAQEA7oDTOxuU4iMAKPuhn5eJjzsM56bQrx3uORa8LKm42oDBCkBBg8PDw8PDwN96s39UOSCwMJmMQZzNjfNAPCbRRyke1B4VRKqOZ0NHlIAA2woQ13XO4Ug2aQ/cW4WBricVcUVqobFUU0dnRPtfIHeAxuYERXsV6HwdGjW\u002BhtpM0FEkw/mllbH5pyhn\u002BBx4r8wBAQEBAQEBAQEBAQEBAQEJAEBCgPXvpMqBogTeGhXjtFY4Rsn9bY/PgNX0l4iYOHMzUBQQgQCAugD") + proof := &result.ProofWithKey{} + r := io.NewBinReaderFromBuf(b) + proof.DecodeBinary(r) + return c.VerifyProof(root, proof) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":"invalid"}`, + fails: true, + }, + }, "findstates": { { name: "positive", diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 031dab5ee..86d8c7346 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -23,12 +23,15 @@ import ( "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/fee" + "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "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/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neorpc" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" @@ -1764,6 +1767,33 @@ func TestClient_GetNotaryServiceFeePerKey(t *testing.T) { require.Equal(t, defaultNotaryServiceFeePerKey, actual) } +func TestClient_States(t *testing.T) { + chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) + defer chain.Close() + defer rpcSrv.Shutdown() + + c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{}) + require.NoError(t, err) + require.NoError(t, c.Init()) + + stateheight, err := c.GetStateHeight() + assert.NoError(t, err) + assert.Equal(t, chain.BlockHeight(), stateheight.Local) + + stateroot, err := c.GetStateRootByHeight(stateheight.Local) + assert.NoError(t, err) + + t.Run("proof", func(t *testing.T) { + policy, err := chain.GetNativeContractScriptHash(nativenames.Policy) + assert.NoError(t, err) + proof, err := c.GetProof(stateroot.Root, policy, []byte{19}) // storagePrice key in policy contract + assert.NoError(t, err) + value, err := c.VerifyProof(stateroot.Root, proof) + assert.NoError(t, err) + assert.Equal(t, big.NewInt(native.DefaultStoragePrice), bigint.FromBytes(value)) + }) +} + func TestClientOracle(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close()