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/io"
"github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/network/metrics" "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/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.uber.org/zap" "go.uber.org/zap"
@ -341,18 +341,18 @@ func startServer(ctx *cli.Context) error {
return err return err
} }
server, err := network.NewServer(serverConfig, chain, log) serv, err := network.NewServer(serverConfig, chain, log)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create network server: %v", err), 1) 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) errChan := make(chan error)
go server.Start(errChan) go serv.Start(errChan)
go rpcServer.Start(errChan) go rpcServer.Start(errChan)
fmt.Println(logo()) fmt.Println(logo())
fmt.Println(server.UserAgent) fmt.Println(serv.UserAgent)
fmt.Println() fmt.Println()
var shutdownErr error var shutdownErr error
@ -364,7 +364,7 @@ Main:
cancel() cancel()
case <-grace.Done(): case <-grace.Done():
server.Shutdown() serv.Shutdown()
if serverErr := rpcServer.Shutdown(); serverErr != nil { if serverErr := rpcServer.Shutdown(); serverErr != nil {
shutdownErr = errors.Wrap(serverErr, "Error encountered whilst shutting down server") 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/compiler"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "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/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm" "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. // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place.
if !ctx.Bool("skip-details") { if !ctx.Bool("skip-details") {
details := parseContractDetails() details := parseContractDetails()
details.ReturnType = rpc.ByteArray details.ReturnType = request.ByteArray
details.Parameters = make([]rpc.StackParamType, 2) details.Parameters = make([]request.StackParamType, 2)
details.Parameters[0] = rpc.String details.Parameters[0] = request.String
details.Parameters[1] = rpc.Array details.Parameters[1] = request.Array
project := &ProjectConfig{Contract: details} project := &ProjectConfig{Contract: details}
b, err := yaml.Marshal(project) b, err := yaml.Marshal(project)
@ -362,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
operation string operation string
params = make([]smartcontract.Parameter, 0) params = make([]smartcontract.Parameter, 0)
paramsStart = 1 paramsStart = 1
resp *rpc.InvokeScriptResponse resp *response.InvokeResult
wif *keys.WIF wif *keys.WIF
) )
@ -398,34 +400,34 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
return err return err
} }
} }
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) c, err := client.New(context.TODO(), endpoint, client.Options{})
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if withMethod { if withMethod {
resp, err = client.InvokeFunction(script, operation, params) resp, err = c.InvokeFunction(script, operation, params)
} else { } else {
resp, err = client.Invoke(script, params) resp, err = c.Invoke(script, params)
} }
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if signAndPush { 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) 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 { if err != nil {
return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1) 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 { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
} }
fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE()) fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE())
} else { } else {
b, err := json.MarshalIndent(resp.Result, "", " ") b, err := json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -451,18 +453,18 @@ func testInvokeScript(ctx *cli.Context) error {
return cli.NewExitError(err, 1) 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 { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
scriptHex := hex.EncodeToString(b) scriptHex := hex.EncodeToString(b)
resp, err := client.InvokeScript(scriptHex) resp, err := c.InvokeScript(scriptHex)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
b, err = json.MarshalIndent(resp.Result, "", " ") b, err = json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -475,11 +477,11 @@ func testInvokeScript(ctx *cli.Context) error {
// ProjectConfig contains project metadata. // ProjectConfig contains project metadata.
type ProjectConfig struct { type ProjectConfig struct {
Version uint Version uint
Contract rpc.ContractDetails `yaml:"project"` Contract request.ContractDetails `yaml:"project"`
} }
func parseContractDetails() rpc.ContractDetails { func parseContractDetails() request.ContractDetails {
details := rpc.ContractDetails{} details := request.ContractDetails{}
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Print("Author: ") fmt.Print("Author: ")
@ -571,17 +573,17 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("bad config: %v", err), 1) 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 { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract) txScript, err := request.CreateDeploymentScript(avm, &conf.Contract)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) 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 { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) 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/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/network" "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" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
@ -55,7 +55,7 @@ func prepareData(t *testing.B) []*transaction.Transaction {
for n := 0; n < t.N; n++ { for n := 0; n < t.N; n++ {
tx := getTX(t, wif) tx := getTX(t, wif)
require.NoError(t, rpc.SignTx(tx, wif)) require.NoError(t, request.SignTx(tx, wif))
data = append(data, tx) data = append(data, tx)
} }
return data 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 // UnconnectedPeers returns a list of peers that are in the discovery peer list
// but are not connected to the server. // but are not connected to the server.
func (s *Server) UnconnectedPeers() []string { func (s *Server) UnconnectedPeers() []string {
return []string{} return s.discovery.UnconnectedPeers()
} }
// BadPeers returns a list of peers the are flagged as "bad" peers. // BadPeers returns a list of peers the are flagged as "bad" peers.
func (s *Server) BadPeers() []string { 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 // run is a goroutine that starts another goroutine to manage protocol specifics

View file

@ -1,4 +1,4 @@
package rpc package client
import ( import (
"bytes" "bytes"
@ -14,6 +14,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "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/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -37,13 +39,13 @@ type Client struct {
wifMu *sync.Mutex wifMu *sync.Mutex
wif *keys.WIF wif *keys.WIF
balancerMu *sync.Mutex 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 // All Values are optional. If any duration is not specified
// a default of 3 seconds will be used. // a default of 3 seconds will be used.
type ClientOptions struct { type Options struct {
Cert string Cert string
Key string Key string
CACert string CACert string
@ -55,8 +57,8 @@ type ClientOptions struct {
Version string Version string
} }
// NewClient returns a new Client ready to use. // New returns a new Client ready to use.
func NewClient(ctx context.Context, endpoint string, opts ClientOptions) (*Client, error) { func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
url, err := url.Parse(endpoint) url, err := url.Parse(endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,14 +123,14 @@ func (c *Client) SetWIF(wif string) error {
} }
// Balancer is a getter for balance field. // Balancer is a getter for balance field.
func (c *Client) Balancer() BalanceGetter { func (c *Client) Balancer() request.BalanceGetter {
c.balancerMu.Lock() c.balancerMu.Lock()
defer c.balancerMu.Unlock() defer c.balancerMu.Unlock()
return c.balancer return c.balancer
} }
// SetBalancer is a setter for balance field. // SetBalancer is a setter for balance field.
func (c *Client) SetBalancer(b BalanceGetter) { func (c *Client) SetBalancer(b request.BalanceGetter) {
c.balancerMu.Lock() c.balancerMu.Lock()
defer c.balancerMu.Unlock() defer c.balancerMu.Unlock()
@ -161,13 +163,10 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F
var utxos state.UnspentBalances var utxos state.UnspentBalances
resp, err := c.GetUnspents(address) resp, err := c.GetUnspents(address)
if err != nil || resp.Error != nil { if err != nil {
if err == nil {
err = fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message)
}
return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address) 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) { if asset.Equals(ubi.AssetHash) {
utxos = ubi.Unspents utxos = ubi.Unspents
break 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 ( var (
r = request{ r = request.Raw{
JSONRPC: c.version, JSONRPC: c.version,
Method: method, Method: method,
Params: p.values, RawParams: p.Values,
ID: 1, ID: 1,
} }
buf = new(bytes.Buffer) buf = new(bytes.Buffer)
raw = &response.Raw{}
) )
if err := json.NewEncoder(buf).Encode(r); err != nil { 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() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { // The node might send us proper JSON anyway, so look there first and if
return fmt.Errorf("remote responded with a non 200 response: %d", resp.StatusCode) // 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. // 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 ( import (
"encoding/json" "encoding/json"
@ -8,11 +8,38 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "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" "github.com/CityOfZion/neo-go/pkg/util"
errs "github.com/pkg/errors" 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. // GetBalance performs a request to get balance for the address specified.
func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) { func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) {
var ( var (
@ -57,7 +84,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256,
err error err error
us []*Unspent us []*Unspent
assetUnspent Unspent assetUnspent Unspent
assetID = wrappers.GlobalAssets[assetIDUint.StringLE()] assetID = result.GlobalAssets[assetIDUint.StringLE()]
) )
if us, err = s.GetBalance(address); err != nil { if us, err = s.GetBalance(address); err != nil {
return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address) 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 ( import (
"encoding/hex" "encoding/hex"
"fmt"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "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/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -17,11 +19,11 @@ import (
// missing output wrapper at the moment, thus commented out // missing output wrapper at the moment, thus commented out
// func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) { // func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) {
// var ( // var (
// params = newParams(indexOrHash) // params = request.NewRawParams(indexOrHash)
// resp = &response{} // resp = &response{}
// ) // )
// if verbose { // if verbose {
// params = newParams(indexOrHash, 1) // params = request.NewRawParams(indexOrHash, 1)
// } // }
// if err := c.performRequest("getblock", params, resp); err != nil { // if err := c.performRequest("getblock", params, resp); err != nil {
// return nil, err // return nil, err
@ -30,10 +32,10 @@ import (
// } // }
// GetAccountState returns detailed information about a NEO account. // 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 ( var (
params = newParams(address) params = request.NewRawParams(address)
resp = &AccountStateResponse{} resp = &result.AccountState{}
) )
if err := c.performRequest("getaccountstate", params, resp); err != nil { if err := c.performRequest("getaccountstate", params, resp); err != nil {
return nil, err return nil, err
@ -42,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*AccountStateResponse, error)
} }
// GetUnspents returns UTXOs for the given NEO account. // 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 ( var (
params = newParams(address) params = request.NewRawParams(address)
resp = &UnspentResponse{} resp = &result.Unspents{}
) )
if err := c.performRequest("getunspents", params, resp); err != nil { if err := c.performRequest("getunspents", params, resp); err != nil {
return nil, err 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. // 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. // 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 ( var (
params = newParams(script) params = request.NewRawParams(script)
resp = &InvokeScriptResponse{} resp = &response.InvokeResult{}
) )
if err := c.performRequest("invokescript", params, resp); err != nil { if err := c.performRequest("invokescript", params, resp); err != nil {
return nil, err 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 // InvokeFunction returns the results after calling the smart contract scripthash
// with the given operation and parameters. // with the given operation and parameters.
// NOTE: this is test invoke and will not affect the blockchain. // 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 ( var (
p = newParams(script, operation, params) p = request.NewRawParams(script, operation, params)
resp = &InvokeScriptResponse{} resp = &response.InvokeResult{}
) )
if err := c.performRequest("invokefunction", p, resp); err != nil { if err := c.performRequest("invokefunction", p, resp); err != nil {
return nil, err 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 // Invoke returns the results after calling the smart contract scripthash
// with the given parameters. // 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 ( var (
p = newParams(script, params) p = request.NewRawParams(script, params)
resp = &InvokeScriptResponse{} resp = &response.InvokeResult{}
) )
if err := c.performRequest("invoke", p, resp); err != nil { if err := c.performRequest("invoke", p, resp); err != nil {
return nil, err 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 // missing output wrapper at the moment, thus commented out
// func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) { // func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) {
// var ( // var (
// params = newParams(hash, verbose) // params = request.NewRawParams(hash, verbose)
// resp = &response{} // resp = &response{}
// ) // )
// if err := c.performRequest("getrawtransaction", params, resp); err != nil { // 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. // The given hex string needs to be signed with a keypair.
// When the result of the response object is true, the TX has successfully // When the result of the response object is true, the TX has successfully
// been broadcasted to the network. // been broadcasted to the network.
func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, error) { func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) error {
var ( var (
params = newParams(hex.EncodeToString(rawTX.Bytes())) params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes()))
resp = &response{} resp bool
) )
if err := c.performRequest("sendrawtransaction", params, resp); err != nil { if err := c.performRequest("sendrawtransaction", params, &resp); err != nil {
return nil, err 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. // SendToAddress sends an amount of specific asset to a given address.
// This call requires open wallet. (`wif` key in client struct.) // 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. // 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 ( var (
err error err error
rawTx *transaction.Transaction rawTx *transaction.Transaction
txParams = ContractTxParams{ txParams = request.ContractTxParams{
assetID: asset, AssetID: asset,
address: address, Address: address,
value: amount, Value: amount,
wif: c.WIF(), WIF: c.WIF(),
balancer: c.Balancer(), Balancer: c.Balancer(),
} }
resp *response resp util.Uint256
response = &SendToAddressResponse{}
) )
if rawTx, err = CreateRawContractTransaction(txParams); err != nil { if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil {
return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") return resp, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`")
} }
if resp, err = c.sendRawTransaction(rawTx); err != nil { if err = c.sendRawTransaction(rawTx); err != nil {
return nil, errors.Wrap(err, "failed to send raw transaction") return resp, errors.Wrap(err, "failed to send raw transaction")
} }
response.Error = resp.Error return rawTx.Hash(), nil
response.ID = resp.ID
response.JSONRPC = resp.JSONRPC
response.Result = &TxResponse{
TxID: rawTx.Hash().StringLE(),
}
return response, nil
} }
// SignAndPushInvocationTx signs and pushes given script as an invocation // 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() fromAddress := wif.PrivateKey.Address()
if gas > 0 { 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") 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") return txHash, errors.Wrap(err, "failed to sign tx")
} }
txHash = tx.Hash() txHash = tx.Hash()
resp, err := c.sendRawTransaction(tx) err = c.sendRawTransaction(tx)
if err != nil { if err != nil {
return txHash, errors.Wrap(err, "failed sendning tx") 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 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 ( import (
"bytes" "bytes"
@ -29,12 +29,13 @@ type (
} }
) )
// These are parameter types accepted by RPC server.
const ( const (
defaultT paramType = iota defaultT paramType = iota
stringT StringT
numberT NumberT
arrayT ArrayT
funcParamT FuncParamT
) )
func (p Param) String() string { func (p Param) String() string {
@ -123,7 +124,7 @@ func (p Param) GetBytesHex() ([]byte, error) {
func (p *Param) UnmarshalJSON(data []byte) error { func (p *Param) UnmarshalJSON(data []byte) error {
var s string var s string
if err := json.Unmarshal(data, &s); err == nil { if err := json.Unmarshal(data, &s); err == nil {
p.Type = stringT p.Type = StringT
p.Value = s p.Value = s
return nil return nil
@ -131,7 +132,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
var num float64 var num float64
if err := json.Unmarshal(data, &num); err == nil { if err := json.Unmarshal(data, &num); err == nil {
p.Type = numberT p.Type = NumberT
p.Value = int(num) p.Value = int(num)
return nil return nil
@ -142,7 +143,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
jd.DisallowUnknownFields() jd.DisallowUnknownFields()
var fp FuncParam var fp FuncParam
if err := jd.Decode(&fp); err == nil { if err := jd.Decode(&fp); err == nil {
p.Type = funcParamT p.Type = FuncParamT
p.Value = fp p.Value = fp
return nil return nil
@ -150,7 +151,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
var ps []Param var ps []Param
if err := json.Unmarshal(data, &ps); err == nil { if err := json.Unmarshal(data, &ps); err == nil {
p.Type = arrayT p.Type = ArrayT
p.Value = ps p.Value = ps
return nil return nil

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package rpc package request
/* /*
Definition of types, interfaces and variables Definition of types, interfaces and variables
@ -16,14 +16,14 @@ type (
// includes parameters duplication `sendtoaddress` RPC call params // includes parameters duplication `sendtoaddress` RPC call params
// and also some utility data; // and also some utility data;
ContractTxParams struct { ContractTxParams struct {
assetID util.Uint256 AssetID util.Uint256
address string Address string
value util.Fixed8 Value util.Fixed8
wif keys.WIF // a WIF to send the transaction WIF keys.WIF // a WIF to send the transaction
// since there are many ways to provide unspents, // since there are many ways to provide unspents,
// transaction composer stays agnostic to that how // transaction composer stays agnostic to that how
// unspents was got; // unspents was got;
balancer BalanceGetter Balancer BalanceGetter
} }
// BalanceGetter is an interface supporting CalculateInputs() method. // 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 ( import (
"fmt" "fmt"
@ -18,10 +18,13 @@ type (
) )
var ( 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{ return &Error{
Code: code, Code: code,
HTTPCode: httpCode, 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 // NewParseError creates a new error with code
// -32700.:%s // -32700.:%s
func NewParseError(data string, cause error) *Error { 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 // NewInvalidRequestError creates a new error with
// code -32600. // code -32600.
func NewInvalidRequestError(data string, cause error) *Error { 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 // NewMethodNotFoundError creates a new error with
// code -32601. // code -32601.
func NewMethodNotFoundError(data string, cause error) *Error { 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 // NewInvalidParamsError creates a new error with
// code -32602. // code -32602.
func NewInvalidParamsError(data string, cause error) *Error { 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 // NewInternalServerError creates a new error with
// code -32603. // code -32603.
func NewInternalServerError(data string, cause error) *Error { 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 // NewRPCError creates a new error with
// code -100 // code -100
func NewRPCError(message string, data string, cause error) *Error { 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. // 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) 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 ( import (
"bytes" "bytes"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package wrappers package result
import ( import (
"github.com/CityOfZion/neo-go/pkg/core" "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" import "github.com/prometheus/client_golang/prometheus"

View file

@ -1,8 +1,9 @@
package rpc package server
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -11,10 +12,12 @@ import (
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "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/io"
"github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc/result" "github.com/CityOfZion/neo-go/pkg/rpc/request"
"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/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "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) 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. // New creates a new Server struct.
func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server { func New(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server {
httpServer := &http.Server{ httpServer := &http.Server{
Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10), 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) { func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) {
req := NewRequest() req := request.NewIn()
if httpRequest.Method != "POST" { if httpRequest.Method != "POST" {
s.WriteErrorResponse( s.WriteErrorResponse(
req, req,
w, w,
NewInvalidParamsError( response.NewInvalidParamsError(
fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil, 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) err := req.DecodeData(httpRequest.Body)
if err != nil { 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 return
} }
reqParams, err := req.Params() reqParams, err := req.Params()
if err != nil { 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 return
} }
s.methodHandler(w, req, *reqParams) 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", s.log.Debug("processing rpc request",
zap.String("method", req.Method), zap.String("method", req.Method),
zap.String("params", fmt.Sprintf("%v", reqParams))) zap.String("params", fmt.Sprintf("%v", reqParams)))
@ -121,38 +124,38 @@ Methods:
param, ok := reqParams.Value(0) param, ok := reqParams.Value(0)
if !ok { if !ok {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
switch param.Type { switch param.Type {
case stringT: case request.StringT:
var err error var err error
hash, err = param.GetUint256() hash, err = param.GetUint256()
if err != nil { if err != nil {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
case numberT: case request.NumberT:
num, err := s.blockHeightFromParam(param) num, err := s.blockHeightFromParam(param)
if err != nil { if err != nil {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
hash = s.chain.GetHeaderHash(num) hash = s.chain.GetHeaderHash(num)
default: default:
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
block, err := s.chain.GetBlock(hash) block, err := s.chain.GetBlock(hash)
if err != nil { 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 break
} }
if len(reqParams) == 2 && reqParams[1].Value == 1 { if len(reqParams) == 2 && reqParams[1].Value == 1 {
results = wrappers.NewBlock(block, s.chain) results = result.NewBlock(block, s.chain)
} else { } else {
writer := io.NewBufBinWriter() writer := io.NewBufBinWriter()
block.EncodeBinary(writer.BinWriter) block.EncodeBinary(writer.BinWriter)
@ -165,14 +168,14 @@ Methods:
case "getblockhash": case "getblockhash":
getblockHashCalled.Inc() getblockHashCalled.Inc()
param, ok := reqParams.ValueWithType(0, numberT) param, ok := reqParams.ValueWithType(0, request.NumberT)
if !ok { if !ok {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
num, err := s.blockHeightFromParam(param) num, err := s.blockHeightFromParam(param)
if err != nil { if err != nil {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
@ -192,19 +195,10 @@ Methods:
case "getpeers": case "getpeers":
getpeersCalled.Inc() getpeersCalled.Inc()
peers := result.NewPeers() peers := result.NewGetPeers()
for _, addr := range s.coreServer.UnconnectedPeers() { peers.AddUnconnected(s.coreServer.UnconnectedPeers())
peers.AddPeer("unconnected", addr) peers.AddConnected(s.coreServer.ConnectedPeers())
} peers.AddBad(s.coreServer.BadPeers())
for _, addr := range s.coreServer.BadPeers() {
peers.AddPeer("bad", addr)
}
for addr := range s.coreServer.Peers() {
peers.AddPeer("connected", addr.PeerAddr().String())
}
results = peers results = peers
case "getstorage": case "getstorage":
@ -215,30 +209,30 @@ Methods:
validateaddressCalled.Inc() validateaddressCalled.Inc()
param, ok := reqParams.Value(0) param, ok := reqParams.Value(0)
if !ok { if !ok {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
results = wrappers.ValidateAddress(param.Value) results = validateAddress(param.Value)
case "getassetstate": case "getassetstate":
getassetstateCalled.Inc() getassetstateCalled.Inc()
param, ok := reqParams.ValueWithType(0, stringT) param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok { if !ok {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break Methods break Methods
} }
paramAssetID, err := param.GetUint256() paramAssetID, err := param.GetUint256()
if err != nil { if err != nil {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
break break
} }
as := s.chain.GetAssetState(paramAssetID) as := s.chain.GetAssetState(paramAssetID)
if as != nil { if as != nil {
results = wrappers.NewAssetState(as) results = result.NewAssetState(as)
} else { } else {
resultsErr = NewRPCError("Unknown asset", "", nil) resultsErr = response.NewRPCError("Unknown asset", "", nil)
} }
case "getaccountstate": case "getaccountstate":
@ -275,7 +269,7 @@ Methods:
results, resultsErr = s.sendrawtransaction(reqParams) results, resultsErr = s.sendrawtransaction(reqParams)
default: 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 { if resultsErr != nil {
@ -286,27 +280,27 @@ Methods:
s.WriteResponse(req, w, results) 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) param, ok := ps.Value(0)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
scriptHash, err := param.GetUint160FromHex() scriptHash, err := param.GetUint160FromHex()
if err != nil { if err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
scriptHash = scriptHash.Reverse() scriptHash = scriptHash.Reverse()
param, ok = ps.Value(1) param, ok = ps.Value(1)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
key, err := param.GetBytesHex() key, err := param.GetBytesHex()
if err != nil { if err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
item := s.chain.GetStorageItem(scriptHash.Reverse(), key) 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 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 resultsErr error
var results interface{} var results interface{}
if param0, ok := reqParams.Value(0); !ok { if param0, ok := reqParams.Value(0); !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else if txHash, err := param0.GetUint256(); err != nil { } else if txHash, err := param0.GetUint256(); err != nil {
resultsErr = errInvalidParams resultsErr = response.ErrInvalidParams
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil { } else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash) 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 { } else if len(reqParams) >= 2 {
_header := s.chain.GetHeaderHash(int(height)) _header := s.chain.GetHeaderHash(int(height))
header, err := s.chain.GetHeader(_header) header, err := s.chain.GetHeader(_header)
if err != nil { if err != nil {
resultsErr = NewInvalidParamsError(err.Error(), err) resultsErr = response.NewInvalidParamsError(err.Error(), err)
} }
param1, _ := reqParams.Value(1) 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" { if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" {
results = hex.EncodeToString(tx.Bytes()) results = hex.EncodeToString(tx.Bytes())
} else { } else {
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) results = result.NewTransactionOutputRaw(tx, header, s.chain)
} }
default: default:
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) results = result.NewTransactionOutputRaw(tx, header, s.chain)
} }
} else { } else {
results = hex.EncodeToString(tx.Bytes()) results = hex.EncodeToString(tx.Bytes())
@ -354,70 +348,70 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
return results, resultsErr 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) p, ok := ps.Value(0)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
h, err := p.GetUint256() h, err := p.GetUint256()
if err != nil { 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 { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
num, err := p.GetInt() num, err := p.GetInt()
if err != nil || num < 0 { if err != nil || num < 0 {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
tx, _, err := s.chain.GetTransaction(h) tx, _, err := s.chain.GetTransaction(h)
if err != nil { if err != nil {
return nil, NewInvalidParamsError(err.Error(), err) return nil, response.NewInvalidParamsError(err.Error(), err)
} }
if num >= len(tx.Outputs) { 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] 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). // 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{} var results interface{}
param, ok := reqParams.ValueWithType(0, stringT) param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else if scriptHash, err := param.GetUint160FromHex(); err != nil { } else if scriptHash, err := param.GetUint160FromHex(); err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else { } else {
cs := s.chain.GetContractState(scriptHash) cs := s.chain.GetContractState(scriptHash)
if cs != nil { if cs != nil {
results = wrappers.NewContractState(cs) results = result.NewContractState(cs)
} else { } else {
return nil, NewRPCError("Unknown contract", "", nil) return nil, response.NewRPCError("Unknown contract", "", nil)
} }
} }
return results, nil return results, nil
} }
// getAccountState returns account state either in short or full (unspents included) form. // 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 resultsErr error
var results interface{} var results interface{}
param, ok := reqParams.ValueWithType(0, stringT) param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil { } else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else { } else {
as := s.chain.GetAccountState(scriptHash) as := s.chain.GetAccountState(scriptHash)
if as == nil { if as == nil {
@ -426,35 +420,35 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
if unspents { if unspents {
str, err := param.GetString() str, err := param.GetString()
if err != nil { 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 { } else {
results = wrappers.NewAccountState(as) results = result.NewAccountState(as)
} }
} }
return results, resultsErr return results, resultsErr
} }
// invoke implements the `invoke` RPC call. // invoke implements the `invoke` RPC call.
func (s *Server) invoke(reqParams Params) (interface{}, error) { func (s *Server) invoke(reqParams request.Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, stringT) scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
scriptHash, err := scriptHashHex.GetUint160FromHex() scriptHash, err := scriptHashHex.GetUint160FromHex()
if err != nil { if err != nil {
return nil, err return nil, err
} }
sliceP, ok := reqParams.ValueWithType(1, arrayT) sliceP, ok := reqParams.ValueWithType(1, request.ArrayT)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
slice, err := sliceP.GetArray() slice, err := sliceP.GetArray()
if err != nil { if err != nil {
return nil, err return nil, err
} }
script, err := CreateInvocationScript(scriptHash, slice) script, err := request.CreateInvocationScript(scriptHash, slice)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -462,16 +456,16 @@ func (s *Server) invoke(reqParams Params) (interface{}, error) {
} }
// invokescript implements the `invokescript` RPC call. // invokescript implements the `invokescript` RPC call.
func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) {
scriptHashHex, ok := reqParams.ValueWithType(0, stringT) scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
if !ok { if !ok {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
scriptHash, err := scriptHashHex.GetUint160FromHex() scriptHash, err := scriptHashHex.GetUint160FromHex()
if err != nil { if err != nil {
return nil, err return nil, err
} }
script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:]) script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -479,14 +473,14 @@ func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
} }
// invokescript implements the `invokescript` RPC call. // invokescript implements the `invokescript` RPC call.
func (s *Server) invokescript(reqParams Params) (interface{}, error) { func (s *Server) invokescript(reqParams request.Params) (interface{}, error) {
if len(reqParams) < 1 { if len(reqParams) < 1 {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
script, err := reqParams[0].GetBytesHex() script, err := reqParams[0].GetBytesHex()
if err != nil { if err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} }
return s.runScriptInVM(script), nil 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 // runScriptInVM runs given script in a new test VM and returns the invocation
// result. // result.
func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult { func (s *Server) runScriptInVM(script []byte) *result.Invoke {
vm, _ := s.chain.GetTestVM() vm, _ := s.chain.GetTestVM()
vm.SetGasLimit(s.config.MaxGasInvoke) vm.SetGasLimit(s.config.MaxGasInvoke)
vm.LoadScript(script) vm.LoadScript(script)
_ = vm.Run() _ = vm.Run()
result := &wrappers.InvokeResult{ result := &result.Invoke{
State: vm.State(), State: vm.State(),
GasConsumed: vm.GasConsumed().String(), GasConsumed: vm.GasConsumed().String(),
Script: hex.EncodeToString(script), Script: hex.EncodeToString(script),
@ -508,14 +502,14 @@ func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult {
return result return result
} }
func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) {
var resultsErr error var resultsErr error
var results interface{} var results interface{}
if len(reqParams) < 1 { if len(reqParams) < 1 {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else if byteTx, err := reqParams[0].GetBytesHex(); err != nil { } else if byteTx, err := reqParams[0].GetBytesHex(); err != nil {
return nil, errInvalidParams return nil, response.ErrInvalidParams
} else { } else {
r := io.NewBinReaderFromBuf(byteTx) r := io.NewBinReaderFromBuf(byteTx)
tx := &transaction.Transaction{} tx := &transaction.Transaction{}
@ -542,14 +536,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
} }
} }
if err != nil { if err != nil {
resultsErr = NewInternalServerError(err.Error(), err) resultsErr = response.NewInternalServerError(err.Error(), err)
} }
} }
return results, resultsErr return results, resultsErr
} }
func (s Server) blockHeightFromParam(param *Param) (int, error) { func (s *Server) blockHeightFromParam(param *request.Param) (int, error) {
num, err := param.GetInt() num, err := param.GetInt()
if err != nil { if err != nil {
return 0, nil return 0, nil
@ -560,3 +554,87 @@ func (s Server) blockHeightFromParam(param *Param) (int, error) {
} }
return num, nil 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 ( import (
"bytes" "bytes"
@ -14,7 +14,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/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/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -43,23 +44,23 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, result: func(e *executor) interface{} { return &result.AccountState{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := result.(*GetAccountStateResponse) res, ok := acc.(*result.AccountState)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, 1, len(res.Result.Balances)) assert.Equal(t, 1, len(res.Balances))
assert.Equal(t, false, res.Result.Frozen) assert.Equal(t, false, res.IsFrozen)
}, },
}, },
{ {
name: "positive null", name: "positive null",
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, result: func(e *executor) interface{} { return &result.AccountState{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := result.(*GetAccountStateResponse) res, ok := acc.(*result.AccountState)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, 0, len(res.Result.Balances)) assert.Equal(t, 0, len(res.Balances))
assert.Equal(t, false, res.Result.Frozen) assert.Equal(t, false, res.IsFrozen)
}, },
}, },
{ {
@ -77,13 +78,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27"]`, params: `["1a696b32e239dd5eace3f025cac0a193a5746a27"]`,
result: func(e *executor) interface{} { return &GetContractStateResponce{} }, result: func(e *executor) interface{} { return &result.ContractState{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := result.(*GetContractStateResponce) res, ok := cs.(*result.ContractState)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, byte(0), res.Result.Version) 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.Result.ScriptHash) 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.Result.CodeVersion) assert.Equal(t, "0.99", res.CodeVersion)
}, },
}, },
{ {
@ -106,21 +107,17 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "746573746b6579"]`, params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "746573746b6579"]`,
result: func(e *executor) interface{} { return &StringResultResponse{} }, result: func(e *executor) interface{} {
check: func(t *testing.T, e *executor, result interface{}) { v := hex.EncodeToString([]byte("testvalue"))
res, ok := result.(*StringResultResponse) return &v
require.True(t, ok)
assert.Equal(t, hex.EncodeToString([]byte("testvalue")), res.Result)
}, },
}, },
{ {
name: "missing key", name: "missing key",
params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "7465"]`, params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "7465"]`,
result: func(e *executor) interface{} { return &StringResultResponse{} }, result: func(e *executor) interface{} {
check: func(t *testing.T, e *executor, result interface{}) { v := ""
res, ok := result.(*StringResultResponse) return &v
require.True(t, ok)
assert.Equal(t, "", res.Result)
}, },
}, },
{ {
@ -148,12 +145,12 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`, params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`,
result: func(e *executor) interface{} { return &GetAssetResponse{} }, result: func(e *executor) interface{} { return &result.AssetState{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, as interface{}) {
res, ok := result.(*GetAssetResponse) res, ok := as.(*result.AssetState)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "00", res.Result.Owner) assert.Equal(t, "00", res.Owner)
assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin) assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Admin)
}, },
}, },
{ {
@ -176,7 +173,8 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
params: "[]", params: "[]",
result: func(e *executor) interface{} { 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", name: "positive",
params: "[1, 1]", params: "[1, 1]",
result: func(e *executor) interface{} { return &GetBlockResponse{} }, result: func(e *executor) interface{} { return &result.Block{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, blockRes interface{}) {
res, ok := result.(*GetBlockResponse) res, ok := blockRes.(*result.Block)
require.True(t, ok) require.True(t, ok)
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoErrorf(t, err, "could not get block") require.NoErrorf(t, err, "could not get block")
assert.Equal(t, block.Hash(), res.Result.Hash) assert.Equal(t, block.Hash(), res.Hash)
for i := range res.Result.Tx { for i := range res.Tx {
tx := res.Result.Tx[i] tx := res.Tx[i]
require.Equal(t, transaction.MinerType, tx.Type) require.Equal(t, transaction.MinerType, tx.Type)
miner, ok := block.Transactions[i].Data.(*transaction.MinerTX) miner, ok := block.Transactions[i].Data.(*transaction.MinerTX)
@ -269,22 +267,21 @@ var rpcTestCases = map[string][]rpcTestCase{
"getblockcount": { "getblockcount": {
{ {
params: "[]", 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": { "getblockhash": {
{ {
params: "[1]", params: "[1]",
result: func(e *executor) interface{} { return "" }, result: func(e *executor) interface{} {
check: func(t *testing.T, e *executor, result interface{}) { // We don't have `t` here for proper handling, but
res, ok := result.(*StringResultResponse) // error here would lead to panic down below.
require.True(t, ok) block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1))
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoErrorf(t, err, "could not get block")
expectedHash := "0x" + block.Hash().StringLE() expectedHash := "0x" + block.Hash().StringLE()
assert.Equal(t, expectedHash, res.Result) return &expectedHash
}, },
}, },
{ {
@ -301,25 +298,20 @@ var rpcTestCases = map[string][]rpcTestCase{
"getconnectioncount": { "getconnectioncount": {
{ {
params: "[]", params: "[]",
result: func(*executor) interface{} { return 0 }, result: func(*executor) interface{} {
v := 0
return &v
},
}, },
}, },
"getpeers": { "getpeers": {
{ {
params: "[]", params: "[]",
result: func(*executor) interface{} { result: func(*executor) interface{} {
return &GetPeersResponse{ return &result.GetPeers{
Jsonrpc: defaultJSONRPC, Unconnected: []result.Peer{},
Result: struct { Connected: []result.Peer{},
Unconnected []int `json:"unconnected"` Bad: []result.Peer{},
Connected []int `json:"connected"`
Bad []int `json:"bad"`
}{
Unconnected: []int{},
Connected: []int{},
Bad: []int{},
},
ID: defaultID,
} }
}, },
}, },
@ -345,33 +337,33 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
result: func(e *executor) interface{} { return &GetUnspents{} }, result: func(e *executor) interface{} { return &result.Unspents{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, unsp interface{}) {
res, ok := result.(*GetUnspents) res, ok := unsp.(*result.Unspents)
require.True(t, ok) require.True(t, ok)
require.Equal(t, 1, len(res.Result.Balance)) require.Equal(t, 1, len(res.Balance))
assert.Equal(t, 1, len(res.Result.Balance[0].Unspents)) assert.Equal(t, 1, len(res.Balance[0].Unspents))
}, },
}, },
{ {
name: "positive null", name: "positive null",
params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`,
result: func(e *executor) interface{} { return &GetUnspents{} }, result: func(e *executor) interface{} { return &result.Unspents{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, unsp interface{}) {
res, ok := result.(*GetUnspents) res, ok := unsp.(*result.Unspents)
require.True(t, ok) require.True(t, ok)
require.Equal(t, 0, len(res.Result.Balance)) require.Equal(t, 0, len(res.Balance))
}, },
}, },
}, },
"getversion": { "getversion": {
{ {
params: "[]", params: "[]",
result: func(*executor) interface{} { return &GetVersionResponse{} }, result: func(*executor) interface{} { return &result.Version{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, ver interface{}) {
resp, ok := result.(*GetVersionResponse) resp, ok := ver.(*result.Version)
require.True(t, ok) 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", name: "positive",
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`, params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`,
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := result.(*InvokeFunctionResponse) res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Result.Script) assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Script)
assert.NotEqual(t, "", res.Result.State) assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.Result.GasConsumed) assert.NotEqual(t, 0, res.GasConsumed)
}, },
}, },
{ {
@ -418,13 +410,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`, params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := result.(*InvokeFunctionResponse) res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok) require.True(t, ok)
assert.NotEqual(t, "", res.Result.Script) assert.NotEqual(t, "", res.Script)
assert.NotEqual(t, "", res.Result.State) assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.Result.GasConsumed) assert.NotEqual(t, 0, res.GasConsumed)
}, },
}, },
{ {
@ -452,13 +444,13 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`, params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`,
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, result: func(e *executor) interface{} { return &InvokeFunctionResult{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := result.(*InvokeFunctionResponse) res, ok := inv.(*InvokeFunctionResult)
require.True(t, ok) require.True(t, ok)
assert.NotEqual(t, "", res.Result.Script) assert.NotEqual(t, "", res.Script)
assert.NotEqual(t, "", res.Result.State) assert.NotEqual(t, "", res.State)
assert.NotEqual(t, 0, res.Result.GasConsumed) assert.NotEqual(t, 0, res.GasConsumed)
}, },
}, },
{ {
@ -481,11 +473,9 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`, params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`,
result: func(e *executor) interface{} { return &SendTXResponse{} }, result: func(e *executor) interface{} {
check: func(t *testing.T, e *executor, result interface{}) { v := true
res, ok := result.(*SendTXResponse) return &v
require.True(t, ok)
assert.True(t, res.Result)
}, },
}, },
{ {
@ -513,25 +503,21 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`, params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`,
result: func(*executor) interface{} { return &ValidateAddrResponse{} }, result: func(*executor) interface{} { return &result.ValidateAddress{} },
check: func(t *testing.T, e *executor, result interface{}) { check: func(t *testing.T, e *executor, va interface{}) {
res, ok := result.(*ValidateAddrResponse) res, ok := va.(*result.ValidateAddress)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address) assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Address)
assert.True(t, res.Result.IsValid) assert.True(t, res.IsValid)
}, },
}, },
{ {
name: "negative", name: "negative",
params: "[1]", params: "[1]",
result: func(*executor) interface{} { result: func(*executor) interface{} {
return &ValidateAddrResponse{ return &result.ValidateAddress{
Jsonrpc: defaultJSONRPC,
Result: wrappers.ValidateAddressResponse{
Address: float64(1), Address: float64(1),
IsValid: false, IsValid: false,
},
ID: defaultID,
} }
}, },
}, },
@ -551,14 +537,14 @@ func TestRPC(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, 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 { if tc.fail {
return return
} }
expected, res := tc.getResultPair(e) expected, res := tc.getResultPair(e)
err := json.Unmarshal(body, res) err := json.Unmarshal(result, res)
require.NoErrorf(t, err, "could not parse response: %s", body) require.NoErrorf(t, err, "could not parse response: %s", result)
if tc.check == nil { if tc.check == nil {
assert.Equal(t, expected, res) assert.Equal(t, expected, res)
@ -575,11 +561,11 @@ func TestRPC(t *testing.T) {
TXHash := block.Transactions[1].Hash() TXHash := block.Transactions[1].Hash()
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE()) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE())
body := doRPCCall(rpc, handler, t) body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false) result := checkErrGetResult(t, body, false)
var res StringResultResponse var res string
err := json.Unmarshal(body, &res) err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", body) require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res)
}) })
t.Run("getrawtransaction 2 arguments", func(t *testing.T) { t.Run("getrawtransaction 2 arguments", func(t *testing.T) {
@ -587,11 +573,11 @@ func TestRPC(t *testing.T) {
TXHash := block.Transactions[1].Hash() TXHash := block.Transactions[1].Hash()
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE()) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE())
body := doRPCCall(rpc, handler, t) body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false) result := checkErrGetResult(t, body, false)
var res StringResultResponse var res string
err := json.Unmarshal(body, &res) err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", body) require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res)
}) })
t.Run("gettxout", func(t *testing.T) { 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]}"`, rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "gettxout", "params": [%s, %d]}"`,
`"`+tx.Hash().StringLE()+`"`, 0) `"`+tx.Hash().StringLE()+`"`, 0)
body := doRPCCall(rpc, handler, t) body := doRPCCall(rpc, handler, t)
checkErrResponse(t, body, false) res := checkErrGetResult(t, body, false)
var result GetTxOutResponse var txOut result.TransactionOutput
err := json.Unmarshal(body, &result) err := json.Unmarshal(res, &txOut)
require.NoErrorf(t, err, "could not parse response: %s", body) require.NoErrorf(t, err, "could not parse response: %s", res)
assert.Equal(t, 0, result.Result.N) assert.Equal(t, 0, txOut.N)
assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", result.Result.Asset) assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset)
assert.Equal(t, util.Fixed8FromInt64(100000000), result.Result.Value) assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value)
assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", result.Result.Address) assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address)
}) })
} }
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) { func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
expected = tc.result(e) 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()) resVal := reflect.New(reflect.TypeOf(expected).Elem())
res = resVal.Interface() return expected, resVal.Interface()
} }
return func checkErrGetResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage {
} var resp response.Raw
err := json.Unmarshal(body, &resp)
func checkErrResponse(t *testing.T, body []byte, expectingFail bool) {
var errresp ErrorResponse
err := json.Unmarshal(body, &errresp)
require.Nil(t, err) require.Nil(t, err)
if expectingFail { if expectingFail {
assert.NotEqual(t, 0, errresp.Error.Code) assert.NotEqual(t, 0, resp.Error.Code)
assert.NotEqual(t, "", errresp.Error.Message) assert.NotEqual(t, "", resp.Error.Message)
} else { } else {
assert.Equal(t, 0, errresp.Error.Code) assert.Nil(t, resp.Error)
assert.Equal(t, "", errresp.Error.Message)
} }
return resp.Result
} }
func doRPCCall(rpcCall string, handler http.HandlerFunc, t *testing.T) []byte { 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 return Hash160Type, nil
case "hash256": case "hash256":
return Hash256Type, nil return Hash256Type, nil
case "bytes": case "bytes", "bytearray":
return ByteArrayType, nil return ByteArrayType, nil
case "key": case "key":
return PublicKeyType, nil return PublicKeyType, nil