Merge pull request #676 from nspcc-dev/dedup-rpc-types

Split up RPC into separate packages

Closes #510.
This commit is contained in:
Roman Khimov 2020-02-21 16:22:36 +03:00 committed by GitHub
commit 56f87cd44e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1046 additions and 1243 deletions

View file

@ -14,7 +14,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/network/metrics"
"github.com/CityOfZion/neo-go/pkg/rpc"
"github.com/CityOfZion/neo-go/pkg/rpc/server"
"github.com/pkg/errors"
"github.com/urfave/cli"
"go.uber.org/zap"
@ -341,18 +341,18 @@ func startServer(ctx *cli.Context) error {
return err
}
server, err := network.NewServer(serverConfig, chain, log)
serv, err := network.NewServer(serverConfig, chain, log)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create network server: %v", err), 1)
}
rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPC, server, log)
rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, serv, log)
errChan := make(chan error)
go server.Start(errChan)
go serv.Start(errChan)
go rpcServer.Start(errChan)
fmt.Println(logo())
fmt.Println(server.UserAgent)
fmt.Println(serv.UserAgent)
fmt.Println()
var shutdownErr error
@ -364,7 +364,7 @@ Main:
cancel()
case <-grace.Done():
server.Shutdown()
serv.Shutdown()
if serverErr := rpcServer.Shutdown(); serverErr != nil {
shutdownErr = errors.Wrap(serverErr, "Error encountered whilst shutting down server")
}

View file

@ -14,7 +14,9 @@ import (
"github.com/CityOfZion/neo-go/pkg/compiler"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/rpc"
"github.com/CityOfZion/neo-go/pkg/rpc/client"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/rpc/response"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
@ -294,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error {
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place.
if !ctx.Bool("skip-details") {
details := parseContractDetails()
details.ReturnType = rpc.ByteArray
details.Parameters = make([]rpc.StackParamType, 2)
details.Parameters[0] = rpc.String
details.Parameters[1] = rpc.Array
details.ReturnType = request.ByteArray
details.Parameters = make([]request.StackParamType, 2)
details.Parameters[0] = request.String
details.Parameters[1] = request.Array
project := &ProjectConfig{Contract: details}
b, err := yaml.Marshal(project)
@ -362,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
operation string
params = make([]smartcontract.Parameter, 0)
paramsStart = 1
resp *rpc.InvokeScriptResponse
resp *response.InvokeResult
wif *keys.WIF
)
@ -398,34 +400,34 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
return err
}
}
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
c, err := client.New(context.TODO(), endpoint, client.Options{})
if err != nil {
return cli.NewExitError(err, 1)
}
if withMethod {
resp, err = client.InvokeFunction(script, operation, params)
resp, err = c.InvokeFunction(script, operation, params)
} else {
resp, err = client.Invoke(script, params)
resp, err = c.Invoke(script, params)
}
if err != nil {
return cli.NewExitError(err, 1)
}
if signAndPush {
if len(resp.Result.Script) == 0 {
if len(resp.Script) == 0 {
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
}
script, err := hex.DecodeString(resp.Result.Script)
script, err := hex.DecodeString(resp.Script)
if err != nil {
return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1)
}
txHash, err := client.SignAndPushInvocationTx(script, wif, gas)
txHash, err := c.SignAndPushInvocationTx(script, wif, gas)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
}
fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE())
} else {
b, err := json.MarshalIndent(resp.Result, "", " ")
b, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return cli.NewExitError(err, 1)
}
@ -451,18 +453,18 @@ func testInvokeScript(ctx *cli.Context) error {
return cli.NewExitError(err, 1)
}
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
c, err := client.New(context.TODO(), endpoint, client.Options{})
if err != nil {
return cli.NewExitError(err, 1)
}
scriptHex := hex.EncodeToString(b)
resp, err := client.InvokeScript(scriptHex)
resp, err := c.InvokeScript(scriptHex)
if err != nil {
return cli.NewExitError(err, 1)
}
b, err = json.MarshalIndent(resp.Result, "", " ")
b, err = json.MarshalIndent(resp, "", " ")
if err != nil {
return cli.NewExitError(err, 1)
}
@ -475,11 +477,11 @@ func testInvokeScript(ctx *cli.Context) error {
// ProjectConfig contains project metadata.
type ProjectConfig struct {
Version uint
Contract rpc.ContractDetails `yaml:"project"`
Contract request.ContractDetails `yaml:"project"`
}
func parseContractDetails() rpc.ContractDetails {
details := rpc.ContractDetails{}
func parseContractDetails() request.ContractDetails {
details := request.ContractDetails{}
reader := bufio.NewReader(os.Stdin)
fmt.Print("Author: ")
@ -571,17 +573,17 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("bad config: %v", err), 1)
}
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
c, err := client.New(context.TODO(), endpoint, client.Options{})
if err != nil {
return cli.NewExitError(err, 1)
}
txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract)
txScript, err := request.CreateDeploymentScript(avm, &conf.Contract)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1)
}
txHash, err := client.SignAndPushInvocationTx(txScript, wif, gas)
txHash, err := c.SignAndPushInvocationTx(txScript, wif, gas)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
}

View file

