2020-02-17 12:17:02 +00:00
package server
2018-03-23 20:36:59 +00:00
import (
"context"
2019-02-20 17:39:32 +00:00
"encoding/hex"
2020-01-14 12:02:38 +00:00
"encoding/json"
2020-08-06 14:44:08 +00:00
"errors"
2018-03-23 20:36:59 +00:00
"fmt"
2020-03-05 12:39:53 +00:00
"math"
2020-07-09 09:57:24 +00:00
"math/big"
2020-03-10 11:56:18 +00:00
"net"
2018-03-23 20:36:59 +00:00
"net/http"
2019-01-25 11:20:35 +00:00
"strconv"
2020-05-10 22:00:19 +00:00
"sync"
2020-04-29 12:25:58 +00:00
"time"
2018-03-23 20:36:59 +00:00
2020-04-29 12:25:58 +00:00
"github.com/gorilla/websocket"
2020-06-18 09:00:51 +00:00
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/core"
2020-03-02 17:01:32 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/block"
2020-04-29 12:25:58 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
2020-03-05 14:48:30 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2020-03-03 14:21:42 +00:00
"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"
2020-04-29 12:25:58 +00:00
"github.com/nspcc-dev/neo-go/pkg/rpc"
2020-03-03 14:21:42 +00:00
"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"
2020-03-11 12:07:14 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2019-12-30 08:44:52 +00:00
"go.uber.org/zap"
2018-03-23 20:36:59 +00:00
)
type (
// Server represents the JSON-RPC 2.0 server.
Server struct {
* http . Server
2020-04-08 10:56:04 +00:00
chain blockchainer . Blockchainer
2020-03-25 15:30:21 +00:00
config rpc . Config
2020-06-18 09:00:51 +00:00
network netmode . Magic
2018-03-23 20:36:59 +00:00
coreServer * network . Server
2019-12-30 08:44:52 +00:00
log * zap . Logger
2020-03-10 11:56:18 +00:00
https * http . Server
2020-05-10 22:00:19 +00:00
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
2018-03-23 20:36:59 +00:00
}
)
2020-04-29 12:25:58 +00:00
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
2020-05-10 22:00:19 +00:00
// Maximum number of subscribers per Server. Each websocket client is
// treated like subscriber, so technically it's a limit on websocket
// connections.
maxSubscribers = 64
2020-09-14 14:48:17 +00:00
// Maximum number of elements for get*transfers requests.
maxTransfersLimit = 1000
2020-04-29 12:25:58 +00:00
)
2020-04-28 19:35:19 +00:00
var rpcHandlers = map [ string ] func ( * Server , request . Params ) ( interface { } , * response . Error ) {
2020-10-01 12:26:54 +00:00
"getapplicationlog" : ( * Server ) . getApplicationLog ,
"getbestblockhash" : ( * Server ) . getBestBlockHash ,
"getblock" : ( * Server ) . getBlock ,
"getblockcount" : ( * Server ) . getBlockCount ,
"getblockhash" : ( * Server ) . getBlockHash ,
"getblockheader" : ( * Server ) . getBlockHeader ,
"getblocksysfee" : ( * Server ) . getBlockSysFee ,
"getcommittee" : ( * Server ) . getCommittee ,
"getconnectioncount" : ( * Server ) . getConnectionCount ,
"getcontractstate" : ( * Server ) . getContractState ,
"getnep5balances" : ( * Server ) . getNEP5Balances ,
"getnep5transfers" : ( * Server ) . getNEP5Transfers ,
"getpeers" : ( * Server ) . getPeers ,
"getrawmempool" : ( * Server ) . getRawMempool ,
"getrawtransaction" : ( * Server ) . getrawtransaction ,
"getstorage" : ( * Server ) . getStorage ,
"gettransactionheight" : ( * Server ) . getTransactionHeight ,
"getunclaimedgas" : ( * Server ) . getUnclaimedGas ,
"getnextblockvalidators" : ( * Server ) . getNextBlockValidators ,
"getversion" : ( * Server ) . getVersion ,
"invokefunction" : ( * Server ) . invokeFunction ,
"invokescript" : ( * Server ) . invokescript ,
"sendrawtransaction" : ( * Server ) . sendrawtransaction ,
"submitblock" : ( * Server ) . submitBlock ,
"validateaddress" : ( * Server ) . validateAddress ,
2020-03-13 07:29:49 +00:00
}
2020-05-10 22:00:19 +00:00
var rpcWsHandlers = map [ string ] func ( * Server , request . Params , * subscriber ) ( interface { } , * response . Error ) {
"subscribe" : ( * Server ) . subscribe ,
"unsubscribe" : ( * Server ) . unsubscribe ,
}
2020-04-28 19:35:19 +00:00
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 )
2019-11-21 13:42:51 +00:00
}
2018-03-25 10:13:47 +00:00
2020-04-29 12:25:58 +00:00
// upgrader is a no-op websocket.Upgrader that reuses HTTP server buffers and
// doesn't set any Error function.
var upgrader = websocket . Upgrader { }
2020-02-17 12:17:02 +00:00
// New creates a new Server struct.
2020-04-08 10:56:04 +00:00
func New ( chain blockchainer . Blockchainer , conf rpc . Config , coreServer * network . Server , log * zap . Logger ) Server {
2019-11-01 10:23:46 +00:00
httpServer := & http . Server {
Addr : conf . Address + ":" + strconv . FormatUint ( uint64 ( conf . Port ) , 10 ) ,
}
2020-03-10 11:56:18 +00:00
var tlsServer * http . Server
if cfg := conf . TLSConfig ; cfg . Enabled {
tlsServer = & http . Server {
Addr : net . JoinHostPort ( cfg . Address , strconv . FormatUint ( uint64 ( cfg . Port ) , 10 ) ) ,
}
}
2018-03-23 20:36:59 +00:00
return Server {
2019-11-01 10:23:46 +00:00
Server : httpServer ,
2018-03-23 20:36:59 +00:00
chain : chain ,
2019-11-01 10:23:46 +00:00
config : conf ,
2020-06-18 09:00:51 +00:00
network : chain . GetConfig ( ) . Magic ,
2018-03-23 20:36:59 +00:00
coreServer : coreServer ,
2019-12-30 08:44:52 +00:00
log : log ,
2020-03-10 11:56:18 +00:00
https : tlsServer ,
2020-05-10 22:00:19 +00:00
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 ) ,
2018-03-23 20:36:59 +00:00
}
}
2020-05-09 20:59:21 +00:00
// 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.
2018-03-23 20:36:59 +00:00
func ( s * Server ) Start ( errChan chan error ) {
2019-11-01 10:23:46 +00:00
if ! s . config . Enabled {
2019-12-30 08:44:52 +00:00
s . log . Info ( "RPC server is not enabled" )
2019-11-01 10:23:46 +00:00
return
}
2020-04-28 13:56:33 +00:00
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 ) )
2018-03-23 20:36:59 +00:00
2020-05-10 22:00:19 +00:00
go s . handleSubEvents ( )
2020-03-10 11:56:18 +00:00
if cfg := s . config . TLSConfig ; cfg . Enabled {
2020-04-28 13:56:33 +00:00
s . https . Handler = http . HandlerFunc ( s . handleHTTPRequest )
2020-03-10 11:56:18 +00:00
s . log . Info ( "starting rpc-server (https)" , zap . String ( "endpoint" , s . https . Addr ) )
go func ( ) {
2020-08-31 12:35:55 +00:00
ln , err := net . Listen ( "tcp" , s . https . Addr )
if err != nil {
errChan <- err
return
}
s . https . Addr = ln . Addr ( ) . String ( )
err = s . https . ServeTLS ( ln , cfg . CertFile , cfg . KeyFile )
2020-06-25 16:31:45 +00:00
if err != http . ErrServerClosed {
2020-03-10 11:56:18 +00:00
s . log . Error ( "failed to start TLS RPC server" , zap . Error ( err ) )
2020-06-25 16:31:45 +00:00
errChan <- err
2020-03-10 11:56:18 +00:00
}
} ( )
}
2020-08-31 12:35:55 +00:00
ln , err := net . Listen ( "tcp" , s . Addr )
if err != nil {
2020-06-25 16:31:45 +00:00
errChan <- err
2020-08-31 12:35:55 +00:00
return
2020-03-10 11:56:18 +00:00
}
2020-08-31 12:35:55 +00:00
s . Addr = ln . Addr ( ) . String ( ) // set Addr to the actual address
go func ( ) {
err = s . Serve ( ln )
if err != http . ErrServerClosed {
s . log . Error ( "failed to start RPC server" , zap . Error ( err ) )
errChan <- err
}
} ( )
2018-03-23 20:36:59 +00:00
}
2019-10-22 14:56:03 +00:00
// Shutdown overrides the http.Server Shutdown
2018-03-23 20:36:59 +00:00
// method.
func ( s * Server ) Shutdown ( ) error {
2020-03-10 11:56:18 +00:00
var httpsErr error
2020-05-10 22:00:19 +00:00
// Signal to websocket writer routines and handleSubEvents.
close ( s . shutdown )
2020-03-10 11:56:18 +00:00
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 ) )
2020-03-10 11:56:18 +00:00
err := s . Server . Shutdown ( context . Background ( ) )
2020-05-10 22:00:19 +00:00
// Wait for handleSubEvents to finish.
<- s . executionCh
2020-03-10 11:56:18 +00:00
if err == nil {
return httpsErr
}
return err
2018-03-23 20:36:59 +00:00
}
2020-04-28 13:56:33 +00:00
func ( s * Server ) handleHTTPRequest ( w http . ResponseWriter , httpRequest * http . Request ) {
2020-05-10 22:00:19 +00:00
req := request . NewIn ( )
2020-04-29 12:25:58 +00:00
if httpRequest . URL . Path == "/ws" && httpRequest . Method == "GET" {
2020-05-10 22:00:19 +00:00
// 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 (
req ,
w ,
response . NewInternalServerError ( "websocket users limit reached" , nil ) ,
)
return
}
2020-04-29 12:25:58 +00:00
ws , err := upgrader . Upgrade ( w , httpRequest , nil )
if err != nil {
s . log . Info ( "websocket connection upgrade failed" , zap . Error ( err ) )
return
}
2020-08-31 15:27:32 +00:00
resChan := make ( chan response . Abstract )
2020-05-10 22:00:19 +00:00
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 )
2020-04-29 12:25:58 +00:00
return
}
2018-03-23 20:36:59 +00:00
if httpRequest . Method != "POST" {
2020-04-28 19:56:19 +00:00
s . writeHTTPErrorResponse (
2019-12-30 08:44:52 +00:00
req ,
2018-03-23 20:36:59 +00:00
w ,
2020-01-14 12:02:38 +00:00
response . NewInvalidParamsError (
2018-03-23 20:36:59 +00:00
fmt . Sprintf ( "Invalid method '%s', please retry with 'POST'" , httpRequest . Method ) , nil ,
) ,
)
return
}
err := req . DecodeData ( httpRequest . Body )
if err != nil {
2020-04-28 19:56:19 +00:00
s . writeHTTPErrorResponse ( req , w , response . NewParseError ( "Problem parsing JSON-RPC request body" , err ) )
2018-03-23 20:36:59 +00:00
return
}
2020-05-10 22:00:19 +00:00
resp := s . handleRequest ( req , nil )
2020-04-28 19:56:19 +00:00
s . writeHTTPServerResponse ( req , w , resp )
2020-04-28 13:56:33 +00:00
}
2020-08-31 15:27:32 +00:00
func ( s * Server ) handleRequest ( req * request . In , sub * subscriber ) response . Abstract {
2020-05-10 22:00:19 +00:00
var res interface { }
var resErr * response . Error
2018-03-23 20:36:59 +00:00
reqParams , err := req . Params ( )
if err != nil {
2020-08-31 15:27:32 +00:00
return s . packResponse ( req , nil , response . NewInvalidParamsError ( "Problem parsing request parameters" , err ) )
2018-03-23 20:36:59 +00:00
}
2020-01-13 13:45:36 +00:00
s . log . Debug ( "processing rpc request" ,
2019-12-30 08:44:52 +00:00
zap . String ( "method" , req . Method ) ,
2020-10-08 13:29:30 +00:00
zap . Stringer ( "params" , reqParams ) )
2018-03-23 20:36:59 +00:00
2020-03-12 17:36:36 +00:00
incCounter ( req . Method )
2020-05-10 22:00:19 +00:00
resErr = response . NewMethodNotFoundError ( fmt . Sprintf ( "Method '%s' not supported" , req . Method ) , nil )
2020-03-13 07:29:49 +00:00
handler , ok := rpcHandlers [ req . Method ]
2020-05-10 22:00:19 +00:00
if ok {
res , resErr = handler ( s , * reqParams )
} else if sub != nil {
handler , ok := rpcWsHandlers [ req . Method ]
if ok {
res , resErr = handler ( s , * reqParams , sub )
}
2018-03-23 20:36:59 +00:00
}
2020-08-31 15:27:32 +00:00
return s . packResponse ( req , res , resErr )
2020-03-13 07:01:49 +00:00
}
2020-08-31 15:27:32 +00:00
func ( s * Server ) handleWsWrites ( ws * websocket . Conn , resChan <- chan response . Abstract , subChan <- chan * websocket . PreparedMessage ) {
2020-04-29 12:25:58 +00:00
pingTicker := time . NewTicker ( wsPingPeriod )
2020-05-12 19:38:29 +00:00
eventloop :
2020-04-29 12:25:58 +00:00
for {
select {
2020-05-10 22:00:19 +00:00
case <- s . shutdown :
2020-05-12 19:38:29 +00:00
break eventloop
2020-05-10 22:00:19 +00:00
case event , ok := <- subChan :
if ! ok {
2020-05-12 19:38:29 +00:00
break eventloop
2020-05-10 22:00:19 +00:00
}
ws . SetWriteDeadline ( time . Now ( ) . Add ( wsWriteLimit ) )
if err := ws . WritePreparedMessage ( event ) ; err != nil {
2020-05-12 19:38:29 +00:00
break eventloop
2020-05-10 22:00:19 +00:00
}
2020-04-29 12:25:58 +00:00
case res , ok := <- resChan :
if ! ok {
2020-05-12 19:38:29 +00:00
break eventloop
2020-04-29 12:25:58 +00:00
}
ws . SetWriteDeadline ( time . Now ( ) . Add ( wsWriteLimit ) )
if err := ws . WriteJSON ( res ) ; err != nil {
2020-05-12 19:38:29 +00:00
break eventloop
2020-04-29 12:25:58 +00:00
}
case <- pingTicker . C :
ws . SetWriteDeadline ( time . Now ( ) . Add ( wsWriteLimit ) )
if err := ws . WriteMessage ( websocket . PingMessage , [ ] byte { } ) ; err != nil {
2020-05-12 19:38:29 +00:00
break eventloop
2020-04-29 12:25:58 +00:00
}
}
}
2020-05-12 19:38:29 +00:00
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
}
}
2020-04-29 12:25:58 +00:00
}
2020-08-31 15:27:32 +00:00
func ( s * Server ) handleWsReads ( ws * websocket . Conn , resChan chan <- response . Abstract , subscr * subscriber ) {
2020-04-29 12:25:58 +00:00
ws . SetReadLimit ( wsReadLimit )
ws . SetReadDeadline ( time . Now ( ) . Add ( wsPongLimit ) )
ws . SetPongHandler ( func ( string ) error { ws . SetReadDeadline ( time . Now ( ) . Add ( wsPongLimit ) ) ; return nil } )
2020-05-10 22:00:19 +00:00
requestloop :
2020-04-29 12:25:58 +00:00
for {
req := new ( request . In )
err := ws . ReadJSON ( req )
if err != nil {
break
}
2020-05-10 22:00:19 +00:00
res := s . handleRequest ( req , subscr )
2020-04-29 12:25:58 +00:00
if res . Error != nil {
s . logRequestError ( req , res . Error )
}
2020-05-10 22:00:19 +00:00
select {
case <- s . shutdown :
break requestloop
case resChan <- res :
}
}
s . subsLock . Lock ( )
delete ( s . subscribers , subscr )
for _ , e := range subscr . feeds {
2020-05-13 14:13:33 +00:00
if e . event != response . InvalidEventID {
s . unsubscribeFromChannel ( e . event )
2020-05-10 22:00:19 +00:00
}
2020-04-29 12:25:58 +00:00
}
2020-05-10 22:00:19 +00:00
s . subsLock . Unlock ( )
2020-04-29 12:25:58 +00:00
close ( resChan )
ws . Close ( )
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getBestBlockHash ( _ request . Params ) ( interface { } , * response . Error ) {
2020-03-13 07:14:48 +00:00
return "0x" + s . chain . CurrentBlockHash ( ) . StringLE ( ) , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getBlockCount ( _ request . Params ) ( interface { } , * response . Error ) {
2020-03-13 07:14:48 +00:00
return s . chain . BlockHeight ( ) + 1 , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getConnectionCount ( _ request . Params ) ( interface { } , * response . Error ) {
2020-03-13 07:14:48 +00:00
return s . coreServer . PeerCount ( ) , nil
}
2020-06-05 13:02:55 +00:00
func ( s * Server ) blockHashFromParam ( param * request . Param ) ( util . Uint256 , * response . Error ) {
2020-03-13 07:08:30 +00:00
var hash util . Uint256
2020-06-04 11:58:47 +00:00
if param == nil {
return hash , response . ErrInvalidParams
}
2020-03-13 07:08:30 +00:00
switch param . Type {
case request . StringT :
var err error
hash , err = param . GetUint256 ( )
if err != nil {
2020-06-05 13:02:55 +00:00
return hash , response . ErrInvalidParams
2020-03-13 07:08:30 +00:00
}
case request . NumberT :
num , err := s . blockHeightFromParam ( param )
if err != nil {
2020-06-05 13:02:55 +00:00
return hash , response . ErrInvalidParams
2020-03-13 07:08:30 +00:00
}
hash = s . chain . GetHeaderHash ( num )
default :
2020-06-05 13:02:55 +00:00
return hash , response . ErrInvalidParams
}
return hash , nil
}
func ( s * Server ) getBlock ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
param := reqParams . Value ( 0 )
2020-06-05 13:02:55 +00:00
hash , respErr := s . blockHashFromParam ( param )
if respErr != nil {
return nil , respErr
}
2020-03-13 07:08:30 +00:00
block , err := s . chain . GetBlock ( hash )
if err != nil {
return nil , response . NewInternalServerError ( fmt . Sprintf ( "Problem locating block with hash: %s" , hash ) , err )
}
2020-06-04 12:43:37 +00:00
if reqParams . Value ( 1 ) . GetBoolean ( ) {
2020-03-13 07:08:30 +00:00
return result . NewBlock ( block , s . chain ) , nil
}
writer := io . NewBufBinWriter ( )
block . EncodeBinary ( writer . BinWriter )
return hex . EncodeToString ( writer . Bytes ( ) ) , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getBlockHash ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
param := reqParams . ValueWithType ( 0 , request . NumberT )
if param == nil {
2020-03-13 07:06:52 +00:00
return nil , response . ErrInvalidParams
}
num , err := s . blockHeightFromParam ( param )
if err != nil {
return nil , response . ErrInvalidParams
}
return s . chain . GetHeaderHash ( num ) , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getVersion ( _ request . Params ) ( interface { } , * response . Error ) {
2020-06-10 07:01:21 +00:00
port , err := s . coreServer . Port ( )
if err != nil {
return nil , response . NewInternalServerError ( "Cannot fetch tcp port" , err )
}
2020-03-13 07:04:57 +00:00
return result . Version {
2020-10-14 13:52:49 +00:00
Magic : s . network ,
2020-06-10 07:01:21 +00:00
TCPPort : port ,
2020-03-13 07:04:57 +00:00
Nonce : s . coreServer . ID ( ) ,
UserAgent : s . coreServer . UserAgent ,
} , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getPeers ( _ request . Params ) ( interface { } , * response . Error ) {
2020-03-13 07:03:58 +00:00
peers := result . NewGetPeers ( )
peers . AddUnconnected ( s . coreServer . UnconnectedPeers ( ) )
peers . AddConnected ( s . coreServer . ConnectedPeers ( ) )
peers . AddBad ( s . coreServer . BadPeers ( ) )
return peers , nil
}
2020-07-27 14:27:21 +00:00
func ( s * Server ) getRawMempool ( reqParams request . Params ) ( interface { } , * response . Error ) {
verbose := reqParams . Value ( 0 ) . GetBoolean ( )
2020-03-13 07:03:01 +00:00
mp := s . chain . GetMemPool ( )
hashList := make ( [ ] util . Uint256 , 0 )
for _ , item := range mp . GetVerifiedTransactions ( ) {
2020-06-05 16:01:10 +00:00
hashList = append ( hashList , item . Hash ( ) )
2020-03-13 07:03:01 +00:00
}
2020-07-27 14:27:21 +00:00
if ! verbose {
return hashList , nil
}
return result . RawMempool {
Height : s . chain . BlockHeight ( ) ,
Verified : hashList ,
} , nil
2020-03-13 07:03:01 +00:00
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) validateAddress ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
param := reqParams . Value ( 0 )
if param == nil {
2020-03-13 07:01:49 +00:00
return nil , response . ErrInvalidParams
}
return validateAddress ( param . Value ) , nil
2018-03-25 10:13:47 +00:00
}
2020-09-21 11:08:15 +00:00
// getApplicationLog returns the contract log based on the specified txid or blockid.
2020-04-28 19:35:19 +00:00
func ( s * Server ) getApplicationLog ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-09-21 11:08:15 +00:00
hash , err := reqParams . Value ( 0 ) . GetUint256 ( )
2020-02-21 14:56:28 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-09-21 11:08:15 +00:00
appExecResult , err := s . chain . GetAppExecResult ( hash )
2020-02-21 14:56:28 +00:00
if err != nil {
2020-09-21 11:08:15 +00:00
return nil , response . NewRPCError ( "Unknown transaction or block" , "" , err )
2020-02-21 14:56:28 +00:00
}
2020-09-03 16:58:50 +00:00
return appExecResult , nil
2020-02-21 14:56:28 +00:00
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getNEP5Balances ( ps request . Params ) ( interface { } , * response . Error ) {
2020-07-03 15:10:07 +00:00
u , err := ps . Value ( 0 ) . GetUint160FromAddressOrHex ( )
2020-03-05 11:50:06 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-03-11 15:22:46 +00:00
as := s . chain . GetNEP5Balances ( u )
2020-03-11 12:03:20 +00:00
bs := & result . NEP5Balances {
Address : address . Uint160ToString ( u ) ,
Balances : [ ] result . NEP5Balance { } ,
}
2020-03-05 11:50:06 +00:00
if as != nil {
2020-07-29 12:09:59 +00:00
cache := make ( map [ int32 ] decimals )
2020-07-28 09:23:58 +00:00
for id , bal := range as . Trackers {
2020-07-29 12:09:59 +00:00
dec , err := s . getDecimals ( id , cache )
2020-07-28 09:23:58 +00:00
if err != nil {
continue
}
2020-07-29 12:09:59 +00:00
amount := amountToString ( & bal . Balance , dec . Value )
2020-03-05 11:50:06 +00:00
bs . Balances = append ( bs . Balances , result . NEP5Balance {
2020-07-29 12:09:59 +00:00
Asset : dec . Hash ,
2020-03-05 11:50:06 +00:00
Amount : amount ,
LastUpdated : bal . LastUpdatedBlock ,
} )
}
}
return bs , nil
}
2020-09-12 21:12:45 +00:00
func getTimestampsAndLimit ( ps request . Params , index int ) ( uint64 , uint64 , int , int , error ) {
2020-08-07 06:42:44 +00:00
var start , end uint64
2020-09-12 21:12:45 +00:00
var limit , page int
2020-09-14 14:48:17 +00:00
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
}
2020-09-11 19:33:17 +00:00
if pLimit != nil {
l , err := pLimit . GetInt ( )
2020-08-07 06:42:44 +00:00
if err != nil {
2020-09-12 21:12:45 +00:00
return 0 , 0 , 0 , 0 , err
2020-08-07 06:42:44 +00:00
}
2020-09-11 19:33:17 +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" )
2020-09-11 19:33:17 +00:00
}
2020-09-14 14:48:17 +00:00
if l > maxTransfersLimit {
return 0 , 0 , 0 , 0 , errors . New ( "too big limit requested" )
}
2020-09-11 19:33:17 +00:00
limit = l
2020-08-07 06:42:44 +00:00
}
2020-09-11 19:33:17 +00:00
if pEnd != nil {
val , err := pEnd . GetInt ( )
2020-08-07 06:42:44 +00:00
if err != nil {
2020-09-12 21:12:45 +00:00
return 0 , 0 , 0 , 0 , err
2020-08-07 06:42:44 +00:00
}
end = uint64 ( val )
2020-09-11 19:33:17 +00:00
} else {
end = uint64 ( time . Now ( ) . Unix ( ) * 1000 )
2020-08-07 06:42:44 +00:00
}
2020-09-11 19:33:17 +00:00
if pStart != nil {
val , err := pStart . GetInt ( )
2020-09-08 09:56:52 +00:00
if err != nil {
2020-09-12 21:12:45 +00:00
return 0 , 0 , 0 , 0 , err
2020-09-08 09:56:52 +00:00
}
2020-09-11 19:33:17 +00:00
start = uint64 ( val )
} else {
start = uint64 ( time . Now ( ) . Add ( - time . Hour * 24 * 7 ) . Unix ( ) * 1000 )
2020-09-08 09:56:52 +00:00
}
2020-09-12 21:12:45 +00:00
return start , end , limit , page , nil
2020-08-07 06:42:44 +00:00
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getNEP5Transfers ( ps request . Params ) ( interface { } , * response . Error ) {
2020-07-03 15:25:18 +00:00
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 )
2020-08-07 06:42:44 +00:00
if err != nil {
return nil , response . NewInvalidParamsError ( err . Error ( ) , err )
}
2020-03-11 12:03:20 +00:00
bs := & result . NEP5Transfers {
Address : address . Uint160ToString ( u ) ,
Received : [ ] result . NEP5Transfer { } ,
Sent : [ ] result . NEP5Transfer { } ,
}
2020-07-29 12:09:59 +00:00
cache := make ( map [ int32 ] decimals )
2020-09-12 21:12:45 +00:00
var resCount , frameCount int
2020-09-08 12:29:07 +00:00
err = s . chain . ForEachNEP5Transfer ( u , func ( tr * state . NEP5Transfer ) ( bool , error ) {
2020-09-12 21:12:45 +00:00
// Iterating from newest to oldest, not yet reached required
// time frame, continue looping.
2020-09-08 12:29:07 +00:00
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 {
2020-09-08 12:29:07 +00:00
return false , nil
2020-08-07 06:42:44 +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-07-29 12:09:59 +00:00
d , err := s . getDecimals ( tr . Asset , cache )
2020-07-28 16:05:16 +00:00
if err != nil {
2020-09-08 12:29:07 +00:00
return false , err
2020-07-28 16:05:16 +00:00
}
2020-09-12 21:12:45 +00:00
2020-03-05 12:16:03 +00:00
transfer := result . NEP5Transfer {
Timestamp : tr . Timestamp ,
2020-07-29 12:09:59 +00:00
Asset : d . Hash ,
2020-03-05 12:16:03 +00:00
Index : tr . Block ,
TxHash : tr . Tx ,
}
2020-07-09 09:57:24 +00:00
if tr . Amount . Sign ( ) > 0 { // token was received
2020-07-29 12:09:59 +00:00
transfer . Amount = amountToString ( & tr . Amount , d . Value )
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 {
transfer . Amount = amountToString ( new ( big . Int ) . Neg ( & tr . Amount ) , d . Value )
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
}
2020-09-08 12:29:07 +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
}
2020-07-09 09:57:24 +00:00
func amountToString ( amount * big . Int , decimals int64 ) string {
2020-03-05 12:39:53 +00:00
if decimals == 0 {
2020-07-09 09:57:24 +00:00
return amount . String ( )
2020-03-05 12:39:53 +00:00
}
pow := int64 ( math . Pow10 ( int ( decimals ) ) )
2020-07-09 09:57:24 +00:00
q , r := new ( big . Int ) . DivMod ( amount , big . NewInt ( pow ) , new ( big . Int ) )
if r . Sign ( ) == 0 {
return q . String ( )
2020-03-05 12:39:53 +00:00
}
fs := fmt . Sprintf ( "%%d.%%0%dd" , decimals )
return fmt . Sprintf ( fs , q , r )
}
2020-07-29 12:09:59 +00:00
// decimals represents decimals value for the contract with the specified scripthash.
type decimals struct {
Hash util . Uint160
Value int64
}
func ( s * Server ) getDecimals ( contractID int32 , cache map [ int32 ] decimals ) ( decimals , error ) {
if d , ok := cache [ contractID ] ; ok {
2020-03-05 12:39:53 +00:00
return d , nil
}
2020-07-29 12:09:59 +00:00
h , err := s . chain . GetContractScriptHash ( contractID )
if err != nil {
return decimals { } , err
}
2020-03-11 12:07:14 +00:00
script , err := request . CreateFunctionInvocationScript ( h , request . Params {
{
Type : request . StringT ,
Value : "decimals" ,
} ,
{
Type : request . ArrayT ,
Value : [ ] request . Param { } ,
} ,
} )
if err != nil {
2020-08-06 16:09:57 +00:00
return decimals { } , fmt . Errorf ( "can't create script: %w" , err )
2020-03-05 12:39:53 +00:00
}
2020-06-10 11:45:55 +00:00
res := s . runScriptInVM ( script , nil )
2020-10-05 13:33:20 +00:00
if res == nil {
return decimals { } , fmt . Errorf ( "execution error: no result" )
}
if res . State != "HALT" {
return decimals { } , fmt . Errorf ( "execution error: bad VM state %s due to an error %s" , res . State , res . FaultException )
}
if len ( res . Stack ) == 0 {
return decimals { } , fmt . Errorf ( "execution error: empty stack" )
2020-03-05 12:39:53 +00:00
}
2020-03-11 12:07:14 +00:00
2020-07-29 12:09:59 +00:00
d := decimals { Hash : h }
2020-07-31 12:26:28 +00:00
bi , err := res . Stack [ len ( res . Stack ) - 1 ] . TryInteger ( )
if err != nil {
return decimals { } , err
2020-03-05 12:39:53 +00:00
}
2020-07-31 12:26:28 +00:00
d . Value = bi . Int64 ( )
2020-07-29 12:09:59 +00:00
if d . Value < 0 {
return d , errors . New ( "incorrect result: negative result" )
2020-03-05 12:39:53 +00:00
}
2020-07-29 12:09:59 +00:00
cache [ contractID ] = d
2020-03-05 12:39:53 +00:00
return d , nil
}
2020-06-18 10:50:30 +00:00
func ( s * Server ) contractIDFromParam ( param * request . Param ) ( int32 , * response . Error ) {
var result int32
2020-06-04 11:58:47 +00:00
if param == nil {
return 0 , response . ErrInvalidParams
}
2020-06-18 10:50:30 +00:00
switch param . Type {
case request . StringT :
var err error
scriptHash , err := param . GetUint160FromHex ( )
if err != nil {
return 0 , response . ErrInvalidParams
}
cs := s . chain . GetContractState ( scriptHash )
if cs == nil {
return 0 , response . ErrUnknown
}
result = cs . ID
case request . NumberT :
id , err := param . GetInt ( )
if err != nil {
return 0 , response . ErrInvalidParams
}
result = int32 ( id )
default :
return 0 , response . ErrInvalidParams
}
return result , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getStorage ( ps request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
id , rErr := s . contractIDFromParam ( ps . Value ( 0 ) )
2020-06-18 10:50:30 +00:00
if rErr == response . ErrUnknown {
return nil , nil
}
if rErr != nil {
return nil , rErr
2020-01-30 08:03:44 +00:00
}
2020-06-04 11:58:47 +00:00
key , err := ps . Value ( 1 ) . GetBytesHex ( )
2020-01-30 08:03:44 +00:00
if err != nil {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
2020-01-30 08:03:44 +00:00
}
2020-06-18 10:50:30 +00:00
item := s . chain . GetStorageItem ( id , key )
2020-01-30 08:03:44 +00:00
if item == nil {
2020-08-31 15:27:32 +00:00
return "" , nil
2020-01-30 08:03:44 +00:00
}
return hex . EncodeToString ( item . Value ) , nil
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getrawtransaction ( reqParams request . Params ) ( interface { } , * response . Error ) {
var resultsErr * response . Error
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
var results interface { }
2020-06-04 11:58:47 +00:00
if txHash , err := reqParams . Value ( 0 ) . GetUint256 ( ) ; err != nil {
2020-01-14 12:02:38 +00:00
resultsErr = response . ErrInvalidParams
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
} else if tx , height , err := s . chain . GetTransaction ( txHash ) ; err != nil {
2020-08-06 14:44:08 +00:00
err = fmt . Errorf ( "invalid transaction %s: %w" , txHash , err )
2020-01-14 12:02:38 +00:00
return nil , response . NewRPCError ( "Unknown transaction" , err . Error ( ) , err )
2020-06-04 12:43:37 +00:00
} else if reqParams . Value ( 1 ) . GetBoolean ( ) {
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
_header := s . chain . GetHeaderHash ( int ( height ) )
header , err := s . chain . GetHeader ( _header )
if err != nil {
2020-07-27 15:07:05 +00:00
return nil , response . NewInvalidParamsError ( err . Error ( ) , err )
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
}
2020-07-27 15:07:05 +00:00
st , err := s . chain . GetAppExecResult ( txHash )
if err != nil {
return nil , response . NewRPCError ( "Unknown transaction" , err . Error ( ) , err )
}
results = result . NewTransactionOutputRaw ( tx , header , st , s . chain )
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
} else {
results = hex . EncodeToString ( tx . Bytes ( ) )
}
return results , resultsErr
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) getTransactionHeight ( ps request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
h , err := ps . Value ( 0 ) . GetUint256 ( )
2020-03-05 14:20:50 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
_ , height , err := s . chain . GetTransaction ( h )
if err != nil {
return nil , response . NewRPCError ( "unknown transaction" , "" , nil )
}
return height , nil
}
2020-02-15 16:53:08 +00:00
// getContractState returns contract state (contract information, according to the contract script hash).
2020-04-28 19:35:19 +00:00
func ( s * Server ) getContractState ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
scriptHash , err := reqParams . ValueWithType ( 0 , request . StringT ) . GetUint160FromHex ( )
if err != nil {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
2020-02-15 16:53:08 +00:00
}
2020-06-04 11:58:47 +00:00
cs := s . chain . GetContractState ( scriptHash )
if cs == nil {
return nil , response . NewRPCError ( "Unknown contract" , "" , nil )
}
return cs , nil
2020-02-15 16:53:08 +00:00
}
2020-02-19 09:44:31 +00:00
// getBlockSysFee returns the system fees of the block, based on the specified index.
2020-04-28 19:35:19 +00:00
func ( s * Server ) getBlockSysFee ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
param := reqParams . ValueWithType ( 0 , request . NumberT )
if param == nil {
2020-02-19 09:44:31 +00:00
return 0 , response . ErrInvalidParams
}
num , err := s . blockHeightFromParam ( param )
if err != nil {
return 0 , response . NewRPCError ( "Invalid height" , "" , nil )
}
headerHash := s . chain . GetHeaderHash ( num )
2020-04-28 19:35:19 +00:00
block , errBlock := s . chain . GetBlock ( headerHash )
if errBlock != nil {
return 0 , response . NewRPCError ( errBlock . Error ( ) , "" , nil )
2020-02-19 09:44:31 +00:00
}
2020-06-23 14:15:35 +00:00
var blockSysFee int64
2020-02-19 09:44:31 +00:00
for _ , tx := range block . Transactions {
2020-05-08 17:54:24 +00:00
blockSysFee += tx . SystemFee
2020-02-19 09:44:31 +00:00
}
return blockSysFee , nil
}
2020-03-04 17:35:37 +00:00
// getBlockHeader returns the corresponding block header information according to the specified script hash.
2020-04-28 19:35:19 +00:00
func ( s * Server ) getBlockHeader ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
param := reqParams . Value ( 0 )
2020-06-05 13:02:55 +00:00
hash , respErr := s . blockHashFromParam ( param )
if respErr != nil {
return nil , respErr
2020-03-04 17:35:37 +00:00
}
2020-06-04 12:43:37 +00:00
verbose := reqParams . Value ( 1 ) . GetBoolean ( )
2020-03-04 17:35:37 +00:00
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 {
2020-04-28 19:35:19 +00:00
return nil , response . NewInternalServerError ( "encoding error" , buf . Err )
2020-03-04 17:35:37 +00:00
}
return hex . EncodeToString ( buf . Bytes ( ) ) , nil
}
2020-06-01 20:27:03 +00:00
// getUnclaimedGas returns unclaimed GAS amount of the specified address.
func ( s * Server ) getUnclaimedGas ( ps request . Params ) ( interface { } , * response . Error ) {
2020-09-21 14:25:43 +00:00
u , err := ps . ValueWithType ( 0 , request . StringT ) . GetUint160FromAddressOrHex ( )
2020-03-06 17:38:17 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-06-01 20:27:03 +00:00
neo , neoHeight := s . chain . GetGoverningTokenBalance ( u )
2020-07-09 09:57:24 +00:00
if neo . Sign ( ) == 0 {
2020-07-09 14:25:26 +00:00
return result . UnclaimedGas {
Address : u ,
} , nil
2020-03-06 17:38:17 +00:00
}
2020-06-23 14:15:35 +00:00
gas := s . chain . CalculateClaimable ( neo , neoHeight , s . chain . BlockHeight ( ) + 1 ) // +1 as in C#, for the next block.
2020-07-09 14:25:26 +00:00
return result . UnclaimedGas {
Address : u ,
Unclaimed : * gas ,
} , nil
2020-03-06 17:38:17 +00:00
}
2020-10-01 12:26:54 +00:00
// getNextBlockValidators returns validators for the next block with voting status.
func ( s * Server ) getNextBlockValidators ( _ request . Params ) ( interface { } , * response . Error ) {
2020-03-05 14:48:30 +00:00
var validators keys . PublicKeys
2020-10-01 12:26:54 +00:00
validators , err := s . chain . GetNextBlockValidators ( )
2020-03-05 14:48:30 +00:00
if err != nil {
2020-10-01 12:26:54 +00:00
return nil , response . NewRPCError ( "can't get next block validators" , "" , err )
2020-03-05 14:48:30 +00:00
}
enrollments , err := s . chain . GetEnrollments ( )
if err != nil {
2020-04-28 19:35:19 +00:00
return nil , response . NewRPCError ( "can't get enrollments" , "" , err )
2020-03-05 14:48:30 +00:00
}
2020-08-10 14:50:19 +00:00
var res = make ( [ ] result . Validator , 0 )
2020-03-05 14:48:30 +00:00
for _ , v := range enrollments {
res = append ( res , result . Validator {
2020-04-26 17:04:16 +00:00
PublicKey : * v . Key ,
Votes : v . Votes . Int64 ( ) ,
Active : validators . Contains ( v . Key ) ,
2020-03-05 14:48:30 +00:00
} )
}
return res , nil
}
2020-09-21 12:34:04 +00:00
// getCommittee returns the current list of NEO committee members
func ( s * Server ) getCommittee ( _ request . Params ) ( interface { } , * response . Error ) {
keys , err := s . chain . GetCommittee ( )
if err != nil {
return nil , response . NewInternalServerError ( "can't get committee members" , err )
}
return keys , nil
}
2020-06-10 08:53:11 +00:00
// invokeFunction implements the `invokeFunction` RPC call.
2020-04-28 19:35:19 +00:00
func ( s * Server ) invokeFunction ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
scriptHash , err := reqParams . ValueWithType ( 0 , request . StringT ) . GetUint160FromHex ( )
2019-11-26 10:13:17 +00:00
if err != nil {
2020-04-28 19:35:19 +00:00
return nil , response . ErrInvalidParams
2019-11-26 10:13:17 +00:00
}
2020-06-10 11:45:55 +00:00
tx := & transaction . Transaction { }
checkWitnessHashesIndex := len ( reqParams )
if checkWitnessHashesIndex > 3 {
2020-07-29 16:57:38 +00:00
signers , err := reqParams [ 3 ] . GetSigners ( )
2020-06-10 11:45:55 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-07-29 16:57:38 +00:00
tx . Signers = signers
2020-06-10 11:45:55 +00:00
checkWitnessHashesIndex --
}
2020-07-29 16:57:38 +00:00
if len ( tx . Signers ) == 0 {
2020-10-01 12:26:51 +00:00
tx . Signers = [ ] transaction . Signer { { Account : util . Uint160 { } , Scopes : transaction . None } }
2020-07-29 16:57:38 +00:00
}
2020-06-10 11:45:55 +00:00
script , err := request . CreateFunctionInvocationScript ( scriptHash , reqParams [ 1 : checkWitnessHashesIndex ] )
2019-11-26 10:13:17 +00:00
if err != nil {
2020-04-28 19:35:19 +00:00
return nil , response . NewInternalServerError ( "can't create invocation script" , err )
2019-11-26 10:13:17 +00:00
}
2020-07-14 14:32:59 +00:00
tx . Script = script
2020-06-10 11:45:55 +00:00
return s . runScriptInVM ( script , tx ) , nil
2019-11-26 10:13:17 +00:00
}
2019-10-29 15:31:39 +00:00
// invokescript implements the `invokescript` RPC call.
2020-04-28 19:35:19 +00:00
func ( s * Server ) invokescript ( reqParams request . Params ) ( interface { } , * response . Error ) {
2019-11-21 14:42:02 +00:00
if len ( reqParams ) < 1 {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
2019-10-29 15:31:39 +00:00
}
2019-11-21 14:42:02 +00:00
2020-10-14 13:46:06 +00:00
script , err := reqParams [ 0 ] . GetBytesBase64 ( )
2019-10-29 15:31:39 +00:00
if err != nil {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
2019-10-29 15:31:39 +00:00
}
2019-11-21 14:42:02 +00:00
2020-06-10 11:45:55 +00:00
tx := & transaction . Transaction { }
if len ( reqParams ) > 1 {
2020-07-29 16:57:38 +00:00
signers , err := reqParams [ 1 ] . GetSigners ( )
2020-06-10 11:45:55 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-07-29 16:57:38 +00:00
tx . Signers = signers
}
if len ( tx . Signers ) == 0 {
2020-10-01 12:26:51 +00:00
tx . Signers = [ ] transaction . Signer { { Account : util . Uint160 { } , Scopes : transaction . None } }
2020-06-10 11:45:55 +00:00
}
2020-07-14 14:32:59 +00:00
tx . Script = script
2020-06-10 11:45:55 +00:00
return s . runScriptInVM ( script , tx ) , nil
2019-11-28 16:12:23 +00:00
}
// runScriptInVM runs given script in a new test VM and returns the invocation
// result.
2020-06-10 11:45:55 +00:00
func ( s * Server ) runScriptInVM ( script [ ] byte , tx * transaction . Transaction ) * result . Invoke {
vm := s . chain . GetTestVM ( tx )
2020-06-23 14:15:35 +00:00
vm . GasLimit = int64 ( s . config . MaxGasInvoke )
2020-06-10 14:21:26 +00:00
vm . LoadScriptWithFlags ( script , smartcontract . All )
2020-10-05 13:33:20 +00:00
err := vm . Run ( )
var faultException string
if err != nil {
faultException = err . Error ( )
}
2020-01-13 09:27:34 +00:00
result := & result . Invoke {
2020-10-05 13:33:20 +00:00
State : vm . State ( ) . String ( ) ,
GasConsumed : vm . GasConsumed ( ) ,
2020-10-14 13:46:06 +00:00
Script : script ,
2020-10-05 13:33:20 +00:00
Stack : vm . Estack ( ) . ToArray ( ) ,
FaultException : faultException ,
2019-10-29 15:31:39 +00:00
}
2019-11-28 16:12:23 +00:00
return result
2019-10-29 15:31:39 +00:00
}
2020-03-02 17:01:32 +00:00
// submitBlock broadcasts a raw block over the NEO network.
2020-04-28 19:35:19 +00:00
func ( s * Server ) submitBlock ( reqParams request . Params ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
blockBytes , err := reqParams . ValueWithType ( 0 , request . StringT ) . GetBytesHex ( )
2020-03-02 17:01:32 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
2020-06-18 09:00:51 +00:00
b := block . New ( s . network )
2020-03-02 17:01:32 +00:00
r := io . NewBinReaderFromBuf ( blockBytes )
b . DecodeBinary ( r )
if r . Err != nil {
return nil , response . ErrInvalidParams
}
2020-06-18 09:00:51 +00:00
err = s . chain . AddBlock ( b )
2020-03-02 17:01:32 +00:00
if err != nil {
2020-08-06 15:34:44 +00:00
switch {
case errors . Is ( err , core . ErrInvalidBlockIndex ) || errors . Is ( err , core . ErrAlreadyExists ) :
2020-03-02 17:01:32 +00:00
return nil , response . ErrAlreadyExists
default :
return nil , response . ErrValidationFailed
}
}
2020-07-21 07:41:18 +00:00
return & result . RelayResult {
Hash : b . Hash ( ) ,
} , nil
2020-03-02 17:01:32 +00:00
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) sendrawtransaction ( reqParams request . Params ) ( interface { } , * response . Error ) {
var resultsErr * response . Error
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
var results interface { }
2019-11-21 14:42:02 +00:00
if len ( reqParams ) < 1 {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
2019-11-21 14:42:02 +00:00
} else if byteTx , err := reqParams [ 0 ] . GetBytesHex ( ) ; err != nil {
2020-01-14 12:02:38 +00:00
return nil , response . ErrInvalidParams
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
} else {
2020-06-18 09:00:51 +00:00
tx , err := transaction . NewTransactionFromBytes ( s . network , byteTx )
2020-04-13 09:02:18 +00:00
if err != nil {
2020-03-05 17:04:05 +00:00
return nil , response . ErrInvalidParams
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
}
2020-03-05 17:04:05 +00:00
relayReason := s . coreServer . RelayTxn ( tx )
switch relayReason {
case network . RelaySucceed :
2020-07-21 07:31:45 +00:00
results = result . RelayResult {
Hash : tx . Hash ( ) ,
}
2020-03-05 17:04:05 +00:00
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
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
}
}
return results , resultsErr
}
2020-05-10 22:00:19 +00:00
// subscribe handles subscription requests from websocket clients.
func ( s * Server ) subscribe ( reqParams request . Params , sub * subscriber ) ( interface { } , * response . Error ) {
2020-06-04 11:58:47 +00:00
streamName , err := reqParams . Value ( 0 ) . GetString ( )
2020-05-10 22:00:19 +00:00
if err != nil {
return nil , response . ErrInvalidParams
}
event , err := response . GetEventIDFromString ( streamName )
2020-05-12 19:38:29 +00:00
if err != nil || event == response . MissedEventID {
2020-05-10 22:00:19 +00:00
return nil , response . ErrInvalidParams
}
2020-05-13 14:13:33 +00:00
// Optional filter.
var filter interface { }
2020-06-04 11:58:47 +00:00
if p := reqParams . Value ( 1 ) ; p != nil {
2020-05-13 14:13:33 +00:00
switch event {
case response . BlockEventID :
if p . Type != request . BlockFilterT {
return nil , response . ErrInvalidParams
}
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
}
2020-05-10 22:00:19 +00:00
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 ++ {
2020-05-13 14:13:33 +00:00
if sub . feeds [ id ] . event == response . InvalidEventID {
2020-05-10 22:00:19 +00:00
break
}
}
if id == len ( sub . feeds ) {
return nil , response . NewInternalServerError ( "maximum number of subscriptions is reached" , nil )
}
2020-05-13 14:13:33 +00:00
sub . feeds [ id ] . event = event
sub . feeds [ id ] . filter = filter
2020-05-10 22:00:19 +00:00
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 ) {
2020-06-04 11:58:47 +00:00
id , err := reqParams . Value ( 0 ) . GetInt ( )
2020-05-10 22:00:19 +00:00
if err != nil || id < 0 {
return nil , response . ErrInvalidParams
}
s . subsLock . Lock ( )
defer s . subsLock . Unlock ( )
2020-05-13 14:13:33 +00:00
if len ( sub . feeds ) <= id || sub . feeds [ id ] . event == response . InvalidEventID {
2020-05-10 22:00:19 +00:00
return nil , response . ErrInvalidParams
}
2020-05-13 14:13:33 +00:00
event := sub . feeds [ id ] . event
sub . feeds [ id ] . event = response . InvalidEventID
sub . feeds [ id ] . filter = nil
2020-05-10 22:00:19 +00:00
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 ( ) {
2020-05-12 19:38:29 +00:00
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
}
2020-05-10 22:00:19 +00:00
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
2020-09-03 16:58:50 +00:00
resp . Payload [ 0 ] = execution
2020-05-10 22:00:19 +00:00
case notification := <- s . notificationCh :
resp . Event = response . NotificationEventID
2020-10-19 10:44:20 +00:00
resp . Payload [ 0 ] = notification
2020-05-10 22:00:19 +00:00
case tx := <- s . transactionCh :
resp . Event = response . TransactionEventID
resp . Payload [ 0 ] = tx
}
s . subsLock . RLock ( )
subloop :
for sub := range s . subscribers {
2020-05-12 19:38:29 +00:00
if sub . overflown . Load ( ) {
continue
}
2020-05-13 14:13:33 +00:00
for i := range sub . feeds {
if sub . feeds [ i ] . Matches ( & resp ) {
2020-05-10 22:00:19 +00:00
if msg == nil {
2020-05-12 19:38:29 +00:00
b , err = json . Marshal ( resp )
2020-05-10 22:00:19 +00:00
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
}
}
2020-05-12 19:38:29 +00:00
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 )
}
2020-05-10 22:00:19 +00:00
// 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 )
}
2020-04-28 19:35:19 +00:00
func ( s * Server ) blockHeightFromParam ( param * request . Param ) ( int , * response . Error ) {
2019-11-26 10:13:17 +00:00
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
2018-03-23 20:36:59 +00:00
}
2020-01-13 08:27:22 +00:00
2020-08-31 15:27:32 +00:00
func ( s * Server ) packResponse ( r * request . In , result interface { } , respErr * response . Error ) response . Abstract {
resp := response . Abstract {
2020-01-14 12:02:38 +00:00
HeaderAndError : response . HeaderAndError {
Header : response . Header {
JSONRPC : r . JSONRPC ,
ID : r . RawID ,
} ,
} ,
}
2020-04-28 19:56:19 +00:00
if respErr != nil {
resp . Error = respErr
} else {
2020-08-31 15:27:32 +00:00
resp . Result = result
2020-04-28 19:56:19 +00:00
}
return resp
}
2020-01-14 12:02:38 +00:00
2020-04-28 19:56:19 +00:00
// logRequestError is a request error logger.
func ( s * Server ) logRequestError ( r * request . In , jsonErr * response . Error ) {
2020-01-14 12:02:38 +00:00
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 ... )
}
2020-04-28 19:56:19 +00:00
// writeHTTPErrorResponse writes an error response to the ResponseWriter.
func ( s * Server ) writeHTTPErrorResponse ( r * request . In , w http . ResponseWriter , jsonErr * response . Error ) {
2020-08-31 15:27:32 +00:00
resp := s . packResponse ( r , nil , jsonErr )
2020-04-28 19:56:19 +00:00
s . writeHTTPServerResponse ( r , w , resp )
2020-01-14 12:02:38 +00:00
}
2020-08-31 15:27:32 +00:00
func ( s * Server ) writeHTTPServerResponse ( r * request . In , w http . ResponseWriter , resp response . Abstract ) {
2020-04-28 19:56:19 +00:00
// Errors can happen in many places and we can only catch ALL of them here.
if resp . Error != nil {
s . logRequestError ( r , resp . Error )
w . WriteHeader ( resp . Error . HTTPCode )
}
2020-01-14 12:02:38 +00:00
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 ) )
}
}
2020-01-13 08:27:22 +00:00
// 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
2020-01-13 08:33:04 +00:00
func validateAddress ( addr interface { } ) result . ValidateAddress {
resp := result . ValidateAddress { Address : addr }
2020-01-13 08:27:22 +00:00
if addr , ok := addr . ( string ) ; ok {
_ , err := address . StringToUint160 ( addr )
resp . IsValid = ( err == nil )
}
return resp
}