mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 13:38:35 +00:00
b1b660c779
Returning error string as a result (not an error) is utterly wrong, but C# implementation just returns a zero balance for unknown addresses, so we should follow that.
532 lines
14 KiB
Go
532 lines
14 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/core"
|
|
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type executor struct {
|
|
chain *core.Blockchain
|
|
handler http.HandlerFunc
|
|
}
|
|
|
|
const (
|
|
defaultJSONRPC = "2.0"
|
|
defaultID = 1
|
|
)
|
|
|
|
type rpcTestCase struct {
|
|
name string
|
|
params string
|
|
fail bool
|
|
result func(e *executor) interface{}
|
|
check func(t *testing.T, e *executor, result interface{})
|
|
}
|
|
|
|
var rpcTestCases = map[string][]rpcTestCase{
|
|
"getaccountstate": {
|
|
{
|
|
name: "positive",
|
|
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
|
|
result: func(e *executor) interface{} { return &GetAccountStateResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetAccountStateResponse)
|
|
require.True(t, ok)
|
|
assert.Equal(t, 1, len(res.Result.Balances))
|
|
assert.Equal(t, false, res.Result.Frozen)
|
|
},
|
|
},
|
|
{
|
|
name: "positive null",
|
|
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
|
|
result: func(e *executor) interface{} { return &GetAccountStateResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetAccountStateResponse)
|
|
require.True(t, ok)
|
|
assert.Equal(t, 0, len(res.Result.Balances))
|
|
assert.Equal(t, false, res.Result.Frozen)
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid address",
|
|
params: `["notabase58"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"getassetstate": {
|
|
{
|
|
name: "positive",
|
|
params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`,
|
|
result: func(e *executor) interface{} { return &GetAssetResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetAssetResponse)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "00", res.Result.Owner)
|
|
assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin)
|
|
},
|
|
},
|
|
{
|
|
name: "negative",
|
|
params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de2"]`,
|
|
result: func(e *executor) interface{} { return "Invalid assetid" },
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid hash",
|
|
params: `["notahex"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"getbestblockhash": {
|
|
{
|
|
params: "[]",
|
|
result: func(e *executor) interface{} {
|
|
return "0x" + e.chain.CurrentBlockHash().StringLE()
|
|
},
|
|
},
|
|
{
|
|
params: "1",
|
|
fail: true,
|
|
},
|
|
},
|
|
"getblock": {
|
|
{
|
|
name: "positive",
|
|
params: "[1, 1]",
|
|
result: func(e *executor) interface{} { return &GetBlockResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetBlockResponse)
|
|
require.True(t, ok)
|
|
|
|
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
|
|
require.NoErrorf(t, err, "could not get block")
|
|
|
|
expectedHash := "0x" + block.Hash().StringLE()
|
|
assert.Equal(t, expectedHash, res.Result.Hash)
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "bad params",
|
|
params: `[[]]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid height",
|
|
params: `[-1]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid hash",
|
|
params: `["notahex"]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "missing hash",
|
|
params: `["` + util.Uint256{}.String() + `"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"getblockcount": {
|
|
{
|
|
params: "[]",
|
|
result: func(e *executor) interface{} { return int(e.chain.BlockHeight() + 1) },
|
|
},
|
|
},
|
|
"getblockhash": {
|
|
{
|
|
params: "[1]",
|
|
result: func(e *executor) interface{} { return "" },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*StringResultResponse)
|
|
require.True(t, ok)
|
|
|
|
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
|
|
require.NoErrorf(t, err, "could not get block")
|
|
|
|
expectedHash := "0x" + block.Hash().StringLE()
|
|
assert.Equal(t, expectedHash, res.Result)
|
|
},
|
|
},
|
|
{
|
|
name: "string height",
|
|
params: `["first"]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid number height",
|
|
params: `[-2]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"getconnectioncount": {
|
|
{
|
|
params: "[]",
|
|
result: func(*executor) interface{} { return 0 },
|
|
},
|
|
},
|
|
"getpeers": {
|
|
{
|
|
params: "[]",
|
|
result: func(*executor) interface{} {
|
|
return &GetPeersResponse{
|
|
Jsonrpc: defaultJSONRPC,
|
|
Result: struct {
|
|
Unconnected []int `json:"unconnected"`
|
|
Connected []int `json:"connected"`
|
|
Bad []int `json:"bad"`
|
|
}{
|
|
Unconnected: []int{},
|
|
Connected: []int{},
|
|
Bad: []int{},
|
|
},
|
|
ID: defaultID,
|
|
}
|
|
},
|
|
},
|
|
},
|
|
"getrawtransaction": {
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid hash",
|
|
params: `["notahex"]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "missing hash",
|
|
params: `["` + util.Uint256{}.String() + `"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"getunspents": {
|
|
{
|
|
name: "positive",
|
|
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
|
|
result: func(e *executor) interface{} { return &GetUnspents{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetUnspents)
|
|
require.True(t, ok)
|
|
require.Equal(t, 1, len(res.Result.Balance))
|
|
assert.Equal(t, 1, len(res.Result.Balance[0].Unspents))
|
|
},
|
|
},
|
|
{
|
|
name: "positive null",
|
|
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
|
|
result: func(e *executor) interface{} { return &GetUnspents{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*GetUnspents)
|
|
require.True(t, ok)
|
|
require.Equal(t, 0, len(res.Result.Balance))
|
|
},
|
|
},
|
|
},
|
|
"getversion": {
|
|
{
|
|
params: "[]",
|
|
result: func(*executor) interface{} { return &GetVersionResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
resp, ok := result.(*GetVersionResponse)
|
|
require.True(t, ok)
|
|
require.Equal(t, "/NEO-GO:/", resp.Result.UserAgent)
|
|
},
|
|
},
|
|
},
|
|
"invoke": {
|
|
{
|
|
name: "positive",
|
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`,
|
|
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*InvokeFunctionResponse)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Result.Script)
|
|
assert.NotEqual(t, "", res.Result.State)
|
|
assert.NotEqual(t, 0, res.Result.GasConsumed)
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not a string",
|
|
params: `[42, []]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not a scripthash",
|
|
params: `["qwerty", []]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not an array",
|
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", 42]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "bad params",
|
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "Integer", "value": "qwerty"}]]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"invokefunction": {
|
|
{
|
|
name: "positive",
|
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
|
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*InvokeFunctionResponse)
|
|
require.True(t, ok)
|
|
assert.NotEqual(t, "", res.Result.Script)
|
|
assert.NotEqual(t, "", res.Result.State)
|
|
assert.NotEqual(t, 0, res.Result.GasConsumed)
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not a string",
|
|
params: `[42, "test", []]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not a scripthash",
|
|
params: `["qwerty", "test", []]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "bad params",
|
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"invokescript": {
|
|
{
|
|
name: "positive",
|
|
params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`,
|
|
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*InvokeFunctionResponse)
|
|
require.True(t, ok)
|
|
assert.NotEqual(t, "", res.Result.Script)
|
|
assert.NotEqual(t, "", res.Result.State)
|
|
assert.NotEqual(t, 0, res.Result.GasConsumed)
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "not a string",
|
|
params: `[42]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "bas string",
|
|
params: `["qwerty"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"sendrawtransaction": {
|
|
{
|
|
name: "positive",
|
|
params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`,
|
|
result: func(e *executor) interface{} { return &SendTXResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*SendTXResponse)
|
|
require.True(t, ok)
|
|
assert.True(t, res.Result)
|
|
},
|
|
},
|
|
{
|
|
name: "negative",
|
|
params: `["0274d792072617720636f6e7472616374207472616e73616374696f6e206465736372697074696f6e01949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5004f2418010000001cc9c05cefffe6cdd7b182816a9152ec218d2ec0014140dbd3cddac5cb2bd9bf6d93701f1a6f1c9dbe2d1b480c54628bbb2a4d536158c747a6af82698edf9f8af1cac3850bcb772bd9c8e4ac38f80704751cc4e0bd0e67232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "no params",
|
|
params: `[]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid string",
|
|
params: `["notahex"]`,
|
|
fail: true,
|
|
},
|
|
{
|
|
name: "invalid tx",
|
|
params: `["0274d792072617720636f6e747261637"]`,
|
|
fail: true,
|
|
},
|
|
},
|
|
"validateaddress": {
|
|
{
|
|
name: "positive",
|
|
params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`,
|
|
result: func(*executor) interface{} { return &ValidateAddrResponse{} },
|
|
check: func(t *testing.T, e *executor, result interface{}) {
|
|
res, ok := result.(*ValidateAddrResponse)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address)
|
|
assert.True(t, res.Result.IsValid)
|
|
},
|
|
},
|
|
{
|
|
name: "negative",
|
|
params: "[1]",
|
|
result: func(*executor) interface{} {
|
|
return &ValidateAddrResponse{
|
|
Jsonrpc: defaultJSONRPC,
|
|
Result: wrappers.ValidateAddressResponse{
|
|
Address: float64(1),
|
|
IsValid: false,
|
|
},
|
|
ID: defaultID,
|
|
}
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestRPC(t *testing.T) {
|
|
chain, handler := initServerWithInMemoryChain(t)
|
|
|
|
defer chain.Close()
|
|
|
|
e := &executor{chain: chain, handler: handler}
|
|
for method, cases := range rpcTestCases {
|
|
t.Run(method, func(t *testing.T) {
|
|
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}`
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, t)
|
|
checkErrResponse(t, body, tc.fail)
|
|
if tc.fail {
|
|
return
|
|
}
|
|
|
|
expected, res := tc.getResultPair(e)
|
|
err := json.Unmarshal(body, res)
|
|
require.NoErrorf(t, err, "could not parse response: %s", body)
|
|
|
|
if tc.check == nil {
|
|
assert.Equal(t, expected, res)
|
|
} else {
|
|
tc.check(t, e, res)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("getrawtransaction", func(t *testing.T) {
|
|
block, _ := chain.GetBlock(chain.GetHeaderHash(0))
|
|
TXHash := block.Transactions[1].Hash()
|
|
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE())
|
|
body := doRPCCall(rpc, handler, t)
|
|
checkErrResponse(t, body, false)
|
|
var res StringResultResponse
|
|
err := json.Unmarshal(body, &res)
|
|
require.NoErrorf(t, err, "could not parse response: %s", body)
|
|
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
|
|
})
|
|
|
|
t.Run("getrawtransaction 2 arguments", func(t *testing.T) {
|
|
block, _ := chain.GetBlock(chain.GetHeaderHash(0))
|
|
TXHash := block.Transactions[1].Hash()
|
|
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE())
|
|
body := doRPCCall(rpc, handler, t)
|
|
checkErrResponse(t, body, false)
|
|
var res StringResultResponse
|
|
err := json.Unmarshal(body, &res)
|
|
require.NoErrorf(t, err, "could not parse response: %s", body)
|
|
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
|
|
})
|
|
}
|
|
|
|
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
|
|
expected = tc.result(e)
|
|
switch exp := expected.(type) {
|
|
case string:
|
|
res = new(StringResultResponse)
|
|
expected = &StringResultResponse{
|
|
Jsonrpc: defaultJSONRPC,
|
|
Result: exp,
|
|
ID: defaultID,
|
|
}
|
|
case int:
|
|
res = new(IntResultResponse)
|
|
expected = &IntResultResponse{
|
|
Jsonrpc: defaultJSONRPC,
|
|
Result: exp,
|
|
ID: defaultID,
|
|
}
|
|
default:
|
|
resVal := reflect.New(reflect.TypeOf(expected).Elem())
|
|
res = resVal.Interface()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func checkErrResponse(t *testing.T, body []byte, expectingFail bool) {
|
|
var errresp ErrorResponse
|
|
err := json.Unmarshal(body, &errresp)
|
|
require.Nil(t, err)
|
|
if expectingFail {
|
|
assert.NotEqual(t, 0, errresp.Error.Code)
|
|
assert.NotEqual(t, "", errresp.Error.Message)
|
|
} else {
|
|
assert.Equal(t, 0, errresp.Error.Code)
|
|
assert.Equal(t, "", errresp.Error.Message)
|
|
}
|
|
}
|
|
|
|
func doRPCCall(rpcCall string, handler http.HandlerFunc, t *testing.T) []byte {
|
|
req := httptest.NewRequest("POST", "http://0.0.0.0:20333/", strings.NewReader(rpcCall))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
handler(w, req)
|
|
resp := w.Result()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoErrorf(t, err, "could not read response from the request: %s", rpcCall)
|
|
return bytes.TrimSpace(body)
|
|
}
|