Merge pull request #2234 from nspcc-dev/rpc/params-parsing

rpc: method-specific parameters parsing optimisation
This commit is contained in:
Roman Khimov 2021-11-10 20:45:44 +03:00 committed by GitHub
commit 6b8e615094
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 655 additions and 535 deletions

View file

@ -95,10 +95,17 @@ it only works for native contracts.
VM state is included to verbose response along with other transaction fields if VM state is included to verbose response along with other transaction fields if
the transaction is already on chain. the transaction is already on chain.
##### `getstateroot`
This method is able to accept state root hash instead of index, unlike the C# node
where only index is accepted.
##### `getstorage` ##### `getstorage`
This method doesn't work for the Ledger contract, you can get data via regular This method doesn't work for the Ledger contract, you can get data via regular
`getblock` and `getrawtransaction` calls. `getblock` and `getrawtransaction` calls. This method is able to get storage of
the native contract by its name (case-insensitive), unlike the C# node where
it only possible for index or hash.
#### `getnep17balances` #### `getnep17balances`

View file

@ -66,7 +66,7 @@ func Call(ic *interop.Context) error {
} }
md := cs.Manifest.ABI.GetMethod(method, len(args)) md := cs.Manifest.ABI.GetMethod(method, len(args))
if md == nil { if md == nil {
return errors.New("method not found") return fmt.Errorf("method not found: %s/%d", method, len(args))
} }
hasReturn := md.ReturnType != smartcontract.VoidType hasReturn := md.ReturnType != smartcontract.VoidType
if !hasReturn { if !hasReturn {

View file

@ -1649,8 +1649,8 @@ func wrapInitResponse(r *request.In, resp string) string {
case "getversion": case "getversion":
response = `{"id":1,"jsonrpc":"2.0","result":{"network":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` response = `{"id":1,"jsonrpc":"2.0","result":{"network":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`
case "getcontractstate": case "getcontractstate":
p, _ := r.Params() p := request.Params(r.RawParams)
name, _ := p.ValueWithType(0, request.StringT).GetString() name, _ := p.Value(0).GetString()
switch name { switch name {
case "NeoToken": case "NeoToken":
response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"name":"NEO","abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getСommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getСommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}` response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"name":"NEO","abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getСommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getСommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}`

View file

@ -134,9 +134,7 @@ readloop:
// Bad event received. // Bad event received.
break break
} }
var slice []json.RawMessage if event != response.MissedEventID && len(rr.RawParams) != 1 {
err = json.Unmarshal(rr.RawParams, &slice)
if err != nil || (event != response.MissedEventID && len(slice) != 1) {
// Bad event received. // Bad event received.
break break
} }
@ -159,7 +157,7 @@ readloop:
break readloop break readloop
} }
if event != response.MissedEventID { if event != response.MissedEventID {
err = json.Unmarshal(slice[0], val) err = json.Unmarshal(rr.RawParams[0].RawMessage, val)
if err != nil { if err != nil {
// Bad event received. // Bad event received.
break break

View file

@ -187,10 +187,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.BlockFilter) filt := new(request.BlockFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, 3, filt.Primary) require.Equal(t, 3, filt.Primary)
}, },
}, },
@ -202,10 +200,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.TxFilter) filt := new(request.TxFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender)
require.Nil(t, filt.Signer) require.Nil(t, filt.Signer)
}, },
@ -218,10 +214,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.TxFilter) filt := new(request.TxFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Nil(t, filt.Sender) require.Nil(t, filt.Sender)
require.Equal(t, util.Uint160{0, 42}, *filt.Signer) require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
}, },
@ -235,10 +229,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.TxFilter) filt := new(request.TxFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender)
require.Equal(t, util.Uint160{0, 42}, *filt.Signer) require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
}, },
@ -251,10 +243,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.NotificationFilter) filt := new(request.NotificationFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract) require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract)
require.Nil(t, filt.Name) require.Nil(t, filt.Name)
}, },
@ -267,10 +257,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.NotificationFilter) filt := new(request.NotificationFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, "my_pretty_notification", *filt.Name) require.Equal(t, "my_pretty_notification", *filt.Name)
require.Nil(t, filt.Contract) require.Nil(t, filt.Contract)
}, },
@ -284,10 +272,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.NotificationFilter) filt := new(request.NotificationFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract) require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract)
require.Equal(t, "my_pretty_notification", *filt.Name) require.Equal(t, "my_pretty_notification", *filt.Name)
}, },
@ -300,10 +286,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
param := p.Value(1) param := p.Value(1)
raw, ok := param.Value.(json.RawMessage)
require.True(t, ok)
filt := new(request.ExecutionFilter) filt := new(request.ExecutionFilter)
require.NoError(t, json.Unmarshal(raw, filt)) require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, "FAULT", filt.State) require.Equal(t, "FAULT", filt.State)
}, },
}, },
@ -320,9 +304,8 @@ func TestWSFilteredSubscriptions(t *testing.T) {
req := request.In{} req := request.In{}
err = ws.ReadJSON(&req) err = ws.ReadJSON(&req)
require.NoError(t, err) require.NoError(t, err)
params, err := req.Params() params := request.Params(req.RawParams)
require.NoError(t, err) c.serverCode(t, &params)
c.serverCode(t, params)
err = ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) err = ws.SetWriteDeadline(time.Now().Add(2 * time.Second))
require.NoError(t, err) require.NoError(t, err)
err = ws.WriteMessage(1, []byte(`{"jsonrpc": "2.0", "id": 1, "result": "0"}`)) err = ws.WriteMessage(1, []byte(`{"jsonrpc": "2.0", "id": 1, "result": "0"}`))

View file

@ -22,11 +22,10 @@ type (
// the server or to send to a server using // the server or to send to a server using
// the client. // the client.
Param struct { Param struct {
Type paramType json.RawMessage
Value interface{} cache interface{}
} }
paramType int
// FuncParam represents a function argument parameter used in the // FuncParam represents a function argument parameter used in the
// invokefunction RPC method. // invokefunction RPC method.
FuncParam struct { FuncParam struct {
@ -64,68 +63,209 @@ type (
} }
) )
// These are parameter types accepted by RPC server. var (
const ( jsonNullBytes = []byte("null")
defaultT paramType = iota jsonFalseBytes = []byte("false")
StringT jsonTrueBytes = []byte("true")
NumberT errMissingParameter = errors.New("parameter is missing")
BooleanT errNotAString = errors.New("not a string")
ArrayT errNotAnInt = errors.New("not an integer")
FuncParamT errNotABool = errors.New("not a boolean")
BlockFilterT errNotAnArray = errors.New("not an array")
TxFilterT
NotificationFilterT
ExecutionFilterT
SignerWithWitnessT
) )
var errMissingParameter = errors.New("parameter is missing")
func (p Param) String() string { func (p Param) String() string {
return fmt.Sprintf("%v", p.Value) str, _ := p.GetString()
return str
} }
// GetString returns string value of the parameter. // GetStringStrict returns string value of the parameter.
func (p *Param) GetStringStrict() (string, error) {
if p == nil {
return "", errMissingParameter
}
if p.IsNull() {
return "", errNotAString
}
if p.cache == nil {
var s string
err := json.Unmarshal(p.RawMessage, &s)
if err != nil {
return "", errNotAString
}
p.cache = s
}
if s, ok := p.cache.(string); ok {
return s, nil
}
return "", errNotAString
}
// GetString returns string value of the parameter or tries to cast parameter to a string value.
func (p *Param) GetString() (string, error) { func (p *Param) GetString() (string, error) {
if p == nil { if p == nil {
return "", errMissingParameter return "", errMissingParameter
} }
str, ok := p.Value.(string) if p.IsNull() {
if !ok { return "", errNotAString
return "", errors.New("not a string")
} }
return str, nil if p.cache == nil {
} var s string
err := json.Unmarshal(p.RawMessage, &s)
// GetBoolean returns boolean value of the parameter. if err == nil {
func (p *Param) GetBoolean() bool { p.cache = s
if p == nil { } else {
return false var i int
err = json.Unmarshal(p.RawMessage, &i)
if err == nil {
p.cache = i
} else {
var b bool
err = json.Unmarshal(p.RawMessage, &b)
if err == nil {
p.cache = b
} else {
return "", errNotAString
} }
switch p.Type { }
case NumberT: }
return p.Value != 0 }
case StringT: switch t := p.cache.(type) {
return p.Value != "" case string:
case BooleanT: return t, nil
return p.Value == true case int:
return strconv.Itoa(t), nil
case bool:
if t {
return "true", nil
}
return "false", nil
default: default:
return true return "", errNotAString
} }
} }
// GetInt returns int value of te parameter. // GetBooleanStrict returns boolean value of the parameter.
func (p *Param) GetBooleanStrict() (bool, error) {
if p == nil {
return false, errMissingParameter
}
if bytes.Equal(p.RawMessage, jsonTrueBytes) {
p.cache = true
return true, nil
}
if bytes.Equal(p.RawMessage, jsonFalseBytes) {
p.cache = false
return false, nil
}
return false, errNotABool
}
// GetBoolean returns boolean value of the parameter or tries to cast parameter to a bool value.
func (p *Param) GetBoolean() (bool, error) {
if p == nil {
return false, errMissingParameter
}
if p.IsNull() {
return false, errNotABool
}
var b bool
if p.cache == nil {
err := json.Unmarshal(p.RawMessage, &b)
if err == nil {
p.cache = b
} else {
var s string
err = json.Unmarshal(p.RawMessage, &s)
if err == nil {
p.cache = s
} else {
var i int
err = json.Unmarshal(p.RawMessage, &i)
if err == nil {
p.cache = i
} else {
return false, errNotABool
}
}
}
}
switch t := p.cache.(type) {
case bool:
return t, nil
case string:
return t != "", nil
case int:
return t != 0, nil
default:
return false, errNotABool
}
}
// GetIntStrict returns int value of the parameter if the parameter is integer.
func (p *Param) GetIntStrict() (int, error) {
if p == nil {
return 0, errMissingParameter
}
if p.IsNull() {
return 0, errNotAnInt
}
if p.cache == nil {
var i int
err := json.Unmarshal(p.RawMessage, &i)
if err != nil {
return i, errNotAnInt
}
p.cache = i
}
if i, ok := p.cache.(int); ok {
return i, nil
}
return 0, errNotAnInt
}
// GetInt returns int value of the parameter or tries to cast parameter to an int value.
func (p *Param) GetInt() (int, error) { func (p *Param) GetInt() (int, error) {
if p == nil { if p == nil {
return 0, errMissingParameter return 0, errMissingParameter
} }
i, ok := p.Value.(int) if p.IsNull() {
if ok { return 0, errNotAnInt
return i, nil }
} else if s, ok := p.Value.(string); ok { if p.cache == nil {
return strconv.Atoi(s) var i int
err := json.Unmarshal(p.RawMessage, &i)
if err == nil {
p.cache = i
} else {
var s string
err = json.Unmarshal(p.RawMessage, &s)
if err == nil {
p.cache = s
} else {
var b bool
err = json.Unmarshal(p.RawMessage, &b)
if err == nil {
p.cache = b
} else {
return 0, errNotAnInt
}
}
}
}
switch t := p.cache.(type) {
case int:
return t, nil
case string:
return strconv.Atoi(t)
case bool:
if t {
return 1, nil
}
return 0, nil
default:
return 0, errNotAnInt
} }
return 0, errors.New("not an integer")
} }
// GetArray returns a slice of Params stored in the parameter. // GetArray returns a slice of Params stored in the parameter.
@ -133,22 +273,21 @@ func (p *Param) GetArray() ([]Param, error) {
if p == nil { if p == nil {
return nil, errMissingParameter return nil, errMissingParameter
} }
a, ok := p.Value.([]Param) if p.IsNull() {
if ok { return nil, errNotAnArray
return a, nil
} }
raw, ok := p.Value.(json.RawMessage) if p.cache == nil {
if !ok { a := []Param{}
return nil, errors.New("not an array") err := json.Unmarshal(p.RawMessage, &a)
}
a = []Param{}
err := json.Unmarshal(raw, &a)
if err != nil { if err != nil {
return nil, errors.New("not an array") return nil, errNotAnArray
} }
p.Value = a p.cache = a
}
if a, ok := p.cache.([]Param); ok {
return a, nil return a, nil
}
return nil, errNotAnArray
} }
// GetUint256 returns Uint256 value of the parameter. // GetUint256 returns Uint256 value of the parameter.
@ -167,11 +306,8 @@ func (p *Param) GetUint160FromHex() (util.Uint160, error) {
if err != nil { if err != nil {
return util.Uint160{}, err return util.Uint160{}, err
} }
if len(s) == 2*util.Uint160Size+2 && s[0] == '0' && s[1] == 'x' {
s = s[2:]
}
return util.Uint160DecodeStringLE(s) return util.Uint160DecodeStringLE(strings.TrimPrefix(s, "0x"))
} }
// GetUint160FromAddress returns Uint160 value of the parameter that was // GetUint160FromAddress returns Uint160 value of the parameter that was
@ -200,20 +336,10 @@ func (p *Param) GetFuncParam() (FuncParam, error) {
if p == nil { if p == nil {
return FuncParam{}, errMissingParameter return FuncParam{}, errMissingParameter
} }
fp, ok := p.Value.(FuncParam) // This one doesn't need to be cached, it's used only once.
if ok { fp := FuncParam{}
return fp, nil err := json.Unmarshal(p.RawMessage, &fp)
}
raw, ok := p.Value.(json.RawMessage)
if !ok {
return FuncParam{}, errors.New("not a function parameter")
}
err := json.Unmarshal(raw, &fp)
if err != nil {
return fp, err return fp, err
}
p.Value = fp
return fp, nil
} }
// GetBytesHex returns []byte value of the parameter if // GetBytesHex returns []byte value of the parameter if
@ -240,25 +366,20 @@ func (p *Param) GetBytesBase64() ([]byte, error) {
// GetSignerWithWitness returns SignerWithWitness value of the parameter. // GetSignerWithWitness returns SignerWithWitness value of the parameter.
func (p *Param) GetSignerWithWitness() (SignerWithWitness, error) { func (p *Param) GetSignerWithWitness() (SignerWithWitness, error) {
c, ok := p.Value.(SignerWithWitness) // This one doesn't need to be cached, it's used only once.
if ok {
return c, nil
}
raw, ok := p.Value.(json.RawMessage)
if !ok {
return SignerWithWitness{}, errors.New("not a signer")
}
aux := new(signerWithWitnessAux) aux := new(signerWithWitnessAux)
err := json.Unmarshal(raw, aux) err := json.Unmarshal(p.RawMessage, aux)
if err != nil { if err != nil {
return SignerWithWitness{}, errors.New("not a signer") return SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
} }
accParam := Param{StringT, aux.Account} acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
acc, err := accParam.GetUint160FromAddressOrHex()
if err != nil { if err != nil {
return SignerWithWitness{}, errors.New("not a signer") acc, err = address.StringToUint160(aux.Account)
} }
c = SignerWithWitness{ if err != nil {
return SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
}
c := SignerWithWitness{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: acc, Account: acc,
Scopes: aux.Scopes, Scopes: aux.Scopes,
@ -270,7 +391,6 @@ func (p *Param) GetSignerWithWitness() (SignerWithWitness, error) {
VerificationScript: aux.VerificationScript, VerificationScript: aux.VerificationScript,
}, },
} }
p.Value = c
return c, nil return c, nil
} }
@ -309,48 +429,9 @@ func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Wi
return signers, witnesses, nil return signers, witnesses, nil
} }
// UnmarshalJSON implements json.Unmarshaler interface. // IsNull returns whether parameter represents JSON nil value.
func (p *Param) UnmarshalJSON(data []byte) error { func (p *Param) IsNull() bool {
r := bytes.NewReader(data) return bytes.Equal(p.RawMessage, jsonNullBytes)
jd := json.NewDecoder(r)
jd.UseNumber()
tok, err := jd.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case json.Delim:
if t == json.Delim('[') {
var arr []Param
err := json.Unmarshal(data, &arr)
if err != nil {
return err
}
p.Type = ArrayT
p.Value = arr
} else {
p.Type = defaultT
p.Value = json.RawMessage(data)
}
case bool:
p.Type = BooleanT
p.Value = t
case float64: // unexpected because of `UseNumber`.
panic("unexpected")
case json.Number:
value, err := strconv.Atoi(string(t))
if err != nil {
return err
}
p.Type = NumberT
p.Value = value
case string:
p.Type = StringT
p.Value = t
default: // null
p.Type = defaultT
}
return nil
} }
// signerWithWitnessAux is an auxiluary struct for JSON marshalling. We need it because of // signerWithWitnessAux is an auxiluary struct for JSON marshalling. We need it because of

View file

@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -15,66 +16,178 @@ import (
) )
func TestParam_UnmarshalJSON(t *testing.T) { func TestParam_UnmarshalJSON(t *testing.T) {
msg := `["str1", 123, null, ["str2", 3], [{"type": "String", "value": "jajaja"}], type testCase struct {
check func(t *testing.T, p *Param)
expectedRawMessage []byte
}
msg := `["123", 123, null, ["str2", 3], [{"type": "String", "value": "jajaja"}],
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}, {"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"},
{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}, {"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"},
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]` [{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]`
expected := Params{ expected := []testCase{
{ {
Type: StringT, check: func(t *testing.T, p *Param) {
Value: "str1", expectedS := "123"
actualS, err := p.GetStringStrict()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
actualS, err = p.GetString()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
expectedI := 123
_, err = p.GetIntStrict()
require.Error(t, err)
actualI, err := p.GetInt()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
expectedB := true
_, err = p.GetBooleanStrict()
require.Error(t, err)
actualB, err := p.GetBoolean()
require.NoError(t, err)
require.Equal(t, expectedB, actualB)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`"123"`),
}, },
{ {
Type: NumberT, check: func(t *testing.T, p *Param) {
Value: 123, expectedS := "123"
_, err := p.GetStringStrict()
require.Error(t, err)
actualS, err := p.GetString()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
expectedI := 123
actualI, err := p.GetIntStrict()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
actualI, err = p.GetInt()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
expectedB := true
_, err = p.GetBooleanStrict()
require.Error(t, err)
actualB, err := p.GetBoolean()
require.NoError(t, err)
require.Equal(t, expectedB, actualB)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`123`),
}, },
{ {
Type: defaultT, check: func(t *testing.T, p *Param) {
_, err := p.GetStringStrict()
require.Error(t, err)
_, err = p.GetString()
require.Error(t, err)
_, err = p.GetIntStrict()
require.Error(t, err)
_, err = p.GetInt()
require.Error(t, err)
_, err = p.GetBooleanStrict()
require.Error(t, err)
_, err = p.GetBoolean()
require.Error(t, err)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`null`),
}, },
{ {
Type: ArrayT, check: func(t *testing.T, p *Param) {
Value: []Param{ _, err := p.GetStringStrict()
{ require.Error(t, err)
Type: StringT, _, err = p.GetString()
Value: "str2", require.Error(t, err)
_, err = p.GetIntStrict()
require.Error(t, err)
_, err = p.GetInt()
require.Error(t, err)
_, err = p.GetBooleanStrict()
require.Error(t, err)
_, err = p.GetBoolean()
require.Error(t, err)
a, err := p.GetArray()
require.NoError(t, err)
require.Equal(t, []Param{
{RawMessage: json.RawMessage(`"str2"`)},
{RawMessage: json.RawMessage(`3`)},
}, a)
},
expectedRawMessage: []byte(`["str2", 3]`),
}, },
{ {
Type: NumberT, check: func(t *testing.T, p *Param) {
Value: 3, a, err := p.GetArray()
}, require.NoError(t, err)
require.Equal(t, 1, len(a))
fp, err := a[0].GetFuncParam()
require.NoError(t, err)
require.Equal(t, smartcontract.StringType, fp.Type)
strVal, err := fp.Value.GetStringStrict()
require.NoError(t, err)
require.Equal(t, "jajaja", strVal)
}, },
expectedRawMessage: []byte(`[{"type": "String", "value": "jajaja"}]`),
}, },
{ {
Type: ArrayT, check: func(t *testing.T, p *Param) {
Value: []Param{ actual, err := p.GetSignerWithWitness()
{ require.NoError(t, err)
Type: defaultT, expectedAcc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
Value: json.RawMessage(`{"type": "String", "value": "jajaja"}`), require.NoError(t, err)
}, require.Equal(t, SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc}}, actual)
}, },
expectedRawMessage: []byte(`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`),
}, },
{ {
Type: defaultT, check: func(t *testing.T, p *Param) {
Value: json.RawMessage(`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`), actual, err := p.GetSignerWithWitness()
require.NoError(t, err)
expectedAcc, err := address.StringToUint160("NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag")
require.NoError(t, err)
require.Equal(t, SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}}, actual)
},
expectedRawMessage: []byte(`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`),
}, },
{ {
Type: defaultT, check: func(t *testing.T, p *Param) {
Value: json.RawMessage(`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`), actualSigs, actualWtns, err := p.GetSignersWithWitnesses()
}, require.NoError(t, err)
{ require.Equal(t, 1, len(actualSigs))
Type: ArrayT, require.Equal(t, 1, len(actualWtns))
Value: []Param{ expectedAcc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
{ require.NoError(t, err)
Type: defaultT, require.Equal(t, transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}, actualSigs[0])
Value: json.RawMessage(`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`), require.Equal(t, transaction.Witness{}, actualWtns[0])
},
}, },
expectedRawMessage: []byte(`[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]`),
}, },
} }
var ps Params var ps Params
require.NoError(t, json.Unmarshal([]byte(msg), &ps)) require.NoError(t, json.Unmarshal([]byte(msg), &ps))
require.Equal(t, expected, ps) require.Equal(t, len(expected), len(ps))
for i, tc := range expected {
require.NotNil(t, ps[i])
require.Equal(t, json.RawMessage(tc.expectedRawMessage), ps[i].RawMessage, i)
tc.check(t, &ps[i])
}
} }
func TestGetWitness(t *testing.T) { func TestGetWitness(t *testing.T) {
@ -108,11 +221,10 @@ func TestGetWitness(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
p := Param{Value: json.RawMessage(tc.raw)} p := Param{RawMessage: json.RawMessage(tc.raw)}
actual, err := p.GetSignerWithWitness() actual, err := p.GetSignerWithWitness()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tc.expected, actual) require.Equal(t, tc.expected, actual)
require.Equal(t, tc.expected, p.Value)
actual, err = p.GetSignerWithWitness() // valid second invocation. actual, err = p.GetSignerWithWitness() // valid second invocation.
require.NoError(t, err) require.NoError(t, err)
@ -120,57 +232,24 @@ func TestGetWitness(t *testing.T) {
} }
} }
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) { func TestParamGetUint256(t *testing.T) {
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
u256, _ := util.Uint256DecodeStringLE(gas) u256, _ := util.Uint256DecodeStringLE(gas)
p := Param{StringT, gas} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, gas))}
u, err := p.GetUint256() u, err := p.GetUint256()
assert.Equal(t, u256, u) assert.Equal(t, u256, u)
require.Nil(t, err) require.Nil(t, err)
p = Param{StringT, "0x" + gas} p = Param{RawMessage: []byte(fmt.Sprintf(`"0x%s"`, gas))}
u, err = p.GetUint256() u, err = p.GetUint256()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, u256, u) assert.Equal(t, u256, u)
p = Param{StringT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint256() _, err = p.GetUint256()
require.NotNil(t, err) require.NotNil(t, err)
p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} p = Param{RawMessage: []byte(`"qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"`)}
_, err = p.GetUint256() _, err = p.GetUint256()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -178,16 +257,16 @@ func TestParamGetUint256(t *testing.T) {
func TestParamGetUint160FromHex(t *testing.T) { func TestParamGetUint160FromHex(t *testing.T) {
in := "50befd26fdf6e4d957c11e078b24ebce6291456f" in := "50befd26fdf6e4d957c11e078b24ebce6291456f"
u160, _ := util.Uint160DecodeStringLE(in) u160, _ := util.Uint160DecodeStringLE(in)
p := Param{StringT, in} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromHex() u, err := p.GetUint160FromHex()
assert.Equal(t, u160, u) assert.Equal(t, u160, u)
require.Nil(t, err) require.Nil(t, err)
p = Param{StringT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint160FromHex() _, err = p.GetUint160FromHex()
require.NotNil(t, err) require.NotNil(t, err)
p = Param{StringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} p = Param{RawMessage: []byte(`"wwbefd26fdf6e4d957c11e078b24ebce6291456f"`)}
_, err = p.GetUint160FromHex() _, err = p.GetUint160FromHex()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -195,16 +274,16 @@ func TestParamGetUint160FromHex(t *testing.T) {
func TestParamGetUint160FromAddress(t *testing.T) { func TestParamGetUint160FromAddress(t *testing.T) {
in := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8" in := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8"
u160, _ := address.StringToUint160(in) u160, _ := address.StringToUint160(in)
p := Param{StringT, in} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromAddress() u, err := p.GetUint160FromAddress()
assert.Equal(t, u160, u) assert.Equal(t, u160, u)
require.Nil(t, err) require.Nil(t, err)
p = Param{StringT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint160FromAddress() _, err = p.GetUint160FromAddress()
require.NotNil(t, err) require.NotNil(t, err)
p = Param{StringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} p = Param{RawMessage: []byte(`"QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"`)}
_, err = p.GetUint160FromAddress() _, err = p.GetUint160FromAddress()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -214,14 +293,14 @@ func TestParam_GetUint160FromAddressOrHex(t *testing.T) {
inHex, _ := address.StringToUint160(in) inHex, _ := address.StringToUint160(in)
t.Run("Address", func(t *testing.T) { t.Run("Address", func(t *testing.T) {
p := Param{StringT, in} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromAddressOrHex() u, err := p.GetUint160FromAddressOrHex()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, inHex, u) require.Equal(t, inHex, u)
}) })
t.Run("Hex", func(t *testing.T) { t.Run("Hex", func(t *testing.T) {
p := Param{StringT, inHex.StringLE()} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, inHex.StringLE()))}
u, err := p.GetUint160FromAddressOrHex() u, err := p.GetUint160FromAddressOrHex()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, inHex, u) require.Equal(t, inHex, u)
@ -231,20 +310,14 @@ func TestParam_GetUint160FromAddressOrHex(t *testing.T) {
func TestParamGetFuncParam(t *testing.T) { func TestParamGetFuncParam(t *testing.T) {
fp := FuncParam{ fp := FuncParam{
Type: smartcontract.StringType, Type: smartcontract.StringType,
Value: Param{ Value: Param{RawMessage: []byte(`"jajaja"`)},
Type: StringT,
Value: "jajaja",
},
}
p := Param{
Type: FuncParamT,
Value: fp,
} }
p := Param{RawMessage: []byte(`{"type": "String", "value": "jajaja"}`)}
newfp, err := p.GetFuncParam() newfp, err := p.GetFuncParam()
assert.Equal(t, fp, newfp) assert.Equal(t, fp, newfp)
require.Nil(t, err) require.Nil(t, err)
p = Param{FuncParamT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetFuncParam() _, err = p.GetFuncParam()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -252,16 +325,17 @@ func TestParamGetFuncParam(t *testing.T) {
func TestParamGetBytesHex(t *testing.T) { func TestParamGetBytesHex(t *testing.T) {
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
inb, _ := hex.DecodeString(in) inb, _ := hex.DecodeString(in)
p := Param{StringT, in} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
bh, err := p.GetBytesHex() bh, err := p.GetBytesHex()
assert.Equal(t, inb, bh) assert.Equal(t, inb, bh)
require.Nil(t, err) require.Nil(t, err)
p = Param{StringT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetBytesHex() h, err := p.GetBytesHex()
require.NotNil(t, err) assert.Equal(t, []byte{0x42}, h) // that's the way how C# works: 42 -> "42" -> []byte{0x42}
require.Nil(t, err)
p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} p = Param{RawMessage: []byte(`"qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"`)}
_, err = p.GetBytesHex() _, err = p.GetBytesHex()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -270,16 +344,16 @@ func TestParamGetBytesBase64(t *testing.T) {
in := "Aj4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm" in := "Aj4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm"
inb, err := base64.StdEncoding.DecodeString(in) inb, err := base64.StdEncoding.DecodeString(in)
require.NoError(t, err) require.NoError(t, err)
p := Param{StringT, in} p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
bh, err := p.GetBytesBase64() bh, err := p.GetBytesBase64()
assert.Equal(t, inb, bh) assert.Equal(t, inb, bh)
require.Nil(t, err) require.Nil(t, err)
p = Param{StringT, 42} p = Param{RawMessage: []byte(`42`)}
_, err = p.GetBytesBase64() _, err = p.GetBytesBase64()
require.NotNil(t, err) require.NotNil(t, err)
p = Param{StringT, "@j4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm"} p = Param{RawMessage: []byte("@j4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm")}
_, err = p.GetBytesBase64() _, err = p.GetBytesBase64()
require.NotNil(t, err) require.NotNil(t, err)
} }
@ -296,12 +370,12 @@ func TestParamGetSigner(t *testing.T) {
VerificationScript: []byte{1, 2, 3}, VerificationScript: []byte{1, 2, 3},
}, },
} }
p := Param{Type: SignerWithWitnessT, Value: c} p := Param{RawMessage: []byte(`{"account": "0x0000000000000000000000000000000004030201", "scopes": "Global", "invocation": "AQID", "verification": "AQID"}`)}
actual, err := p.GetSignerWithWitness() actual, err := p.GetSignerWithWitness()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c, actual) require.Equal(t, c, actual)
p = Param{Type: SignerWithWitnessT, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`} p = Param{RawMessage: []byte(`"not a signer"`)}
_, err = p.GetSignerWithWitness() _, err = p.GetSignerWithWitness()
require.Error(t, err) require.Error(t, err)
} }
@ -310,10 +384,7 @@ func TestParamGetSigners(t *testing.T) {
u1 := util.Uint160{1, 2, 3, 4} u1 := util.Uint160{1, 2, 3, 4}
u2 := util.Uint160{5, 6, 7, 8} u2 := util.Uint160{5, 6, 7, 8}
t.Run("from hashes", func(t *testing.T) { t.Run("from hashes", func(t *testing.T) {
p := Param{ArrayT, []Param{ p := Param{RawMessage: []byte(fmt.Sprintf(`["%s", "%s"]`, u1.StringLE(), u2.StringLE()))}
{Type: StringT, Value: u1.StringLE()},
{Type: StringT, Value: u2.StringLE()},
}}
actual, _, err := p.GetSignersWithWitnesses() actual, _, err := p.GetSignersWithWitnesses()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(actual)) require.Equal(t, 2, len(actual))
@ -321,56 +392,8 @@ func TestParamGetSigners(t *testing.T) {
require.True(t, u2.Equals(actual[1].Account)) require.True(t, u2.Equals(actual[1].Account))
}) })
t.Run("from signers", func(t *testing.T) {
c1 := SignerWithWitness{
Signer: transaction.Signer{
Account: u1,
Scopes: transaction.Global,
},
Witness: transaction.Witness{
InvocationScript: []byte{1, 2, 3},
VerificationScript: []byte{1, 2, 3},
},
}
c2 := SignerWithWitness{
Signer: transaction.Signer{
Account: u2,
Scopes: transaction.CustomContracts,
AllowedContracts: []util.Uint160{
{1, 2, 3},
{4, 5, 6},
},
},
}
p := Param{ArrayT, []Param{
{Type: SignerWithWitnessT, Value: c1},
{Type: SignerWithWitnessT, Value: c2},
}}
actualS, actualW, err := p.GetSignersWithWitnesses()
require.NoError(t, err)
require.Equal(t, 2, len(actualS))
require.Equal(t, transaction.Signer{
Account: c1.Account,
Scopes: c1.Scopes,
}, actualS[0])
require.Equal(t, transaction.Signer{
Account: c2.Account,
Scopes: c2.Scopes,
AllowedContracts: c2.AllowedContracts,
}, actualS[1])
require.EqualValues(t, 2, len(actualW))
require.EqualValues(t, transaction.Witness{
InvocationScript: c1.InvocationScript,
VerificationScript: c1.VerificationScript,
}, actualW[0])
require.Equal(t, transaction.Witness{}, actualW[1])
})
t.Run("bad format", func(t *testing.T) { t.Run("bad format", func(t *testing.T) {
p := Param{ArrayT, []Param{ p := Param{RawMessage: []byte(`"not a signer"`)}
{Type: StringT, Value: u1.StringLE()},
{Type: StringT, Value: "bla"},
}}
_, _, err := p.GetSignersWithWitnesses() _, _, err := p.GetSignersWithWitnesses()
require.Error(t, err) require.Error(t, err)
}) })

View file

@ -17,15 +17,6 @@ func (p Params) Value(index int) *Param {
return nil return nil
} }
// ValueWithType returns the param struct at the given index if it
// exists and matches the given type.
func (p Params) ValueWithType(index int, valType paramType) *Param {
if val := p.Value(index); val != nil && val.Type == valType {
return val
}
return nil
}
func (p Params) String() string { func (p Params) String() string {
return fmt.Sprintf("%v", []Param(p)) return fmt.Sprintf("%v", []Param(p))
} }

View file

@ -70,8 +70,8 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
} }
emit.Int(script, int64(val)) emit.Int(script, int64(val))
case smartcontract.BoolType: case smartcontract.BoolType:
val, ok := fp.Value.Value.(bool) val, err := fp.Value.GetBoolean() // not GetBooleanStrict(), because that's the way C# code works
if !ok { if err != nil {
return errors.New("not a bool") return errors.New("not a bool")
} }
if val { if val {
@ -90,6 +90,10 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
} }
emit.Int(script, int64(len(val))) emit.Int(script, int64(len(val)))
emit.Opcodes(script, opcode.PACK) emit.Opcodes(script, opcode.PACK)
case smartcontract.AnyType:
if fp.Value.IsNull() {
emit.Opcodes(script, opcode.PUSHNULL)
}
default: default:
return fmt.Errorf("parameter type %v is not supported", fp.Type) return fmt.Errorf("parameter type %v is not supported", fp.Type)
} }
@ -102,29 +106,21 @@ func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error {
func CreateFunctionInvocationScript(contract util.Uint160, method string, params Params) ([]byte, error) { func CreateFunctionInvocationScript(contract util.Uint160, method string, params Params) ([]byte, error) {
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
for i := len(params) - 1; i >= 0; i-- { for i := len(params) - 1; i >= 0; i-- {
switch params[i].Type { if slice, err := params[i].GetArray(); err == nil {
case StringT:
emit.String(script.BinWriter, params[i].String())
case NumberT:
num, err := params[i].GetInt()
if err != nil {
return nil, err
}
emit.String(script.BinWriter, strconv.Itoa(num))
case BooleanT:
val := params[i].GetBoolean()
emit.Bool(script.BinWriter, val)
case ArrayT:
slice, err := params[i].GetArray()
if err != nil {
return nil, err
}
err = ExpandArrayIntoScript(script.BinWriter, slice) err = ExpandArrayIntoScript(script.BinWriter, slice)
if err != nil { if err != nil {
return nil, err return nil, err
} }
emit.Int(script.BinWriter, int64(len(slice))) emit.Int(script.BinWriter, int64(len(slice)))
emit.Opcodes(script.BinWriter, opcode.PACK) emit.Opcodes(script.BinWriter, opcode.PACK)
} else if s, err := params[i].GetStringStrict(); err == nil {
emit.String(script.BinWriter, s)
} else if n, err := params[i].GetIntStrict(); err == nil {
emit.String(script.BinWriter, strconv.Itoa(n))
} else if b, err := params[i].GetBooleanStrict(); err == nil {
emit.Bool(script.BinWriter, b)
} else {
return nil, fmt.Errorf("failed to convert parmeter %s to script parameter", params[i])
} }
} }
if len(params) == 0 { if len(params) == 0 {

View file

@ -2,10 +2,10 @@ package request
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -13,7 +13,7 @@ import (
) )
func TestInvocationScriptCreationGood(t *testing.T) { func TestInvocationScriptCreationGood(t *testing.T) {
p := Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"} p := Param{RawMessage: []byte(`"50befd26fdf6e4d957c11e078b24ebce6291456f"`)}
contract, err := p.GetUint160FromHex() contract, err := p.GetUint160FromHex()
require.Nil(t, err) require.Nil(t, err)
@ -21,49 +21,60 @@ func TestInvocationScriptCreationGood(t *testing.T) {
ps Params ps Params
script string script string
}{{ }{{
ps: Params{{Type: StringT, Value: "transfer"}}, ps: Params{{RawMessage: []byte(`"transfer"`)}},
script: "c21f0c087472616e736665720c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "c21f0c087472616e736665720c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: NumberT, Value: 42}}, ps: Params{{RawMessage: []byte(`42`)}},
script: "c21f0c0234320c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "c21f0c0234320c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "m"}, {Type: BooleanT, Value: true}}, ps: Params{{RawMessage: []byte(`"m"`)}, {RawMessage: []byte(`true`)}},
script: "11db201f0c016d0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "11db201f0c016d0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[]`)}},
script: "10c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "10c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "AwEtR+diEK7HO+Oas9GG4KQP6Nhr+j1Pq/2le6E7iPlq"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "ByteString", "value": "AwEtR+diEK7HO+Oas9GG4KQP6Nhr+j1Pq/2le6E7iPlq"}]`)}},
script: "0c2103012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96a11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c2103012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96a11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Signature", "value": "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}]`)}},
script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "String", "value": "50befd26fdf6e4d957c11e078b24ebce6291456f"}]`)}},
script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Hash160", "value": "50befd26fdf6e4d957c11e078b24ebce6291456f"}]`)}},
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Hash256", "value": "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}]`)}},
script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "PublicKey", "value": "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}]`)}},
script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Integer", "value": 42}]`)}},
script: "002a11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "002a11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: BooleanT, Value: true}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Integer", "value": "42"}]`)}}, // C# code doesn't use strict type assertions for JSON-ised params
script: "002a11c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, {
ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Integer", "value": true}]`)}}, // C# code doesn't use strict type assertions for JSON-ised params
script: "1111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "1111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, { }, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: BooleanT, Value: false}}}}}}, ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Boolean", "value": true}]`)}},
script: "1111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, {
ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Boolean", "value": false}]`)}},
script: "1011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", script: "1011c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}, {
ps: Params{{RawMessage: []byte(`"a"`)}, {RawMessage: []byte(`[{"type": "Boolean", "value": "blah"}]`)}}, // C# code doesn't use strict type assertions for JSON-ised params
script: "1111c01f0c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
}} }}
for _, ps := range paramScripts { for i, ps := range paramScripts {
script, err := CreateFunctionInvocationScript(contract, ps.ps[0].String(), ps.ps[1:]) method, err := ps.ps[0].GetString()
require.NoError(t, err, fmt.Sprintf("testcase #%d", i))
script, err := CreateFunctionInvocationScript(contract, method, ps.ps[1:])
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, ps.script, hex.EncodeToString(script)) assert.Equal(t, ps.script, hex.EncodeToString(script), fmt.Sprintf("testcase #%d", i))
} }
} }
@ -71,25 +82,18 @@ func TestInvocationScriptCreationBad(t *testing.T) {
contract := util.Uint160{} contract := util.Uint160{}
var testParams = []Params{ var testParams = []Params{
{{Type: NumberT, Value: "qwerty"}}, {{RawMessage: []byte(`[{"type": "ByteArray", "value": "qwerty"}]`)}},
{{Type: ArrayT, Value: 42}}, {{RawMessage: []byte(`[{"type": "Signature", "value": "qwerty"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}}, {{RawMessage: []byte(`[{"type": "Hash160", "value": "qwerty"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, {{RawMessage: []byte(`[{"type": "Hash256", "value": "qwerty"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, {{RawMessage: []byte(`[{"type": "PublicKey", "value": 42}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: NumberT, Value: 42}}}}}}, {{RawMessage: []byte(`[{"type": "PublicKey", "value": "qwerty"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, {{RawMessage: []byte(`[{"type": "Integer", "value": "123q"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, {{RawMessage: []byte(`[{"type": "Unknown"}]`)}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: NumberT, Value: 42}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: BooleanT, Value: true}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: NumberT, Value: 42}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.UnknownType, Value: Param{}}}}}},
} }
for _, ps := range testParams { for i, ps := range testParams {
_, err := CreateFunctionInvocationScript(contract, "", ps) _, err := CreateFunctionInvocationScript(contract, "", ps)
assert.NotNil(t, err) assert.NotNil(t, err, fmt.Sprintf("testcase #%d", i))
} }
} }
@ -99,11 +103,11 @@ func TestExpandArrayIntoScript(t *testing.T) {
Expected []byte Expected []byte
}{ }{
{ {
Input: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Value: "a"}}}}, Input: []Param{{RawMessage: []byte(`{"type": "String", "value": "a"}`)}},
Expected: []byte{byte(opcode.PUSHDATA1), 1, byte('a')}, Expected: []byte{byte(opcode.PUSHDATA1), 1, byte('a')},
}, },
{ {
Input: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ArrayType, Value: Param{Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Value: "a"}}}}}}}}, Input: []Param{{RawMessage: []byte(`{"type": "Array", "value": [{"type": "String", "value": "a"}]}`)}},
Expected: []byte{byte(opcode.PUSHDATA1), 1, byte('a'), byte(opcode.PUSH1), byte(opcode.PACK)}, Expected: []byte{byte(opcode.PUSHDATA1), 1, byte('a'), byte(opcode.PUSH1), byte(opcode.PACK)},
}, },
} }
@ -115,10 +119,10 @@ func TestExpandArrayIntoScript(t *testing.T) {
} }
errorCases := [][]Param{ errorCases := [][]Param{
{ {
{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ArrayType, Value: Param{Value: "a"}}}, {RawMessage: []byte(`{"type": "Array", "value": "a"}`)},
}, },
{ {
{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ArrayType, Value: Param{Value: []Param{{Type: FuncParamT, Value: nil}}}}}, {RawMessage: []byte(`{"type": "Array", "value": null}`)},
}, },
} }
for _, c := range errorCases { for _, c := range errorCases {

View file

@ -53,7 +53,7 @@ type Request struct {
type In struct { type In struct {
JSONRPC string `json:"jsonrpc"` JSONRPC string `json:"jsonrpc"`
Method string `json:"method"` Method string `json:"method"`
RawParams json.RawMessage `json:"params,omitempty"` RawParams []Param `json:"params,omitempty"`
RawID json.RawMessage `json:"id,omitempty"` RawID json.RawMessage `json:"id,omitempty"`
} }
@ -134,16 +134,3 @@ func NewIn() *In {
JSONRPC: JSONRPCVersion, JSONRPC: JSONRPCVersion,
} }
} }
// Params takes a slice of any type and attempts to bind
// the params to it.
func (r *In) Params() (*Params, error) {
params := Params{}
err := json.Unmarshal(r.RawParams, &params)
if err != nil {
return nil, fmt.Errorf("error parsing params: %w", err)
}
return &params, nil
}

View file

@ -0,0 +1,49 @@
package request
import (
"bytes"
"encoding/json"
"io"
"testing"
)
type readCloser struct {
io.Reader
}
func (readCloser) Close() error { return nil }
func BenchmarkUnmarshal(b *testing.B) {
req := []byte(`{"jsonrpc":"2.0", "method":"invokefunction","params":["0x50befd26fdf6e4d957c11e078b24ebce6291456f", "someMethod", [{"type": "String", "value": "50befd26fdf6e4d957c11e078b24ebce6291456f"}, {"type": "Integer", "value": "42"}, {"type": "Boolean", "value": false}]]}`)
b.Run("single", func(b *testing.B) {
b.ReportAllocs()
b.Run("unmarshal", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
in := new(In)
b.StartTimer()
err := json.Unmarshal(req, in)
if err != nil {
b.FailNow()
}
}
})
b.Run("decode data", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
r := NewRequest()
r.In = new(In)
rd := bytes.NewReader(req)
b.StartTimer()
err := r.DecodeData(readCloser{rd})
if err != nil {
b.FailNow()
}
}
})
})
}

View file

@ -335,10 +335,7 @@ func (s *Server) handleIn(req *request.In, sub *subscriber) response.Abstract {
return s.packResponse(req, nil, response.NewInvalidParamsError("Problem parsing JSON", fmt.Errorf("invalid version, expected 2.0 got: '%s'", req.JSONRPC))) return s.packResponse(req, nil, response.NewInvalidParamsError("Problem parsing JSON", fmt.Errorf("invalid version, expected 2.0 got: '%s'", req.JSONRPC)))
} }
reqParams, err := req.Params() reqParams := request.Params(req.RawParams)
if err != nil {
return s.packResponse(req, nil, response.NewInvalidParamsError("Problem parsing request parameters", err))
}
s.log.Debug("processing rpc request", s.log.Debug("processing rpc request",
zap.String("method", req.Method), zap.String("method", req.Method),
@ -349,11 +346,11 @@ func (s *Server) handleIn(req *request.In, sub *subscriber) response.Abstract {
resErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) resErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
handler, ok := rpcHandlers[req.Method] handler, ok := rpcHandlers[req.Method]
if ok { if ok {
res, resErr = handler(s, *reqParams) res, resErr = handler(s, reqParams)
} else if sub != nil { } else if sub != nil {
handler, ok := rpcWsHandlers[req.Method] handler, ok := rpcWsHandlers[req.Method]
if ok { if ok {
res, resErr = handler(s, *reqParams, sub) res, resErr = handler(s, reqParams, sub)
} }
} }
return s.packResponse(req, res, resErr) return s.packResponse(req, res, resErr)
@ -462,27 +459,20 @@ func (s *Server) getConnectionCount(_ request.Params) (interface{}, *response.Er
} }
func (s *Server) blockHashFromParam(param *request.Param) (util.Uint256, *response.Error) { func (s *Server) blockHashFromParam(param *request.Param) (util.Uint256, *response.Error) {
var hash util.Uint256 var (
hash util.Uint256
err error
)
if param == nil { if param == nil {
return hash, response.ErrInvalidParams return hash, response.ErrInvalidParams
} }
switch param.Type { if hash, err = param.GetUint256(); err != nil {
case request.StringT: num, respErr := s.blockHeightFromParam(param)
var err error if respErr != nil {
hash, err = param.GetUint256() return hash, respErr
if err != nil {
return hash, response.ErrInvalidParams
}
case request.NumberT:
num, err := s.blockHeightFromParam(param)
if err != nil {
return hash, response.ErrInvalidParams
} }
hash = s.chain.GetHeaderHash(num) hash = s.chain.GetHeaderHash(num)
default:
return hash, response.ErrInvalidParams
} }
return hash, nil return hash, nil
} }
@ -499,7 +489,7 @@ func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Erro
return nil, response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) return nil, response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
} }
if reqParams.Value(1).GetBoolean() { if v, _ := reqParams.Value(1).GetBoolean(); v {
return result.NewBlock(block, s.chain), nil return result.NewBlock(block, s.chain), nil
} }
writer := io.NewBufBinWriter() writer := io.NewBufBinWriter()
@ -508,11 +498,7 @@ func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Erro
} }
func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.ValueWithType(0, request.NumberT) num, err := s.blockHeightFromParam(reqParams.Value(0))
if param == nil {
return nil, response.ErrInvalidParams
}
num, err := s.blockHeightFromParam(param)
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
@ -557,7 +543,7 @@ func (s *Server) getPeers(_ request.Params) (interface{}, *response.Error) {
} }
func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response.Error) {
verbose := reqParams.Value(0).GetBoolean() verbose, _ := reqParams.Value(0).GetBoolean()
mp := s.chain.GetMemPool() mp := s.chain.GetMemPool()
hashList := make([]util.Uint256, 0) hashList := make([]util.Uint256, 0)
for _, item := range mp.GetVerifiedTransactions() { for _, item := range mp.GetVerifiedTransactions() {
@ -574,11 +560,15 @@ func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response
} }
func (s *Server) validateAddress(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) validateAddress(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.Value(0) param, err := reqParams.Value(0).GetString()
if param == nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
return validateAddress(param.Value), nil
return result.ValidateAddress{
Address: reqParams.Value(0),
IsValid: validateAddress(param),
}, nil
} }
// calculateNetworkFee calculates network fee for the transaction. // calculateNetworkFee calculates network fee for the transaction.
@ -644,11 +634,11 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
trig := trigger.All trig := trigger.All
if len(reqParams) > 1 { if len(reqParams) > 1 {
trigString := reqParams.ValueWithType(1, request.StringT) trigString, err := reqParams.Value(1).GetString()
if trigString == nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
trig, err = trigger.FromString(trigString.String()) trig, err = trigger.FromString(trigString)
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
@ -877,19 +867,13 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err
if param == nil { if param == nil {
return 0, response.ErrInvalidParams return 0, response.ErrInvalidParams
} }
switch param.Type { if scriptHash, err := param.GetUint160FromHex(); err == nil {
case request.StringT:
var err error
scriptHash, err := param.GetUint160FromHex()
if err != nil {
return 0, response.ErrInvalidParams
}
cs := s.chain.GetContractState(scriptHash) cs := s.chain.GetContractState(scriptHash)
if cs == nil { if cs == nil {
return 0, response.ErrUnknown return 0, response.ErrUnknown
} }
result = cs.ID result = cs.ID
case request.NumberT: } else {
id, err := param.GetInt() id, err := param.GetInt()
if err != nil { if err != nil {
return 0, response.ErrInvalidParams return 0, response.ErrInvalidParams
@ -898,8 +882,6 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err
return 0, response.WrapErrorWithData(response.ErrInvalidParams, err) return 0, response.WrapErrorWithData(response.ErrInvalidParams, err)
} }
result = int32(id) result = int32(id)
default:
return 0, response.ErrInvalidParams
} }
return result, nil return result, nil
} }
@ -910,25 +892,21 @@ func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160
if param == nil { if param == nil {
return result, response.ErrInvalidParams return result, response.ErrInvalidParams
} }
switch param.Type { nameOrHashOrIndex, err := param.GetString()
case request.StringT: if err != nil {
var err error return result, response.ErrInvalidParams
}
result, err = param.GetUint160FromAddressOrHex() result, err = param.GetUint160FromAddressOrHex()
if err == nil { if err == nil {
return result, nil return result, nil
} }
name, err := param.GetString() result, err = s.chain.GetNativeContractScriptHash(nameOrHashOrIndex)
if err != nil { if err == nil {
return result, response.ErrInvalidParams return result, nil
} }
result, err = s.chain.GetNativeContractScriptHash(name) id, err := strconv.Atoi(nameOrHashOrIndex)
if err != nil { if err != nil {
return result, response.NewRPCError("Unknown contract: querying by name is supported for native contracts only", "", nil) return result, response.NewRPCError("Unknown contract", "", err)
}
case request.NumberT:
id, err := param.GetInt()
if err != nil {
return result, response.ErrInvalidParams
} }
if err := checkInt32(id); err != nil { if err := checkInt32(id); err != nil {
return result, response.WrapErrorWithData(response.ErrInvalidParams, err) return result, response.WrapErrorWithData(response.ErrInvalidParams, err)
@ -937,9 +915,6 @@ func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160
if err != nil { if err != nil {
return result, response.NewRPCError("Unknown contract", "", err) return result, response.NewRPCError("Unknown contract", "", err)
} }
default:
return result, response.ErrInvalidParams
}
return result, nil return result, nil
} }
@ -1168,7 +1143,7 @@ func (s *Server) getStateRoot(ps request.Params) (interface{}, *response.Error)
} }
var rt *state.MPTRoot var rt *state.MPTRoot
var h util.Uint256 var h util.Uint256
height, err := p.GetInt() height, err := p.GetIntStrict()
if err == nil { if err == nil {
if err := checkUint32(height); err != nil { if err := checkUint32(height); err != nil {
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err) return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
@ -1219,7 +1194,7 @@ func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, *resp
err = fmt.Errorf("invalid transaction %s: %w", txHash, err) err = fmt.Errorf("invalid transaction %s: %w", txHash, err)
return nil, response.NewRPCError("Unknown transaction", err.Error(), err) return nil, response.NewRPCError("Unknown transaction", err.Error(), err)
} }
if reqParams.Value(1).GetBoolean() { if v, _ := reqParams.Value(1).GetBoolean(); v {
if height == math.MaxUint32 { if height == math.MaxUint32 {
return result.NewTransactionOutputRaw(tx, nil, nil, s.chain), nil return result.NewTransactionOutputRaw(tx, nil, nil, s.chain), nil
} }
@ -1274,12 +1249,7 @@ func (s *Server) getNativeContracts(_ request.Params) (interface{}, *response.Er
// getBlockSysFee returns the system fees of the block, based on the specified index. // getBlockSysFee returns the system fees of the block, based on the specified index.
func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.ValueWithType(0, request.NumberT) num, err := s.blockHeightFromParam(reqParams.Value(0))
if param == nil {
return 0, response.ErrInvalidParams
}
num, err := s.blockHeightFromParam(param)
if err != nil { if err != nil {
return 0, response.NewRPCError("Invalid height", "", nil) return 0, response.NewRPCError("Invalid height", "", nil)
} }
@ -1306,7 +1276,7 @@ func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *respons
return nil, respErr return nil, respErr
} }
verbose := reqParams.Value(1).GetBoolean() verbose, _ := reqParams.Value(1).GetBoolean()
h, err := s.chain.GetHeader(hash) h, err := s.chain.GetHeader(hash)
if err != nil { if err != nil {
return nil, response.NewRPCError("unknown block", "", nil) return nil, response.NewRPCError("unknown block", "", nil)
@ -1326,7 +1296,7 @@ func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *respons
// getUnclaimedGas returns unclaimed GAS amount of the specified address. // getUnclaimedGas returns unclaimed GAS amount of the specified address.
func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Error) { func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Error) {
u, err := ps.ValueWithType(0, request.StringT).GetUint160FromAddressOrHex() u, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
@ -1398,7 +1368,11 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
if len(tx.Signers) == 0 { if len(tx.Signers) == 0 {
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}} tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
} }
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1].String(), reqParams[2:checkWitnessHashesIndex]) method, err := reqParams[1].GetString()
if err != nil {
return nil, response.ErrInvalidParams
}
script, err := request.CreateFunctionInvocationScript(scriptHash, method, reqParams[2:checkWitnessHashesIndex])
if err != nil { if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err) return nil, response.NewInternalServerError("can't create invocation script", err)
} }
@ -1520,7 +1494,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
// submitBlock broadcasts a raw block over the NEO network. // submitBlock broadcasts a raw block over the NEO network.
func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.Error) {
blockBytes, err := reqParams.ValueWithType(0, request.StringT).GetBytesBase64() blockBytes, err := reqParams.Value(0).GetBytesBase64()
if err != nil { if err != nil {
return nil, response.NewInvalidParamsError("missing parameter or not base64", err) return nil, response.NewInvalidParamsError("missing parameter or not base64", err)
} }
@ -1550,10 +1524,7 @@ func (s *Server) submitNotaryRequest(ps request.Params) (interface{}, *response.
return nil, response.NewInternalServerError("P2PNotaryRequest was received, but P2PSignatureExtensions are disabled", nil) return nil, response.NewInternalServerError("P2PNotaryRequest was received, but P2PSignatureExtensions are disabled", nil)
} }
if len(ps) < 1 { bytePayload, err := ps.Value(0).GetBytesBase64()
return nil, response.NewInvalidParamsError("not enough parameters", nil)
}
bytePayload, err := ps[0].GetBytesBase64()
if err != nil { if err != nil {
return nil, response.NewInvalidParamsError("not base64", err) return nil, response.NewInvalidParamsError("not base64", err)
} }
@ -1645,34 +1616,27 @@ func (s *Server) subscribe(reqParams request.Params, sub *subscriber) (interface
// Optional filter. // Optional filter.
var filter interface{} var filter interface{}
if p := reqParams.Value(1); p != nil { if p := reqParams.Value(1); p != nil {
param, ok := p.Value.(json.RawMessage) param := *p
if !ok { jd := json.NewDecoder(bytes.NewReader(param.RawMessage))
return nil, response.ErrInvalidParams
}
jd := json.NewDecoder(bytes.NewReader(param))
jd.DisallowUnknownFields() jd.DisallowUnknownFields()
switch event { switch event {
case response.BlockEventID: case response.BlockEventID:
flt := new(request.BlockFilter) flt := new(request.BlockFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
p.Type = request.BlockFilterT filter = *flt
p.Value = *flt
case response.TransactionEventID, response.NotaryRequestEventID: case response.TransactionEventID, response.NotaryRequestEventID:
flt := new(request.TxFilter) flt := new(request.TxFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
p.Type = request.TxFilterT filter = *flt
p.Value = *flt
case response.NotificationEventID: case response.NotificationEventID:
flt := new(request.NotificationFilter) flt := new(request.NotificationFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
p.Type = request.NotificationFilterT filter = *flt
p.Value = *flt
case response.ExecutionEventID: case response.ExecutionEventID:
flt := new(request.ExecutionFilter) flt := new(request.ExecutionFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
if err == nil && (flt.State == "HALT" || flt.State == "FAULT") { if err == nil && (flt.State == "HALT" || flt.State == "FAULT") {
p.Type = request.ExecutionFilterT filter = *flt
p.Value = *flt
} else if err == nil { } else if err == nil {
err = errors.New("invalid state") err = errors.New("invalid state")
} }
@ -1680,7 +1644,6 @@ func (s *Server) subscribe(reqParams request.Params, sub *subscriber) (interface
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
filter = p.Value
} }
s.subsLock.Lock() s.subsLock.Lock()
@ -1912,7 +1875,7 @@ drainloop:
func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.Error) { func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.Error) {
num, err := param.GetInt() num, err := param.GetInt()
if err != nil { if err != nil {
return 0, nil return 0, response.ErrInvalidParams
} }
if num < 0 || num > int(s.chain.BlockHeight()) { if num < 0 || num > int(s.chain.BlockHeight()) {
@ -1946,11 +1909,9 @@ func (s *Server) logRequestError(r *request.Request, jsonErr *response.Error) {
if r.In != nil { if r.In != nil {
logFields = append(logFields, zap.String("method", r.In.Method)) logFields = append(logFields, zap.String("method", r.In.Method))
params, err := r.In.Params() params := request.Params(r.In.RawParams)
if err == nil {
logFields = append(logFields, zap.Any("params", params)) logFields = append(logFields, zap.Any("params", params))
} }
}
s.log.Error("Error encountered with rpc request", logFields...) s.log.Error("Error encountered with rpc request", logFields...)
} }
@ -1996,11 +1957,10 @@ func (s *Server) writeHTTPServerResponse(r *request.Request, w http.ResponseWrit
// validateAddress verifies that the address is a correct NEO address // validateAddress verifies that the address is a correct NEO address
// see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html // see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html
func validateAddress(addr interface{}) result.ValidateAddress { func validateAddress(addr interface{}) bool {
resp := result.ValidateAddress{Address: addr}
if addr, ok := addr.(string); ok { if addr, ok := addr.(string); ok {
_, err := address.StringToUint160(addr) _, err := address.StringToUint160(addr)
resp.IsValid = (err == nil) return err == nil
} }
return resp return false
} }

View file

@ -28,7 +28,7 @@ const (
notaryPass = "one" notaryPass = "one"
) )
func getUnitTestChain(t *testing.T, enableOracle bool, enableNotary bool) (*core.Blockchain, *oracle.Oracle, config.Config, *zap.Logger) { func getUnitTestChain(t testing.TB, enableOracle bool, enableNotary bool) (*core.Blockchain, *oracle.Oracle, config.Config, *zap.Logger) {
net := netmode.UnitTestNet net := netmode.UnitTestNet
configPath := "../../../config" configPath := "../../../config"
cfg, err := config.Load(configPath, net) cfg, err := config.Load(configPath, net)
@ -97,7 +97,7 @@ func getTestBlocks(t *testing.T) []*block.Block {
return blocks return blocks
} }
func initClearServerWithServices(t *testing.T, needOracle bool, needNotary bool) (*core.Blockchain, *Server, *httptest.Server) { func initClearServerWithServices(t testing.TB, needOracle bool, needNotary bool) (*core.Blockchain, *Server, *httptest.Server) {
chain, orc, cfg, logger := getUnitTestChain(t, needOracle, needNotary) chain, orc, cfg, logger := getUnitTestChain(t, needOracle, needNotary)
serverConfig := network.NewServerConfig(cfg) serverConfig := network.NewServerConfig(cfg)
@ -113,7 +113,7 @@ func initClearServerWithServices(t *testing.T, needOracle bool, needNotary bool)
return chain, &rpcServer, srv return chain, &rpcServer, srv
} }
func initClearServerWithInMemoryChain(t *testing.T) (*core.Blockchain, *Server, *httptest.Server) { func initClearServerWithInMemoryChain(t testing.TB) (*core.Blockchain, *Server, *httptest.Server) {
return initClearServerWithServices(t, false, false) return initClearServerWithServices(t, false, false)
} }

View file

@ -27,7 +27,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response" "github.com/nspcc-dev/neo-go/pkg/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster"
@ -39,6 +41,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
) )
type executor struct { type executor struct {
@ -452,10 +455,6 @@ var rpcTestCases = map[string][]rpcTestCase{
return &v return &v
}, },
}, },
{
params: "1",
fail: true,
},
}, },
"getblock": { "getblock": {
{ {
@ -2160,3 +2159,45 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
} }
require.Equal(t, arr, res.Received) require.Equal(t, arr, res.Received)
} }
func BenchmarkHandleIn(b *testing.B) {
chain, orc, cfg, logger := getUnitTestChain(b, false, false)
serverConfig := network.NewServerConfig(cfg)
serverConfig.LogLevel = zapcore.FatalLevel
server, err := network.NewServer(serverConfig, chain, logger)
require.NoError(b, err)
rpcServer := New(chain, cfg.ApplicationConfiguration.RPC, server, orc, logger)
defer chain.Close()
do := func(b *testing.B, req []byte) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
in := new(request.In)
b.StartTimer()
err := json.Unmarshal(req, in)
if err != nil {
b.FailNow()
}
res := rpcServer.handleIn(in, nil)
if res.Error != nil {
b.FailNow()
}
}
b.StopTimer()
}
b.Run("no extra params", func(b *testing.B) {
do(b, []byte(`{"jsonrpc":"2.0", "method":"validateaddress","params":["Nbb1qkwcwNSBs9pAnrVVrnFbWnbWBk91U2"]}`))
})
b.Run("with extra params", func(b *testing.B) {
do(b, []byte(`{"jsonrpc":"2.0", "method":"validateaddress","params":["Nbb1qkwcwNSBs9pAnrVVrnFbWnbWBk91U2",
"set", "of", "different", "parameters", "to", "see", "the", "difference", "between", "unmarshalling", "algorithms", 1234, 5678, 1234567, 765432, true, false, null,
"0x50befd26fdf6e4d957c11e078b24ebce6291456f", "someMethod", [{"type": "String", "value": "50befd26fdf6e4d957c11e078b24ebce6291456f"},
{"type": "Integer", "value": "42"}, {"type": "Boolean", "value": false}]]}`))
})
}