neo-go/pkg/rpc/server/server.go

1923 lines
54 KiB
Go
Raw Normal View History

package server
import (
"context"
Implemented rpc server method GetRawTransaction (#135) * Added utility function GetVarSize * 1) Added Size method: this implied that Fixed8 implements now the serializable interface. 2) Added few arithmetic operation (Add, Sub, div): this will be used to calculated networkfeeand feePerByte. Changed return value of the Value() method to int instead of int64. Modified fixed8_test accordingly. * Implemented Size or MarshalJSON method. - Structs accepting the Size method implement the serializable interface. - Structs accepting the MarshalJSON method implements the customized json marshaller interface. * Added fee calculation * Implemented rcp server method GetRawTransaction * Updated Tests * Fixed: 1) NewFixed8 will accept as input int64 2) race condition affecting configDeafault, blockchainDefault * Simplified Size calculation * 1) Removed global variable blockchainDefault, configDefault 2) Extended Blockchainer interface to include the methods: References, FeePerByte, SystemFee, NetworkFee 3) Deleted fee_test.go, fee.go. Moved corresponding methods to blockchain_test.go and blockchain.go respectively 4) Amended tx_raw_output.go * Simplified GetVarSize Method * Replaced ValueAtAndType with ValueWithType * Cosmetic changes + Added test case getrawtransaction_7 * Clean up Print statement * Filled up keys * Aligned verbose logic with the C#-neo implementation * Implemented @Kim requests Refactor server_test.go * Small fixes * Fixed verbose logic Added more tests Cosmetic changes * Replaced assert.NoError with require.NoError * Fixed tests by adding context.Background() as argument * Fixed tests
2019-02-20 17:39:32 +00:00
"encoding/hex"
"encoding/json"
"fmt"
"math"
"net"
"net/http"
"strconv"
2020-08-04 15:16:32 +00:00
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
2020-06-04 08:59:22 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/rpc"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/pkg/errors"
2019-12-30 08:44:52 +00:00
"go.uber.org/zap"
)
type (
// Server represents the JSON-RPC 2.0 server.
Server struct {
*http.Server
chain core.Blockchainer
config rpc.Config
coreServer *network.Server
2019-12-30 08:44:52 +00:00
log *zap.Logger
https *http.Server
shutdown chan struct{}
subsLock sync.RWMutex
subscribers map[*subscriber]bool
subsGroup sync.WaitGroup
blockSubs int
executionSubs int
notificationSubs int
transactionSubs int
blockCh chan *block.Block
executionCh chan *state.AppExecResult
notificationCh chan *state.NotificationEvent
transactionCh chan *transaction.Transaction
}
)
const (
// Message limit for receiving side.
wsReadLimit = 4096
// Disconnection timeout.
wsPongLimit = 60 * time.Second
// Ping period for connection liveness check.
wsPingPeriod = wsPongLimit / 2
// Write deadline.
wsWriteLimit = wsPingPeriod / 2
// Maximum number of subscribers per Server. Each websocket client is
// treated like subscriber, so technically it's a limit on websocket
// connections.
maxSubscribers = 64
// Maximum number of elements for get*transfers requests.
maxTransfersLimit = 1000
)
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
"getaccountstate": (*Server).getAccountState,
"getalltransfertx": (*Server).getAllTransferTx,
"getapplicationlog": (*Server).getApplicationLog,
"getassetstate": (*Server).getAssetState,
"getbestblockhash": (*Server).getBestBlockHash,
"getblock": (*Server).getBlock,
"getblockcount": (*Server).getBlockCount,
"getblockhash": (*Server).getBlockHash,
"getblockheader": (*Server).getBlockHeader,
"getblocksysfee": (*Server).getBlockSysFee,
"getblocktransfertx": (*Server).getBlockTransferTx,
"getclaimable": (*Server).getClaimable,
"getconnectioncount": (*Server).getConnectionCount,
"getcontractstate": (*Server).getContractState,
"getminimumnetworkfee": (*Server).getMinimumNetworkFee,
"getnep5balances": (*Server).getNEP5Balances,
"getnep5transfers": (*Server).getNEP5Transfers,
"getpeers": (*Server).getPeers,
"getrawmempool": (*Server).getRawMempool,
"getrawtransaction": (*Server).getrawtransaction,
2020-06-04 08:59:22 +00:00
"getproof": (*Server).getProof,
"getstateheight": (*Server).getStateHeight,
2020-06-03 15:09:36 +00:00
"getstateroot": (*Server).getStateRoot,
"getstorage": (*Server).getStorage,
"gettransactionheight": (*Server).getTransactionHeight,
"gettxout": (*Server).getTxOut,
"getunclaimed": (*Server).getUnclaimed,
"getunspents": (*Server).getUnspents,
"getvalidators": (*Server).getValidators,
"getversion": (*Server).getVersion,
2020-08-04 15:16:32 +00:00
"getutxotransfers": (*Server).getUTXOTransfers,
"invoke": (*Server).invoke,
"invokefunction": (*Server).invokeFunction,
"invokescript": (*Server).invokescript,
"sendrawtransaction": (*Server).sendrawtransaction,
"submitblock": (*Server).submitBlock,
"validateaddress": (*Server).validateAddress,
"verifyproof": (*Server).verifyProof,
}
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
"subscribe": (*Server).subscribe,
"unsubscribe": (*Server).unsubscribe,
}
var invalidBlockHeightError = func(index int, height int) *response.Error {
return response.NewRPCError(fmt.Sprintf("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), "", nil)
}
// upgrader is a no-op websocket.Upgrader that reuses HTTP server buffers and
// doesn't set any Error function.
var upgrader = websocket.Upgrader{}
// New creates a new Server struct.
func New(chain core.Blockchainer, conf rpc.Config, coreServer *network.Server, log *zap.Logger) Server {
httpServer := &http.Server{
Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10),
}
var tlsServer *http.Server
if cfg := conf.TLSConfig; cfg.Enabled {
tlsServer = &http.Server{
Addr: net.JoinHostPort(cfg.Address, strconv.FormatUint(uint64(cfg.Port), 10)),
}
}
return Server{
Server: httpServer,
chain: chain,
config: conf,
coreServer: coreServer,
2019-12-30 08:44:52 +00:00
log: log,
https: tlsServer,
shutdown: make(chan struct{}),
subscribers: make(map[*subscriber]bool),
// These are NOT buffered to preserve original order of events.
blockCh: make(chan *block.Block),
executionCh: make(chan *state.AppExecResult),
notificationCh: make(chan *state.NotificationEvent),
transactionCh: make(chan *transaction.Transaction),
}
}
// Start creates a new JSON-RPC server listening on the configured port. It's
// supposed to be run as a separate goroutine (like http.Server's Serve) and it
// returns its errors via given errChan.
func (s *Server) Start(errChan chan error) {
if !s.config.Enabled {
2019-12-30 08:44:52 +00:00
s.log.Info("RPC server is not enabled")
return
}
s.Handler = http.HandlerFunc(s.handleHTTPRequest)
2019-12-30 08:44:52 +00:00
s.log.Info("starting rpc-server", zap.String("endpoint", s.Addr))
go s.handleSubEvents()
if cfg := s.config.TLSConfig; cfg.Enabled {
s.https.Handler = http.HandlerFunc(s.handleHTTPRequest)
s.log.Info("starting rpc-server (https)", zap.String("endpoint", s.https.Addr))
go func() {
err := s.https.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
rpc/server: fix error reporting in Start This error message makes no sense when shutting down the server: 2020-06-25T19:29:53.251+0300 ERROR failed to start RPC server {"error": "http: Server closed"} And ListenAndServer is documented to always return non-nil error one of which is http.ErrServerClosed. This should also fix the following test failure: ================== WARNING: DATA RACE Read at 0x00c000254243 by goroutine 49: testing.(*common).logDepth() /usr/local/go/src/testing/testing.go:665 +0xa1 testing.(*common).Logf() /usr/local/go/src/testing/testing.go:658 +0x8f testing.(*T).Logf() <autogenerated>:1 +0x75 go.uber.org/zap/zaptest.testingWriter.Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zaptest/logger.go:130 +0x11f go.uber.org/zap/zaptest.(*testingWriter).Write() <autogenerated>:1 +0xa9 go.uber.org/zap/zapcore.(*ioCore).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/core.go:90 +0x1c3 go.uber.org/zap/zapcore.(*CheckedEntry).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/entry.go:215 +0x1e7 go.uber.org/zap.(*Logger).Error() /go/pkg/mod/go.uber.org/zap@v1.10.0/logger.go:203 +0x95 github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).Start() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server.go:179 +0x5c5 Previous write at 0x00c000254243 by goroutine 44: testing.tRunner.func1() /usr/local/go/src/testing/testing.go:900 +0x353 testing.tRunner() /usr/local/go/src/testing/testing.go:913 +0x1bb Goroutine 49 (running) created at: github.com/nspcc-dev/neo-go/pkg/rpc/server.initClearServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:69 +0x305 github.com/nspcc-dev/neo-go/pkg/rpc/server.initServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:78 +0x3c github.com/nspcc-dev/neo-go/pkg/rpc/server.testRPCProtocol() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:805 +0x53 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC.func1() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:793 +0x44 testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 Goroutine 44 (finished) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:960 +0x651 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:792 +0x5d testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 ==================
2020-06-25 16:31:45 +00:00
if err != http.ErrServerClosed {
s.log.Error("failed to start TLS RPC server", zap.Error(err))
rpc/server: fix error reporting in Start This error message makes no sense when shutting down the server: 2020-06-25T19:29:53.251+0300 ERROR failed to start RPC server {"error": "http: Server closed"} And ListenAndServer is documented to always return non-nil error one of which is http.ErrServerClosed. This should also fix the following test failure: ================== WARNING: DATA RACE Read at 0x00c000254243 by goroutine 49: testing.(*common).logDepth() /usr/local/go/src/testing/testing.go:665 +0xa1 testing.(*common).Logf() /usr/local/go/src/testing/testing.go:658 +0x8f testing.(*T).Logf() <autogenerated>:1 +0x75 go.uber.org/zap/zaptest.testingWriter.Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zaptest/logger.go:130 +0x11f go.uber.org/zap/zaptest.(*testingWriter).Write() <autogenerated>:1 +0xa9 go.uber.org/zap/zapcore.(*ioCore).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/core.go:90 +0x1c3 go.uber.org/zap/zapcore.(*CheckedEntry).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/entry.go:215 +0x1e7 go.uber.org/zap.(*Logger).Error() /go/pkg/mod/go.uber.org/zap@v1.10.0/logger.go:203 +0x95 github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).Start() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server.go:179 +0x5c5 Previous write at 0x00c000254243 by goroutine 44: testing.tRunner.func1() /usr/local/go/src/testing/testing.go:900 +0x353 testing.tRunner() /usr/local/go/src/testing/testing.go:913 +0x1bb Goroutine 49 (running) created at: github.com/nspcc-dev/neo-go/pkg/rpc/server.initClearServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:69 +0x305 github.com/nspcc-dev/neo-go/pkg/rpc/server.initServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:78 +0x3c github.com/nspcc-dev/neo-go/pkg/rpc/server.testRPCProtocol() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:805 +0x53 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC.func1() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:793 +0x44 testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 Goroutine 44 (finished) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:960 +0x651 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:792 +0x5d testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 ==================
2020-06-25 16:31:45 +00:00
errChan <- err
}
}()
}
err := s.ListenAndServe()
rpc/server: fix error reporting in Start This error message makes no sense when shutting down the server: 2020-06-25T19:29:53.251+0300 ERROR failed to start RPC server {"error": "http: Server closed"} And ListenAndServer is documented to always return non-nil error one of which is http.ErrServerClosed. This should also fix the following test failure: ================== WARNING: DATA RACE Read at 0x00c000254243 by goroutine 49: testing.(*common).logDepth() /usr/local/go/src/testing/testing.go:665 +0xa1 testing.(*common).Logf() /usr/local/go/src/testing/testing.go:658 +0x8f testing.(*T).Logf() <autogenerated>:1 +0x75 go.uber.org/zap/zaptest.testingWriter.Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zaptest/logger.go:130 +0x11f go.uber.org/zap/zaptest.(*testingWriter).Write() <autogenerated>:1 +0xa9 go.uber.org/zap/zapcore.(*ioCore).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/core.go:90 +0x1c3 go.uber.org/zap/zapcore.(*CheckedEntry).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/entry.go:215 +0x1e7 go.uber.org/zap.(*Logger).Error() /go/pkg/mod/go.uber.org/zap@v1.10.0/logger.go:203 +0x95 github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).Start() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server.go:179 +0x5c5 Previous write at 0x00c000254243 by goroutine 44: testing.tRunner.func1() /usr/local/go/src/testing/testing.go:900 +0x353 testing.tRunner() /usr/local/go/src/testing/testing.go:913 +0x1bb Goroutine 49 (running) created at: github.com/nspcc-dev/neo-go/pkg/rpc/server.initClearServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:69 +0x305 github.com/nspcc-dev/neo-go/pkg/rpc/server.initServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:78 +0x3c github.com/nspcc-dev/neo-go/pkg/rpc/server.testRPCProtocol() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:805 +0x53 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC.func1() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:793 +0x44 testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 Goroutine 44 (finished) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:960 +0x651 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:792 +0x5d testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 ==================
2020-06-25 16:31:45 +00:00
if err != http.ErrServerClosed {
s.log.Error("failed to start RPC server", zap.Error(err))
rpc/server: fix error reporting in Start This error message makes no sense when shutting down the server: 2020-06-25T19:29:53.251+0300 ERROR failed to start RPC server {"error": "http: Server closed"} And ListenAndServer is documented to always return non-nil error one of which is http.ErrServerClosed. This should also fix the following test failure: ================== WARNING: DATA RACE Read at 0x00c000254243 by goroutine 49: testing.(*common).logDepth() /usr/local/go/src/testing/testing.go:665 +0xa1 testing.(*common).Logf() /usr/local/go/src/testing/testing.go:658 +0x8f testing.(*T).Logf() <autogenerated>:1 +0x75 go.uber.org/zap/zaptest.testingWriter.Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zaptest/logger.go:130 +0x11f go.uber.org/zap/zaptest.(*testingWriter).Write() <autogenerated>:1 +0xa9 go.uber.org/zap/zapcore.(*ioCore).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/core.go:90 +0x1c3 go.uber.org/zap/zapcore.(*CheckedEntry).Write() /go/pkg/mod/go.uber.org/zap@v1.10.0/zapcore/entry.go:215 +0x1e7 go.uber.org/zap.(*Logger).Error() /go/pkg/mod/go.uber.org/zap@v1.10.0/logger.go:203 +0x95 github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).Start() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server.go:179 +0x5c5 Previous write at 0x00c000254243 by goroutine 44: testing.tRunner.func1() /usr/local/go/src/testing/testing.go:900 +0x353 testing.tRunner() /usr/local/go/src/testing/testing.go:913 +0x1bb Goroutine 49 (running) created at: github.com/nspcc-dev/neo-go/pkg/rpc/server.initClearServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:69 +0x305 github.com/nspcc-dev/neo-go/pkg/rpc/server.initServerWithInMemoryChain() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_helper_test.go:78 +0x3c github.com/nspcc-dev/neo-go/pkg/rpc/server.testRPCProtocol() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:805 +0x53 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC.func1() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:793 +0x44 testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 Goroutine 44 (finished) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:960 +0x651 github.com/nspcc-dev/neo-go/pkg/rpc/server.TestRPC() /go/src/github.com/nspcc-dev/neo-go/pkg/rpc/server/server_test.go:792 +0x5d testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 ==================
2020-06-25 16:31:45 +00:00
errChan <- err
}
}
2019-10-22 14:56:03 +00:00
// Shutdown overrides the http.Server Shutdown
// method.
func (s *Server) Shutdown() error {
var httpsErr error
// Signal to websocket writer routines and handleSubEvents.
close(s.shutdown)
if s.config.TLSConfig.Enabled {
s.log.Info("shutting down rpc-server (https)", zap.String("endpoint", s.https.Addr))
httpsErr = s.https.Shutdown(context.Background())
}
2019-12-30 08:44:52 +00:00
s.log.Info("shutting down rpc-server", zap.String("endpoint", s.Addr))
err := s.Server.Shutdown(context.Background())
// Wait for handleSubEvents to finish.
<-s.executionCh
if err == nil {
return httpsErr
}
return err
}
func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
req := request.NewRequest()
if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" {
// Technically there is a race between this check and
// s.subscribers modification 20 lines below, but it's tiny
// and not really critical to bother with it. Some additional
// clients may sneak in, no big deal.
s.subsLock.RLock()
numOfSubs := len(s.subscribers)
s.subsLock.RUnlock()
if numOfSubs >= maxSubscribers {
s.writeHTTPErrorResponse(
request.NewIn(),
w,
response.NewInternalServerError("websocket users limit reached", nil),
)
return
}
ws, err := upgrader.Upgrade(w, httpRequest, nil)
if err != nil {
s.log.Info("websocket connection upgrade failed", zap.Error(err))
return
}
resChan := make(chan response.AbstractResult) // response.Raw or response.RawBatch
subChan := make(chan *websocket.PreparedMessage, notificationBufSize)
subscr := &subscriber{writer: subChan, ws: ws}
s.subsLock.Lock()
s.subscribers[subscr] = true
s.subsLock.Unlock()
go s.handleWsWrites(ws, resChan, subChan)
s.handleWsReads(ws, resChan, subscr)
return
}
if httpRequest.Method != "POST" {
s.writeHTTPErrorResponse(
request.NewIn(),
w,
response.NewInvalidParamsError(
fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil,
),
)
return
}
err := req.DecodeData(httpRequest.Body)
if err != nil {
s.writeHTTPErrorResponse(request.NewIn(), w, response.NewParseError("Problem parsing JSON-RPC request body", err))
return
}
resp := s.handleRequest(req, nil)
s.writeHTTPServerResponse(req, w, resp)
}
func (s *Server) handleRequest(req *request.Request, sub *subscriber) response.AbstractResult {
if req.In != nil {
return s.handleIn(req.In, sub)
}
resp := make(response.RawBatch, len(req.Batch))
for i, in := range req.Batch {
resp[i] = s.handleIn(&in, sub)
}
return resp
}
func (s *Server) handleIn(req *request.In, sub *subscriber) response.Raw {
var res interface{}
var resErr *response.Error
if req.JSONRPC != request.JSONRPCVersion {
return s.packResponseToRaw(req, nil, response.NewInvalidParamsError("Problem parsing JSON", fmt.Errorf("invalid version, expected 2.0 got: '%s'", req.JSONRPC)))
}
reqParams, err := req.Params()
if err != nil {
return s.packResponseToRaw(req, nil, response.NewInvalidParamsError("Problem parsing request parameters", err))
}
s.log.Debug("processing rpc request",
2019-12-30 08:44:52 +00:00
zap.String("method", req.Method),
zap.String("params", fmt.Sprintf("%v", reqParams)))
2020-03-12 17:36:36 +00:00
incCounter(req.Method)
resErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
handler, ok := rpcHandlers[req.Method]
if ok {
res, resErr = handler(s, *reqParams)
} else if sub != nil {
handler, ok := rpcWsHandlers[req.Method]
if ok {
res, resErr = handler(s, *reqParams, sub)
}
}
return s.packResponseToRaw(req, res, resErr)
}
func (s *Server) handleWsWrites(ws *websocket.Conn, resChan <-chan response.AbstractResult, subChan <-chan *websocket.PreparedMessage) {
pingTicker := time.NewTicker(wsPingPeriod)
eventloop:
for {
select {
case <-s.shutdown:
break eventloop
case event, ok := <-subChan:
if !ok {
break eventloop
}
ws.SetWriteDeadline(time.Now().Add(wsWriteLimit))
if err := ws.WritePreparedMessage(event); err != nil {
break eventloop
}
case res, ok := <-resChan:
if !ok {
break eventloop
}
ws.SetWriteDeadline(time.Now().Add(wsWriteLimit))
if err := ws.WriteJSON(res); err != nil {
break eventloop
}
case <-pingTicker.C:
ws.SetWriteDeadline(time.Now().Add(wsWriteLimit))
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
break eventloop
}
}
}
ws.Close()
pingTicker.Stop()
// Drain notification channel as there might be some goroutines blocked
// on it.
drainloop:
for {
select {
case _, ok := <-subChan:
if !ok {
break drainloop
}
default:
break drainloop
}
}
}
func (s *Server) handleWsReads(ws *websocket.Conn, resChan chan<- response.AbstractResult, subscr *subscriber) {
ws.SetReadLimit(wsReadLimit)
ws.SetReadDeadline(time.Now().Add(wsPongLimit))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(wsPongLimit)); return nil })
requestloop:
for {
req := request.NewRequest()
err := ws.ReadJSON(req)
if err != nil {
break
}
res := s.handleRequest(req, subscr)
res.RunForErrors(func(jsonErr *response.Error) {
s.logRequestError(req, jsonErr)
})
select {
case <-s.shutdown:
break requestloop
case resChan <- res:
}
}
s.subsLock.Lock()
delete(s.subscribers, subscr)
for _, e := range subscr.feeds {
if e.event != response.InvalidEventID {
s.unsubscribeFromChannel(e.event)
}
}
s.subsLock.Unlock()
close(resChan)
ws.Close()
}
func (s *Server) getBestBlockHash(_ request.Params) (interface{}, *response.Error) {
return "0x" + s.chain.CurrentBlockHash().StringLE(), nil
}
func (s *Server) getBlockCount(_ request.Params) (interface{}, *response.Error) {
return s.chain.BlockHeight() + 1, nil
}
func (s *Server) getConnectionCount(_ request.Params) (interface{}, *response.Error) {
return s.coreServer.PeerCount(), nil
}
func (s *Server) getBlockHashFromParam(param *request.Param) (util.Uint256, *response.Error) {
var hash util.Uint256
if param == nil {
return hash, response.ErrInvalidParams
}
switch param.Type {
case request.StringT:
var err error
hash, err = param.GetUint256()
if err != nil {
return hash, response.ErrInvalidParams
}
case request.NumberT:
num, err := s.blockHeightFromParam(param)
if err != nil {
return hash, response.ErrInvalidParams
}
hash = s.chain.GetHeaderHash(num)
default:
return hash, response.ErrInvalidParams
}
return hash, nil
}
func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Error) {
hash, respErr := s.getBlockHashFromParam(reqParams.Value(0))
if respErr != nil {
return nil, respErr
}
block, err := s.chain.GetBlock(hash)
if err != nil {
return nil, response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
}
if reqParams.Value(1).GetBoolean() {
return result.NewBlock(block, s.chain), nil
}
writer := io.NewBufBinWriter()
block.EncodeBinary(writer.BinWriter)
return hex.EncodeToString(writer.Bytes()), nil
}
func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.ValueWithType(0, request.NumberT)
if param == nil {
return nil, response.ErrInvalidParams
}
num, err := s.blockHeightFromParam(param)
if err != nil {
return nil, err
}
return s.chain.GetHeaderHash(num), nil
}
func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) {
return result.Version{
Port: s.coreServer.Port,
Nonce: s.coreServer.ID(),
UserAgent: s.coreServer.UserAgent,
}, nil
}
2020-09-12 21:12:45 +00:00
func getTimestampsAndLimit(ps request.Params, index int) (uint32, uint32, int, int, error) {
2020-08-04 15:16:32 +00:00
var start, end uint32
2020-09-12 21:12:45 +00:00
var limit, page int
limit = maxTransfersLimit
2020-09-12 21:12:45 +00:00
pStart, pEnd, pLimit, pPage := ps.Value(index), ps.Value(index+1), ps.Value(index+2), ps.Value(index+3)
if pPage != nil {
p, err := pPage.GetInt()
if err != nil {
return 0, 0, 0, 0, err
}
if p < 0 {
return 0, 0, 0, 0, errors.New("can't use negative page")
}
page = p
}
if pLimit != nil {
l, err := pLimit.GetInt()
2020-08-04 15:16:32 +00:00
if err != nil {
2020-09-12 21:12:45 +00:00
return 0, 0, 0, 0, err
2020-08-04 15:16:32 +00:00
}
if l <= 0 {
2020-09-12 21:12:45 +00:00
return 0, 0, 0, 0, errors.New("can't use negative or zero limit")
}
if l > maxTransfersLimit {
return 0, 0, 0, 0, errors.New("too big limit requested")
}
limit = l
2020-08-04 15:16:32 +00:00
}
if pEnd != nil {
val, err := pEnd.GetInt()
2020-08-04 15:16:32 +00:00
if err != nil {
2020-09-12 21:12:45 +00:00
return 0, 0, 0, 0, err
2020-08-04 15:16:32 +00:00
}
end = uint32(val)
} else {
end = uint32(time.Now().Unix())
2020-08-04 15:16:32 +00:00
}
if pStart != nil {
val, err := pStart.GetInt()
if err != nil {
2020-09-12 21:12:45 +00:00
return 0, 0, 0, 0, err
}
start = uint32(val)
} else {
start = uint32(time.Now().Add(-time.Hour * 24 * 7).Unix())
}
2020-09-12 21:12:45 +00:00
return start, end, limit, page, nil
2020-08-04 15:16:32 +00:00
}
func getAssetMaps(name string) (map[util.Uint256]*result.AssetUTXO, map[util.Uint256]*result.AssetUTXO, error) {
sent := make(map[util.Uint256]*result.AssetUTXO)
recv := make(map[util.Uint256]*result.AssetUTXO)
name = strings.ToLower(name)
switch name {
case "neo", "gas", "":
default:
return nil, nil, errors.New("invalid asset")
}
if name == "neo" || name == "" {
sent[core.GoverningTokenID()] = &result.AssetUTXO{
AssetHash: core.GoverningTokenID(),
AssetName: "NEO",
Transactions: []result.UTXO{},
}
recv[core.GoverningTokenID()] = &result.AssetUTXO{
AssetHash: core.GoverningTokenID(),
AssetName: "NEO",
Transactions: []result.UTXO{},
}
}
if name == "gas" || name == "" {
sent[core.UtilityTokenID()] = &result.AssetUTXO{
AssetHash: core.UtilityTokenID(),
AssetName: "GAS",
Transactions: []result.UTXO{},
}
recv[core.UtilityTokenID()] = &result.AssetUTXO{
AssetHash: core.UtilityTokenID(),
AssetName: "GAS",
Transactions: []result.UTXO{},
}
}
return sent, recv, nil
}
func (s *Server) getUTXOTransfers(ps request.Params) (interface{}, *response.Error) {
addr, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil {
return nil, response.NewInvalidParamsError("", err)
}
index := 1
assetName, err := ps.Value(index).GetString()
if err == nil {
index++
}
2020-09-12 21:12:45 +00:00
start, end, limit, page, err := getTimestampsAndLimit(ps, index)
2020-08-04 15:16:32 +00:00
if err != nil {
return nil, response.NewInvalidParamsError("", err)
}
sent, recv, err := getAssetMaps(assetName)
if err != nil {
return nil, response.NewInvalidParamsError("", err)
}
tr := new(state.Transfer)
2020-09-12 21:12:45 +00:00
var resCount, frameCount int
err = s.chain.ForEachTransfer(addr, tr, func() (bool, error) {
2020-09-12 21:12:45 +00:00
// Iterating from newest to oldest, not yet reached required
// time frame, continue looping.
if tr.Timestamp > end {
return true, nil
}
2020-09-12 21:12:45 +00:00
// Iterating from newest to oldest, moved past required
// time frame, stop looping.
if tr.Timestamp < start {
return false, nil
2020-08-04 15:16:32 +00:00
}
2020-09-12 21:12:45 +00:00
frameCount++
// Using limits, not yet reached required page.
if limit != 0 && page*limit >= frameCount {
return true, nil
}
2020-08-04 15:16:32 +00:00
assetID := core.GoverningTokenID()
if !tr.IsGoverning {
assetID = core.UtilityTokenID()
}
2020-08-07 12:37:05 +00:00
m := recv
if tr.IsSent {
m = sent
2020-08-04 15:16:32 +00:00
}
2020-08-07 12:37:05 +00:00
a, ok := m[assetID]
if ok {
2020-08-04 15:16:32 +00:00
a.Transactions = append(a.Transactions, result.UTXO{
Index: tr.Block,
Timestamp: tr.Timestamp,
TxHash: tr.Tx,
Amount: tr.Amount,
})
a.TotalAmount += tr.Amount
}
2020-09-12 21:12:45 +00:00
resCount++
// Using limits, reached limit.
if limit != 0 && resCount >= limit {
return false, nil
}
return true, nil
2020-08-04 15:16:32 +00:00
})
if err != nil {
return nil, response.NewInternalServerError("", err)
}
res := &result.GetUTXO{
Address: address.Uint160ToString(addr),
Sent: []result.AssetUTXO{},
Received: []result.AssetUTXO{},
}
for _, a := range sent {
res.Sent = append(res.Sent, *a)
}
for _, a := range recv {
res.Received = append(res.Received, *a)
}
return res, nil
}
func (s *Server) getPeers(_ request.Params) (interface{}, *response.Error) {
peers := result.NewGetPeers()
peers.AddUnconnected(s.coreServer.UnconnectedPeers())
peers.AddConnected(s.coreServer.ConnectedPeers())
peers.AddBad(s.coreServer.BadPeers())
return peers, nil
}
func (s *Server) getRawMempool(_ request.Params) (interface{}, *response.Error) {
mp := s.chain.GetMemPool()
hashList := make([]util.Uint256, 0)
for _, item := range mp.GetVerifiedTransactions() {
hashList = append(hashList, item.Tx.Hash())
}
return hashList, nil
}
func (s *Server) validateAddress(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.Value(0)
if param == nil {
return nil, response.ErrInvalidParams
}
return validateAddress(param.Value), nil
}
func (s *Server) getAssetState(reqParams request.Params) (interface{}, *response.Error) {
paramAssetID, err := reqParams.ValueWithType(0, request.StringT).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
as := s.chain.GetAssetState(paramAssetID)
if as != nil {
return result.NewAssetState(as), nil
}
return nil, response.NewRPCError("Unknown asset", "", nil)
}
// getApplicationLog returns the contract log based on the specified txid.
func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *response.Error) {
txHash, err := reqParams.Value(0).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
appExecResult, err := s.chain.GetAppExecResult(txHash)
if err != nil {
return nil, response.NewRPCError("Unknown transaction", "", nil)
}
tx, _, err := s.chain.GetTransaction(txHash)
if err != nil {
return nil, response.NewRPCError("Error while getting transaction", "", nil)
}
var scriptHash util.Uint160
switch t := tx.Data.(type) {
case *transaction.InvocationTX:
scriptHash = hash.Hash160(t.Script)
default:
return nil, response.NewRPCError("Invalid transaction type", "", nil)
}
return result.NewApplicationLog(appExecResult, scriptHash), nil
}
func (s *Server) getClaimable(ps request.Params) (interface{}, *response.Error) {
p := ps.ValueWithType(0, request.StringT)
2020-02-26 12:42:04 +00:00
u, err := p.GetUint160FromAddress()
if err != nil {
return nil, response.ErrInvalidParams
}
var unclaimed []state.UnclaimedBalance
if acc := s.chain.GetAccountState(u); acc != nil {
err := acc.Unclaimed.ForEach(func(b *state.UnclaimedBalance) error {
unclaimed = append(unclaimed, *b)
return nil
})
if err != nil {
return nil, response.NewInternalServerError("Unclaimed processing failure", err)
}
2020-02-26 12:42:04 +00:00
}
var sum util.Fixed8
claimable := make([]result.Claimable, 0, len(unclaimed))
for _, ub := range unclaimed {
gen, sys, err := s.chain.CalculateClaimable(ub.Value, ub.Start, ub.End)
if err != nil {
s.log.Info("error while calculating claim bonus", zap.Error(err))
continue
}
uc := gen.Add(sys)
sum += uc
claimable = append(claimable, result.Claimable{
Tx: ub.Tx,
N: int(ub.Index),
Value: ub.Value,
StartHeight: ub.Start,
EndHeight: ub.End,
Generated: gen,
SysFee: sys,
Unclaimed: uc,
})
}
return result.ClaimableInfo{
Spents: claimable,
Address: p.String(),
Unclaimed: sum,
}, nil
}
func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Error) {
u, err := ps.Value(0).GetUint160FromAddressOrHex()
2020-03-05 11:50:06 +00:00
if err != nil {
return nil, response.ErrInvalidParams
}
as := s.chain.GetNEP5Balances(u)
bs := &result.NEP5Balances{
Address: address.Uint160ToString(u),
Balances: []result.NEP5Balance{},
}
2020-03-05 11:50:06 +00:00
if as != nil {
for h, bal := range as.Trackers {
amount := bal.Balance.String()
2020-03-05 11:50:06 +00:00
bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h,
Amount: amount,
LastUpdated: bal.LastUpdatedBlock,
})
}
}
return bs, nil
}
func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Error) {
u, err := ps.Value(0).GetUint160FromAddressOrHex()
2020-03-05 12:16:03 +00:00
if err != nil {
return nil, response.ErrInvalidParams
}
2020-09-12 21:12:45 +00:00
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
if err != nil {
return nil, response.NewInvalidParamsError("", err)
}
bs := &result.NEP5Transfers{
Address: address.Uint160ToString(u),
Received: []result.NEP5Transfer{},
Sent: []result.NEP5Transfer{},
}
tr := new(state.NEP5Transfer)
2020-09-12 21:12:45 +00:00
var resCount, frameCount int
err = s.chain.ForEachNEP5Transfer(u, tr, func() (bool, error) {
2020-09-12 21:12:45 +00:00
// Iterating from newest to oldest, not yet reached required
// time frame, continue looping.
if tr.Timestamp > end {
return true, nil
}
2020-09-12 21:12:45 +00:00
// Iterating from newest to oldest, moved past required
// time frame, stop looping.
if tr.Timestamp < start {
return false, nil
}
2020-09-12 21:12:45 +00:00
frameCount++
// Using limits, not yet reached required page.
if limit != 0 && page*limit >= frameCount {
return true, nil
}
2020-03-05 12:16:03 +00:00
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: tr.Asset,
Index: tr.Block,
TxHash: tr.Tx,
NotifyIndex: tr.Index,
2020-03-05 12:16:03 +00:00
}
if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = tr.Amount.String()
2020-03-05 12:16:03 +00:00
if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From)
}
bs.Received = append(bs.Received, transfer)
2020-09-12 21:12:45 +00:00
} else {
tr.Amount.Neg(tr.Amount)
transfer.Amount = tr.Amount.String()
2020-09-12 21:12:45 +00:00
if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To)
}
bs.Sent = append(bs.Sent, transfer)
2020-03-05 12:16:03 +00:00
}
2020-09-12 21:12:45 +00:00
resCount++
// Using limits, reached limit.
if limit != 0 && resCount >= limit {
return false, nil
2020-03-05 12:16:03 +00:00
}
return true, nil
2020-03-05 12:16:03 +00:00
})
if err != nil {
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)
}
return bs, nil
}
func appendUTXOToTransferTx(transfer *result.TransferTx, tx *transaction.Transaction, chain core.Blockchainer) *response.Error {
inouts, err := chain.References(tx)
if err != nil {
return response.NewInternalServerError("invalid tx", err)
}
for _, inout := range inouts {
var event result.TransferTxEvent
event.Address = address.Uint160ToString(inout.Out.ScriptHash)
event.Type = "input"
event.Value = inout.Out.Amount.String()
event.Asset = inout.Out.AssetID.StringLE()
transfer.Elements = append(transfer.Elements, event)
}
for _, out := range tx.Outputs {
var event result.TransferTxEvent
event.Address = address.Uint160ToString(out.ScriptHash)
event.Type = "output"
event.Value = out.Amount.String()
event.Asset = out.AssetID.StringLE()
transfer.Elements = append(transfer.Elements, event)
}
return nil
}
// uint160ToString converts given hash to address, unless it's zero and an empty
// string is returned then.
func uint160ToString(u util.Uint160) string {
if u.Equals(util.Uint160{}) {
return ""
}
return address.Uint160ToString(u)
}
func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) {
var event result.TransferTxEvent
event.Asset = nepTr.Asset.StringLE()
if nepTr.Amount.Sign() > 0 { // token was received
event.Value = nepTr.Amount.String()
event.Type = "receive"
event.Address = uint160ToString(nepTr.From)
} else {
nepTr.Amount.Neg(nepTr.Amount)
event.Value = nepTr.Amount.String()
nepTr.Amount.Neg(nepTr.Amount)
event.Type = "send"
event.Address = uint160ToString(nepTr.To)
}
transfer.Events = append(transfer.Events, event)
}
func (s *Server) getAllTransferTx(ps request.Params) (interface{}, *response.Error) {
var respErr *response.Error
u, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil {
return nil, response.ErrInvalidParams
}
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
if err != nil {
return nil, response.NewInvalidParamsError("", err)
}
var (
utxoCont = make(chan bool)
nep5Cont = make(chan bool)
utxoTrs = make(chan state.Transfer)
nep5Trs = make(chan state.NEP5Transfer)
)
go func() {
tr := new(state.Transfer)
_ = s.chain.ForEachTransfer(u, tr, func() (bool, error) {
var cont bool
// Iterating from newest to oldest, not yet reached required
// time frame, continue looping.
if tr.Timestamp > end {
return true, nil
}
// Iterating from newest to oldest, moved past required
// time frame, stop looping.
if tr.Timestamp < start {
return false, nil
}
utxoTrs <- *tr
cont = <-utxoCont
return cont, nil
})
close(utxoTrs)
}()
go func() {
tr := new(state.NEP5Transfer)
_ = s.chain.ForEachNEP5Transfer(u, tr, func() (bool, error) {
var cont bool
// Iterating from newest to oldest, not yet reached required
// time frame, continue looping.
if tr.Timestamp > end {
return true, nil
}
// Iterating from newest to oldest, moved past required
// time frame, stop looping.
if tr.Timestamp < start {
return false, nil
}
nep5Trs <- *tr
cont = <-nep5Cont
return cont, nil
})
close(nep5Trs)
}()
var (
res = make([]result.TransferTx, 0, limit)
frameCount int
utxoLast state.Transfer
nep5Last state.NEP5Transfer
haveUtxo, haveNep5 bool
)
utxoLast, haveUtxo = <-utxoTrs
if haveUtxo {
utxoCont <- true
}
nep5Last, haveNep5 = <-nep5Trs
if haveNep5 {
nep5Cont <- true
}
for len(res) < limit {
if !haveUtxo && !haveNep5 {
break
}
var isNep5 = haveNep5 && (!haveUtxo || (nep5Last.Timestamp > utxoLast.Timestamp))
var transfer result.TransferTx
if isNep5 {
transfer.TxID = nep5Last.Tx
transfer.Timestamp = nep5Last.Timestamp
transfer.Index = nep5Last.Block
} else {
transfer.TxID = utxoLast.Tx
transfer.Timestamp = utxoLast.Timestamp
transfer.Index = utxoLast.Block
}
frameCount++
// Using limits, not yet reached required page. But still need
// to drain inputs for this tx.
skipTx := page*limit >= frameCount
if !skipTx {
tx, _, err := s.chain.GetTransaction(transfer.TxID)
if err != nil {
respErr = response.NewInternalServerError("invalid NEP5 transfer log", err)
break
}
transfer.NetworkFee = s.chain.NetworkFee(tx).String()
transfer.SystemFee = s.chain.SystemFee(tx).String()
respErr = appendUTXOToTransferTx(&transfer, tx, s.chain)
if respErr != nil {
break
}
}
// Pick all NEP5 events for this transaction, if there are any.
for haveNep5 && nep5Last.Tx.Equals(transfer.TxID) {
if !skipTx {
appendNEP5ToTransferTx(&transfer, &nep5Last)
}
nep5Last, haveNep5 = <-nep5Trs
if haveNep5 {
nep5Cont <- true
}
}
// Skip UTXO events, we've already got them from inputs and outputs.
for haveUtxo && utxoLast.Tx.Equals(transfer.TxID) {
utxoLast, haveUtxo = <-utxoTrs
if haveUtxo {
utxoCont <- true
}
}
if !skipTx {
res = append(res, transfer)
}
}
if haveUtxo {
_, ok := <-utxoTrs
if ok {
utxoCont <- false
}
}
if haveNep5 {
_, ok := <-nep5Trs
if ok {
nep5Cont <- false
}
}
if respErr != nil {
return nil, respErr
}
return res, nil
}
func (s *Server) getMinimumNetworkFee(ps request.Params) (interface{}, *response.Error) {
return s.chain.GetConfig().MinimumNetworkFee, nil
}
2020-06-04 08:59:22 +00:00
func (s *Server) getProof(ps request.Params) (interface{}, *response.Error) {
root, err := ps.Value(0).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
sc, err := ps.Value(1).GetUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
sc = sc.Reverse()
key, err := ps.Value(2).GetBytesHex()
if err != nil {
return nil, response.ErrInvalidParams
}
skey := mpt.ToNeoStorageKey(append(sc.BytesBE(), key...))
proof, err := s.chain.GetStateProof(root, skey)
return &result.GetProof{
Result: result.ProofWithKey{
Key: skey,
Proof: proof,
},
Success: err == nil,
}, nil
}
func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) {
root, err := ps.Value(0).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
proofStr, err := ps.Value(1).GetString()
if err != nil {
return nil, response.ErrInvalidParams
}
var p result.ProofWithKey
if err := p.FromString(proofStr); err != nil {
return nil, response.ErrInvalidParams
}
vp := new(result.VerifyProof)
val, ok := mpt.VerifyProof(root, p.Key, p.Proof)
if ok {
var si state.StorageItem
r := io.NewBinReaderFromBuf(val[1:])
si.DecodeBinary(r)
if r.Err != nil {
return nil, response.NewInternalServerError("invalid item in trie", r.Err)
}
vp.Value = si.Value
}
return vp, nil
}
func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) {
return &result.StateHeight{
BlockHeight: s.chain.BlockHeight(),
StateHeight: s.chain.StateHeight(),
}, nil
}
2020-06-03 15:09:36 +00:00
func (s *Server) getStateRoot(ps request.Params) (interface{}, *response.Error) {
p := ps.Value(0)
if p == nil {
return nil, response.NewRPCError("Invalid parameter.", "", nil)
}
var rt *state.MPTRootState
var h util.Uint256
height, err := p.GetInt()
if err == nil {
rt, err = s.chain.GetStateRoot(uint32(height))
} else if h, err = p.GetUint256(); err == nil {
hdr, err := s.chain.GetHeader(h)
if err == nil {
rt, err = s.chain.GetStateRoot(hdr.Index)
}
}
if err != nil {
return nil, response.NewRPCError("Unknown state root.", "", err)
}
return rt, nil
}
func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
scriptHash, err := ps.Value(0).GetUint160FromHex()
2020-01-30 08:03:44 +00:00
if err != nil {
return nil, response.ErrInvalidParams
2020-01-30 08:03:44 +00:00
}
scriptHash = scriptHash.Reverse()
key, err := ps.Value(1).GetBytesHex()
2020-01-30 08:03:44 +00:00
if err != nil {
return nil, response.ErrInvalidParams
2020-01-30 08:03:44 +00:00
}
item := s.chain.GetStorageItem(scriptHash.Reverse(), key)
if item == nil {
return nil, nil
}
return hex.EncodeToString(item.Value), nil
}
func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, *response.Error) {
txHash, err := reqParams.Value(0).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
tx, height, err := s.chain.GetTransaction(txHash)
if err != nil {
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
return nil, response.NewRPCError("Unknown transaction", err.Error(), err)
}
if reqParams.Value(1).GetBoolean() {
if height == math.MaxUint32 {
return result.NewTransactionOutputRaw(tx, nil, s.chain), nil
}
_header := s.chain.GetHeaderHash(int(height))
header, err := s.chain.GetHeader(_header)
if err != nil {
return nil, response.NewInvalidParamsError(err.Error(), err)
}
return result.NewTransactionOutputRaw(tx, header, s.chain), nil
}
return hex.EncodeToString(tx.Bytes()), nil
}
func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response.Error) {
h, err := ps.Value(0).GetUint256()
if err != nil {
return nil, response.ErrInvalidParams
}
_, height, err := s.chain.GetTransaction(h)
if err != nil || height == math.MaxUint32 {
return nil, response.NewRPCError("unknown transaction", "", nil)
}
return height, nil
}
func (s *Server) getTxOut(ps request.Params) (interface{}, *response.Error) {
h, err := ps.Value(0).GetUint256()
2020-02-06 12:02:03 +00:00
if err != nil {
return nil, response.ErrInvalidParams
2020-02-06 12:02:03 +00:00
}
num, err := ps.ValueWithType(1, request.NumberT).GetInt()
2020-02-06 12:02:03 +00:00
if err != nil || num < 0 {
return nil, response.ErrInvalidParams
2020-02-06 12:02:03 +00:00
}
ucs := s.chain.GetUnspentCoinState(h)
if ucs == nil {
return nil, response.NewInvalidParamsError("invalid tx hash", errors.New("unknown"))
2020-02-06 12:02:03 +00:00
}
if num >= len(ucs.States) {
return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index"))
2020-02-06 12:02:03 +00:00
}
if ucs.States[num].State&state.CoinSpent != 0 {
return nil, nil
}
return result.NewTxOutput(&ucs.States[num].Output), nil
2020-02-06 12:02:03 +00:00
}
2020-02-15 16:53:08 +00:00
// getContractState returns contract state (contract information, according to the contract script hash).
func (s *Server) getContractState(reqParams request.Params) (interface{}, *response.Error) {
2020-02-15 16:53:08 +00:00
var results interface{}
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
cs := s.chain.GetContractState(scriptHash)
if cs != nil {
results = result.NewContractState(cs)
2020-02-15 16:53:08 +00:00
} else {
return nil, response.NewRPCError("Unknown contract", "", nil)
2020-02-15 16:53:08 +00:00
}
return results, nil
}
func (s *Server) getAccountState(ps request.Params) (interface{}, *response.Error) {
return s.getAccountStateAux(ps, false)
}
func (s *Server) getUnspents(ps request.Params) (interface{}, *response.Error) {
return s.getAccountStateAux(ps, true)
}
// getAccountState returns account state either in short or full (unspents included) form.
func (s *Server) getAccountStateAux(reqParams request.Params, unspents bool) (interface{}, *response.Error) {
var resultsErr *response.Error
var results interface{}
param := reqParams.ValueWithType(0, request.StringT)
scriptHash, err := param.GetUint160FromAddress()
if err != nil {
return nil, response.ErrInvalidParams
}
as := s.chain.GetAccountState(scriptHash)
if as == nil {
as = state.NewAccount(scriptHash)
}
if unspents {
str, err := param.GetString()
if err != nil {
return nil, response.ErrInvalidParams
}
results = result.NewUnspents(as, s.chain, str)
} else {
results = result.NewAccountState(as)
}
return results, resultsErr
}
func (s *Server) getBlockTransferTx(ps request.Params) (interface{}, *response.Error) {
var (
res = make([]result.TransferTx, 0)
respErr *response.Error
)
hash, respErr := s.getBlockHashFromParam(ps.Value(0))
if respErr != nil {
return nil, respErr
}
block, err := s.chain.GetBlock(hash)
if err != nil {
return nil, response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
}
for _, tx := range block.Transactions {
var transfer = result.TransferTx{
TxID: tx.Hash(),
Timestamp: block.Timestamp,
Index: block.Index,
NetworkFee: s.chain.NetworkFee(tx).String(),
SystemFee: s.chain.SystemFee(tx).String(),
}
respErr = appendUTXOToTransferTx(&transfer, tx, s.chain)
if respErr != nil {
break
}
if tx.Type == transaction.InvocationType {
execRes, err := s.chain.GetAppExecResult(tx.Hash())
if err != nil {
respErr = response.NewInternalServerError(fmt.Sprintf("no application log for invocation tx %s", tx.Hash()), err)
break
}
if execRes.VMState != "HALT" {
continue
}
var index uint32
for _, note := range execRes.Events {
nepTr, err := state.NEP5TransferFromNotification(note, tx.Hash(), block.Index, block.Timestamp, index)
// It's OK for event to be something different from NEP5 transfer.
if err != nil {
continue
}
transfer.Events = append(transfer.Events, result.TransferTxEvent{
Asset: nepTr.Asset.StringLE(),
From: uint160ToString(nepTr.From),
To: uint160ToString(nepTr.To),
Value: nepTr.Amount.String(),
})
index++
}
}
if len(transfer.Elements) != 0 || len(transfer.Events) != 0 {
res = append(res, transfer)
}
}
if respErr != nil {
return nil, respErr
}
return res, nil
}
// getBlockSysFee returns the system fees of the block, based on the specified index.
func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) {
param := reqParams.ValueWithType(0, request.NumberT)
if param == nil {
return 0, response.ErrInvalidParams
}
num, err := s.blockHeightFromParam(param)
if err != nil {
return 0, response.NewRPCError("Invalid height", "", nil)
}
headerHash := s.chain.GetHeaderHash(num)
return util.Fixed8FromInt64(int64(s.chain.GetSystemFeeAmount(headerHash))), nil
}
// getBlockHeader returns the corresponding block header information according to the specified script hash.
func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *response.Error) {
hash, respErr := s.getBlockHashFromParam(reqParams.Value(0))
if respErr != nil {
return nil, respErr
}
verbose := reqParams.Value(1).GetBoolean()
h, err := s.chain.GetHeader(hash)
if err != nil {
return nil, response.NewRPCError("unknown block", "", nil)
}
if verbose {
return result.NewHeader(h, s.chain), nil
}
buf := io.NewBufBinWriter()
h.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return nil, response.NewInternalServerError("encoding error", buf.Err)
}
return hex.EncodeToString(buf.Bytes()), nil
}
// getUnclaimed returns unclaimed GAS amount of the specified address.
func (s *Server) getUnclaimed(ps request.Params) (interface{}, *response.Error) {
u, err := ps.ValueWithType(0, request.StringT).GetUint160FromAddress()
if err != nil {
return nil, response.ErrInvalidParams
}
acc := s.chain.GetAccountState(u)
if acc == nil {
return nil, response.NewInternalServerError("unknown account", nil)
}
res, errRes := result.NewUnclaimed(acc, s.chain)
if errRes != nil {
return nil, response.NewInternalServerError("can't create unclaimed response", errRes)
}
return res, nil
}
// getValidators returns the current NEO consensus nodes information and voting status.
func (s *Server) getValidators(_ request.Params) (interface{}, *response.Error) {
var validators keys.PublicKeys
validators, err := s.chain.GetValidators()
if err != nil {
return nil, response.NewRPCError("can't get validators", "", err)
}
enrollments, err := s.chain.GetEnrollments()
if err != nil {
return nil, response.NewRPCError("can't get enrollments", "", err)
}
var res []result.Validator
for _, v := range enrollments {
res = append(res, result.Validator{
PublicKey: *v.PublicKey,
Votes: v.Votes,
Active: validators.Contains(v.PublicKey),
})
}
return res, nil
}
// invoke implements the `invoke` RPC call.
func (s *Server) invoke(reqParams request.Params) (interface{}, *response.Error) {
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
slice, err := reqParams.ValueWithType(1, request.ArrayT).GetArray()
if err != nil {
return nil, response.ErrInvalidParams
}
hashesForVerifying, err := reqParams.ValueWithType(2, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
script, err := request.CreateInvocationScript(scriptHash, slice)
if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err)
}
return s.runScriptInVM(script, hashesForVerifying), nil
}
2020-08-06 16:27:15 +00:00
// invokeFunction implements the `invokefunction` RPC call.
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
var hashesForVerifying []util.Uint160
hashesForVerifyingIndex := len(reqParams)
if hashesForVerifyingIndex > 3 {
hashesForVerifying, err = reqParams.ValueWithType(3, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
hashesForVerifyingIndex--
}
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:hashesForVerifyingIndex])
if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err)
}
return s.runScriptInVM(script, hashesForVerifying), nil
}
// invokescript implements the `invokescript` RPC call.
func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
if len(reqParams) < 1 {
return nil, response.ErrInvalidParams
}
script, err := reqParams[0].GetBytesHex()
if err != nil {
return nil, response.ErrInvalidParams
}
hashesForVerifying, err := reqParams.ValueWithType(1, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
return s.runScriptInVM(script, hashesForVerifying), nil
}
// runScriptInVM runs given script in a new test VM and returns the invocation
// result.
func (s *Server) runScriptInVM(script []byte, scriptHashesForVerifying []util.Uint160) *result.Invoke {
var tx *transaction.Transaction
if count := len(scriptHashesForVerifying); count != 0 {
tx := new(transaction.Transaction)
tx.Attributes = make([]transaction.Attribute, count)
for i, a := range tx.Attributes {
a.Data = scriptHashesForVerifying[i].BytesBE()
a.Usage = transaction.Script
}
}
vm := s.chain.GetTestVM(tx)
vm.SetGasLimit(s.config.MaxGasInvoke)
vm.LoadScript(script)
_ = vm.Run()
result := &result.Invoke{
State: vm.State(),
GasConsumed: vm.GasConsumed().String(),
Script: hex.EncodeToString(script),
Stack: vm.Estack().ToContractParameters(),
}
return result
}
// submitBlock broadcasts a raw block over the NEO network.
func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.Error) {
blockBytes, err := reqParams.ValueWithType(0, request.StringT).GetBytesHex()
if err != nil {
return nil, response.ErrInvalidParams
}
b := block.Block{}
r := io.NewBinReaderFromBuf(blockBytes)
b.DecodeBinary(r)
if r.Err != nil {
return nil, response.ErrInvalidParams
}
err = s.chain.AddBlock(&b)
if err != nil {
switch err {
case core.ErrInvalidBlockIndex, core.ErrAlreadyExists:
return nil, response.ErrAlreadyExists
default:
return nil, response.ErrValidationFailed
}
}
return true, nil
}
func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, *response.Error) {
var resultsErr *response.Error
var results interface{}
if len(reqParams) < 1 {
return nil, response.ErrInvalidParams
} else if byteTx, err := reqParams[0].GetBytesHex(); err != nil {
return nil, response.ErrInvalidParams
} else {
r := io.NewBinReaderFromBuf(byteTx)
tx := &transaction.Transaction{}
tx.DecodeBinary(r)
if r.Err != nil {
return nil, response.ErrInvalidParams
}
relayReason := s.coreServer.RelayTxn(tx)
switch relayReason {
case network.RelaySucceed:
results = true
case network.RelayAlreadyExists:
resultsErr = response.ErrAlreadyExists
case network.RelayOutOfMemory:
resultsErr = response.ErrOutOfMemory
case network.RelayUnableToVerify:
resultsErr = response.ErrUnableToVerify
case network.RelayInvalid:
resultsErr = response.ErrValidationFailed
case network.RelayPolicyFail:
resultsErr = response.ErrPolicyFail
default:
resultsErr = response.ErrUnknown
}
}
return results, resultsErr
}
// subscribe handles subscription requests from websocket clients.
func (s *Server) subscribe(reqParams request.Params, sub *subscriber) (interface{}, *response.Error) {
streamName, err := reqParams.Value(0).GetString()
if err != nil {
return nil, response.ErrInvalidParams
}
event, err := response.GetEventIDFromString(streamName)
if err != nil || event == response.MissedEventID {
return nil, response.ErrInvalidParams
}
// Optional filter.
var filter interface{}
if p := reqParams.Value(1); p != nil {
// It doesn't accept filters.
if event == response.BlockEventID {
return nil, response.ErrInvalidParams
}
switch event {
case response.TransactionEventID:
if p.Type != request.TxFilterT {
return nil, response.ErrInvalidParams
}
case response.NotificationEventID:
if p.Type != request.NotificationFilterT {
return nil, response.ErrInvalidParams
}
case response.ExecutionEventID:
if p.Type != request.ExecutionFilterT {
return nil, response.ErrInvalidParams
}
}
filter = p.Value
}
s.subsLock.Lock()
defer s.subsLock.Unlock()
select {
case <-s.shutdown:
return nil, response.NewInternalServerError("server is shutting down", nil)
default:
}
var id int
for ; id < len(sub.feeds); id++ {
if sub.feeds[id].event == response.InvalidEventID {
break
}
}
if id == len(sub.feeds) {
return nil, response.NewInternalServerError("maximum number of subscriptions is reached", nil)
}
sub.feeds[id].event = event
sub.feeds[id].filter = filter
s.subscribeToChannel(event)
return strconv.FormatInt(int64(id), 10), nil
}
// subscribeToChannel subscribes RPC server to appropriate chain events if
// it's not yet subscribed for them. It's supposed to be called with s.subsLock
// taken by the caller.
func (s *Server) subscribeToChannel(event response.EventID) {
switch event {
case response.BlockEventID:
if s.blockSubs == 0 {
s.chain.SubscribeForBlocks(s.blockCh)
}
s.blockSubs++
case response.TransactionEventID:
if s.transactionSubs == 0 {
s.chain.SubscribeForTransactions(s.transactionCh)
}
s.transactionSubs++
case response.NotificationEventID:
if s.notificationSubs == 0 {
s.chain.SubscribeForNotifications(s.notificationCh)
}
s.notificationSubs++
case response.ExecutionEventID:
if s.executionSubs == 0 {
s.chain.SubscribeForExecutions(s.executionCh)
}
s.executionSubs++
}
}
// unsubscribe handles unsubscription requests from websocket clients.
func (s *Server) unsubscribe(reqParams request.Params, sub *subscriber) (interface{}, *response.Error) {
id, err := reqParams.Value(0).GetInt()
if err != nil || id < 0 {
return nil, response.ErrInvalidParams
}
s.subsLock.Lock()
defer s.subsLock.Unlock()
if len(sub.feeds) <= id || sub.feeds[id].event == response.InvalidEventID {
return nil, response.ErrInvalidParams
}
event := sub.feeds[id].event
sub.feeds[id].event = response.InvalidEventID
sub.feeds[id].filter = nil
s.unsubscribeFromChannel(event)
return true, nil
}
// unsubscribeFromChannel unsubscribes RPC server from appropriate chain events
// if there are no other subscribers for it. It's supposed to be called with
// s.subsLock taken by the caller.
func (s *Server) unsubscribeFromChannel(event response.EventID) {
switch event {
case response.BlockEventID:
s.blockSubs--
if s.blockSubs == 0 {
s.chain.UnsubscribeFromBlocks(s.blockCh)
}
case response.TransactionEventID:
s.transactionSubs--
if s.transactionSubs == 0 {
s.chain.UnsubscribeFromTransactions(s.transactionCh)
}
case response.NotificationEventID:
s.notificationSubs--
if s.notificationSubs == 0 {
s.chain.UnsubscribeFromNotifications(s.notificationCh)
}
case response.ExecutionEventID:
s.executionSubs--
if s.executionSubs == 0 {
s.chain.UnsubscribeFromExecutions(s.executionCh)
}
}
}
func (s *Server) handleSubEvents() {
b, err := json.Marshal(response.Notification{
JSONRPC: request.JSONRPCVersion,
Event: response.MissedEventID,
Payload: make([]interface{}, 0),
})
if err != nil {
s.log.Error("fatal: failed to marshal overflow event", zap.Error(err))
return
}
overflowMsg, err := websocket.NewPreparedMessage(websocket.TextMessage, b)
if err != nil {
s.log.Error("fatal: failed to prepare overflow message", zap.Error(err))
return
}
chloop:
for {
var resp = response.Notification{
JSONRPC: request.JSONRPCVersion,
Payload: make([]interface{}, 1),
}
var msg *websocket.PreparedMessage
select {
case <-s.shutdown:
break chloop
case b := <-s.blockCh:
resp.Event = response.BlockEventID
resp.Payload[0] = b
case execution := <-s.executionCh:
resp.Event = response.ExecutionEventID
resp.Payload[0] = result.NewApplicationLog(execution, util.Uint160{})
case notification := <-s.notificationCh:
resp.Event = response.NotificationEventID
resp.Payload[0] = result.StateEventToResultNotification(*notification)
case tx := <-s.transactionCh:
resp.Event = response.TransactionEventID
resp.Payload[0] = tx
}
s.subsLock.RLock()
subloop:
for sub := range s.subscribers {
if sub.overflown.Load() {
continue
}
for i := range sub.feeds {
if sub.feeds[i].Matches(&resp) {
if msg == nil {
b, err = json.Marshal(resp)
if err != nil {
s.log.Error("failed to marshal notification",
zap.Error(err),
zap.String("type", resp.Event.String()))
break subloop
}
msg, err = websocket.NewPreparedMessage(websocket.TextMessage, b)
if err != nil {
s.log.Error("failed to prepare notification message",
zap.Error(err),
zap.String("type", resp.Event.String()))
break subloop
}
}
select {
case sub.writer <- msg:
default:
sub.overflown.Store(true)
// MissedEvent is to be delivered eventually.
go func(sub *subscriber) {
sub.writer <- overflowMsg
sub.overflown.Store(false)
}(sub)
}
// The message is sent only once per subscriber.
break
}
}
}
s.subsLock.RUnlock()
}
// It's important to do it with lock held because no subscription routine
// should be running concurrently to this one. And even if one is to run
// after unlock, it'll see closed s.shutdown and won't subscribe.
s.subsLock.Lock()
// There might be no subscription in reality, but it's not a problem as
// core.Blockchain allows unsubscribing non-subscribed channels.
s.chain.UnsubscribeFromBlocks(s.blockCh)
s.chain.UnsubscribeFromTransactions(s.transactionCh)
s.chain.UnsubscribeFromNotifications(s.notificationCh)
s.chain.UnsubscribeFromExecutions(s.executionCh)
s.subsLock.Unlock()
drainloop:
for {
select {
case <-s.blockCh:
case <-s.executionCh:
case <-s.notificationCh:
case <-s.transactionCh:
default:
break drainloop
}
}
// It's not required closing these, but since they're drained already
// this is safe and it also allows to give a signal to Shutdown routine.
close(s.blockCh)
close(s.transactionCh)
close(s.notificationCh)
close(s.executionCh)
}
func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.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
}
func (s *Server) packResponseToRaw(r *request.In, result interface{}, respErr *response.Error) response.Raw {
resp := response.Raw{
HeaderAndError: response.HeaderAndError{
Header: response.Header{
JSONRPC: r.JSONRPC,
ID: r.RawID,
},
},
}
if respErr != nil {
resp.Error = respErr
} else {
resJSON, err := json.Marshal(result)
if err != nil {
s.log.Error("failed to marshal result",
zap.Error(err),
zap.String("method", r.Method))
resp.Error = response.NewInternalServerError("failed to encode result", err)
} else {
resp.Result = resJSON
}
}
return resp
}
// logRequestError is a request error logger.
func (s *Server) logRequestError(r *request.Request, jsonErr *response.Error) {
logFields := []zap.Field{
zap.Error(jsonErr.Cause),
}
if r.In != nil {
logFields = append(logFields, zap.String("method", r.In.Method))
params, err := r.In.Params()
if err == nil {
logFields = append(logFields, zap.Any("params", params))
}
}
s.log.Error("Error encountered with rpc request", logFields...)
}
// writeHTTPErrorResponse writes an error response to the ResponseWriter.
func (s *Server) writeHTTPErrorResponse(r *request.In, w http.ResponseWriter, jsonErr *response.Error) {
resp := s.packResponseToRaw(r, nil, jsonErr)
s.writeHTTPServerResponse(&request.Request{In: r}, w, resp)
}
func (s *Server) writeHTTPServerResponse(r *request.Request, w http.ResponseWriter, resp response.AbstractResult) {
// Errors can happen in many places and we can only catch ALL of them here.
resp.RunForErrors(func(jsonErr *response.Error) {
s.logRequestError(r, jsonErr)
})
if r.In != nil {
resp := resp.(response.Raw)
if resp.Error != nil {
w.WriteHeader(resp.Error.HTTPCode)
}
}
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 {
switch {
case r.In != nil:
s.log.Error("Error encountered while encoding response",
zap.String("err", err.Error()),
zap.String("method", r.In.Method))
case r.Batch != nil:
s.log.Error("Error encountered while encoding batch response",
zap.String("err", err.Error()))
}
}
}
// 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
}