forked from TrueCloudLab/neoneo-go
cf39171485
Param getters were redone to return errors because otherwise bad FuncParam values could lead to panic. FuncParam itself might be not the most elegant solution, but it works good enough for now.
433 lines
11 KiB
Go
433 lines
11 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/CityOfZion/neo-go/config"
|
|
"github.com/CityOfZion/neo-go/pkg/core"
|
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
|
"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/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type (
|
|
// Server represents the JSON-RPC 2.0 server.
|
|
Server struct {
|
|
*http.Server
|
|
chain core.Blockchainer
|
|
config config.RPCConfig
|
|
coreServer *network.Server
|
|
}
|
|
)
|
|
|
|
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) Server {
|
|
httpServer := &http.Server{
|
|
Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10),
|
|
}
|
|
|
|
return Server{
|
|
Server: httpServer,
|
|
chain: chain,
|
|
config: conf,
|
|
coreServer: coreServer,
|
|
}
|
|
}
|
|
|
|
// Start creates a new JSON-RPC server
|
|
// listening on the configured port.
|
|
func (s *Server) Start(errChan chan error) {
|
|
if !s.config.Enabled {
|
|
log.Info("RPC server is not enabled")
|
|
return
|
|
}
|
|
s.Handler = http.HandlerFunc(s.requestHandler)
|
|
log.WithFields(log.Fields{
|
|
"endpoint": s.Addr,
|
|
}).Info("starting rpc-server")
|
|
|
|
errChan <- s.ListenAndServe()
|
|
}
|
|
|
|
// Shutdown overrides the http.Server Shutdown
|
|
// method.
|
|
func (s *Server) Shutdown() error {
|
|
log.WithFields(log.Fields{
|
|
"endpoint": s.Addr,
|
|
}).Info("shutting down rpc-server")
|
|
return s.Server.Shutdown(context.Background())
|
|
}
|
|
|
|
func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) {
|
|
req := NewRequest(s.config.EnableCORSWorkaround)
|
|
|
|
if httpRequest.Method != "POST" {
|
|
req.WriteErrorResponse(
|
|
w,
|
|
NewInvalidParamsError(
|
|
fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
err := req.DecodeData(httpRequest.Body)
|
|
if err != nil {
|
|
req.WriteErrorResponse(w, NewParseError("Problem parsing JSON-RPC request body", err))
|
|
return
|
|
}
|
|
|
|
reqParams, err := req.Params()
|
|
if err != nil {
|
|
req.WriteErrorResponse(w, NewInvalidParamsError("Problem parsing request parameters", err))
|
|
return
|
|
}
|
|
|
|
s.methodHandler(w, req, *reqParams)
|
|
}
|
|
|
|
func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) {
|
|
log.WithFields(log.Fields{
|
|
"method": req.Method,
|
|
"params": fmt.Sprintf("%v", reqParams),
|
|
}).Info("processing rpc request")
|
|
|
|
var (
|
|
results interface{}
|
|
resultsErr error
|
|
)
|
|
|
|
Methods:
|
|
switch req.Method {
|
|
case "getbestblockhash":
|
|
getbestblockhashCalled.Inc()
|
|
results = "0x" + s.chain.CurrentBlockHash().ReverseString()
|
|
|
|
case "getblock":
|
|
getbestblockCalled.Inc()
|
|
var hash util.Uint256
|
|
|
|
param, ok := reqParams.Value(0)
|
|
if !ok {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
|
|
switch param.Type {
|
|
case stringT:
|
|
var err error
|
|
hash, err = param.GetUint256()
|
|
if err != nil {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
case numberT:
|
|
num, err := s.blockHeightFromParam(param)
|
|
if err != nil {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
hash = s.chain.GetHeaderHash(num)
|
|
case defaultT:
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
|
|
block, err := s.chain.GetBlock(hash)
|
|
if err != nil {
|
|
resultsErr = NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
|
|
break
|
|
}
|
|
|
|
results = wrappers.NewBlock(block, s.chain)
|
|
case "getblockcount":
|
|
getblockcountCalled.Inc()
|
|
results = s.chain.BlockHeight() + 1
|
|
|
|
case "getblockhash":
|
|
getblockHashCalled.Inc()
|
|
param, ok := reqParams.ValueWithType(0, numberT)
|
|
if !ok {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
num, err := s.blockHeightFromParam(param)
|
|
if err != nil {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
|
|
results = s.chain.GetHeaderHash(num)
|
|
|
|
case "getconnectioncount":
|
|
getconnectioncountCalled.Inc()
|
|
results = s.coreServer.PeerCount()
|
|
|
|
case "getversion":
|
|
getversionCalled.Inc()
|
|
results = result.Version{
|
|
Port: s.coreServer.Port,
|
|
Nonce: s.coreServer.ID(),
|
|
UserAgent: s.coreServer.UserAgent,
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
results = peers
|
|
|
|
case "validateaddress":
|
|
validateaddressCalled.Inc()
|
|
param, ok := reqParams.Value(0)
|
|
if !ok {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
results = wrappers.ValidateAddress(param.Value)
|
|
|
|
case "getassetstate":
|
|
getassetstateCalled.Inc()
|
|
param, ok := reqParams.ValueWithType(0, stringT)
|
|
if !ok {
|
|
resultsErr = errInvalidParams
|
|
break Methods
|
|
}
|
|
|
|
paramAssetID, err := param.GetUint256()
|
|
if err != nil {
|
|
resultsErr = errInvalidParams
|
|
break
|
|
}
|
|
|
|
as := s.chain.GetAssetState(paramAssetID)
|
|
if as != nil {
|
|
results = wrappers.NewAssetState(as)
|
|
} else {
|
|
results = "Invalid assetid"
|
|
}
|
|
|
|
case "getaccountstate":
|
|
getaccountstateCalled.Inc()
|
|
results, resultsErr = s.getAccountState(reqParams, false)
|
|
|
|
case "getrawtransaction":
|
|
getrawtransactionCalled.Inc()
|
|
results, resultsErr = s.getrawtransaction(reqParams)
|
|
|
|
case "getunspents":
|
|
getunspentsCalled.Inc()
|
|
results, resultsErr = s.getAccountState(reqParams, true)
|
|
|
|
case "invokefunction":
|
|
results, resultsErr = s.invokeFunction(reqParams)
|
|
|
|
case "invokescript":
|
|
results, resultsErr = s.invokescript(reqParams)
|
|
|
|
case "sendrawtransaction":
|
|
sendrawtransactionCalled.Inc()
|
|
results, resultsErr = s.sendrawtransaction(reqParams)
|
|
|
|
default:
|
|
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
|
|
}
|
|
|
|
if resultsErr != nil {
|
|
req.WriteErrorResponse(w, resultsErr)
|
|
return
|
|
}
|
|
|
|
req.WriteResponse(w, results)
|
|
}
|
|
|
|
func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
|
|
var resultsErr error
|
|
var results interface{}
|
|
|
|
if param0, ok := reqParams.Value(0); !ok {
|
|
return nil, errInvalidParams
|
|
} else if txHash, err := param0.GetUint256(); err != nil {
|
|
resultsErr = errInvalidParams
|
|
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
|
|
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
|
|
return nil, NewInvalidParamsError(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)
|
|
}
|
|
|
|
param1, _ := reqParams.Value(1)
|
|
switch v := param1.Value.(type) {
|
|
|
|
case int, float64, bool, string:
|
|
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)
|
|
}
|
|
default:
|
|
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
|
|
}
|
|
} else {
|
|
results = hex.EncodeToString(tx.Bytes())
|
|
}
|
|
|
|
return results, resultsErr
|
|
}
|
|
|
|
// getAccountState returns account state either in short or full (unspents included) form.
|
|
func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) {
|
|
var resultsErr error
|
|
var results interface{}
|
|
|
|
param, ok := reqParams.ValueWithType(0, stringT)
|
|
if !ok {
|
|
return nil, errInvalidParams
|
|
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
|
|
return nil, errInvalidParams
|
|
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
|
if unspents {
|
|
str, err := param.GetString()
|
|
if err != nil {
|
|
return nil, errInvalidParams
|
|
}
|
|
results = wrappers.NewUnspents(as, s.chain, str)
|
|
} else {
|
|
results = wrappers.NewAccountState(as)
|
|
}
|
|
} else {
|
|
results = "Invalid public account address"
|
|
}
|
|
return results, resultsErr
|
|
}
|
|
|
|
// invokescript implements the `invokescript` RPC call.
|
|
func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
|
|
scriptHashHex, ok := reqParams.ValueWithType(0, stringT)
|
|
if !ok {
|
|
return nil, errInvalidParams
|
|
}
|
|
scriptHash, err := scriptHashHex.GetUint160FromHex()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vm, _ := s.chain.GetTestVM()
|
|
vm.LoadScript(script)
|
|
_ = vm.Run()
|
|
result := &wrappers.InvokeResult{
|
|
State: vm.State(),
|
|
GasConsumed: "0.1",
|
|
Script: hex.EncodeToString(script),
|
|
Stack: vm.Estack(),
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// invokescript implements the `invokescript` RPC call.
|
|
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
|
if len(reqParams) < 1 {
|
|
return nil, errInvalidParams
|
|
}
|
|
|
|
script, err := reqParams[0].GetBytesHex()
|
|
if err != nil {
|
|
return nil, errInvalidParams
|
|
}
|
|
|
|
vm, _ := s.chain.GetTestVM()
|
|
vm.LoadScript(script)
|
|
_ = vm.Run()
|
|
// It's already being GetBytesHex'ed, so it's a correct string.
|
|
echo, _ := reqParams[0].GetString()
|
|
result := &wrappers.InvokeResult{
|
|
State: vm.State(),
|
|
GasConsumed: "0.1",
|
|
Script: echo,
|
|
Stack: vm.Estack(),
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
|
|
var resultsErr error
|
|
var results interface{}
|
|
|
|
if len(reqParams) < 1 {
|
|
return nil, errInvalidParams
|
|
} else if byteTx, err := reqParams[0].GetBytesHex(); err != nil {
|
|
return nil, errInvalidParams
|
|
} else {
|
|
r := io.NewBinReaderFromBuf(byteTx)
|
|
tx := &transaction.Transaction{}
|
|
tx.DecodeBinary(r)
|
|
if r.Err != nil {
|
|
err = errors.Wrap(r.Err, "transaction DecodeBinary failed")
|
|
} else {
|
|
relayReason := s.coreServer.RelayTxn(tx)
|
|
switch relayReason {
|
|
case network.RelaySucceed:
|
|
results = true
|
|
case network.RelayAlreadyExists:
|
|
err = errors.New("block or transaction already exists and cannot be sent repeatedly")
|
|
case network.RelayOutOfMemory:
|
|
err = errors.New("the memory pool is full and no more transactions can be sent")
|
|
case network.RelayUnableToVerify:
|
|
err = errors.New("the block cannot be validated")
|
|
case network.RelayInvalid:
|
|
err = errors.New("block or transaction validation failed")
|
|
case network.RelayPolicyFail:
|
|
err = errors.New("one of the Policy filters failed")
|
|
default:
|
|
err = errors.New("unknown error")
|
|
}
|
|
}
|
|
if err != nil {
|
|
resultsErr = NewInternalServerError(err.Error(), err)
|
|
}
|
|
}
|
|
|
|
return results, resultsErr
|
|
}
|
|
|
|
func (s Server) blockHeightFromParam(param *Param) (int, error) {
|
|
num, err := param.GetInt()
|
|
if err != nil {
|
|
return 0, nil
|
|
}
|
|
|
|
if num < 0 || num > int(s.chain.BlockHeight()) {
|
|
return 0, invalidBlockHeightError(0, num)
|
|
}
|
|
return num, nil
|
|
}
|