neoneo-go/pkg/rpc/server/params/param.go
Roman Khimov adab83496c rpc: move Request, Params and related code server-side
It's absolutely irrelevant for the client and request/response packages should
only contain code that is useful on both sides of the conversation. It's OK
for client tests to reuse this code, but the package is used by external
developers and they shouldn't be bothered with it. Nothing changed
functionally here except WSClient simplification. Fixes #2236.
2022-07-08 17:38:53 +03:00

445 lines
9.8 KiB
Go

package params
import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type (
// Param represents a param either passed to
// the server or to be sent to a server using
// the client.
Param struct {
json.RawMessage
cache interface{}
}
// FuncParam represents a function argument parameter used in the
// invokefunction RPC method.
FuncParam struct {
Type smartcontract.ParamType `json:"type"`
Value Param `json:"value"`
}
)
var (
jsonNullBytes = []byte("null")
jsonFalseBytes = []byte("false")
jsonTrueBytes = []byte("true")
errMissingParameter = errors.New("parameter is missing")
errNotAString = errors.New("not a string")
errNotAnInt = errors.New("not an integer")
errNotABool = errors.New("not a boolean")
errNotAnArray = errors.New("not an array")
)
func (p Param) String() string {
str, _ := p.GetString()
return str
}
// GetStringStrict returns a 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 a string value of the parameter or tries to cast the parameter to a string value.
func (p *Param) GetString() (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 {
p.cache = s
} else {
var i int64
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 t := p.cache.(type) {
case string:
return t, nil
case int64:
return strconv.FormatInt(t, 10), nil
case bool:
if t {
return "true", nil
}
return "false", nil
default:
return "", errNotAString
}
}
// 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 a boolean value of the parameter or tries to cast the 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 int64
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 int64:
return t != 0, nil
default:
return false, errNotABool
}
}
// GetIntStrict returns an int value of the parameter if the parameter is an integer.
func (p *Param) GetIntStrict() (int, error) {
if p == nil {
return 0, errMissingParameter
}
if p.IsNull() {
return 0, errNotAnInt
}
value, err := p.fillIntCache()
if err != nil {
return 0, err
}
if i, ok := value.(int64); ok && i == int64(int(i)) {
return int(i), nil
}
return 0, errNotAnInt
}
func (p *Param) fillIntCache() (interface{}, error) {
if p.cache != nil {
return p.cache, nil
}
// We could also try unmarshalling to uint64, but JSON reliably supports numbers
// up to 53 bits in size.
var i int64
err := json.Unmarshal(p.RawMessage, &i)
if err == nil {
p.cache = i
return i, nil
}
var s string
err = json.Unmarshal(p.RawMessage, &s)
if err == nil {
p.cache = s
return s, nil
}
var b bool
err = json.Unmarshal(p.RawMessage, &b)
if err == nil {
p.cache = b
return b, nil
}
return nil, errNotAnInt
}
// GetInt returns an int value of the parameter or tries to cast the parameter to an int value.
func (p *Param) GetInt() (int, error) {
if p == nil {
return 0, errMissingParameter
}
if p.IsNull() {
return 0, errNotAnInt
}
value, err := p.fillIntCache()
if err != nil {
return 0, err
}
switch t := value.(type) {
case int64:
if t == int64(int(t)) {
return int(t), nil
}
return 0, errNotAnInt
case string:
return strconv.Atoi(t)
case bool:
if t {
return 1, nil
}
return 0, nil
default:
panic("unreachable")
}
}
// GetBigInt returns a big-integer value of the parameter.
func (p *Param) GetBigInt() (*big.Int, error) {
if p == nil {
return nil, errMissingParameter
}
if p.IsNull() {
return nil, errNotAnInt
}
value, err := p.fillIntCache()
if err != nil {
return nil, err
}
switch t := value.(type) {
case int64:
return big.NewInt(t), nil
case string:
bi, ok := new(big.Int).SetString(t, 10)
if !ok {
return nil, errNotAnInt
}
return bi, nil
case bool:
if t {
return big.NewInt(1), nil
}
return new(big.Int), nil
default:
panic("unreachable")
}
}
// GetArray returns a slice of Params stored in the parameter.
func (p *Param) GetArray() ([]Param, error) {
if p == nil {
return nil, errMissingParameter
}
if p.IsNull() {
return nil, errNotAnArray
}
if p.cache == nil {
a := []Param{}
err := json.Unmarshal(p.RawMessage, &a)
if err != nil {
return nil, errNotAnArray
}
p.cache = a
}
if a, ok := p.cache.([]Param); ok {
return a, nil
}
return nil, errNotAnArray
}
// GetUint256 returns a Uint256 value of the parameter.
func (p *Param) GetUint256() (util.Uint256, error) {
s, err := p.GetString()
if err != nil {
return util.Uint256{}, err
}
return util.Uint256DecodeStringLE(strings.TrimPrefix(s, "0x"))
}
// GetUint160FromHex returns a 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
}
return util.Uint160DecodeStringLE(strings.TrimPrefix(s, "0x"))
}
// GetUint160FromAddress returns a 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 address.StringToUint160(s)
}
// GetUint160FromAddressOrHex returns a Uint160 value of the parameter that was
// supplied either as raw hex or as an address.
func (p *Param) GetUint160FromAddressOrHex() (util.Uint160, error) {
u, err := p.GetUint160FromHex()
if err == nil {
return u, err
}
return p.GetUint160FromAddress()
}
// GetFuncParam returns the current parameter as a function call parameter.
func (p *Param) GetFuncParam() (FuncParam, error) {
if p == nil {
return FuncParam{}, errMissingParameter
}
// This one doesn't need to be cached, it's used only once.
fp := FuncParam{}
err := json.Unmarshal(p.RawMessage, &fp)
return fp, err
}
// GetBytesHex returns a []byte value of the parameter if
// it is a hex-encoded string.
func (p *Param) GetBytesHex() ([]byte, error) {
s, err := p.GetString()
if err != nil {
return nil, err
}
return hex.DecodeString(s)
}
// GetBytesBase64 returns a []byte value of the parameter if
// it is a base64-encoded string.
func (p *Param) GetBytesBase64() ([]byte, error) {
s, err := p.GetString()
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(s)
}
// GetSignerWithWitness returns a request.SignerWithWitness value of the parameter.
func (p *Param) GetSignerWithWitness() (request.SignerWithWitness, error) {
// This one doesn't need to be cached, it's used only once.
c := request.SignerWithWitness{}
err := json.Unmarshal(p.RawMessage, &c)
if err != nil {
return request.SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
}
return c, nil
}
// GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry
// scope from an array of Uint160 or an array of serialized transaction.Signer stored
// in the parameter.
func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
hashes, err := p.GetArray()
if err != nil {
return nil, nil, err
}
signers := make([]transaction.Signer, len(hashes))
witnesses := make([]transaction.Witness, len(hashes))
// try to extract hashes first
for i, h := range hashes {
var u util.Uint160
u, err = h.GetUint160FromHex()
if err != nil {
break
}
signers[i] = transaction.Signer{
Account: u,
Scopes: transaction.CalledByEntry,
}
}
if err != nil {
for i, h := range hashes {
signerWithWitness, err := h.GetSignerWithWitness()
if err != nil {
return nil, nil, err
}
signers[i] = signerWithWitness.Signer
witnesses[i] = signerWithWitness.Witness
}
}
return signers, witnesses, nil
}
// IsNull returns whether the parameter represents JSON nil value.
func (p *Param) IsNull() bool {
return bytes.Equal(p.RawMessage, jsonNullBytes)
}
// GetUUID returns UUID from parameter.
func (p *Param) GetUUID() (uuid.UUID, error) {
s, err := p.GetString()
if err != nil {
return uuid.UUID{}, err
}
id, err := uuid.Parse(s)
if err != nil {
return uuid.UUID{}, fmt.Errorf("not a valid UUID: %w", err)
}
return id, nil
}