@ -11,7 +11,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
@ -55,7 +55,7 @@ func prepareData(t *testing.B) []*transaction.Transaction {
for n := 0; n < t.N; n++ {
tx := getTX(t, wif)
require.NoError(t, rpc.SignTx(tx, wif))
require.NoError(t, request.SignTx(tx, wif))
data = append(data, tx)
}
return data

View file

@ -190,12 +190,25 @@ func (s *Server) Shutdown() {
// UnconnectedPeers returns a list of peers that are in the discovery peer list
// but are not connected to the server.
func (s *Server) UnconnectedPeers() []string {
return []string{}
return s.discovery.UnconnectedPeers()
}
// BadPeers returns a list of peers the are flagged as "bad" peers.
func (s *Server) BadPeers() []string {
return []string{}
return s.discovery.BadPeers()
}
// ConnectedPeers returns a list of currently connected peers.
func (s *Server) ConnectedPeers() []string {
s.lock.RLock()
defer s.lock.RUnlock()
peers := make([]string, 0, len(s.peers))
for k := range s.peers {
peers = append(peers, k.PeerAddr().String())
}
return peers
}
// run is a goroutine that starts another goroutine to manage protocol specifics

View file

@ -1,4 +1,4 @@
package rpc
package client
import (
"bytes"
@ -14,6 +14,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/rpc/response"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors"
)
@ -37,13 +39,13 @@ type Client struct {
wifMu *sync.Mutex
wif *keys.WIF
balancerMu *sync.Mutex
balancer BalanceGetter
balancer request.BalanceGetter
}
// ClientOptions defines options for the RPC client.
// Options defines options for the RPC client.
// All Values are optional. If any duration is not specified
// a default of 3 seconds will be used.
type ClientOptions struct {
type Options struct {
Cert string
Key string
CACert string
@ -55,8 +57,8 @@ type ClientOptions struct {
Version string
}
// NewClient returns a new Client ready to use.
func NewClient(ctx context.Context, endpoint string, opts ClientOptions) (*Client, error) {
// New returns a new Client ready to use.
func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
url, err := url.Parse(endpoint)
if err != nil {
return nil, err
@ -121,14 +123,14 @@ func (c *Client) SetWIF(wif string) error {
}
// Balancer is a getter for balance field.
func (c *Client) Balancer() BalanceGetter {
func (c *Client) Balancer() request.BalanceGetter {
c.balancerMu.Lock()
defer c.balancerMu.Unlock()
return c.balancer
}
// SetBalancer is a setter for balance field.
func (c *Client) SetBalancer(b BalanceGetter) {
func (c *Client) SetBalancer(b request.BalanceGetter) {
c.balancerMu.Lock()
defer c.balancerMu.Unlock()
@ -161,13 +163,10 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F
var utxos state.UnspentBalances
resp, err := c.GetUnspents(address)
if err != nil || resp.Error != nil {
if err == nil {
err = fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message)
}
if err != nil {
return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address)
}
for _, ubi := range resp.Result.Balance {
for _, ubi := range resp.Balance {
if asset.Equals(ubi.AssetHash) {
utxos = ubi.Unspents
break
@ -177,15 +176,16 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F
}
func (c *Client) performRequest(method string, p params, v interface{}) error {
func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error {
var (
r = request{
JSONRPC: c.version,
Method: method,
Params: p.values,
ID: 1,
r = request.Raw{
JSONRPC: c.version,
Method: method,
RawParams: p.Values,
ID: 1,
}
buf = new(bytes.Buffer)
raw = &response.Raw{}
)
if err := json.NewEncoder(buf).Encode(r); err != nil {
@ -202,11 +202,22 @@ func (c *Client) performRequest(method string, p params, v interface{}) error {
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("remote responded with a non 200 response: %d", resp.StatusCode)
// The node might send us proper JSON anyway, so look there first and if
// it parses, then it has more relevant data than HTTP error code.
err = json.NewDecoder(resp.Body).Decode(raw)
if err == nil {
if raw.Error != nil {
err = raw.Error
} else {
err = json.Unmarshal(raw.Result, v)
}
} else if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("HTTP %d/%s", resp.StatusCode, http.StatusText(resp.StatusCode))
} else {
err = errors.Wrap(err, "JSON decoding")
}
return json.NewDecoder(resp.Body).Decode(v)
return err
}
// Ping attempts to create a connection to the endpoint.

69
pkg/rpc/client/doc.go Normal file
View file

@ -0,0 +1,69 @@
/*
Package client implements NEO-specific JSON-RPC 2.0 client.
This package is currently in alpha and is subject to change.
Client
After creating a client instance with or without a ClientConfig
you can interact with the NEO blockchain by its exposed methods.
Some of the methods also allow to pass a verbose bool. This will
return a more pretty printed response from the server instead of
a raw hex string.
An example:
endpoint := "http://seed5.bridgeprotocol.io:10332"
opts := client.Options{}
c, err := client.New(context.TODO(), endpoint, opts)
if err != nil {
log.Fatal(err)
}
if err := c.Ping(); err != nil {
log.Fatal(err)
}
resp, err := c.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP")
if err != nil {
log.Fatal(err)
}
log.Println(resp.Result.ScriptHash)
log.Println(resp.Result.Balances)
TODO:
Merge structs so can be used by both server and client.
Add missing methods to client.
Allow client to connect using client cert.
More in-depth examples.
Supported methods
getblock
getaccountstate
getunspents
invokescript
invokefunction
sendrawtransaction
invoke
getrawtransaction
Unsupported methods
validateaddress
getblocksysfee
getcontractstate
getrawmempool
getstorage
submitblock
gettxout
getassetstate
getpeers
getversion
getconnectioncount
getblockhash
getblockcount
getbestblockhash
*/
package client

View file

@ -1,4 +1,4 @@
package rpc
package client
import (
"encoding/json"
@ -8,11 +8,38 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
"github.com/CityOfZion/neo-go/pkg/util"
errs "github.com/pkg/errors"
)
/*
Definition of types, helper functions and variables
required for calculation of transaction inputs using
NeoScan API.
*/
type (
// NeoScanServer stores NEOSCAN URL and API path.
NeoScanServer struct {
URL string // "protocol://host:port/"
Path string // path to API endpoint without wallet address
}
// Unspent stores Unspents per asset
Unspent struct {
Unspent state.UnspentBalances
Asset string // "NEO" / "GAS"
Amount util.Fixed8 // total unspent of this asset
}
// NeoScanBalance is a struct of NeoScan response to 'get_balance' request
NeoScanBalance struct {
Balance []*Unspent
Address string
}
)
// GetBalance performs a request to get balance for the address specified.
func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) {
var (
@ -57,7 +84,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256,
err error
us []*Unspent
assetUnspent Unspent
assetID = wrappers.GlobalAssets[assetIDUint.StringLE()]
assetID = result.GlobalAssets[assetIDUint.StringLE()]
)
if us, err = s.GetBalance(address); err != nil {
return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address)

View file

@ -1,12 +1,14 @@
package rpc
package client
import (
"encoding/hex"
"fmt"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/rpc/response"
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors"
@ -17,11 +19,11 @@ import (
// missing output wrapper at the moment, thus commented out
// func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) {
// var (
// params = newParams(indexOrHash)
// params = request.NewRawParams(indexOrHash)
// resp = &response{}
// )
// if verbose {
// params = newParams(indexOrHash, 1)
// params = request.NewRawParams(indexOrHash, 1)
// }
// if err := c.performRequest("getblock", params, resp); err != nil {
// return nil, err
@ -30,10 +32,10 @@ import (
// }
// GetAccountState returns detailed information about a NEO account.
func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) {
func (c *Client) GetAccountState(address string) (*result.AccountState, error) {
var (
params = newParams(address)
resp = &AccountStateResponse{}
params = request.NewRawParams(address)
resp = &result.AccountState{}
)
if err := c.performRequest("getaccountstate", params, resp); err != nil {
return nil, err
@ -42,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*AccountStateResponse, error)
}
// GetUnspents returns UTXOs for the given NEO account.
func (c *Client) GetUnspents(address string) (*UnspentResponse, error) {
func (c *Client) GetUnspents(address string) (*result.Unspents, error) {
var (
params = newParams(address)
resp = &UnspentResponse{}
params = request.NewRawParams(address)
resp = &result.Unspents{}
)
if err := c.performRequest("getunspents", params, resp); err != nil {
return nil, err
@ -55,10 +57,10 @@ func (c *Client) GetUnspents(address string) (*UnspentResponse, error) {
// InvokeScript returns the result of the given script after running it true the VM.
// NOTE: This is a test invoke and will not affect the blockchain.
func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) {
func (c *Client) InvokeScript(script string) (*response.InvokeResult, error) {
var (
params = newParams(script)
resp = &InvokeScriptResponse{}
params = request.NewRawParams(script)
resp = &response.InvokeResult{}
)
if err := c.performRequest("invokescript", params, resp); err != nil {
return nil, err
@ -69,10 +71,10 @@ func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) {
// InvokeFunction returns the results after calling the smart contract scripthash
// with the given operation and parameters.
// NOTE: this is test invoke and will not affect the blockchain.
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) {
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeResult, error) {
var (
p = newParams(script, operation, params)
resp = &InvokeScriptResponse{}
p = request.NewRawParams(script, operation, params)
resp = &response.InvokeResult{}
)
if err := c.performRequest("invokefunction", p, resp); err != nil {
return nil, err
@ -82,10 +84,10 @@ func (c *Client) InvokeFunction(script, operation string, params []smartcontract
// Invoke returns the results after calling the smart contract scripthash
// with the given parameters.
func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) {
func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeResult, error) {
var (
p = newParams(script, params)
resp = &InvokeScriptResponse{}
p = request.NewRawParams(script, params)
resp = &response.InvokeResult{}
)
if err := c.performRequest("invoke", p, resp); err != nil {
return nil, err
@ -97,7 +99,7 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok
// missing output wrapper at the moment, thus commented out
// func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) {
// var (
// params = newParams(hash, verbose)
// params = request.NewRawParams(hash, verbose)
// resp = &response{}
// )
// if err := c.performRequest("getrawtransaction", params, resp); err != nil {
@ -110,48 +112,44 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok
// The given hex string needs to be signed with a keypair.
// When the result of the response object is true, the TX has successfully
// been broadcasted to the network.
func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, error) {
func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) error {
var (
params = newParams(hex.EncodeToString(rawTX.Bytes()))
resp = &response{}
params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes()))
resp bool
)
if err := c.performRequest("sendrawtransaction", params, resp); err != nil {
return nil, err
if err := c.performRequest("sendrawtransaction", params, &resp); err != nil {
return err
}
return resp, nil
if !resp {
return errors.New("sendrawtransaction returned false")
}
return nil
}
// SendToAddress sends an amount of specific asset to a given address.
// This call requires open wallet. (`wif` key in client struct.)
// If response.Result is `true` then transaction was formed correctly and was written in blockchain.
func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*SendToAddressResponse, error) {
func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (util.Uint256, error) {
var (
err error
rawTx *transaction.Transaction
txParams = ContractTxParams{
assetID: asset,
address: address,
value: amount,
wif: c.WIF(),
balancer: c.Balancer(),
txParams = request.ContractTxParams{
AssetID: asset,
Address: address,
Value: amount,
WIF: c.WIF(),
Balancer: c.Balancer(),
}
resp *response
response = &SendToAddressResponse{}
resp util.Uint256
)
if rawTx, err = CreateRawContractTransaction(txParams); err != nil {
return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`")
if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil {
return resp, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`")
}
if resp, err = c.sendRawTransaction(rawTx); err != nil {
return nil, errors.Wrap(err, "failed to send raw transaction")
if err = c.sendRawTransaction(rawTx); err != nil {
return resp, errors.Wrap(err, "failed to send raw transaction")
}
response.Error = resp.Error
response.ID = resp.ID
response.JSONRPC = resp.JSONRPC
response.Result = &TxResponse{
TxID: rawTx.Hash().StringLE(),
}
return response, nil
return rawTx.Hash(), nil
}
// SignAndPushInvocationTx signs and pushes given script as an invocation
@ -166,22 +164,19 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util.
fromAddress := wif.PrivateKey.Address()
if gas > 0 {
if err = AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil {
if err = request.AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil {
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
}
}
if err = SignTx(tx, wif); err != nil {
if err = request.SignTx(tx, wif); err != nil {
return txHash, errors.Wrap(err, "failed to sign tx")
}
txHash = tx.Hash()
resp, err := c.sendRawTransaction(tx)
err = c.sendRawTransaction(tx)
if err != nil {
return txHash, errors.Wrap(err, "failed sendning tx")
}
if resp.Error != nil {
return txHash, fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message)
}
return txHash, nil
}

View file

@ -1,111 +0,0 @@
/*
Package rpc implements NEO-specific JSON-RPC 2.0 client and server.
This package is currently in alpha and is subject to change.
Client
After creating a client instance with or without a ClientConfig
you can interact with the NEO blockchain by its exposed methods.
Some of the methods also allow to pass a verbose bool. This will
return a more pretty printed response from the server instead of
a raw hex string.
An example:
endpoint := "http://seed5.bridgeprotocol.io:10332"
opts := rpc.ClientOptions{}
client, err := rpc.NewClient(context.TODO(), endpoint, opts)
if err != nil {
log.Fatal(err)
}
if err := client.Ping(); err != nil {
log.Fatal(err)
}
resp, err := client.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP")
if err != nil {
log.Fatal(err)
}
log.Println(resp.Result.ScriptHash)
log.Println(resp.Result.Balances)
TODO:
Merge structs so can be used by both server and client.
Add missing methods to client.
Allow client to connect using client cert.
More in-depth examples.
Supported methods
getblock
getaccountstate
getunspents
invokescript
invokefunction
sendrawtransaction
invoke
getrawtransaction
Unsupported methods
validateaddress
getblocksysfee
getcontractstate
getrawmempool
getstorage
submitblock
gettxout
getassetstate
getpeers
getversion
getconnectioncount
getblockhash
getblockcount
getbestblockhash
Server
The server is written to support as much of the JSON-RPC 2.0 Spec as possible.
The server is run as part of the node currently.
TODO:
Implement HTTPS server.
Add remaining methods (Documented below).
Add Swagger spec and test using dredd in circleCI.
Example call
An example would be viewing the version of the node:
$ curl -X POST -d '{"jsonrpc": "2.0", "method": "getversion", "params": [], "id": 1}' http://localhost:20332
which would yield the response:
{
"jsonrpc" : "2.0",
"id" : 1,
"result" : {
"port" : 20333,
"useragent" : "/NEO-GO:0.36.0-dev/",
"nonce" : 9318417
}
}
Unsupported methods
getblocksysfee
getcontractstate (needs to be implemented in pkg/core/blockchain.go)
getrawmempool (needs to be implemented on in pkg/network/server.go)
getrawtransaction (needs to be implemented in pkg/core/blockchain.go)
getstorage (lacks VM functionality)
gettxout (needs to be implemented in pkg/core/blockchain.go)
invoke (lacks VM functionality)
invokefunction (lacks VM functionality)
invokescript (lacks VM functionality)
sendrawtransaction (needs to be implemented in pkg/core/blockchain.go)
submitblock (needs to be implemented in pkg/core/blockchain.go)
*/
package rpc

View file

@ -1,33 +0,0 @@
package rpc
import (
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/util"
)
/*
Definition of types, helper functions and variables
required for calculation of transaction inputs using
NeoScan API.
*/
type (
// NeoScanServer stores NEOSCAN URL and API path.
NeoScanServer struct {
URL string // "protocol://host:port/"
Path string // path to API endpoint without wallet address
}
// Unspent stores Unspents per asset
Unspent struct {
Unspent state.UnspentBalances
Asset string // "NEO" / "GAS"
Amount util.Fixed8 // total unspent of this asset
}
// NeoScanBalance is a struct of NeoScan response to 'get_balance' request
NeoScanBalance struct {
Balance []*Unspent
Address string
}
)

View file

@ -1,128 +0,0 @@
package rpc
import (
"encoding/json"
"io"
"net/http"
"github.com/pkg/errors"
"go.uber.org/zap"
)
const (
jsonRPCVersion = "2.0"
)
type (
// Request represents a standard JSON-RPC 2.0
// request: http://www.jsonrpc.org/specification#request_object.
Request struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
RawParams json.RawMessage `json:"params,omitempty"`
RawID json.RawMessage `json:"id,omitempty"`
}
// Response represents a standard JSON-RPC 2.0
// response: http://www.jsonrpc.org/specification#response_object.
Response struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
ID json.RawMessage `json:"id,omitempty"`
}
)
// NewRequest creates a new Request struct.
func NewRequest() *Request {
return &Request{
JSONRPC: jsonRPCVersion,
}
}
// DecodeData decodes the given reader into the the request
// struct.
func (r *Request) DecodeData(data io.ReadCloser) error {
defer data.Close()
err := json.NewDecoder(data).Decode(r)
if err != nil {
return errors.Errorf("error parsing JSON payload: %s", err)
}
if r.JSONRPC != jsonRPCVersion {
return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC)
}
return nil
}
// Params takes a slice of any type and attempts to bind
// the params to it.
func (r *Request) Params() (*Params, error) {
params := Params{}
err := json.Unmarshal(r.RawParams, &params)
if err != nil {
return nil, errors.Errorf("error parsing params field in payload: %s", err)
}
return &params, nil
}
// WriteErrorResponse writes an error response to the ResponseWriter.
func (s *Server) WriteErrorResponse(r *Request, w http.ResponseWriter, err error) {
jsonErr, ok := err.(*Error)
if !ok {
jsonErr = NewInternalServerError("Internal server error", err)
}
response := Response{
JSONRPC: r.JSONRPC,
Error: jsonErr,
ID: r.RawID,
}
logFields := []zap.Field{
zap.Error(jsonErr.Cause),
zap.String("method", r.Method),
}
params, err := r.Params()
if err == nil {
logFields = append(logFields, zap.Any("params", params))
}
s.log.Error("Error encountered with rpc request", logFields...)
w.WriteHeader(jsonErr.HTTPCode)
s.writeServerResponse(r, w, response)
}
// WriteResponse encodes the response and writes it to the ResponseWriter.
func (s *Server) WriteResponse(r *Request, w http.ResponseWriter, result interface{}) {
response := Response{
JSONRPC: r.JSONRPC,
Result: result,
ID: r.RawID,
}
s.writeServerResponse(r, w, response)
}
func (s *Server) writeServerResponse(r *Request, w http.ResponseWriter, response Response) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if s.config.EnableCORSWorkaround {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
}
encoder := json.NewEncoder(w)
err := encoder.Encode(response)
if err != nil {
s.log.Error("Error encountered while encoding response",
zap.String("err", err.Error()),
zap.String("method", r.Method))
}
}

View file

@ -1,4 +1,4 @@
package rpc
package request
import (
"bytes"
@ -29,12 +29,13 @@ type (
}
)
// These are parameter types accepted by RPC server.
const (
defaultT paramType = iota
stringT
numberT
arrayT
funcParamT
StringT
NumberT
ArrayT
FuncParamT
)
func (p Param) String() string {
@ -123,7 +124,7 @@ func (p Param) GetBytesHex() ([]byte, error) {
func (p *Param) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err == nil {
p.Type = stringT
p.Type = StringT
p.Value = s
return nil
@ -131,7 +132,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
var num float64
if err := json.Unmarshal(data, &num); err == nil {
p.Type = numberT
p.Type = NumberT
p.Value = int(num)
return nil
@ -142,7 +143,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
jd.DisallowUnknownFields()
var fp FuncParam
if err := jd.Decode(&fp); err == nil {
p.Type = funcParamT
p.Type = FuncParamT
p.Value = fp
return nil
@ -150,7 +151,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
var ps []Param
if err := json.Unmarshal(data, &ps); err == nil {
p.Type = arrayT
p.Type = ArrayT
p.Value = ps
return nil

View file

@ -1,4 +1,4 @@
package rpc
package request
import (
"encoding/hex"
@ -15,35 +15,35 @@ func TestParam_UnmarshalJSON(t *testing.T) {
msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]`
expected := Params{
{
Type: stringT,
Type: StringT,
Value: "str1",
},
{
Type: numberT,
Type: NumberT,
Value: 123,
},
{
Type: arrayT,
Type: ArrayT,
Value: []Param{
{
Type: stringT,
Type: StringT,
Value: "str2",
},
{
Type: numberT,
Type: NumberT,
Value: 3,
},
},
},
{
Type: arrayT,
Type: ArrayT,
Value: []Param{
{
Type: funcParamT,
Type: FuncParamT,
Value: FuncParam{
Type: String,
Value: Param{
Type: stringT,
Type: StringT,
Value: "jajaja",
},
},
@ -61,34 +61,34 @@ func TestParam_UnmarshalJSON(t *testing.T) {
}
func TestParamGetString(t *testing.T) {
p := Param{stringT, "jajaja"}
p := Param{StringT, "jajaja"}
str, err := p.GetString()
assert.Equal(t, "jajaja", str)
require.Nil(t, err)
p = Param{stringT, int(100500)}
p = Param{StringT, int(100500)}
_, err = p.GetString()
require.NotNil(t, err)
}
func TestParamGetInt(t *testing.T) {
p := Param{numberT, int(100500)}
p := Param{NumberT, int(100500)}
i, err := p.GetInt()
assert.Equal(t, 100500, i)
require.Nil(t, err)
p = Param{numberT, "jajaja"}
p = Param{NumberT, "jajaja"}
_, err = p.GetInt()
require.NotNil(t, err)
}
func TestParamGetArray(t *testing.T) {
p := Param{arrayT, []Param{{numberT, 42}}}
p := Param{ArrayT, []Param{{NumberT, 42}}}
a, err := p.GetArray()
assert.Equal(t, []Param{{numberT, 42}}, a)
assert.Equal(t, []Param{{NumberT, 42}}, a)
require.Nil(t, err)
p = Param{arrayT, 42}
p = Param{ArrayT, 42}
_, err = p.GetArray()
require.NotNil(t, err)
}
@ -96,16 +96,16 @@ func TestParamGetArray(t *testing.T) {
func TestParamGetUint256(t *testing.T) {
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
u256, _ := util.Uint256DecodeStringLE(gas)
p := Param{stringT, gas}
p := Param{StringT, gas}
u, err := p.GetUint256()
assert.Equal(t, u256, u)
require.Nil(t, err)
p = Param{stringT, 42}
p = Param{StringT, 42}
_, err = p.GetUint256()
require.NotNil(t, err)
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
_, err = p.GetUint256()
require.NotNil(t, err)
}
@ -113,16 +113,16 @@ func TestParamGetUint256(t *testing.T) {
func TestParamGetUint160FromHex(t *testing.T) {
in := "50befd26fdf6e4d957c11e078b24ebce6291456f"
u160, _ := util.Uint160DecodeStringLE(in)
p := Param{stringT, in}
p := Param{StringT, in}
u, err := p.GetUint160FromHex()
assert.Equal(t, u160, u)
require.Nil(t, err)
p = Param{stringT, 42}
p = Param{StringT, 42}
_, err = p.GetUint160FromHex()
require.NotNil(t, err)
p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"}
p = Param{StringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"}
_, err = p.GetUint160FromHex()
require.NotNil(t, err)
}
@ -130,16 +130,16 @@ func TestParamGetUint160FromHex(t *testing.T) {
func TestParamGetUint160FromAddress(t *testing.T) {
in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"
u160, _ := address.StringToUint160(in)
p := Param{stringT, in}
p := Param{StringT, in}
u, err := p.GetUint160FromAddress()
assert.Equal(t, u160, u)
require.Nil(t, err)
p = Param{stringT, 42}
p = Param{StringT, 42}
_, err = p.GetUint160FromAddress()
require.NotNil(t, err)
p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"}
p = Param{StringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"}
_, err = p.GetUint160FromAddress()
require.NotNil(t, err)
}
@ -148,19 +148,19 @@ func TestParamGetFuncParam(t *testing.T) {
fp := FuncParam{
Type: String,
Value: Param{
Type: stringT,
Type: StringT,
Value: "jajaja",
},
}
p := Param{
Type: funcParamT,
Type: FuncParamT,
Value: fp,
}
newfp, err := p.GetFuncParam()
assert.Equal(t, fp, newfp)
require.Nil(t, err)
p = Param{funcParamT, 42}
p = Param{FuncParamT, 42}
_, err = p.GetFuncParam()
require.NotNil(t, err)
}
@ -168,16 +168,16 @@ func TestParamGetFuncParam(t *testing.T) {
func TestParamGetBytesHex(t *testing.T) {
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
inb, _ := hex.DecodeString(in)
p := Param{stringT, in}
p := Param{StringT, in}
bh, err := p.GetBytesHex()
assert.Equal(t, inb, bh)
require.Nil(t, err)
p = Param{stringT, 42}
p = Param{StringT, 42}
_, err = p.GetBytesHex()
require.NotNil(t, err)
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
_, err = p.GetBytesHex()
require.NotNil(t, err)
}

View file

@ -1,4 +1,4 @@
package rpc
package request
type (
// Params represents the JSON-RPC params.

View file

@ -1,4 +1,4 @@
package rpc
package request
// ContractDetails contains contract metadata.
type ContractDetails struct {

View file

@ -1,4 +1,4 @@
package rpc
package request
import (
"encoding/binary"

View file

@ -1,4 +1,4 @@
package rpc
package request
import (
"encoding/json"

View file

@ -1,4 +1,4 @@
package rpc
package request
import (
"errors"
@ -25,7 +25,7 @@ func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transac
fromAddress string
receiverOutput *transaction.Output
wif, assetID, toAddress, amount, balancer = params.wif, params.assetID, params.address, params.value, params.balancer
wif, assetID, toAddress, amount, balancer = params.WIF, params.AssetID, params.Address, params.Value, params.Balancer
)
fromAddress = wif.PrivateKey.Address()
@ -214,15 +214,15 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt
script := io.NewBufBinWriter()
for i := len(params) - 1; i >= 0; i-- {
switch params[i].Type {
case stringT:
case StringT:
emit.String(script.BinWriter, params[i].String())
case numberT:
case NumberT:
num, err := params[i].GetInt()
if err != nil {
return nil, err
}
emit.String(script.BinWriter, strconv.Itoa(num))
case arrayT:
case ArrayT:
slice, err := params[i].GetArray()
if err != nil {
return nil, err

View file

@ -1,4 +1,4 @@
package rpc
package request
/*
Definition of types, interfaces and variables
@ -16,14 +16,14 @@ type (
// includes parameters duplication `sendtoaddress` RPC call params
// and also some utility data;
ContractTxParams struct {
assetID util.Uint256
address string
value util.Fixed8
wif keys.WIF // a WIF to send the transaction
AssetID util.Uint256
Address string
Value util.Fixed8
WIF keys.WIF // a WIF to send the transaction
// since there are many ways to provide unspents,
// transaction composer stays agnostic to that how
// unspents was got;
balancer BalanceGetter
Balancer BalanceGetter
}
// BalanceGetter is an interface supporting CalculateInputs() method.

View file

@ -0,0 +1,89 @@
package request
import (
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvocationScriptCreationGood(t *testing.T) {
p := Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}
contract, err := p.GetUint160FromHex()
require.Nil(t, err)
var paramScripts = []struct {
ps Params
script string
}{{
script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "transfer"}},
script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: NumberT, Value: 42}},
script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: NumberT, Value: 42}}}}}},
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "true"}}}}}},
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "false"}}}}}},
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}}
for _, ps := range paramScripts {
script, err := CreateFunctionInvocationScript(contract, ps.ps)
assert.Nil(t, err)
assert.Equal(t, ps.script, hex.EncodeToString(script))
}
}
func TestInvocationScriptCreationBad(t *testing.T) {
contract := util.Uint160{}
var testParams = []Params{
{{Type: NumberT, Value: "qwerty"}},
{{Type: ArrayT, Value: 42}},
{{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: NumberT, Value: 42}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: NumberT, Value: 42}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: NumberT, Value: 42}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Unknown, Value: Param{}}}}}},
}
for _, ps := range testParams {
_, err := CreateFunctionInvocationScript(contract, ps)
assert.NotNil(t, err)
}
}

84
pkg/rpc/request/types.go Normal file
View file

@ -0,0 +1,84 @@
package request
import (
"encoding/json"
"io"
"github.com/pkg/errors"
)
const (
// JSONRPCVersion is the only JSON-RPC protocol version supported.
JSONRPCVersion = "2.0"
)
// RawParams is just a slice of abstract values, used to represent parameters
// passed from client to server.
type RawParams struct {
Values []interface{}
}
// NewRawParams creates RawParams from its parameters.
func NewRawParams(vals ...interface{}) RawParams {
p := RawParams{}
p.Values = make([]interface{}, len(vals))
for i := 0; i < len(p.Values); i++ {
p.Values[i] = vals[i]
}
return p
}
// Raw represents JSON-RPC request.
type Raw struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
RawParams []interface{} `json:"params"`
ID int `json:"id"`
}
// In represents a standard JSON-RPC 2.0
// request: http://www.jsonrpc.org/specification#request_object. It's used in
// server to represent incoming queries.
type In struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
RawParams json.RawMessage `json:"params,omitempty"`
RawID json.RawMessage `json:"id,omitempty"`
}
// NewIn creates a new Request struct.
func NewIn() *In {
return &In{
JSONRPC: JSONRPCVersion,
}
}
// DecodeData decodes the given reader into the the request
// struct.
func (r *In) DecodeData(data io.ReadCloser) error {
defer data.Close()
err := json.NewDecoder(data).Decode(r)
if err != nil {
return errors.Errorf("error parsing JSON payload: %s", err)
}
if r.JSONRPC != JSONRPCVersion {
return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC)
}
return nil
}
// 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, errors.Errorf("error parsing params field in payload: %s", err)
}
return &params, nil
}

View file

@ -1,4 +1,4 @@
package rpc
package response
import (
"fmt"
@ -18,10 +18,13 @@ type (
)
var (
errInvalidParams = NewInvalidParamsError("", nil)
// ErrInvalidParams represents a generic 'invalid parameters' error.
ErrInvalidParams = NewInvalidParamsError("", nil)
)
func newError(code int64, httpCode int, message string, data string, cause error) *Error {
// NewError is an Error constructor that takes Error contents from its
// parameters.
func NewError(code int64, httpCode int, message string, data string, cause error) *Error {
return &Error{
Code: code,
HTTPCode: httpCode,
@ -35,40 +38,40 @@ func newError(code int64, httpCode int, message string, data string, cause error
// NewParseError creates a new error with code
// -32700.:%s
func NewParseError(data string, cause error) *Error {
return newError(-32700, http.StatusBadRequest, "Parse Error", data, cause)
return NewError(-32700, http.StatusBadRequest, "Parse Error", data, cause)
}
// NewInvalidRequestError creates a new error with
// code -32600.
func NewInvalidRequestError(data string, cause error) *Error {
return newError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause)
return NewError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause)
}
// NewMethodNotFoundError creates a new error with
// code -32601.
func NewMethodNotFoundError(data string, cause error) *Error {
return newError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause)
return NewError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause)
}
// NewInvalidParamsError creates a new error with
// code -32602.
func NewInvalidParamsError(data string, cause error) *Error {
return newError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause)
return NewError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause)
}
// NewInternalServerError creates a new error with
// code -32603.
func NewInternalServerError(data string, cause error) *Error {
return newError(-32603, http.StatusInternalServerError, "Internal error", data, cause)
return NewError(-32603, http.StatusInternalServerError, "Internal error", data, cause)
}
// NewRPCError creates a new error with
// code -100
func NewRPCError(message string, data string, cause error) *Error {
return newError(-100, http.StatusUnprocessableEntity, message, data, cause)
return NewError(-100, http.StatusUnprocessableEntity, message, data, cause)
}
// Error implements the error interface.
func (e Error) Error() string {
func (e *Error) Error() string {
return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause)
}

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"bytes"

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/core/state"

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"strconv"

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/core/state"

View file

@ -1,11 +1,12 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/vm"
)
// InvokeResult is used as a wrapper to represent an invokation result.
type InvokeResult struct {
// Invoke represents code invocation result and is used by several RPC calls
// that invoke functions, scripts and generic bytecode.
type Invoke struct {
State string `json:"state"`
GasConsumed string `json:"gas_consumed"`
Script string `json:"script"`

View file

@ -0,0 +1,60 @@
package result
import (
"strings"
)
type (
// GetPeers payload for outputting peers in `getpeers` RPC call.
GetPeers struct {
Unconnected Peers `json:"unconnected"`
Connected Peers `json:"connected"`
Bad Peers `json:"bad"`
}
// Peers represent a slice of peers.
Peers []Peer
// Peer represents the peer.
Peer struct {
Address string `json:"address"`
Port string `json:"port"`
}
)
// NewGetPeers creates a new GetPeers structure.
func NewGetPeers() GetPeers {
return GetPeers{
Unconnected: []Peer{},
Connected: []Peer{},
Bad: []Peer{},
}
}
// AddUnconnected adds a set of peers to the unconnected peers slice.
func (g *GetPeers) AddUnconnected(addrs []string) {
g.Unconnected.addPeers(addrs)
}
// AddConnected adds a set of peers to the connected peers slice.
func (g *GetPeers) AddConnected(addrs []string) {
g.Connected.addPeers(addrs)
}
// AddBad adds a set of peers to the bad peers slice.
func (g *GetPeers) AddBad(addrs []string) {
g.Bad.addPeers(addrs)
}
// addPeers adds a set of peers to the given peer slice.
func (p *Peers) addPeers(addrs []string) {
for i := range addrs {
addressParts := strings.Split(addrs[i], ":")
peer := Peer{
Address: addressParts[0],
Port: addressParts[1],
}
*p = append(*p, peer)
}
}

View file

@ -0,0 +1,26 @@
package result
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetPeers(t *testing.T) {
gp := NewGetPeers()
require.Equal(t, 0, len(gp.Unconnected))
require.Equal(t, 0, len(gp.Connected))
require.Equal(t, 0, len(gp.Bad))
gp.AddUnconnected([]string{"1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"})
gp.AddConnected([]string{"192.168.0.1:10333"})
gp.AddBad([]string{"127.0.0.1:20333"})
require.Equal(t, 3, len(gp.Unconnected))
require.Equal(t, 1, len(gp.Connected))
require.Equal(t, 1, len(gp.Bad))
require.Equal(t, "192.168.0.1", gp.Connected[0].Address)
require.Equal(t, "10333", gp.Connected[0].Port)
require.Equal(t, "127.0.0.1", gp.Bad[0].Address)
require.Equal(t, "20333", gp.Bad[0].Port)
}

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/core/transaction"

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/core"
@ -16,9 +16,9 @@ type TransactionOutputRaw struct {
Size int `json:"size"`
SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"`
Blockhash util.Uint256 `json:"blockhash"`
Confirmations int `json:"confirmations"`
Timestamp uint32 `json:"blocktime"`
Blockhash util.Uint256 `json:"blockhash,omitempty"`
Confirmations int `json:"confirmations,omitempty"`
Timestamp uint32 `json:"blocktime,omitempty"`
}
// NewTransactionOutputRaw returns a new ransactionOutputRaw object.

View file

@ -1,4 +1,4 @@
package wrappers
package result
import (
"github.com/CityOfZion/neo-go/pkg/core"

View file

@ -0,0 +1,9 @@
package result
// ValidateAddress represents result of the `validateaddress` call. Notice that
// Address is an interface{} here because server echoes back whatever address
// value user has sent to it, even if it's not a string.
type ValidateAddress struct {
Address interface{} `json:"address"`
IsValid bool `json:"isvalid"`
}

50
pkg/rpc/response/types.go Normal file
View file

@ -0,0 +1,50 @@
package response
import (
"encoding/json"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
"github.com/CityOfZion/neo-go/pkg/vm"
)
// InvokeResult represents the outcome of a script that is
// executed by the NEO VM.
type InvokeResult struct {
State vm.State `json:"state"`
GasConsumed string `json:"gas_consumed"`
Script string `json:"script"`
Stack []request.StackParam
}
// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version).
type Header struct {
ID json.RawMessage `json:"id"`
JSONRPC string `json:"jsonrpc"`
}
// HeaderAndError adds an Error (that can be empty) to the Header, it's used
// to construct type-specific responses.
type HeaderAndError struct {
Header
Error *Error `json:"error,omitempty"`
}
// Raw represents a standard raw JSON-RPC 2.0
// response: http://www.jsonrpc.org/specification#response_object.
type Raw struct {
HeaderAndError
Result json.RawMessage `json:"result,omitempty"`
}
// GetTxOut represents result of `gettxout` RPC call.
type GetTxOut struct {
HeaderAndError
Result *result.TransactionOutput
}
// GetRawTx represents verbose output of `getrawtransaction` RPC call.
type GetRawTx struct {
HeaderAndError
Result *result.TransactionOutputRaw `json:"result"`
}

View file

@ -1,58 +0,0 @@
package result
import (
"strings"
)
type (
// Peers payload for outputting peers in `getpeers` RPC call.
Peers struct {
Unconnected []Peer `json:"unconnected"`
Connected []Peer `json:"connected"`
Bad []Peer `json:"bad"`
}
// Peer represents the peer.
Peer struct {
Address string `json:"address"`
Port string `json:"port"`
}
)
// NewPeers creates a new Peers struct.
func NewPeers() Peers {
return Peers{
Unconnected: []Peer{},
Connected: []Peer{},
Bad: []Peer{},
}
}
// AddPeer adds a peer to the given peer type slice.
func (p *Peers) AddPeer(peerType string, addr string) {
addressParts := strings.Split(addr, ":")
peer := Peer{
Address: addressParts[0],
Port: addressParts[1],
}
switch peerType {
case "unconnected":
p.Unconnected = append(
p.Unconnected,
peer,
)
case "connected":
p.Connected = append(
p.Connected,
peer,
)
case "bad":
p.Bad = append(
p.Bad,
peer,
)
}
}

48
pkg/rpc/server/doc.go Normal file
View file

@ -0,0 +1,48 @@
/*
Package server implements NEO-specific JSON-RPC 2.0 server.
This package is currently in alpha and is subject to change.
Server
The server is written to support as much of the JSON-RPC 2.0 Spec as possible.
The server is run as part of the node currently.
TODO:
Implement HTTPS server.
Add remaining methods (Documented below).
Add Swagger spec and test using dredd in circleCI.
Example call
An example would be viewing the version of the node:
$ curl -X POST -d '{"jsonrpc": "2.0", "method": "getversion", "params": [], "id": 1}' http://localhost:20332
which would yield the response:
{
"jsonrpc" : "2.0",
"id" : 1,
"result" : {
"port" : 20333,
"useragent" : "/NEO-GO:0.36.0-dev/",
"nonce" : 9318417
}
}
Unsupported methods
getblocksysfee
getcontractstate (needs to be implemented in pkg/core/blockchain.go)
getrawmempool (needs to be implemented on in pkg/network/server.go)
getrawtransaction (needs to be implemented in pkg/core/blockchain.go)
getstorage (lacks VM functionality)
gettxout (needs to be implemented in pkg/core/blockchain.go)
invoke (lacks VM functionality)
invokefunction (lacks VM functionality)
invokescript (lacks VM functionality)
sendrawtransaction (needs to be implemented in pkg/core/blockchain.go)
submitblock (needs to be implemented in pkg/core/blockchain.go)
*/
package server

View file

@ -1,4 +1,4 @@
package rpc
package server
import "github.com/prometheus/client_golang/prometheus"

View file

@ -1,8 +1,9 @@
package rpc
package server
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
@ -11,10 +12,12 @@ import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc/result"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/rpc/response"
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors"
"go.uber.org/zap"
@ -35,8 +38,8 @@ var invalidBlockHeightError = func(index int, height int) error {
return errors.Errorf("Param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height)
}
// NewServer creates a new Server struct.
func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server {
// New creates a new Server struct.
func New(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server {
httpServer := &http.Server{
Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10),
}
@ -71,13 +74,13 @@ func (s *Server) Shutdown() error {
}
func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) {
req := NewRequest()
req := request.NewIn()
if httpRequest.Method != "POST" {
s.WriteErrorResponse(
req,
w,
NewInvalidParamsError(
response.NewInvalidParamsError(
fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil,
),
)
@ -86,20 +89,20 @@ func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request
err := req.DecodeData(httpRequest.Body)
if err != nil {
s.WriteErrorResponse(req, w, NewParseError("Problem parsing JSON-RPC request body", err))
s.WriteErrorResponse(req, w, response.NewParseError("Problem parsing JSON-RPC request body", err))
return
}
reqParams, err := req.Params()
if err != nil {
s.WriteErrorResponse(req, w, NewInvalidParamsError("Problem parsing request parameters", err))
s.WriteErrorResponse(req, w, response.NewInvalidParamsError("Problem parsing request parameters", err))
return
}
s.methodHandler(w, req, *reqParams)
}
func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) {
func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams request.Params) {
s.log.Debug("processing rpc request",
zap.String("method", req.Method),
zap.String("params", fmt.Sprintf("%v", reqParams)))
@ -121,38 +124,38 @@ Methods:
param, ok := reqParams.Value(0)
if !ok {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
switch param.Type {
case stringT:
case request.StringT:
var err error
hash, err = param.GetUint256()
if err != nil {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
case numberT:
case request.NumberT:
num, err := s.blockHeightFromParam(param)
if err != nil {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
hash = s.chain.GetHeaderHash(num)
default:
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
block, err := s.chain.GetBlock(hash)
if err != nil {
resultsErr = NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
resultsErr = response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
break
}
if len(reqParams) == 2 && reqParams[1].Value == 1 {
results = wrappers.NewBlock(block, s.chain)
results = result.NewBlock(block, s.chain)
} else {
writer := io.NewBufBinWriter()
block.EncodeBinary(writer.BinWriter)
@ -165,14 +168,14 @@ Methods:
case "getblockhash":
getblockHashCalled.Inc()
param, ok := reqParams.ValueWithType(0, numberT)
param, ok := reqParams.ValueWithType(0, request.NumberT)
if !ok {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
num, err := s.blockHeightFromParam(param)
if err != nil {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
@ -192,19 +195,10 @@ Methods:
case "getpeers":
getpeersCalled.Inc()
peers := result.NewPeers()
for _, addr := range s.coreServer.UnconnectedPeers() {
peers.AddPeer("unconnected", addr)
}
for _, addr := range s.coreServer.BadPeers() {
peers.AddPeer("bad", addr)
}
for addr := range s.coreServer.Peers() {
peers.AddPeer("connected", addr.PeerAddr().String())
}
peers := result.NewGetPeers()
peers.AddUnconnected(s.coreServer.UnconnectedPeers())
peers.AddConnected(s.coreServer.ConnectedPeers())
peers.AddBad(s.coreServer.BadPeers())
results = peers
case "getstorage":
@ -215,30 +209,30 @@ Methods:
validateaddressCalled.Inc()
param, ok := reqParams.Value(0)
if !ok {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
results = wrappers.ValidateAddress(param.Value)
results = validateAddress(param.Value)
case "getassetstate":
getassetstateCalled.Inc()
param, ok := reqParams.ValueWithType(0, stringT)
param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break Methods
}
paramAssetID, err := param.GetUint256()
if err != nil {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
break
}
as := s.chain.GetAssetState(paramAssetID)
if as != nil {
results = wrappers.NewAssetState(as)
results = result.NewAssetState(as)
} else {
resultsErr = NewRPCError("Unknown asset", "", nil)
resultsErr = response.NewRPCError("Unknown asset", "", nil)
}
case "getaccountstate":
@ -275,7 +269,7 @@ Methods:
results, resultsErr = s.sendrawtransaction(reqParams)
default:
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
resultsErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
}
if resultsErr != nil {
@ -286,27 +280,27 @@ Methods:
s.WriteResponse(req, w, results)
}
func (s *Server) getStorage(ps Params) (interface{}, error) {
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
param, ok := ps.Value(0)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
scriptHash, err := param.GetUint160FromHex()
if err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
scriptHash = scriptHash.Reverse()
param, ok = ps.Value(1)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
key, err := param.GetBytesHex()
if err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
item := s.chain.GetStorageItem(scriptHash.Reverse(), key)
@ -317,22 +311,22 @@ func (s *Server) getStorage(ps Params) (interface{}, error) {
return hex.EncodeToString(item.Value), nil
}
func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, error) {
var resultsErr error
var results interface{}
if param0, ok := reqParams.Value(0); !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else if txHash, err := param0.GetUint256(); err != nil {
resultsErr = errInvalidParams
resultsErr = response.ErrInvalidParams
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
return nil, NewRPCError("Unknown transaction", err.Error(), err)
return nil, response.NewRPCError("Unknown transaction", err.Error(), err)
} else if len(reqParams) >= 2 {
_header := s.chain.GetHeaderHash(int(height))
header, err := s.chain.GetHeader(_header)
if err != nil {
resultsErr = NewInvalidParamsError(err.Error(), err)
resultsErr = response.NewInvalidParamsError(err.Error(), err)
}
param1, _ := reqParams.Value(1)
@ -342,10 +336,10 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" {
results = hex.EncodeToString(tx.Bytes())
} else {
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
results = result.NewTransactionOutputRaw(tx, header, s.chain)
}
default:
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
results = result.NewTransactionOutputRaw(tx, header, s.chain)
}
} else {
results = hex.EncodeToString(tx.Bytes())
@ -354,70 +348,70 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
return results, resultsErr
}
func (s *Server) getTxOut(ps Params) (interface{}, error) {
func (s *Server) getTxOut(ps request.Params) (interface{}, error) {
p, ok := ps.Value(0)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
h, err := p.GetUint256()
if err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
p, ok = ps.ValueWithType(1, numberT)
p, ok = ps.ValueWithType(1, request.NumberT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
num, err := p.GetInt()
if err != nil || num < 0 {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
tx, _, err := s.chain.GetTransaction(h)
if err != nil {
return nil, NewInvalidParamsError(err.Error(), err)
return nil, response.NewInvalidParamsError(err.Error(), err)
}
if num >= len(tx.Outputs) {
return nil, NewInvalidParamsError("invalid index", errors.New("too big index"))
return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index"))
}
out := tx.Outputs[num]
return wrappers.NewTxOutput(&out), nil
return result.NewTxOutput(&out), nil
}
// getContractState returns contract state (contract information, according to the contract script hash).
func (s *Server) getContractState(reqParams Params) (interface{}, error) {
func (s *Server) getContractState(reqParams request.Params) (interface{}, error) {
var results interface{}
param, ok := reqParams.ValueWithType(0, stringT)
param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else if scriptHash, err := param.GetUint160FromHex(); err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else {
cs := s.chain.GetContractState(scriptHash)
if cs != nil {
results = wrappers.NewContractState(cs)
results = result.NewContractState(cs)
} else {
return nil, NewRPCError("Unknown contract", "", nil)
return nil, response.NewRPCError("Unknown contract", "", nil)
}
}
return results, nil
}
// getAccountState returns account state either in short or full (unspents included) form.
func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) {
func (s *Server) getAccountState(reqParams request.Params, unspents bool) (interface{}, error) {
var resultsErr error
var results interface{}
param, ok := reqParams.ValueWithType(0, stringT)
param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else {
as := s.chain.GetAccountState(scriptHash)
if as == nil {
@ -426,35 +420,35 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
if unspents {
str, err := param.GetString()
if err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
results = wrappers.NewUnspents(as, s.chain, str)
results = result.NewUnspents(as, s.chain, str)
} else {
results = wrappers.NewAccountState(as)
results = result.NewAccountState(as)
}
}
return results, resultsErr
}
// invoke implements the `invoke` RPC call.
func (s *Server) invoke(reqParams Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, stringT)
func (s *Server) invoke(reqParams request.Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
scriptHash, err := scriptHashHex.GetUint160FromHex()
if err != nil {
return nil, err
}
sliceP, ok := reqParams.ValueWithType(1, arrayT)
sliceP, ok := reqParams.ValueWithType(1, request.ArrayT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
slice, err := sliceP.GetArray()
if err != nil {
return nil, err
}
script, err := CreateInvocationScript(scriptHash, slice)
script, err := request.CreateInvocationScript(scriptHash, slice)
if err != nil {
return nil, err
}
@ -462,16 +456,16 @@ func (s *Server) invoke(reqParams Params) (interface{}, error) {
}
// invokescript implements the `invokescript` RPC call.
func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, stringT)
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
scriptHash, err := scriptHashHex.GetUint160FromHex()
if err != nil {
return nil, err
}
script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:])
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:])
if err != nil {
return nil, err
}
@ -479,14 +473,14 @@ func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
}
// invokescript implements the `invokescript` RPC call.
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
func (s *Server) invokescript(reqParams request.Params) (interface{}, error) {
if len(reqParams) < 1 {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
script, err := reqParams[0].GetBytesHex()
if err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
}
return s.runScriptInVM(script), nil
@ -494,12 +488,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) {
// runScriptInVM runs given script in a new test VM and returns the invocation
// result.
func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult {
func (s *Server) runScriptInVM(script []byte) *result.Invoke {
vm, _ := s.chain.GetTestVM()
vm.SetGasLimit(s.config.MaxGasInvoke)
vm.LoadScript(script)
_ = vm.Run()
result := &wrappers.InvokeResult{
result := &result.Invoke{
State: vm.State(),
GasConsumed: vm.GasConsumed().String(),
Script: hex.EncodeToString(script),
@ -508,14 +502,14 @@ func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult {
return result
}
func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) {
var resultsErr error
var results interface{}
if len(reqParams) < 1 {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else if byteTx, err := reqParams[0].GetBytesHex(); err != nil {
return nil, errInvalidParams
return nil, response.ErrInvalidParams
} else {
r := io.NewBinReaderFromBuf(byteTx)
tx := &transaction.Transaction{}
@ -542,14 +536,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
}
}
if err != nil {
resultsErr = NewInternalServerError(err.Error(), err)
resultsErr = response.NewInternalServerError(err.Error(), err)
}
}
return results, resultsErr
}
func (s Server) blockHeightFromParam(param *Param) (int, error) {
func (s *Server) blockHeightFromParam(param *request.Param) (int, error) {
num, err := param.GetInt()
if err != nil {
return 0, nil
@ -560,3 +554,87 @@ func (s Server) blockHeightFromParam(param *Param) (int, error) {
}
return num, nil
}
// WriteErrorResponse writes an error response to the ResponseWriter.
func (s *Server) WriteErrorResponse(r *request.In, w http.ResponseWriter, err error) {
jsonErr, ok := err.(*response.Error)
if !ok {
jsonErr = response.NewInternalServerError("Internal server error", err)
}
resp := response.Raw{
HeaderAndError: response.HeaderAndError{
Header: response.Header{
JSONRPC: r.JSONRPC,
ID: r.RawID,
},
Error: jsonErr,
},
}
logFields := []zap.Field{
zap.Error(jsonErr.Cause),
zap.String("method", r.Method),
}
params, err := r.Params()
if err == nil {
logFields = append(logFields, zap.Any("params", params))
}
s.log.Error("Error encountered with rpc request", logFields...)
w.WriteHeader(jsonErr.HTTPCode)
s.writeServerResponse(r, w, resp)
}
// WriteResponse encodes the response and writes it to the ResponseWriter.
func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result interface{}) {
resJSON, err := json.Marshal(result)
if err != nil {
s.log.Error("Error encountered while encoding response",
zap.String("err", err.Error()),
zap.String("method", r.Method))
return
}
resp := response.Raw{
HeaderAndError: response.HeaderAndError{
Header: response.Header{
JSONRPC: r.JSONRPC,
ID: r.RawID,
},
},
Result: resJSON,
}
s.writeServerResponse(r, w, resp)
}
func (s *Server) writeServerResponse(r *request.In, w http.ResponseWriter, resp response.Raw) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if s.config.EnableCORSWorkaround {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
}
encoder := json.NewEncoder(w)
err := encoder.Encode(resp)
if err != nil {
s.log.Error("Error encountered while encoding response",
zap.String("err", err.Error()),
zap.String("method", r.Method))
}
}
// 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
func validateAddress(addr interface{}) result.ValidateAddress {
resp := result.ValidateAddress{Address: addr}
if addr, ok := addr.(string); ok {
_, err := address.StringToUint160(addr)
resp.IsValid = (err == nil)
}
return resp
}

View file

@ -0,0 +1,68 @@
package server
import (
"net/http"
"os"
"testing"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/block"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
// InvokeFunctionResult struct for testing.
type InvokeFunctionResult struct {
Script string `json:"script"`
State string `json:"state"`
GasConsumed string `json:"gas_consumed"`
Stack []request.FuncParam `json:"stack"`
TX string `json:"tx,omitempty"`
}
func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFunc) {
var nBlocks uint32
net := config.ModeUnitTestNet
configPath := "../../../config"
cfg, err := config.Load(configPath, net)
require.NoError(t, err, "could not load config")
memoryStore := storage.NewMemoryStore()
logger := zaptest.NewLogger(t)
chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger)
require.NoError(t, err, "could not create chain")
go chain.Run()
// File "./testdata/testblocks.acc" was generated by function core._
// ("neo-go/pkg/core/helper_test.go").
// To generate new "./testdata/testblocks.acc", follow the steps:
// 1. Rename the function
// 2. Add specific test-case into "neo-go/pkg/core/blockchain_test.go"
// 3. Run tests with `$ make test`
f, err := os.Open("testdata/testblocks.acc")
require.Nil(t, err)
br := io.NewBinReaderFromIO(f)
nBlocks = br.ReadU32LE()
require.Nil(t, br.Err)
for i := 0; i < int(nBlocks); i++ {
b := &block.Block{}
b.DecodeBinary(br)
require.Nil(t, br.Err)
require.NoError(t, chain.AddBlock(b))
}
serverConfig := network.NewServerConfig(cfg)
server, err := network.NewServer(serverConfig, chain, logger)
require.NoError(t, err)
rpcServer := New(chain, cfg.ApplicationConfiguration.RPC, server, logger)
handler := http.HandlerFunc(rpcServer.requestHandler)
return chain, handler
}

View file

@ -1,4 +1,4 @@
package rpc
package server
import (
"bytes"
@ -14,7 +14,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/rpc/response"
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -43,23 +44,23 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
result: func(e *executor) interface{} { return &GetAccountStateResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetAccountStateResponse)
result: func(e *executor) interface{} { return &result.AccountState{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.AccountState)
require.True(t, ok)
assert.Equal(t, 1, len(res.Result.Balances))
assert.Equal(t, false, res.Result.Frozen)
assert.Equal(t, 1, len(res.Balances))
assert.Equal(t, false, res.IsFrozen)
},
},
{
name: "positive null",
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
result: func(e *executor) interface{} { return &GetAccountStateResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetAccountStateResponse)
result: func(e *executor) interface{} { return &result.AccountState{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.AccountState)
require.True(t, ok)
assert.Equal(t, 0, len(res.Result.Balances))
assert.Equal(t, false, res.Result.Frozen)
assert.Equal(t, 0, len(res.Balances))
assert.Equal(t, false, res.IsFrozen)
},
},
{
@ -77,13 +78,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27"]`,
result: func(e *executor) interface{} { return &GetContractStateResponce{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetContractStateResponce)
result: func(e *executor) interface{} { return &result.ContractState{} },
check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*result.ContractState)
require.True(t, ok)
assert.Equal(t, byte(0), res.Result.Version)
assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.Result.ScriptHash)
assert.Equal(t, "0.99", res.Result.CodeVersion)
assert.Equal(t, byte(0), res.Version)
assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.ScriptHash)
assert.Equal(t, "0.99", res.CodeVersion)
},
},
{
@ -106,21 +107,17 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "746573746b6579"]`,
result: func(e *executor) interface{} { return &StringResultResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*StringResultResponse)
require.True(t, ok)
assert.Equal(t, hex.EncodeToString([]byte("testvalue")), res.Result)
result: func(e *executor) interface{} {
v := hex.EncodeToString([]byte("testvalue"))
return &v
},
},
{
name: "missing key",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "7465"]`,
result: func(e *executor) interface{} { return &StringResultResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*StringResultResponse)
require.True(t, ok)
assert.Equal(t, "", res.Result)
result: func(e *executor) interface{} {
v := ""
return &v
},
},
{
@ -148,12 +145,12 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`,
result: func(e *executor) interface{} { return &GetAssetResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetAssetResponse)
result: func(e *executor) interface{} { return &result.AssetState{} },
check: func(t *testing.T, e *executor, as interface{}) {
res, ok := as.(*result.AssetState)
require.True(t, ok)
assert.Equal(t, "00", res.Result.Owner)
assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin)
assert.Equal(t, "00", res.Owner)
assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Admin)
},
},
{
@ -176,7 +173,8 @@ var rpcTestCases = map[string][]rpcTestCase{
{
params: "[]",
result: func(e *executor) interface{} {
return "0x" + e.chain.CurrentBlockHash().StringLE()
v := "0x" + e.chain.CurrentBlockHash().StringLE()
return &v
},
},
{
@ -220,17 +218,17 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: "[1, 1]",
result: func(e *executor) interface{} { return &GetBlockResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetBlockResponse)
result: func(e *executor) interface{} { return &result.Block{} },
check: func(t *testing.T, e *executor, blockRes interface{}) {
res, ok := blockRes.(*result.Block)
require.True(t, ok)
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoErrorf(t, err, "could not get block")
assert.Equal(t, block.Hash(), res.Result.Hash)
for i := range res.Result.Tx {
tx := res.Result.Tx[i]
assert.Equal(t, block.Hash(), res.Hash)
for i := range res.Tx {
tx := res.Tx[i]
require.Equal(t, transaction.MinerType, tx.Type)
miner, ok := block.Transactions[i].Data.(*transaction.MinerTX)
@ -269,22 +267,21 @@ var rpcTestCases = map[string][]rpcTestCase{
"getblockcount": {
{
params: "[]",
result: func(e *executor) interface{} { return int(e.chain.BlockHeight() + 1) },
result: func(e *executor) interface{} {
v := int(e.chain.BlockHeight() + 1)
return &v
},
},
},
"getblockhash": {
{
params: "[1]",
result: func(e *executor) interface{} { return "" },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*StringResultResponse)
require.True(t, ok)
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoErrorf(t, err, "could not get block")
result: func(e *executor) interface{} {
// We don't have `t` here for proper handling, but
// error here would lead to panic down below.
block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1))
expectedHash := "0x" + block.Hash().StringLE()
assert.Equal(t, expectedHash, res.Result)
return &expectedHash
},
},
{
@ -301,25 +298,20 @@ var rpcTestCases = map[string][]rpcTestCase{
"getconnectioncount": {
{
params: "[]",
result: func(*executor) interface{} { return 0 },
result: func(*executor) interface{} {
v := 0
return &v
},
},
},
"getpeers": {
{
params: "[]",
result: func(*executor) interface{} {
return &GetPeersResponse{
Jsonrpc: defaultJSONRPC,
Result: struct {
Unconnected []int `json:"unconnected"`
Connected []int `json:"connected"`
Bad []int `json:"bad"`
}{
Unconnected: []int{},
Connected: []int{},
Bad: []int{},
},
ID: defaultID,
return &result.GetPeers{
Unconnected: []result.Peer{},
Connected: []result.Peer{},
Bad: []result.Peer{},
}
},
},
@ -345,33 +337,33 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
result: func(e *executor) interface{} { return &GetUnspents{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetUnspents)
result: func(e *executor) interface{} { return &result.Unspents{} },
check: func(t *testing.T, e *executor, unsp interface{}) {
res, ok := unsp.(*result.Unspents)
require.True(t, ok)
require.Equal(t, 1, len(res.Result.Balance))
assert.Equal(t, 1, len(res.Result.Balance[0].Unspents))
require.Equal(t, 1, len(res.Balance))
assert.Equal(t, 1, len(res.Balance[0].Unspents))
},
},
{
name: "positive null",
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
result: func(e *executor) interface{} { return &GetUnspents{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*GetUnspents)
result: func(e *executor) interface{} { return &result.Unspents{} },
check: func(t *testing.T, e *executor, unsp interface{}) {
res, ok := unsp.(*result.Unspents)
require.True(t, ok)
require.Equal(t, 0, len(res.Result.Balance))
require.Equal(t, 0, len(res.Balance))
},
},
},
"getversion": {
{
params: "[]",
result: func(*executor) interface{} { return &GetVersionResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
resp, ok := result.(*GetVersionResponse)
result: func(*executor) interface{} { return &result.Version{} },
check: func(t *testing.T, e *executor, ver interface{}) {
resp, ok := ver.(*result.Version)
require.True(t, ok)
require.Equal(t, "/NEO-GO:/", resp.Result.UserAgent)
require.Equal(t, "/NEO-GO:/", resp.UserAgent)
},
},
},
@ -379,13 +371,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{
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)
result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok)
assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Result.Script)
assert.NotEqual(t, "", res.Result.State)
assert.NotEqual(t, 0, res.Result.GasConsumed)
assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Script)
assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.GasConsumed)
},
},
{
@ -418,13 +410,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*InvokeFunctionResponse)
result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok)
assert.NotEqual(t, "", res.Result.Script)
assert.NotEqual(t, "", res.Result.State)
assert.NotEqual(t, 0, res.Result.GasConsumed)
assert.NotEqual(t, "", res.Script)
assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.GasConsumed)
},
},
{
@ -452,13 +444,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`,
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*InvokeFunctionResponse)
result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok)
assert.NotEqual(t, "", res.Result.Script)
assert.NotEqual(t, "", res.Result.State)
assert.NotEqual(t, 0, res.Result.GasConsumed)
assert.NotEqual(t, "", res.Script)
assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.GasConsumed)
},
},
{
@ -481,11 +473,9 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`,
result: func(e *executor) interface{} { return &SendTXResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*SendTXResponse)
require.True(t, ok)
assert.True(t, res.Result)
result: func(e *executor) interface{} {
v := true
return &v
},
},
{
@ -513,25 +503,21 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`,
result: func(*executor) interface{} { return &ValidateAddrResponse{} },
check: func(t *testing.T, e *executor, result interface{}) {
res, ok := result.(*ValidateAddrResponse)
result: func(*executor) interface{} { return &result.ValidateAddress{} },
check: func(t *testing.T, e *executor, va interface{}) {
res, ok := va.(*result.ValidateAddress)
require.True(t, ok)
assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address)
assert.True(t, res.Result.IsValid)
assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Address)
assert.True(t, res.IsValid)
},
},
{
name: "negative",
params: "[1]",
result: func(*executor) interface{} {
return &ValidateAddrResponse{
Jsonrpc: defaultJSONRPC,
Result: wrappers.ValidateAddressResponse{
Address: float64(1),
IsValid: false,
},
ID: defaultID,
return &result.ValidateAddress{
Address: float64(1),
IsValid: false,
}
},
},
@ -551,14 +537,14 @@ func TestRPC(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, t)
checkErrResponse(t, body, tc.fail)
result := checkErrGetResult(t, body, tc.fail)
if tc.fail {
return
}
expected, res := tc.getResultPair(e)
err := json.Unmarshal(body, res)
require.NoErrorf(t, err, "could not parse response: %s", body)
err := json.Unmarshal(result, res)
require.NoErrorf(t, err, "could not parse response: %s", result)
if tc.check == nil {
assert.Equal(t, expected, res)
@ -575,11 +561,11 @@ func TestRPC(t *testing.T) {
TXHash := block.Transactions[1].Hash()
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE())
body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false)
var res StringResultResponse
err := json.Unmarshal(body, &res)
require.NoErrorf(t, err, "could not parse response: %s", body)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
result := checkErrGetResult(t, body, false)
var res string
err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res)
})
t.Run("getrawtransaction 2 arguments", func(t *testing.T) {
@ -587,11 +573,11 @@ func TestRPC(t *testing.T) {
TXHash := block.Transactions[1].Hash()
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE())
body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false)
var res StringResultResponse
err := json.Unmarshal(body, &res)
require.NoErrorf(t, err, "could not parse response: %s", body)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
result := checkErrGetResult(t, body, false)
var res string
err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res)
})
t.Run("gettxout", func(t *testing.T) {
@ -600,54 +586,35 @@ func TestRPC(t *testing.T) {
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "gettxout", "params": [%s, %d]}"`,
`"`+tx.Hash().StringLE()+`"`, 0)
body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false)
res := checkErrGetResult(t, body, false)
var result GetTxOutResponse
err := json.Unmarshal(body, &result)
require.NoErrorf(t, err, "could not parse response: %s", body)
assert.Equal(t, 0, result.Result.N)
assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", result.Result.Asset)
assert.Equal(t, util.Fixed8FromInt64(100000000), result.Result.Value)
assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", result.Result.Address)
var txOut result.TransactionOutput
err := json.Unmarshal(res, &txOut)
require.NoErrorf(t, err, "could not parse response: %s", res)
assert.Equal(t, 0, txOut.N)
assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset)
assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value)
assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address)
})
}
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
expected = tc.result(e)
switch exp := expected.(type) {
case string:
res = new(StringResultResponse)
expected = &StringResultResponse{
Jsonrpc: defaultJSONRPC,
Result: exp,
ID: defaultID,
}
case int:
res = new(IntResultResponse)
expected = &IntResultResponse{
Jsonrpc: defaultJSONRPC,
Result: exp,
ID: defaultID,
}
default:
resVal := reflect.New(reflect.TypeOf(expected).Elem())
res = resVal.Interface()
}
return
resVal := reflect.New(reflect.TypeOf(expected).Elem())
return expected, resVal.Interface()
}
func checkErrResponse(t *testing.T, body []byte, expectingFail bool) {
var errresp ErrorResponse
err := json.Unmarshal(body, &errresp)
func checkErrGetResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage {
var resp response.Raw
err := json.Unmarshal(body, &resp)
require.Nil(t, err)
if expectingFail {
assert.NotEqual(t, 0, errresp.Error.Code)
assert.NotEqual(t, "", errresp.Error.Message)
assert.NotEqual(t, 0, resp.Error.Code)
assert.NotEqual(t, "", resp.Error.Message)
} else {
assert.Equal(t, 0, errresp.Error.Code)
assert.Equal(t, "", errresp.Error.Message)
assert.Nil(t, resp.Error)
}
return resp.Result
}
func doRPCCall(rpcCall string, handler http.HandlerFunc, t *testing.T) []byte {

View file

@ -1,217 +0,0 @@
package rpc
import (
"net/http"
"os"
"testing"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/block"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc/result"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
// ErrorResponse struct represents JSON-RPC error.
type ErrorResponse struct {
Jsonrpc string `json:"jsonrpc"`
Error struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
ID int `json:"id"`
}
// SendTXResponse struct for testing.
type SendTXResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result bool `json:"result"`
ID int `json:"id"`
}
// InvokeFunctionResponse struct for testing.
type InvokeFunctionResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
Script string `json:"script"`
State string `json:"state"`
GasConsumed string `json:"gas_consumed"`
Stack []FuncParam `json:"stack"`
TX string `json:"tx,omitempty"`
} `json:"result"`
ID int `json:"id"`
}
// ValidateAddrResponse struct for testing.
type ValidateAddrResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result wrappers.ValidateAddressResponse `json:"result"`
ID int `json:"id"`
}
// GetPeersResponse struct for testing.
type GetPeersResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
Unconnected []int `json:"unconnected"`
Connected []int `json:"connected"`
Bad []int `json:"bad"`
} `json:"result"`
ID int `json:"id"`
}
// GetVersionResponse struct for testing.
type GetVersionResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result result.Version `json:"result"`
ID int `json:"id"`
}
// IntResultResponse struct for testing.
type IntResultResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result int `json:"result"`
ID int `json:"id"`
}
// StringResultResponse struct for testing.
type StringResultResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result string `json:"result"`
ID int `json:"id"`
}
// GetBlockResponse struct for testing.
type GetBlockResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result wrappers.Block `json:"result"`
ID int `json:"id"`
}
// GetAssetResponse struct for testing.
type GetAssetResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
AssetID string `json:"assetID"`
AssetType int `json:"assetType"`
Name string `json:"name"`
Amount string `json:"amount"`
Available string `json:"available"`
Precision int `json:"precision"`
Fee int `json:"fee"`
Address string `json:"address"`
Owner string `json:"owner"`
Admin string `json:"admin"`
Issuer string `json:"issuer"`
Expiration int `json:"expiration"`
IsFrozen bool `json:"is_frozen"`
} `json:"result"`
ID int `json:"id"`
}
// GetAccountStateResponse struct for testing.
type GetAccountStateResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
Version int `json:"version"`
ScriptHash string `json:"script_hash"`
Frozen bool `json:"frozen"`
Votes []interface{} `json:"votes"`
Balances []struct {
Asset string `json:"asset"`
Value string `json:"value"`
} `json:"balances"`
} `json:"result"`
ID int `json:"id"`
}
// GetUnspents struct for testing.
type GetUnspents struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
Balance []struct {
Unspents []struct {
TxID string `json:"txid"`
Index int `json:"n"`
Value string `json:"value"`
} `json:"unspent"`
AssetHash string `json:"asset_hash"`
Asset string `json:"asset"`
AssetSymbol string `json:"asset_symbol"`
Amount string `json:"amount"`
} `json:"balance"`
Address string `json:"address"`
} `json:"result"`
ID int `json:"id"`
}
// GetContractStateResponse struct for testing.
type GetContractStateResponce struct {
Jsonrpc string `json:"jsonrpc"`
Result struct {
Version byte `json:"version"`
ScriptHash util.Uint160 `json:"hash"`
Script []byte `json:"script"`
ParamList interface{} `json:"parameters"`
ReturnType interface{} `json:"returntype"`
Name string `json:"name"`
CodeVersion string `json:"code_version"`
Author string `json:"author"`
Email string `json:"email"`
Description string `json:"description"`
Properties struct {
HasStorage bool `json:"storage"`
HasDynamicInvoke bool `json:"dynamic_invoke"`
IsPayable bool `json:"is_payable"`
} `json:"properties"`
} `json:"result"`
ID int `json:"id"`
}
func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFunc) {
var nBlocks uint32
net := config.ModeUnitTestNet
configPath := "../../config"
cfg, err := config.Load(configPath, net)
require.NoError(t, err, "could not load config")
memoryStore := storage.NewMemoryStore()
logger := zaptest.NewLogger(t)
chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger)
require.NoError(t, err, "could not create chain")
go chain.Run()
// File "./testdata/testblocks.acc" was generated by function core._
// ("neo-go/pkg/core/helper_test.go").
// To generate new "./testdata/testblocks.acc", follow the steps:
// 1. Rename the function
// 2. Add specific test-case into "neo-go/pkg/core/blockchain_test.go"
// 3. Run tests with `$ make test`
f, err := os.Open("testdata/testblocks.acc")
require.Nil(t, err)
br := io.NewBinReaderFromIO(f)
nBlocks = br.ReadU32LE()
require.Nil(t, br.Err)
for i := 0; i < int(nBlocks); i++ {
b := &block.Block{}
b.DecodeBinary(br)
require.Nil(t, br.Err)
require.NoError(t, chain.AddBlock(b))
}
serverConfig := network.NewServerConfig(cfg)
server, err := network.NewServer(serverConfig, chain, logger)
require.NoError(t, err)
rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPC, server, logger)
handler := http.HandlerFunc(rpcServer.requestHandler)
return chain, handler
}

View file

@ -1,89 +0,0 @@
package rpc
import (
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvocationScriptCreationGood(t *testing.T) {
p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}
contract, err := p.GetUint160FromHex()
require.Nil(t, err)
var paramScripts = []struct {
ps Params
script string
}{{
script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "transfer"}},
script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{numberT, 42}},
script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{}}},
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}},
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}},
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}},
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}}
for _, ps := range paramScripts {
script, err := CreateFunctionInvocationScript(contract, ps.ps)
assert.Nil(t, err)
assert.Equal(t, ps.script, hex.EncodeToString(script))
}
}
func TestInvocationScriptCreationBad(t *testing.T) {
contract := util.Uint160{}
var testParams = []Params{
{{numberT, "qwerty"}},
{{arrayT, 42}},
{{arrayT, []Param{{numberT, 42}}}},
{{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}},
{{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}},
}
for _, ps := range testParams {
_, err := CreateFunctionInvocationScript(contract, ps)
assert.NotNil(t, err)
}
}

View file

@ -1,140 +0,0 @@
package rpc
import (
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/vm"
)
// InvokeScriptResponse stores response for the invoke script call.
type InvokeScriptResponse struct {
responseHeader
Error *Error `json:"error,omitempty"`
Result *InvokeResult `json:"result,omitempty"`
}
// InvokeResult represents the outcome of a script that is
// executed by the NEO VM.
type InvokeResult struct {
State vm.State `json:"state"`
GasConsumed string `json:"gas_consumed"`
Script string `json:"script"`
Stack []StackParam
}
// AccountStateResponse holds the getaccountstate response.
type AccountStateResponse struct {
responseHeader
Result *Account `json:"result"`
}
// UnspentResponse represents server response to the `getunspents` command.
type UnspentResponse struct {
responseHeader
Error *Error `json:"error,omitempty"`
Result *wrappers.Unspents `json:"result,omitempty"`
}
// Account represents details about a NEO account.
type Account struct {
Version int `json:"version"`
ScriptHash string `json:"script_hash"`
Frozen bool
// TODO: need to check this field out.
Votes []interface{}
Balances []*Balance
}
// Balance represents details about a NEO account balance.
type Balance struct {
Asset string `json:"asset"`
Value string `json:"value"`
}
type params struct {
values []interface{}
}
func newParams(vals ...interface{}) params {
p := params{}
p.values = make([]interface{}, len(vals))
for i := 0; i < len(p.values); i++ {
p.values[i] = vals[i]
}
return p
}
type request struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID int `json:"id"`
}
type responseHeader struct {
ID int `json:"id"`
JSONRPC string `json:"jsonrpc"`
}
type response struct {
responseHeader
Error *Error `json:"error"`
Result interface{} `json:"result"`
}
// SendToAddressResponse stores response for the sendtoaddress call.
type SendToAddressResponse struct {
responseHeader
Error *Error `json:"error"`
Result *TxResponse
}
// GetRawTxResponse represents verbose output of `getrawtransaction` RPC call.
type GetRawTxResponse struct {
responseHeader
Error *Error `json:"error"`
Result *RawTxResponse `json:"result"`
}
// GetTxOutResponse represents result of `gettxout` RPC call.
type GetTxOutResponse struct {
responseHeader
Error *Error
Result *wrappers.TransactionOutput
}
// RawTxResponse stores transaction with blockchain metadata to be sent as a response.
type RawTxResponse struct {
TxResponse
BlockHash string `json:"blockhash"`
Confirmations uint `json:"confirmations"`
BlockTime uint `json:"blocktime"`
}
// TxResponse stores transaction to be sent as a response.
type TxResponse struct {
TxID string `json:"txid"`
Size int `json:"size"`
Type string `json:"type"` // todo: convert to TransactionType
Version int `json:"version"`
Attributes []transaction.Attribute `json:"attributes"`
Vins []Vin `json:"vin"`
Vouts []Vout `json:"vout"`
SysFee int `json:"sys_fee"`
NetFee int `json:"net_fee"`
Scripts []transaction.Witness `json:"scripts"`
}
// Vin represents JSON-serializable tx input.
type Vin struct {
TxID string `json:"txid"`
Vout int `json:"vout"`
}
// Vout represents JSON-serializable tx output.
type Vout struct {
N int `json:"n"`
Asset string `json:"asset"`
Value int `json:"value"`
Address string `json:"address"`
}

View file

@ -1,22 +0,0 @@
package wrappers
import (
"github.com/CityOfZion/neo-go/pkg/encoding/address"
)
// ValidateAddressResponse represents response to validate address call.
type ValidateAddressResponse struct {
Address interface{} `json:"address"`
IsValid bool `json:"isvalid"`
}
// 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
func ValidateAddress(addr interface{}) ValidateAddressResponse {
resp := ValidateAddressResponse{Address: addr}
if addr, ok := addr.(string); ok {
_, err := address.StringToUint160(addr)
resp.IsValid = err == nil
}
return resp
}

View file

@ -137,7 +137,7 @@ func parseParamType(typ string) (ParamType, error) {
return Hash160Type, nil
case "hash256":
return Hash256Type, nil
case "bytes":
case "bytes", "bytearray":
return ByteArrayType, nil
case "key":
return PublicKeyType, nil