mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 19:29:39 +00:00
rpc: implement invokefunction, fix #347
Param getters were redone to return errors because otherwise bad FuncParam values could lead to panic. FuncParam itself might be not the most elegant solution, but it works good enough for now.
This commit is contained in:
parent
ea9bc22510
commit
cf39171485
8 changed files with 550 additions and 22 deletions
12
docs/rpc.md
12
docs/rpc.md
|
@ -55,12 +55,22 @@ which would yield the response:
|
||||||
| `getunspents` | Yes |
|
| `getunspents` | Yes |
|
||||||
| `getversion` | Yes |
|
| `getversion` | Yes |
|
||||||
| `invoke` | No (#346) |
|
| `invoke` | No (#346) |
|
||||||
| `invokefunction` | No (#347) |
|
| `invokefunction` | Yes |
|
||||||
| `invokescript` | Yes |
|
| `invokescript` | Yes |
|
||||||
| `sendrawtransaction` | Yes |
|
| `sendrawtransaction` | Yes |
|
||||||
| `submitblock` | No (#344) |
|
| `submitblock` | No (#344) |
|
||||||
| `validateaddress` | Yes |
|
| `validateaddress` | Yes |
|
||||||
|
|
||||||
|
#### Implementation notices
|
||||||
|
|
||||||
|
##### `invokefunction`
|
||||||
|
|
||||||
|
neo-go's implementation of `invokefunction` does not return `tx` field in the
|
||||||
|
answer because that requires signing the transaction with some key in the
|
||||||
|
server which doesn't fit the model of our node-client interactions. Lacking
|
||||||
|
this signature the transaction is almost useless, so there is no point in
|
||||||
|
returning it.
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)
|
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -19,6 +21,12 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
paramType int
|
paramType int
|
||||||
|
// FuncParam represents a function argument parameter used in the
|
||||||
|
// invokefunction RPC method.
|
||||||
|
FuncParam struct {
|
||||||
|
Type StackParamType `json:"type"`
|
||||||
|
Value Param `json:"value"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -26,6 +34,7 @@ const (
|
||||||
stringT
|
stringT
|
||||||
numberT
|
numberT
|
||||||
arrayT
|
arrayT
|
||||||
|
funcParamT
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Param) String() string {
|
func (p Param) String() string {
|
||||||
|
@ -33,27 +42,82 @@ func (p Param) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns string value of the parameter.
|
// GetString returns string value of the parameter.
|
||||||
func (p Param) GetString() string { return p.Value.(string) }
|
func (p Param) GetString() (string, error) {
|
||||||
|
str, ok := p.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("not a string")
|
||||||
|
}
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetInt returns int value of te parameter.
|
// GetInt returns int value of te parameter.
|
||||||
func (p Param) GetInt() int { return p.Value.(int) }
|
func (p Param) GetInt() (int, error) {
|
||||||
|
i, ok := p.Value.(int)
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("not an integer")
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArray returns a slice of Params stored in the parameter.
|
||||||
|
func (p Param) GetArray() ([]Param, error) {
|
||||||
|
a, ok := p.Value.([]Param)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUint256 returns Uint256 value of the parameter.
|
// GetUint256 returns Uint256 value of the parameter.
|
||||||
func (p Param) GetUint256() (util.Uint256, error) {
|
func (p Param) GetUint256() (util.Uint256, error) {
|
||||||
s, ok := p.Value.(string)
|
s, err := p.GetString()
|
||||||
if !ok {
|
if err != nil {
|
||||||
return util.Uint256{}, errors.New("must be a string")
|
return util.Uint256{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.Uint256DecodeReverseString(s)
|
return util.Uint256DecodeReverseString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint160FromHex returns Uint160 value of the parameter encoded in hex.
|
||||||
|
func (p Param) GetUint160FromHex() (util.Uint160, error) {
|
||||||
|
s, err := p.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptHashLE, err := util.Uint160DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
return util.Uint160DecodeBytes(scriptHashLE.BytesReverse())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint160FromAddress returns Uint160 value of the parameter that was
|
||||||
|
// supplied as an address.
|
||||||
|
func (p Param) GetUint160FromAddress() (util.Uint160, error) {
|
||||||
|
s, err := p.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.Uint160DecodeAddress(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFuncParam returns current parameter as a function call parameter.
|
||||||
|
func (p Param) GetFuncParam() (FuncParam, error) {
|
||||||
|
fp, ok := p.Value.(FuncParam)
|
||||||
|
if !ok {
|
||||||
|
return FuncParam{}, errors.New("not a function parameter")
|
||||||
|
}
|
||||||
|
return fp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBytesHex returns []byte value of the parameter if
|
// GetBytesHex returns []byte value of the parameter if
|
||||||
// it is a hex-encoded string.
|
// it is a hex-encoded string.
|
||||||
func (p Param) GetBytesHex() ([]byte, error) {
|
func (p Param) GetBytesHex() ([]byte, error) {
|
||||||
s, ok := p.Value.(string)
|
s, err := p.GetString()
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errors.New("must be a string")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex.DecodeString(s)
|
return hex.DecodeString(s)
|
||||||
|
@ -77,6 +141,17 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
jd := json.NewDecoder(r)
|
||||||
|
jd.DisallowUnknownFields()
|
||||||
|
var fp FuncParam
|
||||||
|
if err := jd.Decode(&fp); err == nil {
|
||||||
|
p.Type = funcParamT
|
||||||
|
p.Value = fp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var ps []Param
|
var ps []Param
|
||||||
if err := json.Unmarshal(data, &ps); err == nil {
|
if err := json.Unmarshal(data, &ps); err == nil {
|
||||||
p.Type = arrayT
|
p.Type = arrayT
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParam_UnmarshalJSON(t *testing.T) {
|
func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
msg := `["str1", 123, ["str2", 3]]`
|
msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]`
|
||||||
expected := Params{
|
expected := Params{
|
||||||
{
|
{
|
||||||
Type: stringT,
|
Type: stringT,
|
||||||
|
@ -31,6 +35,21 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: arrayT,
|
||||||
|
Value: []Param{
|
||||||
|
{
|
||||||
|
Type: funcParamT,
|
||||||
|
Value: FuncParam{
|
||||||
|
Type: String,
|
||||||
|
Value: Param{
|
||||||
|
Type: stringT,
|
||||||
|
Value: "jajaja",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps Params
|
var ps Params
|
||||||
|
@ -40,3 +59,126 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
msg = `[{"2": 3}]`
|
msg = `[{"2": 3}]`
|
||||||
require.Error(t, json.Unmarshal([]byte(msg), &ps))
|
require.Error(t, json.Unmarshal([]byte(msg), &ps))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamGetString(t *testing.T) {
|
||||||
|
p := Param{stringT, "jajaja"}
|
||||||
|
str, err := p.GetString()
|
||||||
|
assert.Equal(t, "jajaja", str)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, int(100500)}
|
||||||
|
_, err = p.GetString()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetInt(t *testing.T) {
|
||||||
|
p := Param{numberT, int(100500)}
|
||||||
|
i, err := p.GetInt()
|
||||||
|
assert.Equal(t, 100500, i)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{numberT, "jajaja"}
|
||||||
|
_, err = p.GetInt()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetArray(t *testing.T) {
|
||||||
|
p := Param{arrayT, []Param{{numberT, 42}}}
|
||||||
|
a, err := p.GetArray()
|
||||||
|
assert.Equal(t, []Param{{numberT, 42}}, a)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{arrayT, 42}
|
||||||
|
_, err = p.GetArray()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint256(t *testing.T) {
|
||||||
|
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
|
u256, _ := util.Uint256DecodeReverseString(gas)
|
||||||
|
p := Param{stringT, gas}
|
||||||
|
u, err := p.GetUint256()
|
||||||
|
assert.Equal(t, u256, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint256()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
|
||||||
|
_, err = p.GetUint256()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint160FromHex(t *testing.T) {
|
||||||
|
in := "50befd26fdf6e4d957c11e078b24ebce6291456f"
|
||||||
|
u160, _ := util.Uint160DecodeString(in)
|
||||||
|
u160, _ = util.Uint160DecodeBytes(util.ArrayReverse(u160[:]))
|
||||||
|
p := Param{stringT, in}
|
||||||
|
u, err := p.GetUint160FromHex()
|
||||||
|
assert.Equal(t, u160, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint160FromHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"}
|
||||||
|
_, err = p.GetUint160FromHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint160FromAddress(t *testing.T) {
|
||||||
|
in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"
|
||||||
|
u160, _ := crypto.Uint160DecodeAddress(in)
|
||||||
|
p := Param{stringT, in}
|
||||||
|
u, err := p.GetUint160FromAddress()
|
||||||
|
assert.Equal(t, u160, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint160FromAddress()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"}
|
||||||
|
_, err = p.GetUint160FromAddress()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetFuncParam(t *testing.T) {
|
||||||
|
fp := FuncParam{
|
||||||
|
Type: String,
|
||||||
|
Value: Param{
|
||||||
|
Type: stringT,
|
||||||
|
Value: "jajaja",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p := Param{
|
||||||
|
Type: funcParamT,
|
||||||
|
Value: fp,
|
||||||
|
}
|
||||||
|
newfp, err := p.GetFuncParam()
|
||||||
|
assert.Equal(t, fp, newfp)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{funcParamT, 42}
|
||||||
|
_, err = p.GetFuncParam()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetBytesHex(t *testing.T) {
|
||||||
|
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
|
inb, _ := hex.DecodeString(in)
|
||||||
|
p := Param{stringT, in}
|
||||||
|
bh, err := p.GetBytesHex()
|
||||||
|
assert.Equal(t, inb, bh)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetBytesHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
|
||||||
|
_, err = p.GetBytesHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
||||||
|
@ -136,12 +135,12 @@ Methods:
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
case numberT:
|
case numberT:
|
||||||
if !s.validBlockHeight(param) {
|
num, err := s.blockHeightFromParam(param)
|
||||||
|
if err != nil {
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
|
hash = s.chain.GetHeaderHash(num)
|
||||||
hash = s.chain.GetHeaderHash(param.GetInt())
|
|
||||||
case defaultT:
|
case defaultT:
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
|
@ -164,12 +163,14 @@ Methods:
|
||||||
if !ok {
|
if !ok {
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
} else if !s.validBlockHeight(param) {
|
}
|
||||||
resultsErr = invalidBlockHeightError(0, param.GetInt())
|
num, err := s.blockHeightFromParam(param)
|
||||||
|
if err != nil {
|
||||||
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
results = s.chain.GetHeaderHash(param.GetInt())
|
results = s.chain.GetHeaderHash(num)
|
||||||
|
|
||||||
case "getconnectioncount":
|
case "getconnectioncount":
|
||||||
getconnectioncountCalled.Inc()
|
getconnectioncountCalled.Inc()
|
||||||
|
@ -242,6 +243,9 @@ Methods:
|
||||||
getunspentsCalled.Inc()
|
getunspentsCalled.Inc()
|
||||||
results, resultsErr = s.getAccountState(reqParams, true)
|
results, resultsErr = s.getAccountState(reqParams, true)
|
||||||
|
|
||||||
|
case "invokefunction":
|
||||||
|
results, resultsErr = s.invokeFunction(reqParams)
|
||||||
|
|
||||||
case "invokescript":
|
case "invokescript":
|
||||||
results, resultsErr = s.invokescript(reqParams)
|
results, resultsErr = s.invokescript(reqParams)
|
||||||
|
|
||||||
|
@ -306,11 +310,15 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
|
||||||
param, ok := reqParams.ValueWithType(0, stringT)
|
param, ok := reqParams.ValueWithType(0, stringT)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errInvalidParams
|
return nil, errInvalidParams
|
||||||
} else if scriptHash, err := crypto.Uint160DecodeAddress(param.GetString()); err != nil {
|
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
|
||||||
return nil, errInvalidParams
|
return nil, errInvalidParams
|
||||||
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
||||||
if unspents {
|
if unspents {
|
||||||
results = wrappers.NewUnspents(as, s.chain, param.GetString())
|
str, err := param.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errInvalidParams
|
||||||
|
}
|
||||||
|
results = wrappers.NewUnspents(as, s.chain, str)
|
||||||
} else {
|
} else {
|
||||||
results = wrappers.NewAccountState(as)
|
results = wrappers.NewAccountState(as)
|
||||||
}
|
}
|
||||||
|
@ -320,6 +328,32 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
|
||||||
return results, resultsErr
|
return results, resultsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invokescript implements the `invokescript` RPC call.
|
||||||
|
func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
|
||||||
|
scriptHashHex, ok := reqParams.ValueWithType(0, stringT)
|
||||||
|
if !ok {
|
||||||
|
return nil, errInvalidParams
|
||||||
|
}
|
||||||
|
scriptHash, err := scriptHashHex.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vm, _ := s.chain.GetTestVM()
|
||||||
|
vm.LoadScript(script)
|
||||||
|
_ = vm.Run()
|
||||||
|
result := &wrappers.InvokeResult{
|
||||||
|
State: vm.State(),
|
||||||
|
GasConsumed: "0.1",
|
||||||
|
Script: hex.EncodeToString(script),
|
||||||
|
Stack: vm.Estack(),
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
||||||
if len(reqParams) < 1 {
|
if len(reqParams) < 1 {
|
||||||
|
@ -334,10 +368,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
||||||
vm, _ := s.chain.GetTestVM()
|
vm, _ := s.chain.GetTestVM()
|
||||||
vm.LoadScript(script)
|
vm.LoadScript(script)
|
||||||
_ = vm.Run()
|
_ = vm.Run()
|
||||||
|
// It's already being GetBytesHex'ed, so it's a correct string.
|
||||||
|
echo, _ := reqParams[0].GetString()
|
||||||
result := &wrappers.InvokeResult{
|
result := &wrappers.InvokeResult{
|
||||||
State: vm.State(),
|
State: vm.State(),
|
||||||
GasConsumed: "0.1",
|
GasConsumed: "0.1",
|
||||||
Script: reqParams[0].GetString(),
|
Script: echo,
|
||||||
Stack: vm.Estack(),
|
Stack: vm.Estack(),
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -384,6 +420,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
|
||||||
return results, resultsErr
|
return results, resultsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) validBlockHeight(param *Param) bool {
|
func (s Server) blockHeightFromParam(param *Param) (int, error) {
|
||||||
return param.GetInt() >= 0 && param.GetInt() <= int(s.chain.BlockHeight())
|
num, err := param.GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < 0 || num > int(s.chain.BlockHeight()) {
|
||||||
|
return 0, invalidBlockHeightError(0, num)
|
||||||
|
}
|
||||||
|
return num, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,19 @@ type SendTXResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeFunctionResponse struct for testing.
|
||||||
|
type InvokeFunctionResponse struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Result struct {
|
||||||
|
Script string `json:"script"`
|
||||||
|
State string `json:"state"`
|
||||||
|
GasConsumed string `json:"gas_consumed"`
|
||||||
|
Stack []FuncParam `json:"stack"`
|
||||||
|
TX string `json:"tx,omitempty"`
|
||||||
|
} `json:"result"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAddrResponse struct for testing.
|
// ValidateAddrResponse struct for testing.
|
||||||
type ValidateAddrResponse struct {
|
type ValidateAddrResponse struct {
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
|
|
@ -246,6 +246,40 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
"sendrawtransaction": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
|
@ -2,6 +2,9 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
@ -162,3 +165,121 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro
|
||||||
}
|
}
|
||||||
return script.Bytes(), nil
|
return script.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFunctionInvocationScript creates a script to invoke given contract with
|
||||||
|
// given parameters.
|
||||||
|
func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) {
|
||||||
|
script := new(bytes.Buffer)
|
||||||
|
for i := len(params) - 1; i >= 0; i-- {
|
||||||
|
switch params[i].Type {
|
||||||
|
case stringT:
|
||||||
|
if err := vm.EmitString(script, params[i].String()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case numberT:
|
||||||
|
num, err := params[i].GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitString(script, strconv.Itoa(num)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case arrayT:
|
||||||
|
slice, err := params[i].GetArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for j := len(slice) - 1; j >= 0; j-- {
|
||||||
|
fp, err := slice[j].GetFuncParam()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch fp.Type {
|
||||||
|
case ByteArray, Signature:
|
||||||
|
str, err := fp.Value.GetBytesHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, str); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case String:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitString(script, str); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Hash160:
|
||||||
|
hash, err := fp.Value.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Hash256:
|
||||||
|
hash, err := fp.Value.GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case PublicKey:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, err := keys.NewPublicKeyFromString(string(str))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, key.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Integer:
|
||||||
|
val, err := fp.Value.GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitInt(script, int64(val)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Boolean:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch str {
|
||||||
|
case "true":
|
||||||
|
err = vm.EmitInt(script, 1)
|
||||||
|
case "false":
|
||||||
|
err = vm.EmitInt(script, 0)
|
||||||
|
default:
|
||||||
|
err = errors.New("wrong boolean value")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("parameter type %v is not supported", fp.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = vm.EmitInt(script, int64(len(slice)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = vm.EmitOpcode(script, vm.PACK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vm.EmitAppCall(script, contract, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return script.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
89
pkg/rpc/tx_builder_test.go
Normal file
89
pkg/rpc/tx_builder_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvocationScriptCreationGood(t *testing.T) {
|
||||||
|
p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}
|
||||||
|
contract, err := p.GetUint160FromHex()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
var paramScripts = []struct {
|
||||||
|
ps Params
|
||||||
|
script string
|
||||||
|
}{{
|
||||||
|
script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "transfer"}},
|
||||||
|
script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{numberT, 42}},
|
||||||
|
script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{}}},
|
||||||
|
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||||
|
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||||
|
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||||
|
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}},
|
||||||
|
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}},
|
||||||
|
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}},
|
||||||
|
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}}
|
||||||
|
for _, ps := range paramScripts {
|
||||||
|
script, err := CreateFunctionInvocationScript(contract, ps.ps)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ps.script, hex.EncodeToString(script))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvocationScriptCreationBad(t *testing.T) {
|
||||||
|
contract := util.Uint160{}
|
||||||
|
|
||||||
|
var testParams = []Params{
|
||||||
|
Params{{numberT, "qwerty"}},
|
||||||
|
Params{{arrayT, 42}},
|
||||||
|
Params{{arrayT, []Param{{numberT, 42}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}},
|
||||||
|
}
|
||||||
|
for _, ps := range testParams {
|
||||||
|
_, err := CreateFunctionInvocationScript(contract, ps)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue