diff --git a/cli/server/server.go b/cli/server/server.go index a8af9571a..f2bc41edd 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/network/metrics" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/server" "github.com/pkg/errors" "github.com/urfave/cli" "go.uber.org/zap" @@ -341,18 +341,18 @@ func startServer(ctx *cli.Context) error { return err } - server, err := network.NewServer(serverConfig, chain, log) + serv, err := network.NewServer(serverConfig, chain, log) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create network server: %v", err), 1) } - rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPC, server, log) + rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, serv, log) errChan := make(chan error) - go server.Start(errChan) + go serv.Start(errChan) go rpcServer.Start(errChan) fmt.Println(logo()) - fmt.Println(server.UserAgent) + fmt.Println(serv.UserAgent) fmt.Println() var shutdownErr error @@ -364,7 +364,7 @@ Main: cancel() case <-grace.Done(): - server.Shutdown() + serv.Shutdown() if serverErr := rpcServer.Shutdown(); serverErr != nil { shutdownErr = errors.Wrap(serverErr, "Error encountered whilst shutting down server") } diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 38e1d0901..31e8c09cf 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -14,7 +14,9 @@ import ( "github.com/CityOfZion/neo-go/pkg/compiler" "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/client" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" @@ -294,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error { // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. if !ctx.Bool("skip-details") { details := parseContractDetails() - details.ReturnType = rpc.ByteArray - details.Parameters = make([]rpc.StackParamType, 2) - details.Parameters[0] = rpc.String - details.Parameters[1] = rpc.Array + details.ReturnType = request.ByteArray + details.Parameters = make([]request.StackParamType, 2) + details.Parameters[0] = request.String + details.Parameters[1] = request.Array project := &ProjectConfig{Contract: details} b, err := yaml.Marshal(project) @@ -362,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { operation string params = make([]smartcontract.Parameter, 0) paramsStart = 1 - resp *rpc.InvokeScriptResponse + resp *response.InvokeResult wif *keys.WIF ) @@ -398,34 +400,34 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { return err } } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } if withMethod { - resp, err = client.InvokeFunction(script, operation, params) + resp, err = c.InvokeFunction(script, operation, params) } else { - resp, err = client.Invoke(script, params) + resp, err = c.Invoke(script, params) } if err != nil { return cli.NewExitError(err, 1) } if signAndPush { - if len(resp.Result.Script) == 0 { + if len(resp.Script) == 0 { return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) } - script, err := hex.DecodeString(resp.Result.Script) + script, err := hex.DecodeString(resp.Script) if err != nil { return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1) } - txHash, err := client.SignAndPushInvocationTx(script, wif, gas) + txHash, err := c.SignAndPushInvocationTx(script, wif, gas) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE()) } else { - b, err := json.MarshalIndent(resp.Result, "", " ") + b, err := json.MarshalIndent(resp, "", " ") if err != nil { return cli.NewExitError(err, 1) } @@ -451,18 +453,18 @@ func testInvokeScript(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } scriptHex := hex.EncodeToString(b) - resp, err := client.InvokeScript(scriptHex) + resp, err := c.InvokeScript(scriptHex) if err != nil { return cli.NewExitError(err, 1) } - b, err = json.MarshalIndent(resp.Result, "", " ") + b, err = json.MarshalIndent(resp, "", " ") if err != nil { return cli.NewExitError(err, 1) } @@ -475,11 +477,11 @@ func testInvokeScript(ctx *cli.Context) error { // ProjectConfig contains project metadata. type ProjectConfig struct { Version uint - Contract rpc.ContractDetails `yaml:"project"` + Contract request.ContractDetails `yaml:"project"` } -func parseContractDetails() rpc.ContractDetails { - details := rpc.ContractDetails{} +func parseContractDetails() request.ContractDetails { + details := request.ContractDetails{} reader := bufio.NewReader(os.Stdin) fmt.Print("Author: ") @@ -571,17 +573,17 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("bad config: %v", err), 1) } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } - txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract) + txScript, err := request.CreateDeploymentScript(avm, &conf.Contract) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) } - txHash, err := client.SignAndPushInvocationTx(txScript, wif, gas) + txHash, err := c.SignAndPushInvocationTx(txScript, wif, gas) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } diff --git a/integration/performance_test.go b/integration/performance_test.go index 42c5e2011..f3b63be2f 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -11,7 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/network" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -55,7 +55,7 @@ func prepareData(t *testing.B) []*transaction.Transaction { for n := 0; n < t.N; n++ { tx := getTX(t, wif) - require.NoError(t, rpc.SignTx(tx, wif)) + require.NoError(t, request.SignTx(tx, wif)) data = append(data, tx) } return data diff --git a/pkg/network/server.go b/pkg/network/server.go index dacac6461..cfe241d9f 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -190,12 +190,25 @@ func (s *Server) Shutdown() { // UnconnectedPeers returns a list of peers that are in the discovery peer list // but are not connected to the server. func (s *Server) UnconnectedPeers() []string { - return []string{} + return s.discovery.UnconnectedPeers() } // BadPeers returns a list of peers the are flagged as "bad" peers. func (s *Server) BadPeers() []string { - return []string{} + return s.discovery.BadPeers() +} + +// ConnectedPeers returns a list of currently connected peers. +func (s *Server) ConnectedPeers() []string { + s.lock.RLock() + defer s.lock.RUnlock() + + peers := make([]string, 0, len(s.peers)) + for k := range s.peers { + peers = append(peers, k.PeerAddr().String()) + } + + return peers } // run is a goroutine that starts another goroutine to manage protocol specifics diff --git a/pkg/rpc/client.go b/pkg/rpc/client/client.go similarity index 78% rename from pkg/rpc/client.go rename to pkg/rpc/client/client.go index e8a75bc81..8f9298e70 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client/client.go @@ -1,4 +1,4 @@ -package rpc +package client import ( "bytes" @@ -14,6 +14,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -37,13 +39,13 @@ type Client struct { wifMu *sync.Mutex wif *keys.WIF balancerMu *sync.Mutex - balancer BalanceGetter + balancer request.BalanceGetter } -// ClientOptions defines options for the RPC client. +// Options defines options for the RPC client. // All Values are optional. If any duration is not specified // a default of 3 seconds will be used. -type ClientOptions struct { +type Options struct { Cert string Key string CACert string @@ -55,8 +57,8 @@ type ClientOptions struct { Version string } -// NewClient returns a new Client ready to use. -func NewClient(ctx context.Context, endpoint string, opts ClientOptions) (*Client, error) { +// New returns a new Client ready to use. +func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { url, err := url.Parse(endpoint) if err != nil { return nil, err @@ -121,14 +123,14 @@ func (c *Client) SetWIF(wif string) error { } // Balancer is a getter for balance field. -func (c *Client) Balancer() BalanceGetter { +func (c *Client) Balancer() request.BalanceGetter { c.balancerMu.Lock() defer c.balancerMu.Unlock() return c.balancer } // SetBalancer is a setter for balance field. -func (c *Client) SetBalancer(b BalanceGetter) { +func (c *Client) SetBalancer(b request.BalanceGetter) { c.balancerMu.Lock() defer c.balancerMu.Unlock() @@ -161,13 +163,10 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F var utxos state.UnspentBalances resp, err := c.GetUnspents(address) - if err != nil || resp.Error != nil { - if err == nil { - err = fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message) - } + if err != nil { return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address) } - for _, ubi := range resp.Result.Balance { + for _, ubi := range resp.Balance { if asset.Equals(ubi.AssetHash) { utxos = ubi.Unspents break @@ -177,15 +176,16 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F } -func (c *Client) performRequest(method string, p params, v interface{}) error { +func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error { var ( - r = request{ - JSONRPC: c.version, - Method: method, - Params: p.values, - ID: 1, + r = request.Raw{ + JSONRPC: c.version, + Method: method, + RawParams: p.Values, + ID: 1, } buf = new(bytes.Buffer) + raw = &response.Raw{} ) if err := json.NewEncoder(buf).Encode(r); err != nil { @@ -202,11 +202,22 @@ func (c *Client) performRequest(method string, p params, v interface{}) error { } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("remote responded with a non 200 response: %d", resp.StatusCode) + // The node might send us proper JSON anyway, so look there first and if + // it parses, then it has more relevant data than HTTP error code. + err = json.NewDecoder(resp.Body).Decode(raw) + if err == nil { + if raw.Error != nil { + err = raw.Error + } else { + err = json.Unmarshal(raw.Result, v) + } + } else if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("HTTP %d/%s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } else { + err = errors.Wrap(err, "JSON decoding") } - return json.NewDecoder(resp.Body).Decode(v) + return err } // Ping attempts to create a connection to the endpoint. diff --git a/pkg/rpc/client/doc.go b/pkg/rpc/client/doc.go new file mode 100644 index 000000000..8c82d20c9 --- /dev/null +++ b/pkg/rpc/client/doc.go @@ -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 diff --git a/pkg/rpc/neoScanBalanceGetter.go b/pkg/rpc/client/neoscan.go similarity index 77% rename from pkg/rpc/neoScanBalanceGetter.go rename to pkg/rpc/client/neoscan.go index c0e95c595..76d65bcfc 100644 --- a/pkg/rpc/neoScanBalanceGetter.go +++ b/pkg/rpc/client/neoscan.go @@ -1,4 +1,4 @@ -package rpc +package client import ( "encoding/json" @@ -8,11 +8,38 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" errs "github.com/pkg/errors" ) +/* + Definition of types, helper functions and variables + required for calculation of transaction inputs using + NeoScan API. +*/ + +type ( + // NeoScanServer stores NEOSCAN URL and API path. + NeoScanServer struct { + URL string // "protocol://host:port/" + Path string // path to API endpoint without wallet address + } + + // Unspent stores Unspents per asset + Unspent struct { + Unspent state.UnspentBalances + Asset string // "NEO" / "GAS" + Amount util.Fixed8 // total unspent of this asset + } + + // NeoScanBalance is a struct of NeoScan response to 'get_balance' request + NeoScanBalance struct { + Balance []*Unspent + Address string + } +) + // GetBalance performs a request to get balance for the address specified. func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) { var ( @@ -57,7 +84,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256, err error us []*Unspent assetUnspent Unspent - assetID = wrappers.GlobalAssets[assetIDUint.StringLE()] + assetID = result.GlobalAssets[assetIDUint.StringLE()] ) if us, err = s.GetBalance(address); err != nil { return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address) diff --git a/pkg/rpc/rpc.go b/pkg/rpc/client/rpc.go similarity index 66% rename from pkg/rpc/rpc.go rename to pkg/rpc/client/rpc.go index 8ba1af6c5..44f69e419 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -1,12 +1,14 @@ -package rpc +package client import ( "encoding/hex" - "fmt" "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -17,11 +19,11 @@ import ( // missing output wrapper at the moment, thus commented out // func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) { // var ( -// params = newParams(indexOrHash) +// params = request.NewRawParams(indexOrHash) // resp = &response{} // ) // if verbose { -// params = newParams(indexOrHash, 1) +// params = request.NewRawParams(indexOrHash, 1) // } // if err := c.performRequest("getblock", params, resp); err != nil { // return nil, err @@ -30,10 +32,10 @@ import ( // } // GetAccountState returns detailed information about a NEO account. -func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) { +func (c *Client) GetAccountState(address string) (*result.AccountState, error) { var ( - params = newParams(address) - resp = &AccountStateResponse{} + params = request.NewRawParams(address) + resp = &result.AccountState{} ) if err := c.performRequest("getaccountstate", params, resp); err != nil { return nil, err @@ -42,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) } // GetUnspents returns UTXOs for the given NEO account. -func (c *Client) GetUnspents(address string) (*UnspentResponse, error) { +func (c *Client) GetUnspents(address string) (*result.Unspents, error) { var ( - params = newParams(address) - resp = &UnspentResponse{} + params = request.NewRawParams(address) + resp = &result.Unspents{} ) if err := c.performRequest("getunspents", params, resp); err != nil { return nil, err @@ -55,10 +57,10 @@ func (c *Client) GetUnspents(address string) (*UnspentResponse, error) { // InvokeScript returns the result of the given script after running it true the VM. // NOTE: This is a test invoke and will not affect the blockchain. -func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) { +func (c *Client) InvokeScript(script string) (*response.InvokeResult, error) { var ( - params = newParams(script) - resp = &InvokeScriptResponse{} + params = request.NewRawParams(script) + resp = &response.InvokeResult{} ) if err := c.performRequest("invokescript", params, resp); err != nil { return nil, err @@ -69,10 +71,10 @@ func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) { // InvokeFunction returns the results after calling the smart contract scripthash // with the given operation and parameters. // NOTE: this is test invoke and will not affect the blockchain. -func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeResult, error) { var ( - p = newParams(script, operation, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, operation, params) + resp = &response.InvokeResult{} ) if err := c.performRequest("invokefunction", p, resp); err != nil { return nil, err @@ -82,10 +84,10 @@ func (c *Client) InvokeFunction(script, operation string, params []smartcontract // Invoke returns the results after calling the smart contract scripthash // with the given parameters. -func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeResult, error) { var ( - p = newParams(script, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, params) + resp = &response.InvokeResult{} ) if err := c.performRequest("invoke", p, resp); err != nil { return nil, err @@ -97,7 +99,7 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // missing output wrapper at the moment, thus commented out // func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) { // var ( -// params = newParams(hash, verbose) +// params = request.NewRawParams(hash, verbose) // resp = &response{} // ) // if err := c.performRequest("getrawtransaction", params, resp); err != nil { @@ -110,48 +112,44 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // The given hex string needs to be signed with a keypair. // When the result of the response object is true, the TX has successfully // been broadcasted to the network. -func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, error) { +func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) error { var ( - params = newParams(hex.EncodeToString(rawTX.Bytes())) - resp = &response{} + params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes())) + resp bool ) - if err := c.performRequest("sendrawtransaction", params, resp); err != nil { - return nil, err + if err := c.performRequest("sendrawtransaction", params, &resp); err != nil { + return err } - return resp, nil + if !resp { + return errors.New("sendrawtransaction returned false") + } + return nil } // SendToAddress sends an amount of specific asset to a given address. // This call requires open wallet. (`wif` key in client struct.) // If response.Result is `true` then transaction was formed correctly and was written in blockchain. -func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*SendToAddressResponse, error) { +func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (util.Uint256, error) { var ( err error rawTx *transaction.Transaction - txParams = ContractTxParams{ - assetID: asset, - address: address, - value: amount, - wif: c.WIF(), - balancer: c.Balancer(), + txParams = request.ContractTxParams{ + AssetID: asset, + Address: address, + Value: amount, + WIF: c.WIF(), + Balancer: c.Balancer(), } - resp *response - response = &SendToAddressResponse{} + resp util.Uint256 ) - if rawTx, err = CreateRawContractTransaction(txParams); err != nil { - return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") + if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil { + return resp, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } - if resp, err = c.sendRawTransaction(rawTx); err != nil { - return nil, errors.Wrap(err, "failed to send raw transaction") + if err = c.sendRawTransaction(rawTx); err != nil { + return resp, errors.Wrap(err, "failed to send raw transaction") } - response.Error = resp.Error - response.ID = resp.ID - response.JSONRPC = resp.JSONRPC - response.Result = &TxResponse{ - TxID: rawTx.Hash().StringLE(), - } - return response, nil + return rawTx.Hash(), nil } // SignAndPushInvocationTx signs and pushes given script as an invocation @@ -166,22 +164,19 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util. fromAddress := wif.PrivateKey.Address() if gas > 0 { - if err = AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil { + if err = request.AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil { return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") } } - if err = SignTx(tx, wif); err != nil { + if err = request.SignTx(tx, wif); err != nil { return txHash, errors.Wrap(err, "failed to sign tx") } txHash = tx.Hash() - resp, err := c.sendRawTransaction(tx) + err = c.sendRawTransaction(tx) if err != nil { return txHash, errors.Wrap(err, "failed sendning tx") } - if resp.Error != nil { - return txHash, fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message) - } return txHash, nil } diff --git a/pkg/rpc/doc.go b/pkg/rpc/doc.go deleted file mode 100644 index 5ce0884ef..000000000 --- a/pkg/rpc/doc.go +++ /dev/null @@ -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 diff --git a/pkg/rpc/neoScanTypes.go b/pkg/rpc/neoScanTypes.go deleted file mode 100644 index beb1f8fe1..000000000 --- a/pkg/rpc/neoScanTypes.go +++ /dev/null @@ -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 - } -) diff --git a/pkg/rpc/request.go b/pkg/rpc/request.go deleted file mode 100644 index 5a3c21b9a..000000000 --- a/pkg/rpc/request.go +++ /dev/null @@ -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, ¶ms) - if err != nil { - return nil, errors.Errorf("error parsing params field in payload: %s", err) - } - - return ¶ms, 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)) - } -} diff --git a/pkg/rpc/param.go b/pkg/rpc/request/param.go similarity index 94% rename from pkg/rpc/param.go rename to pkg/rpc/request/param.go index 1dc57eb07..e9f453175 100644 --- a/pkg/rpc/param.go +++ b/pkg/rpc/request/param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "bytes" @@ -29,12 +29,13 @@ type ( } ) +// These are parameter types accepted by RPC server. const ( defaultT paramType = iota - stringT - numberT - arrayT - funcParamT + StringT + NumberT + ArrayT + FuncParamT ) func (p Param) String() string { @@ -123,7 +124,7 @@ func (p Param) GetBytesHex() ([]byte, error) { func (p *Param) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err == nil { - p.Type = stringT + p.Type = StringT p.Value = s return nil @@ -131,7 +132,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var num float64 if err := json.Unmarshal(data, &num); err == nil { - p.Type = numberT + p.Type = NumberT p.Value = int(num) return nil @@ -142,7 +143,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { jd.DisallowUnknownFields() var fp FuncParam if err := jd.Decode(&fp); err == nil { - p.Type = funcParamT + p.Type = FuncParamT p.Value = fp return nil @@ -150,7 +151,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var ps []Param if err := json.Unmarshal(data, &ps); err == nil { - p.Type = arrayT + p.Type = ArrayT p.Value = ps return nil diff --git a/pkg/rpc/param_test.go b/pkg/rpc/request/param_test.go similarity index 76% rename from pkg/rpc/param_test.go rename to pkg/rpc/request/param_test.go index 58cc9b879..127fab9ad 100644 --- a/pkg/rpc/param_test.go +++ b/pkg/rpc/request/param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/hex" @@ -15,35 +15,35 @@ func TestParam_UnmarshalJSON(t *testing.T) { msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]` expected := Params{ { - Type: stringT, + Type: StringT, Value: "str1", }, { - Type: numberT, + Type: NumberT, Value: 123, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: stringT, + Type: StringT, Value: "str2", }, { - Type: numberT, + Type: NumberT, Value: 3, }, }, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: funcParamT, + Type: FuncParamT, Value: FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, }, @@ -61,34 +61,34 @@ func TestParam_UnmarshalJSON(t *testing.T) { } func TestParamGetString(t *testing.T) { - p := Param{stringT, "jajaja"} + p := Param{StringT, "jajaja"} str, err := p.GetString() assert.Equal(t, "jajaja", str) require.Nil(t, err) - p = Param{stringT, int(100500)} + p = Param{StringT, int(100500)} _, err = p.GetString() require.NotNil(t, err) } func TestParamGetInt(t *testing.T) { - p := Param{numberT, int(100500)} + p := Param{NumberT, int(100500)} i, err := p.GetInt() assert.Equal(t, 100500, i) require.Nil(t, err) - p = Param{numberT, "jajaja"} + p = Param{NumberT, "jajaja"} _, err = p.GetInt() require.NotNil(t, err) } func TestParamGetArray(t *testing.T) { - p := Param{arrayT, []Param{{numberT, 42}}} + p := Param{ArrayT, []Param{{NumberT, 42}}} a, err := p.GetArray() - assert.Equal(t, []Param{{numberT, 42}}, a) + assert.Equal(t, []Param{{NumberT, 42}}, a) require.Nil(t, err) - p = Param{arrayT, 42} + p = Param{ArrayT, 42} _, err = p.GetArray() require.NotNil(t, err) } @@ -96,16 +96,16 @@ func TestParamGetArray(t *testing.T) { func TestParamGetUint256(t *testing.T) { gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" u256, _ := util.Uint256DecodeStringLE(gas) - p := Param{stringT, gas} + p := Param{StringT, gas} u, err := p.GetUint256() assert.Equal(t, u256, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint256() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetUint256() require.NotNil(t, err) } @@ -113,16 +113,16 @@ func TestParamGetUint256(t *testing.T) { func TestParamGetUint160FromHex(t *testing.T) { in := "50befd26fdf6e4d957c11e078b24ebce6291456f" u160, _ := util.Uint160DecodeStringLE(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromHex() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromHex() require.NotNil(t, err) - p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} + p = Param{StringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} _, err = p.GetUint160FromHex() require.NotNil(t, err) } @@ -130,16 +130,16 @@ func TestParamGetUint160FromHex(t *testing.T) { func TestParamGetUint160FromAddress(t *testing.T) { in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y" u160, _ := address.StringToUint160(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromAddress() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromAddress() require.NotNil(t, err) - p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} + p = Param{StringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} _, err = p.GetUint160FromAddress() require.NotNil(t, err) } @@ -148,19 +148,19 @@ func TestParamGetFuncParam(t *testing.T) { fp := FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, } p := Param{ - Type: funcParamT, + Type: FuncParamT, Value: fp, } newfp, err := p.GetFuncParam() assert.Equal(t, fp, newfp) require.Nil(t, err) - p = Param{funcParamT, 42} + p = Param{FuncParamT, 42} _, err = p.GetFuncParam() require.NotNil(t, err) } @@ -168,16 +168,16 @@ func TestParamGetFuncParam(t *testing.T) { func TestParamGetBytesHex(t *testing.T) { in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" inb, _ := hex.DecodeString(in) - p := Param{stringT, in} + p := Param{StringT, in} bh, err := p.GetBytesHex() assert.Equal(t, inb, bh) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetBytesHex() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetBytesHex() require.NotNil(t, err) } diff --git a/pkg/rpc/params.go b/pkg/rpc/request/params.go similarity index 97% rename from pkg/rpc/params.go rename to pkg/rpc/request/params.go index 1affcf267..8b1945cb1 100644 --- a/pkg/rpc/params.go +++ b/pkg/rpc/request/params.go @@ -1,4 +1,4 @@ -package rpc +package request type ( // Params represents the JSON-RPC params. diff --git a/pkg/rpc/scdetails.go b/pkg/rpc/request/scdetails.go similarity index 96% rename from pkg/rpc/scdetails.go rename to pkg/rpc/request/scdetails.go index 5988e3436..f6f910aff 100644 --- a/pkg/rpc/scdetails.go +++ b/pkg/rpc/request/scdetails.go @@ -1,4 +1,4 @@ -package rpc +package request // ContractDetails contains contract metadata. type ContractDetails struct { diff --git a/pkg/rpc/stack_param.go b/pkg/rpc/request/stack_param.go similarity index 99% rename from pkg/rpc/stack_param.go rename to pkg/rpc/request/stack_param.go index 6c9a7b5e2..192a2f0ef 100644 --- a/pkg/rpc/stack_param.go +++ b/pkg/rpc/request/stack_param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/binary" diff --git a/pkg/rpc/stack_param_test.go b/pkg/rpc/request/stack_param_test.go similarity index 99% rename from pkg/rpc/stack_param_test.go rename to pkg/rpc/request/stack_param_test.go index dfd289e19..2d2029cff 100644 --- a/pkg/rpc/stack_param_test.go +++ b/pkg/rpc/request/stack_param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/json" diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/request/txBuilder.go similarity index 97% rename from pkg/rpc/txBuilder.go rename to pkg/rpc/request/txBuilder.go index 349343bc6..df14e84c2 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "errors" @@ -25,7 +25,7 @@ func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transac fromAddress string receiverOutput *transaction.Output - wif, assetID, toAddress, amount, balancer = params.wif, params.assetID, params.address, params.value, params.balancer + wif, assetID, toAddress, amount, balancer = params.WIF, params.AssetID, params.Address, params.Value, params.Balancer ) fromAddress = wif.PrivateKey.Address() @@ -214,15 +214,15 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt script := io.NewBufBinWriter() for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { - case stringT: + case StringT: emit.String(script.BinWriter, params[i].String()) - case numberT: + case NumberT: num, err := params[i].GetInt() if err != nil { return nil, err } emit.String(script.BinWriter, strconv.Itoa(num)) - case arrayT: + case ArrayT: slice, err := params[i].GetArray() if err != nil { return nil, err diff --git a/pkg/rpc/txTypes.go b/pkg/rpc/request/txTypes.go similarity index 88% rename from pkg/rpc/txTypes.go rename to pkg/rpc/request/txTypes.go index 29065d41a..b97809bf0 100644 --- a/pkg/rpc/txTypes.go +++ b/pkg/rpc/request/txTypes.go @@ -1,4 +1,4 @@ -package rpc +package request /* Definition of types, interfaces and variables @@ -16,14 +16,14 @@ type ( // includes parameters duplication `sendtoaddress` RPC call params // and also some utility data; ContractTxParams struct { - assetID util.Uint256 - address string - value util.Fixed8 - wif keys.WIF // a WIF to send the transaction + AssetID util.Uint256 + Address string + Value util.Fixed8 + WIF keys.WIF // a WIF to send the transaction // since there are many ways to provide unspents, // transaction composer stays agnostic to that how // unspents was got; - balancer BalanceGetter + Balancer BalanceGetter } // BalanceGetter is an interface supporting CalculateInputs() method. diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/request/tx_builder_test.go new file mode 100644 index 000000000..a4e29edd5 --- /dev/null +++ b/pkg/rpc/request/tx_builder_test.go @@ -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) + } +} diff --git a/pkg/rpc/request/types.go b/pkg/rpc/request/types.go new file mode 100644 index 000000000..9020d508e --- /dev/null +++ b/pkg/rpc/request/types.go @@ -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, ¶ms) + if err != nil { + return nil, errors.Errorf("error parsing params field in payload: %s", err) + } + + return ¶ms, nil +} diff --git a/pkg/rpc/errors.go b/pkg/rpc/response/errors.go similarity index 67% rename from pkg/rpc/errors.go rename to pkg/rpc/response/errors.go index 966f5f678..a0d468136 100644 --- a/pkg/rpc/errors.go +++ b/pkg/rpc/response/errors.go @@ -1,4 +1,4 @@ -package rpc +package response import ( "fmt" @@ -18,10 +18,13 @@ type ( ) var ( - errInvalidParams = NewInvalidParamsError("", nil) + // ErrInvalidParams represents a generic 'invalid parameters' error. + ErrInvalidParams = NewInvalidParamsError("", nil) ) -func newError(code int64, httpCode int, message string, data string, cause error) *Error { +// NewError is an Error constructor that takes Error contents from its +// parameters. +func NewError(code int64, httpCode int, message string, data string, cause error) *Error { return &Error{ Code: code, HTTPCode: httpCode, @@ -35,40 +38,40 @@ func newError(code int64, httpCode int, message string, data string, cause error // NewParseError creates a new error with code // -32700.:%s func NewParseError(data string, cause error) *Error { - return newError(-32700, http.StatusBadRequest, "Parse Error", data, cause) + return NewError(-32700, http.StatusBadRequest, "Parse Error", data, cause) } // NewInvalidRequestError creates a new error with // code -32600. func NewInvalidRequestError(data string, cause error) *Error { - return newError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) + return NewError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) } // NewMethodNotFoundError creates a new error with // code -32601. func NewMethodNotFoundError(data string, cause error) *Error { - return newError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) + return NewError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) } // NewInvalidParamsError creates a new error with // code -32602. func NewInvalidParamsError(data string, cause error) *Error { - return newError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) + return NewError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) } // NewInternalServerError creates a new error with // code -32603. func NewInternalServerError(data string, cause error) *Error { - return newError(-32603, http.StatusInternalServerError, "Internal error", data, cause) + return NewError(-32603, http.StatusInternalServerError, "Internal error", data, cause) } // NewRPCError creates a new error with // code -100 func NewRPCError(message string, data string, cause error) *Error { - return newError(-100, http.StatusUnprocessableEntity, message, data, cause) + return NewError(-100, http.StatusUnprocessableEntity, message, data, cause) } // Error implements the error interface. -func (e Error) Error() string { +func (e *Error) Error() string { return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause) } diff --git a/pkg/rpc/wrappers/account_state.go b/pkg/rpc/response/result/account_state.go similarity index 98% rename from pkg/rpc/wrappers/account_state.go rename to pkg/rpc/response/result/account_state.go index b56981e38..df1dad2b9 100644 --- a/pkg/rpc/wrappers/account_state.go +++ b/pkg/rpc/response/result/account_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "bytes" diff --git a/pkg/rpc/wrappers/asset_state.go b/pkg/rpc/response/result/asset_state.go similarity index 98% rename from pkg/rpc/wrappers/asset_state.go rename to pkg/rpc/response/result/asset_state.go index 5ee365a97..ffedbb6bb 100644 --- a/pkg/rpc/wrappers/asset_state.go +++ b/pkg/rpc/response/result/asset_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/state" diff --git a/pkg/rpc/wrappers/block.go b/pkg/rpc/response/result/block.go similarity index 99% rename from pkg/rpc/wrappers/block.go rename to pkg/rpc/response/result/block.go index a203437ff..aa8a20377 100644 --- a/pkg/rpc/wrappers/block.go +++ b/pkg/rpc/response/result/block.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "strconv" diff --git a/pkg/rpc/wrappers/contract_state.go b/pkg/rpc/response/result/contract_state.go similarity index 99% rename from pkg/rpc/wrappers/contract_state.go rename to pkg/rpc/response/result/contract_state.go index ca1b11716..08e8a277a 100644 --- a/pkg/rpc/wrappers/contract_state.go +++ b/pkg/rpc/response/result/contract_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/state" diff --git a/pkg/rpc/wrappers/invoke_result.go b/pkg/rpc/response/result/invoke.go similarity index 52% rename from pkg/rpc/wrappers/invoke_result.go rename to pkg/rpc/response/result/invoke.go index 4cc790bf5..f79d4d365 100644 --- a/pkg/rpc/wrappers/invoke_result.go +++ b/pkg/rpc/response/result/invoke.go @@ -1,11 +1,12 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/vm" ) -// InvokeResult is used as a wrapper to represent an invokation result. -type InvokeResult struct { +// Invoke represents code invocation result and is used by several RPC calls +// that invoke functions, scripts and generic bytecode. +type Invoke struct { State string `json:"state"` GasConsumed string `json:"gas_consumed"` Script string `json:"script"` diff --git a/pkg/rpc/response/result/peers.go b/pkg/rpc/response/result/peers.go new file mode 100644 index 000000000..b18acc0e8 --- /dev/null +++ b/pkg/rpc/response/result/peers.go @@ -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) + } +} diff --git a/pkg/rpc/response/result/peers_test.go b/pkg/rpc/response/result/peers_test.go new file mode 100644 index 000000000..6e5a9339e --- /dev/null +++ b/pkg/rpc/response/result/peers_test.go @@ -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) +} diff --git a/pkg/rpc/wrappers/tx_output.go b/pkg/rpc/response/result/tx_output.go similarity index 97% rename from pkg/rpc/wrappers/tx_output.go rename to pkg/rpc/response/result/tx_output.go index e624dff8e..19a6fc66b 100644 --- a/pkg/rpc/wrappers/tx_output.go +++ b/pkg/rpc/response/result/tx_output.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/transaction" diff --git a/pkg/rpc/wrappers/tx_raw_output.go b/pkg/rpc/response/result/tx_raw_output.go similarity index 86% rename from pkg/rpc/wrappers/tx_raw_output.go rename to pkg/rpc/response/result/tx_raw_output.go index cdc2723cb..512514471 100644 --- a/pkg/rpc/wrappers/tx_raw_output.go +++ b/pkg/rpc/response/result/tx_raw_output.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core" @@ -16,9 +16,9 @@ type TransactionOutputRaw struct { Size int `json:"size"` SysFee util.Fixed8 `json:"sys_fee"` NetFee util.Fixed8 `json:"net_fee"` - Blockhash util.Uint256 `json:"blockhash"` - Confirmations int `json:"confirmations"` - Timestamp uint32 `json:"blocktime"` + Blockhash util.Uint256 `json:"blockhash,omitempty"` + Confirmations int `json:"confirmations,omitempty"` + Timestamp uint32 `json:"blocktime,omitempty"` } // NewTransactionOutputRaw returns a new ransactionOutputRaw object. diff --git a/pkg/rpc/wrappers/unspents.go b/pkg/rpc/response/result/unspents.go similarity index 98% rename from pkg/rpc/wrappers/unspents.go rename to pkg/rpc/response/result/unspents.go index 38c14b901..d903f8a30 100644 --- a/pkg/rpc/wrappers/unspents.go +++ b/pkg/rpc/response/result/unspents.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core" diff --git a/pkg/rpc/response/result/validate_address.go b/pkg/rpc/response/result/validate_address.go new file mode 100644 index 000000000..bb4963893 --- /dev/null +++ b/pkg/rpc/response/result/validate_address.go @@ -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"` +} diff --git a/pkg/rpc/result/version.go b/pkg/rpc/response/result/version.go similarity index 100% rename from pkg/rpc/result/version.go rename to pkg/rpc/response/result/version.go diff --git a/pkg/rpc/response/types.go b/pkg/rpc/response/types.go new file mode 100644 index 000000000..6b13cb018 --- /dev/null +++ b/pkg/rpc/response/types.go @@ -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"` +} diff --git a/pkg/rpc/result/peers.go b/pkg/rpc/result/peers.go deleted file mode 100644 index 98442acaa..000000000 --- a/pkg/rpc/result/peers.go +++ /dev/null @@ -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, - ) - } -} diff --git a/pkg/rpc/server/doc.go b/pkg/rpc/server/doc.go new file mode 100644 index 000000000..dbf7aae9a --- /dev/null +++ b/pkg/rpc/server/doc.go @@ -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 diff --git a/pkg/rpc/prometheus.go b/pkg/rpc/server/prometheus.go similarity index 99% rename from pkg/rpc/prometheus.go rename to pkg/rpc/server/prometheus.go index 2e5d5b4db..63312433d 100644 --- a/pkg/rpc/prometheus.go +++ b/pkg/rpc/server/prometheus.go @@ -1,4 +1,4 @@ -package rpc +package server import "github.com/prometheus/client_golang/prometheus" diff --git a/pkg/rpc/server.go b/pkg/rpc/server/server.go similarity index 57% rename from pkg/rpc/server.go rename to pkg/rpc/server/server.go index 0ed89cb6e..805c41168 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server/server.go @@ -1,8 +1,9 @@ -package rpc +package server import ( "context" "encoding/hex" + "encoding/json" "fmt" "net/http" "strconv" @@ -11,10 +12,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" - "github.com/CityOfZion/neo-go/pkg/rpc/result" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" "go.uber.org/zap" @@ -35,8 +38,8 @@ var invalidBlockHeightError = func(index int, height int) error { return errors.Errorf("Param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height) } -// NewServer creates a new Server struct. -func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server { +// New creates a new Server struct. +func New(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server { httpServer := &http.Server{ Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10), } @@ -71,13 +74,13 @@ func (s *Server) Shutdown() error { } func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) { - req := NewRequest() + req := request.NewIn() if httpRequest.Method != "POST" { s.WriteErrorResponse( req, w, - NewInvalidParamsError( + response.NewInvalidParamsError( fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil, ), ) @@ -86,20 +89,20 @@ func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request err := req.DecodeData(httpRequest.Body) if err != nil { - s.WriteErrorResponse(req, w, NewParseError("Problem parsing JSON-RPC request body", err)) + s.WriteErrorResponse(req, w, response.NewParseError("Problem parsing JSON-RPC request body", err)) return } reqParams, err := req.Params() if err != nil { - s.WriteErrorResponse(req, w, NewInvalidParamsError("Problem parsing request parameters", err)) + s.WriteErrorResponse(req, w, response.NewInvalidParamsError("Problem parsing request parameters", err)) return } s.methodHandler(w, req, *reqParams) } -func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) { +func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams request.Params) { s.log.Debug("processing rpc request", zap.String("method", req.Method), zap.String("params", fmt.Sprintf("%v", reqParams))) @@ -121,38 +124,38 @@ Methods: param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } switch param.Type { - case stringT: + case request.StringT: var err error hash, err = param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } - case numberT: + case request.NumberT: num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } hash = s.chain.GetHeaderHash(num) default: - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } block, err := s.chain.GetBlock(hash) if err != nil { - resultsErr = NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) + resultsErr = response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) break } if len(reqParams) == 2 && reqParams[1].Value == 1 { - results = wrappers.NewBlock(block, s.chain) + results = result.NewBlock(block, s.chain) } else { writer := io.NewBufBinWriter() block.EncodeBinary(writer.BinWriter) @@ -165,14 +168,14 @@ Methods: case "getblockhash": getblockHashCalled.Inc() - param, ok := reqParams.ValueWithType(0, numberT) + param, ok := reqParams.ValueWithType(0, request.NumberT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } @@ -192,19 +195,10 @@ Methods: case "getpeers": getpeersCalled.Inc() - peers := result.NewPeers() - for _, addr := range s.coreServer.UnconnectedPeers() { - peers.AddPeer("unconnected", addr) - } - - for _, addr := range s.coreServer.BadPeers() { - peers.AddPeer("bad", addr) - } - - for addr := range s.coreServer.Peers() { - peers.AddPeer("connected", addr.PeerAddr().String()) - } - + peers := result.NewGetPeers() + peers.AddUnconnected(s.coreServer.UnconnectedPeers()) + peers.AddConnected(s.coreServer.ConnectedPeers()) + peers.AddBad(s.coreServer.BadPeers()) results = peers case "getstorage": @@ -215,30 +209,30 @@ Methods: validateaddressCalled.Inc() param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } - results = wrappers.ValidateAddress(param.Value) + results = validateAddress(param.Value) case "getassetstate": getassetstateCalled.Inc() - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } paramAssetID, err := param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break } as := s.chain.GetAssetState(paramAssetID) if as != nil { - results = wrappers.NewAssetState(as) + results = result.NewAssetState(as) } else { - resultsErr = NewRPCError("Unknown asset", "", nil) + resultsErr = response.NewRPCError("Unknown asset", "", nil) } case "getaccountstate": @@ -275,7 +269,7 @@ Methods: results, resultsErr = s.sendrawtransaction(reqParams) default: - resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) + resultsErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) } if resultsErr != nil { @@ -286,27 +280,27 @@ Methods: s.WriteResponse(req, w, results) } -func (s *Server) getStorage(ps Params) (interface{}, error) { +func (s *Server) getStorage(ps request.Params) (interface{}, error) { param, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := param.GetUint160FromHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash = scriptHash.Reverse() param, ok = ps.Value(1) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } key, err := param.GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } item := s.chain.GetStorageItem(scriptHash.Reverse(), key) @@ -317,22 +311,22 @@ func (s *Server) getStorage(ps Params) (interface{}, error) { return hex.EncodeToString(item.Value), nil } -func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if param0, ok := reqParams.Value(0); !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if txHash, err := param0.GetUint256(); err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams } else if tx, height, err := s.chain.GetTransaction(txHash); err != nil { err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash) - return nil, NewRPCError("Unknown transaction", err.Error(), err) + return nil, response.NewRPCError("Unknown transaction", err.Error(), err) } else if len(reqParams) >= 2 { _header := s.chain.GetHeaderHash(int(height)) header, err := s.chain.GetHeader(_header) if err != nil { - resultsErr = NewInvalidParamsError(err.Error(), err) + resultsErr = response.NewInvalidParamsError(err.Error(), err) } param1, _ := reqParams.Value(1) @@ -342,10 +336,10 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" { results = hex.EncodeToString(tx.Bytes()) } else { - results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) + results = result.NewTransactionOutputRaw(tx, header, s.chain) } default: - results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) + results = result.NewTransactionOutputRaw(tx, header, s.chain) } } else { results = hex.EncodeToString(tx.Bytes()) @@ -354,70 +348,70 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { return results, resultsErr } -func (s *Server) getTxOut(ps Params) (interface{}, error) { +func (s *Server) getTxOut(ps request.Params) (interface{}, error) { p, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } h, err := p.GetUint256() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } - p, ok = ps.ValueWithType(1, numberT) + p, ok = ps.ValueWithType(1, request.NumberT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } num, err := p.GetInt() if err != nil || num < 0 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } tx, _, err := s.chain.GetTransaction(h) if err != nil { - return nil, NewInvalidParamsError(err.Error(), err) + return nil, response.NewInvalidParamsError(err.Error(), err) } if num >= len(tx.Outputs) { - return nil, NewInvalidParamsError("invalid index", errors.New("too big index")) + return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index")) } out := tx.Outputs[num] - return wrappers.NewTxOutput(&out), nil + return result.NewTxOutput(&out), nil } // getContractState returns contract state (contract information, according to the contract script hash). -func (s *Server) getContractState(reqParams Params) (interface{}, error) { +func (s *Server) getContractState(reqParams request.Params) (interface{}, error) { var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { cs := s.chain.GetContractState(scriptHash) if cs != nil { - results = wrappers.NewContractState(cs) + results = result.NewContractState(cs) } else { - return nil, NewRPCError("Unknown contract", "", nil) + return nil, response.NewRPCError("Unknown contract", "", nil) } } return results, nil } // getAccountState returns account state either in short or full (unspents included) form. -func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) { +func (s *Server) getAccountState(reqParams request.Params, unspents bool) (interface{}, error) { var resultsErr error var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromAddress(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { as := s.chain.GetAccountState(scriptHash) if as == nil { @@ -426,35 +420,35 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, if unspents { str, err := param.GetString() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } - results = wrappers.NewUnspents(as, s.chain, str) + results = result.NewUnspents(as, s.chain, str) } else { - results = wrappers.NewAccountState(as) + results = result.NewAccountState(as) } } return results, resultsErr } // invoke implements the `invoke` RPC call. -func (s *Server) invoke(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invoke(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { return nil, err } - sliceP, ok := reqParams.ValueWithType(1, arrayT) + sliceP, ok := reqParams.ValueWithType(1, request.ArrayT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } slice, err := sliceP.GetArray() if err != nil { return nil, err } - script, err := CreateInvocationScript(scriptHash, slice) + script, err := request.CreateInvocationScript(scriptHash, slice) if err != nil { return nil, err } @@ -462,16 +456,16 @@ func (s *Server) invoke(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { return nil, err } - script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:]) + script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:]) if err != nil { return nil, err } @@ -479,14 +473,14 @@ func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokescript(reqParams Params) (interface{}, error) { +func (s *Server) invokescript(reqParams request.Params) (interface{}, error) { if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } script, err := reqParams[0].GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } return s.runScriptInVM(script), nil @@ -494,12 +488,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) { // runScriptInVM runs given script in a new test VM and returns the invocation // result. -func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult { +func (s *Server) runScriptInVM(script []byte) *result.Invoke { vm, _ := s.chain.GetTestVM() vm.SetGasLimit(s.config.MaxGasInvoke) vm.LoadScript(script) _ = vm.Run() - result := &wrappers.InvokeResult{ + result := &result.Invoke{ State: vm.State(), GasConsumed: vm.GasConsumed().String(), Script: hex.EncodeToString(script), @@ -508,14 +502,14 @@ func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult { return result } -func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if byteTx, err := reqParams[0].GetBytesHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { r := io.NewBinReaderFromBuf(byteTx) tx := &transaction.Transaction{} @@ -542,14 +536,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { } } if err != nil { - resultsErr = NewInternalServerError(err.Error(), err) + resultsErr = response.NewInternalServerError(err.Error(), err) } } return results, resultsErr } -func (s Server) blockHeightFromParam(param *Param) (int, error) { +func (s *Server) blockHeightFromParam(param *request.Param) (int, error) { num, err := param.GetInt() if err != nil { return 0, nil @@ -560,3 +554,87 @@ func (s Server) blockHeightFromParam(param *Param) (int, error) { } return num, nil } + +// WriteErrorResponse writes an error response to the ResponseWriter. +func (s *Server) WriteErrorResponse(r *request.In, w http.ResponseWriter, err error) { + jsonErr, ok := err.(*response.Error) + if !ok { + jsonErr = response.NewInternalServerError("Internal server error", err) + } + + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + Error: jsonErr, + }, + } + + logFields := []zap.Field{ + zap.Error(jsonErr.Cause), + zap.String("method", r.Method), + } + + params, err := r.Params() + if err == nil { + logFields = append(logFields, zap.Any("params", params)) + } + + s.log.Error("Error encountered with rpc request", logFields...) + + w.WriteHeader(jsonErr.HTTPCode) + s.writeServerResponse(r, w, resp) +} + +// WriteResponse encodes the response and writes it to the ResponseWriter. +func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result interface{}) { + resJSON, err := json.Marshal(result) + if err != nil { + s.log.Error("Error encountered while encoding response", + zap.String("err", err.Error()), + zap.String("method", r.Method)) + return + } + + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + }, + Result: resJSON, + } + + s.writeServerResponse(r, w, resp) +} + +func (s *Server) writeServerResponse(r *request.In, w http.ResponseWriter, resp response.Raw) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if s.config.EnableCORSWorkaround { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") + } + + encoder := json.NewEncoder(w) + err := encoder.Encode(resp) + + if err != nil { + s.log.Error("Error encountered while encoding response", + zap.String("err", err.Error()), + zap.String("method", r.Method)) + } +} + +// validateAddress verifies that the address is a correct NEO address +// see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html +func validateAddress(addr interface{}) result.ValidateAddress { + resp := result.ValidateAddress{Address: addr} + if addr, ok := addr.(string); ok { + _, err := address.StringToUint160(addr) + resp.IsValid = (err == nil) + } + return resp +} diff --git a/pkg/rpc/server/server_helper_test.go b/pkg/rpc/server/server_helper_test.go new file mode 100644 index 000000000..1c24132da --- /dev/null +++ b/pkg/rpc/server/server_helper_test.go @@ -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 +} diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server/server_test.go similarity index 62% rename from pkg/rpc/server_test.go rename to pkg/rpc/server/server_test.go index 71826a559..cccddc12e 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1,4 +1,4 @@ -package rpc +package server import ( "bytes" @@ -14,7 +14,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/response" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,23 +44,23 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, - result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAccountStateResponse) + result: func(e *executor) interface{} { return &result.AccountState{} }, + check: func(t *testing.T, e *executor, acc interface{}) { + res, ok := acc.(*result.AccountState) require.True(t, ok) - assert.Equal(t, 1, len(res.Result.Balances)) - assert.Equal(t, false, res.Result.Frozen) + assert.Equal(t, 1, len(res.Balances)) + assert.Equal(t, false, res.IsFrozen) }, }, { name: "positive null", params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, - result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAccountStateResponse) + result: func(e *executor) interface{} { return &result.AccountState{} }, + check: func(t *testing.T, e *executor, acc interface{}) { + res, ok := acc.(*result.AccountState) require.True(t, ok) - assert.Equal(t, 0, len(res.Result.Balances)) - assert.Equal(t, false, res.Result.Frozen) + assert.Equal(t, 0, len(res.Balances)) + assert.Equal(t, false, res.IsFrozen) }, }, { @@ -77,13 +78,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27"]`, - result: func(e *executor) interface{} { return &GetContractStateResponce{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetContractStateResponce) + result: func(e *executor) interface{} { return &result.ContractState{} }, + check: func(t *testing.T, e *executor, cs interface{}) { + res, ok := cs.(*result.ContractState) require.True(t, ok) - assert.Equal(t, byte(0), res.Result.Version) - assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.Result.ScriptHash) - assert.Equal(t, "0.99", res.Result.CodeVersion) + assert.Equal(t, byte(0), res.Version) + assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.ScriptHash) + assert.Equal(t, "0.99", res.CodeVersion) }, }, { @@ -106,21 +107,17 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "746573746b6579"]`, - result: func(e *executor) interface{} { return &StringResultResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - assert.Equal(t, hex.EncodeToString([]byte("testvalue")), res.Result) + result: func(e *executor) interface{} { + v := hex.EncodeToString([]byte("testvalue")) + return &v }, }, { name: "missing key", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "7465"]`, - result: func(e *executor) interface{} { return &StringResultResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - assert.Equal(t, "", res.Result) + result: func(e *executor) interface{} { + v := "" + return &v }, }, { @@ -148,12 +145,12 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`, - result: func(e *executor) interface{} { return &GetAssetResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAssetResponse) + result: func(e *executor) interface{} { return &result.AssetState{} }, + check: func(t *testing.T, e *executor, as interface{}) { + res, ok := as.(*result.AssetState) require.True(t, ok) - assert.Equal(t, "00", res.Result.Owner) - assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin) + assert.Equal(t, "00", res.Owner) + assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Admin) }, }, { @@ -176,7 +173,8 @@ var rpcTestCases = map[string][]rpcTestCase{ { params: "[]", result: func(e *executor) interface{} { - return "0x" + e.chain.CurrentBlockHash().StringLE() + v := "0x" + e.chain.CurrentBlockHash().StringLE() + return &v }, }, { @@ -220,17 +218,17 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: "[1, 1]", - result: func(e *executor) interface{} { return &GetBlockResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetBlockResponse) + result: func(e *executor) interface{} { return &result.Block{} }, + check: func(t *testing.T, e *executor, blockRes interface{}) { + res, ok := blockRes.(*result.Block) require.True(t, ok) block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) require.NoErrorf(t, err, "could not get block") - assert.Equal(t, block.Hash(), res.Result.Hash) - for i := range res.Result.Tx { - tx := res.Result.Tx[i] + assert.Equal(t, block.Hash(), res.Hash) + for i := range res.Tx { + tx := res.Tx[i] require.Equal(t, transaction.MinerType, tx.Type) miner, ok := block.Transactions[i].Data.(*transaction.MinerTX) @@ -269,22 +267,21 @@ var rpcTestCases = map[string][]rpcTestCase{ "getblockcount": { { params: "[]", - result: func(e *executor) interface{} { return int(e.chain.BlockHeight() + 1) }, + result: func(e *executor) interface{} { + v := int(e.chain.BlockHeight() + 1) + return &v + }, }, }, "getblockhash": { { params: "[1]", - result: func(e *executor) interface{} { return "" }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - - block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) - require.NoErrorf(t, err, "could not get block") - + result: func(e *executor) interface{} { + // We don't have `t` here for proper handling, but + // error here would lead to panic down below. + block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1)) expectedHash := "0x" + block.Hash().StringLE() - assert.Equal(t, expectedHash, res.Result) + return &expectedHash }, }, { @@ -301,25 +298,20 @@ var rpcTestCases = map[string][]rpcTestCase{ "getconnectioncount": { { params: "[]", - result: func(*executor) interface{} { return 0 }, + result: func(*executor) interface{} { + v := 0 + return &v + }, }, }, "getpeers": { { params: "[]", result: func(*executor) interface{} { - return &GetPeersResponse{ - Jsonrpc: defaultJSONRPC, - Result: struct { - Unconnected []int `json:"unconnected"` - Connected []int `json:"connected"` - Bad []int `json:"bad"` - }{ - Unconnected: []int{}, - Connected: []int{}, - Bad: []int{}, - }, - ID: defaultID, + return &result.GetPeers{ + Unconnected: []result.Peer{}, + Connected: []result.Peer{}, + Bad: []result.Peer{}, } }, }, @@ -345,33 +337,33 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, - result: func(e *executor) interface{} { return &GetUnspents{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetUnspents) + result: func(e *executor) interface{} { return &result.Unspents{} }, + check: func(t *testing.T, e *executor, unsp interface{}) { + res, ok := unsp.(*result.Unspents) require.True(t, ok) - require.Equal(t, 1, len(res.Result.Balance)) - assert.Equal(t, 1, len(res.Result.Balance[0].Unspents)) + require.Equal(t, 1, len(res.Balance)) + assert.Equal(t, 1, len(res.Balance[0].Unspents)) }, }, { name: "positive null", params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, - result: func(e *executor) interface{} { return &GetUnspents{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetUnspents) + result: func(e *executor) interface{} { return &result.Unspents{} }, + check: func(t *testing.T, e *executor, unsp interface{}) { + res, ok := unsp.(*result.Unspents) require.True(t, ok) - require.Equal(t, 0, len(res.Result.Balance)) + require.Equal(t, 0, len(res.Balance)) }, }, }, "getversion": { { params: "[]", - result: func(*executor) interface{} { return &GetVersionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - resp, ok := result.(*GetVersionResponse) + result: func(*executor) interface{} { return &result.Version{} }, + check: func(t *testing.T, e *executor, ver interface{}) { + resp, ok := ver.(*result.Version) require.True(t, ok) - require.Equal(t, "/NEO-GO:/", resp.Result.UserAgent) + require.Equal(t, "/NEO-GO:/", resp.UserAgent) }, }, }, @@ -379,13 +371,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -418,13 +410,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.NotEqual(t, "", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.NotEqual(t, "", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -452,13 +444,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.NotEqual(t, "", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.NotEqual(t, "", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -481,11 +473,9 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`, - result: func(e *executor) interface{} { return &SendTXResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*SendTXResponse) - require.True(t, ok) - assert.True(t, res.Result) + result: func(e *executor) interface{} { + v := true + return &v }, }, { @@ -513,25 +503,21 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`, - result: func(*executor) interface{} { return &ValidateAddrResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*ValidateAddrResponse) + result: func(*executor) interface{} { return &result.ValidateAddress{} }, + check: func(t *testing.T, e *executor, va interface{}) { + res, ok := va.(*result.ValidateAddress) require.True(t, ok) - assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address) - assert.True(t, res.Result.IsValid) + assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Address) + assert.True(t, res.IsValid) }, }, { name: "negative", params: "[1]", result: func(*executor) interface{} { - return &ValidateAddrResponse{ - Jsonrpc: defaultJSONRPC, - Result: wrappers.ValidateAddressResponse{ - Address: float64(1), - IsValid: false, - }, - ID: defaultID, + return &result.ValidateAddress{ + Address: float64(1), + IsValid: false, } }, }, @@ -551,14 +537,14 @@ func TestRPC(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, t) - checkErrResponse(t, body, tc.fail) + result := checkErrGetResult(t, body, tc.fail) if tc.fail { return } expected, res := tc.getResultPair(e) - err := json.Unmarshal(body, res) - require.NoErrorf(t, err, "could not parse response: %s", body) + err := json.Unmarshal(result, res) + require.NoErrorf(t, err, "could not parse response: %s", result) if tc.check == nil { assert.Equal(t, expected, res) @@ -575,11 +561,11 @@ func TestRPC(t *testing.T) { TXHash := block.Transactions[1].Hash() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE()) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) + result := checkErrGetResult(t, body, false) + var res string + err := json.Unmarshal(result, &res) + require.NoErrorf(t, err, "could not parse response: %s", result) + assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res) }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { @@ -587,11 +573,11 @@ func TestRPC(t *testing.T) { TXHash := block.Transactions[1].Hash() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE()) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) + result := checkErrGetResult(t, body, false) + var res string + err := json.Unmarshal(result, &res) + require.NoErrorf(t, err, "could not parse response: %s", result) + assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res) }) t.Run("gettxout", func(t *testing.T) { @@ -600,54 +586,35 @@ func TestRPC(t *testing.T) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "gettxout", "params": [%s, %d]}"`, `"`+tx.Hash().StringLE()+`"`, 0) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) + res := checkErrGetResult(t, body, false) - var result GetTxOutResponse - err := json.Unmarshal(body, &result) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, 0, result.Result.N) - assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", result.Result.Asset) - assert.Equal(t, util.Fixed8FromInt64(100000000), result.Result.Value) - assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", result.Result.Address) + var txOut result.TransactionOutput + err := json.Unmarshal(res, &txOut) + require.NoErrorf(t, err, "could not parse response: %s", res) + assert.Equal(t, 0, txOut.N) + assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset) + assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value) + assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address) }) } func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) { expected = tc.result(e) - switch exp := expected.(type) { - case string: - res = new(StringResultResponse) - expected = &StringResultResponse{ - Jsonrpc: defaultJSONRPC, - Result: exp, - ID: defaultID, - } - case int: - res = new(IntResultResponse) - expected = &IntResultResponse{ - Jsonrpc: defaultJSONRPC, - Result: exp, - ID: defaultID, - } - default: - resVal := reflect.New(reflect.TypeOf(expected).Elem()) - res = resVal.Interface() - } - - return + resVal := reflect.New(reflect.TypeOf(expected).Elem()) + return expected, resVal.Interface() } -func checkErrResponse(t *testing.T, body []byte, expectingFail bool) { - var errresp ErrorResponse - err := json.Unmarshal(body, &errresp) +func checkErrGetResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage { + var resp response.Raw + err := json.Unmarshal(body, &resp) require.Nil(t, err) if expectingFail { - assert.NotEqual(t, 0, errresp.Error.Code) - assert.NotEqual(t, "", errresp.Error.Message) + assert.NotEqual(t, 0, resp.Error.Code) + assert.NotEqual(t, "", resp.Error.Message) } else { - assert.Equal(t, 0, errresp.Error.Code) - assert.Equal(t, "", errresp.Error.Message) + assert.Nil(t, resp.Error) } + return resp.Result } func doRPCCall(rpcCall string, handler http.HandlerFunc, t *testing.T) []byte { diff --git a/pkg/rpc/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm similarity index 100% rename from pkg/rpc/testdata/test_contract.avm rename to pkg/rpc/server/testdata/test_contract.avm diff --git a/pkg/rpc/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc similarity index 100% rename from pkg/rpc/testdata/testblocks.acc rename to pkg/rpc/server/testdata/testblocks.acc diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go deleted file mode 100644 index 5451d4e1b..000000000 --- a/pkg/rpc/server_helper_test.go +++ /dev/null @@ -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 -} diff --git a/pkg/rpc/tx_builder_test.go b/pkg/rpc/tx_builder_test.go deleted file mode 100644 index 2a705ae8e..000000000 --- a/pkg/rpc/tx_builder_test.go +++ /dev/null @@ -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) - } -} diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go deleted file mode 100644 index 50beee7d5..000000000 --- a/pkg/rpc/types.go +++ /dev/null @@ -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"` -} diff --git a/pkg/rpc/wrappers/validate_address.go b/pkg/rpc/wrappers/validate_address.go deleted file mode 100644 index dfde7b833..000000000 --- a/pkg/rpc/wrappers/validate_address.go +++ /dev/null @@ -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 -} diff --git a/pkg/smartcontract/param_context.go b/pkg/smartcontract/param_context.go index 5e2f6f8ff..a60d13c1e 100644 --- a/pkg/smartcontract/param_context.go +++ b/pkg/smartcontract/param_context.go @@ -137,7 +137,7 @@ func parseParamType(typ string) (ParamType, error) { return Hash160Type, nil case "hash256": return Hash256Type, nil - case "bytes": + case "bytes", "bytearray": return ByteArrayType, nil case "key": return PublicKeyType, nil