rpc: implement server-side 'invoke' method, fix #346

This commit is contained in:
Roman Khimov 2019-11-28 19:08:31 +03:00
parent 2d41450ac9
commit e216139108
4 changed files with 188 additions and 84 deletions

View file

@ -54,7 +54,7 @@ which would yield the response:
| `gettxout` | No (#345) | | `gettxout` | No (#345) |
| `getunspents` | Yes | | `getunspents` | Yes |
| `getversion` | Yes | | `getversion` | Yes |
| `invoke` | No (#346) | | `invoke` | Yes |
| `invokefunction` | Yes | | `invokefunction` | Yes |
| `invokescript` | Yes | | `invokescript` | Yes |
| `sendrawtransaction` | Yes | | `sendrawtransaction` | Yes |
@ -63,13 +63,15 @@ which would yield the response:
#### Implementation notices #### Implementation notices
##### `invokefunction` ##### `invokefunction` and `invoke`
neo-go's implementation of `invokefunction` does not return `tx` field in the neo-go's implementation of `invokefunction` and `invoke` does not return `tx`
answer because that requires signing the transaction with some key in the field in the answer because that requires signing the transaction with some
server which doesn't fit the model of our node-client interactions. Lacking key in the server which doesn't fit the model of our node-client interactions.
this signature the transaction is almost useless, so there is no point in Lacking this signature the transaction is almost useless, so there is no point
returning it. in returning it.
Both methods also don't currently support arrays in function parameters.
## Reference ## Reference

View file

@ -243,6 +243,9 @@ Methods:
getunspentsCalled.Inc() getunspentsCalled.Inc()
results, resultsErr = s.getAccountState(reqParams, true) results, resultsErr = s.getAccountState(reqParams, true)
case "invoke":
results, resultsErr = s.invoke(reqParams)
case "invokefunction": case "invokefunction":
results, resultsErr = s.invokeFunction(reqParams) results, resultsErr = s.invokeFunction(reqParams)
@ -328,6 +331,40 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
return results, resultsErr return results, resultsErr
} }
// invoke implements the `invoke` RPC call.
func (s *Server) invoke(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
}
sliceP, ok := reqParams.ValueWithType(1, arrayT)
if !ok {
return nil, errInvalidParams
}
slice, err := sliceP.GetArray()
if err != nil {
return nil, err
}
script, err := CreateInvocationScript(scriptHash, slice)
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) invokeFunction(reqParams Params) (interface{}, error) { func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, stringT) scriptHashHex, ok := reqParams.ValueWithType(0, stringT)

View file

@ -251,6 +251,45 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
}, },
}, },
"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": { "invokefunction": {
{ {
name: "positive", name: "positive",

View file

@ -166,6 +166,90 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro
return script.Bytes(), nil return script.Bytes(), nil
} }
// expandArrayIntoScript pushes all FuncParam parameters from the given array
// into the given buffer in reverse order.
func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error {
for j := len(slice) - 1; j >= 0; j-- {
fp, err := slice[j].GetFuncParam()
if err != nil {
return err
}
switch fp.Type {
case ByteArray, Signature:
str, err := fp.Value.GetBytesHex()
if err != nil {
return err
}
if err := vm.EmitBytes(script, str); err != nil {
return err
}
case String:
str, err := fp.Value.GetString()
if err != nil {
return err
}
if err := vm.EmitString(script, str); err != nil {
return err
}
case Hash160:
hash, err := fp.Value.GetUint160FromHex()
if err != nil {
return err
}
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
return err
}
case Hash256:
hash, err := fp.Value.GetUint256()
if err != nil {
return err
}
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
return err
}
case PublicKey:
str, err := fp.Value.GetString()
if err != nil {
return err
}
key, err := keys.NewPublicKeyFromString(string(str))
if err != nil {
return err
}
if err := vm.EmitBytes(script, key.Bytes()); err != nil {
return err
}
case Integer:
val, err := fp.Value.GetInt()
if err != nil {
return err
}
if err := vm.EmitInt(script, int64(val)); err != nil {
return err
}
case Boolean:
str, err := fp.Value.GetString()
if err != nil {
return 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 err
}
default:
return fmt.Errorf("parameter type %v is not supported", fp.Type)
}
}
return nil
}
// CreateFunctionInvocationScript creates a script to invoke given contract with // CreateFunctionInvocationScript creates a script to invoke given contract with
// given parameters. // given parameters.
func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) {
@ -189,83 +273,9 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt
if err != nil { if err != nil {
return nil, err return nil, err
} }
for j := len(slice) - 1; j >= 0; j-- { err = expandArrayIntoScript(script, slice)
fp, err := slice[j].GetFuncParam() if err != nil {
if err != nil { return nil, err
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))) err = vm.EmitInt(script, int64(len(slice)))
if err != nil { if err != nil {
@ -283,3 +293,19 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt
} }
return script.Bytes(), nil return script.Bytes(), nil
} }
// CreateInvocationScript creates a script to invoke given contract with
// given parameters. It differs from CreateFunctionInvocationScript in that it
// expects one array of FuncParams and expands it onto the stack as independent
// elements.
func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, error) {
script := new(bytes.Buffer)
err := expandArrayIntoScript(script, funcParams)
if err != nil {
return nil, err
}
if err = vm.EmitAppCall(script, contract, false); err != nil {
return nil, err
}
return script.Bytes(), nil
}