upgrade the websocket in xdpos (#349)

This commit is contained in:
Banana-J 2023-11-06 15:37:14 +11:00 committed by GitHub
parent 64a2c84ed3
commit 39f6e6ee56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 3797 additions and 2492 deletions

1
.gitignore vendored
View file

@ -52,3 +52,4 @@ coverage.txt
go.sum go.sum
cicd/devnet/terraform/.terraform* cicd/devnet/terraform/.terraform*
cicd/devnet/tmp cicd/devnet/tmp
.env

View file

@ -4,7 +4,7 @@
GOBIN = $(shell pwd)/build/bin GOBIN = $(shell pwd)/build/bin
GOFMT = gofmt GOFMT = gofmt
GO ?= 1.13.1 GO ?= 1.14
GO_PACKAGES = . GO_PACKAGES = .
GO_FILES := $(shell find $(shell go list -f '{{.Dir}}' $(GO_PACKAGES)) -name \*.go) GO_FILES := $(shell find $(shell go list -f '{{.Dir}}' $(GO_PACKAGES)) -name \*.go)

View file

@ -58,7 +58,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"golang.org/x/net/websocket" "github.com/gorilla/websocket"
) )
var ( var (
@ -204,7 +204,7 @@ type faucet struct {
nonce uint64 // Current pending nonce of the faucet nonce uint64 // Current pending nonce of the faucet
price *big.Int // Current gas price to issue funds with price *big.Int // Current gas price to issue funds with
conns []*websocket.Conn // Currently live websocket connections conns []*wsConn // Currently live websocket connections
timeouts map[string]time.Time // History of users and their funding timeouts timeouts map[string]time.Time // History of users and their funding timeouts
reqs []*request // Currently pending funding requests reqs []*request // Currently pending funding requests
update chan struct{} // Channel to signal request updates update chan struct{} // Channel to signal request updates
@ -212,6 +212,13 @@ type faucet struct {
lock sync.RWMutex // Lock protecting the faucet's internals lock sync.RWMutex // Lock protecting the faucet's internals
} }
// wsConn wraps a websocket connection with a write mutex as the underlying
// websocket library does not synchronize access to the stream.
type wsConn struct {
conn *websocket.Conn
wlock sync.Mutex
}
func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
// Assemble the raw devp2p protocol stack // Assemble the raw devp2p protocol stack
stack, err := node.New(&node.Config{ stack, err := node.New(&node.Config{
@ -289,7 +296,7 @@ func (f *faucet) listenAndServe(port int) error {
go f.loop() go f.loop()
http.HandleFunc("/", f.webHandler) http.HandleFunc("/", f.webHandler)
http.Handle("/api", websocket.Handler(f.apiHandler)) http.HandleFunc("/api", f.apiHandler)
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
} }
@ -301,18 +308,24 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
} }
// apiHandler handles requests for Ether grants and transaction statuses. // apiHandler handles requests for Ether grants and transaction statuses.
func (f *faucet) apiHandler(conn *websocket.Conn) { func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
// Start tracking the connection and drop at the end // Start tracking the connection and drop at the end
defer conn.Close() defer conn.Close()
f.lock.Lock() f.lock.Lock()
f.conns = append(f.conns, conn) wsconn := &wsConn{conn: conn}
f.conns = append(f.conns, wsconn)
f.lock.Unlock() f.lock.Unlock()
defer func() { defer func() {
f.lock.Lock() f.lock.Lock()
for i, c := range f.conns { for i, c := range f.conns {
if c == conn { if c.conn == conn {
f.conns = append(f.conns[:i], f.conns[i+1:]...) f.conns = append(f.conns[:i], f.conns[i+1:]...)
break break
} }
@ -324,7 +337,6 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
head *types.Header head *types.Header
balance *big.Int balance *big.Int
nonce uint64 nonce uint64
err error
) )
for { for {
// Attempt to retrieve the stats, may error on no faucet connectivity // Attempt to retrieve the stats, may error on no faucet connectivity
@ -340,7 +352,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
// If stats retrieval failed, wait a bit and retry // If stats retrieval failed, wait a bit and retry
if err != nil { if err != nil {
if err = sendError(conn, errors.New("Faucet offline: "+err.Error())); err != nil { if err = sendError(wsconn, errors.New("Faucet offline: "+err.Error())); err != nil {
log.Warn("Failed to send faucet error to client", "err", err) log.Warn("Failed to send faucet error to client", "err", err)
return return
} }
@ -351,7 +363,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
break break
} }
// Send over the initial stats and the latest header // Send over the initial stats and the latest header
if err = send(conn, map[string]interface{}{ if err = send(wsconn, map[string]interface{}{
"funds": balance.Div(balance, ether), "funds": balance.Div(balance, ether),
"funded": nonce, "funded": nonce,
"peers": f.stack.Server().PeerCount(), "peers": f.stack.Server().PeerCount(),
@ -360,7 +372,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
log.Warn("Failed to send initial stats to client", "err", err) log.Warn("Failed to send initial stats to client", "err", err)
return return
} }
if err = send(conn, head, 3*time.Second); err != nil { if err = send(wsconn, head, 3*time.Second); err != nil {
log.Warn("Failed to send initial header to client", "err", err) log.Warn("Failed to send initial header to client", "err", err)
return return
} }
@ -372,19 +384,19 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
Tier uint `json:"tier"` Tier uint `json:"tier"`
Captcha string `json:"captcha"` Captcha string `json:"captcha"`
} }
if err = websocket.JSON.Receive(conn, &msg); err != nil { if err = conn.ReadJSON(&msg); err != nil {
return return
} }
if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") && if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { !strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil {
log.Warn("Failed to send URL error to client", "err", err) log.Warn("Failed to send URL error to client", "err", err)
return return
} }
continue continue
} }
if msg.Tier >= uint(*tiersFlag) { if msg.Tier >= uint(*tiersFlag) {
if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil { if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil {
log.Warn("Failed to send tier error to client", "err", err) log.Warn("Failed to send tier error to client", "err", err)
return return
} }
@ -400,7 +412,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form) res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
if err != nil { if err != nil {
if err = sendError(conn, err); err != nil { if err = sendError(wsconn, err); err != nil {
log.Warn("Failed to send captcha post error to client", "err", err) log.Warn("Failed to send captcha post error to client", "err", err)
return return
} }
@ -413,7 +425,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
err = json.NewDecoder(res.Body).Decode(&result) err = json.NewDecoder(res.Body).Decode(&result)
res.Body.Close() res.Body.Close()
if err != nil { if err != nil {
if err = sendError(conn, err); err != nil { if err = sendError(wsconn, err); err != nil {
log.Warn("Failed to send captcha decode error to client", "err", err) log.Warn("Failed to send captcha decode error to client", "err", err)
return return
} }
@ -421,7 +433,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
} }
if !result.Success { if !result.Success {
log.Warn("Captcha verification failed", "err", string(result.Errors)) log.Warn("Captcha verification failed", "err", string(result.Errors))
if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil { if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil {
log.Warn("Failed to send captcha failure to client", "err", err) log.Warn("Failed to send captcha failure to client", "err", err)
return return
} }
@ -436,7 +448,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
) )
switch { switch {
case strings.HasPrefix(msg.URL, "https://gist.github.com/"): case strings.HasPrefix(msg.URL, "https://gist.github.com/"):
if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil { if err = sendError(wsconn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil {
log.Warn("Failed to send GitHub deprecation to client", "err", err) log.Warn("Failed to send GitHub deprecation to client", "err", err)
return return
} }
@ -453,7 +465,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
err = errors.New("Something funky happened, please open an issue at https://github.com/XinFinOrg/XDPoSChain/issues") err = errors.New("Something funky happened, please open an issue at https://github.com/XinFinOrg/XDPoSChain/issues")
} }
if err != nil { if err != nil {
if err = sendError(conn, err); err != nil { if err = sendError(wsconn, err); err != nil {
log.Warn("Failed to send prefix error to client", "err", err) log.Warn("Failed to send prefix error to client", "err", err)
return return
} }
@ -477,7 +489,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId) signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
if err != nil { if err != nil {
f.lock.Unlock() f.lock.Unlock()
if err = sendError(conn, err); err != nil { if err = sendError(wsconn, err); err != nil {
log.Warn("Failed to send transaction creation error to client", "err", err) log.Warn("Failed to send transaction creation error to client", "err", err)
return return
} }
@ -486,7 +498,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
// Submit the transaction and mark as funded if successful // Submit the transaction and mark as funded if successful
if err := f.client.SendTransaction(context.Background(), signed); err != nil { if err := f.client.SendTransaction(context.Background(), signed); err != nil {
f.lock.Unlock() f.lock.Unlock()
if err = sendError(conn, err); err != nil { if err = sendError(wsconn, err); err != nil {
log.Warn("Failed to send transaction transmission error to client", "err", err) log.Warn("Failed to send transaction transmission error to client", "err", err)
return return
} }
@ -505,13 +517,13 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
// Send an error if too frequent funding, othewise a success // Send an error if too frequent funding, othewise a success
if !fund { if !fund {
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { // nolint: gosimple if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { // nolint: gosimple
log.Warn("Failed to send funding error to client", "err", err) log.Warn("Failed to send funding error to client", "err", err)
return return
} }
continue continue
} }
if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil {
log.Warn("Failed to send funding success to client", "err", err) log.Warn("Failed to send funding success to client", "err", err)
return return
} }
@ -581,12 +593,12 @@ func (f *faucet) loop() {
"requests": f.reqs, "requests": f.reqs,
}, time.Second); err != nil { }, time.Second); err != nil {
log.Warn("Failed to send stats to client", "err", err) log.Warn("Failed to send stats to client", "err", err)
conn.Close() conn.conn.Close()
continue continue
} }
if err := send(conn, head, time.Second); err != nil { if err := send(conn, head, time.Second); err != nil {
log.Warn("Failed to send header to client", "err", err) log.Warn("Failed to send header to client", "err", err)
conn.Close() conn.conn.Close()
} }
} }
f.lock.RUnlock() f.lock.RUnlock()
@ -608,7 +620,7 @@ func (f *faucet) loop() {
for _, conn := range f.conns { for _, conn := range f.conns {
if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil {
log.Warn("Failed to send requests to client", "err", err) log.Warn("Failed to send requests to client", "err", err)
conn.Close() conn.conn.Close()
} }
} }
f.lock.RUnlock() f.lock.RUnlock()
@ -618,23 +630,25 @@ func (f *faucet) loop() {
// sends transmits a data packet to the remote end of the websocket, but also // sends transmits a data packet to the remote end of the websocket, but also
// setting a write deadline to prevent waiting forever on the node. // setting a write deadline to prevent waiting forever on the node.
func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error { func send(conn *wsConn, value interface{}, timeout time.Duration) error {
if timeout == 0 { if timeout == 0 {
timeout = 60 * time.Second timeout = 60 * time.Second
} }
conn.SetWriteDeadline(time.Now().Add(timeout)) conn.wlock.Lock()
return websocket.JSON.Send(conn, value) defer conn.wlock.Unlock()
conn.conn.SetWriteDeadline(time.Now().Add(timeout))
return conn.conn.WriteJSON(value)
} }
// sendError transmits an error to the remote end of the websocket, also setting // sendError transmits an error to the remote end of the websocket, also setting
// the write deadline to 1 second to prevent waiting forever. // the write deadline to 1 second to prevent waiting forever.
func sendError(conn *websocket.Conn, err error) error { func sendError(conn *wsConn, err error) error {
return send(conn, map[string]string{"error": err.Error()}, time.Second) return send(conn, map[string]string{"error": err.Error()}, time.Second)
} }
// sendSuccess transmits a success message to the remote end of the websocket, also // sendSuccess transmits a success message to the remote end of the websocket, also
// setting the write deadline to 1 second to prevent waiting forever. // setting the write deadline to 1 second to prevent waiting forever.
func sendSuccess(conn *websocket.Conn, msg string) error { func sendSuccess(conn *wsConn, msg string) error {
return send(conn, map[string]string{"success": msg}, time.Second) return send(conn, map[string]string{"success": msg}, time.Second)
} }

View file

@ -23,11 +23,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"net" "net/http"
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
@ -42,7 +43,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"golang.org/x/net/websocket" "github.com/gorilla/websocket"
) )
const ( const (
@ -89,6 +90,51 @@ type Service struct {
histCh chan []uint64 // History request block numbers are fed into this channel histCh chan []uint64 // History request block numbers are fed into this channel
} }
// connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
// websocket.
//
// From Gorilla websocket docs:
// Connections support one concurrent reader and one concurrent writer.
// Applications are responsible for ensuring that no more than one goroutine calls the write methods
// - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
// concurrently and that no more than one goroutine calls the read methods
// - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
// concurrently.
// The Close and WriteControl methods can be called concurrently with all other methods.
type connWrapper struct {
conn *websocket.Conn
rlock sync.Mutex
wlock sync.Mutex
}
func newConnectionWrapper(conn *websocket.Conn) *connWrapper {
return &connWrapper{conn: conn}
}
// WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling
func (w *connWrapper) WriteJSON(v interface{}) error {
w.wlock.Lock()
defer w.wlock.Unlock()
return w.conn.WriteJSON(v)
}
// ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling
func (w *connWrapper) ReadJSON(v interface{}) error {
w.rlock.Lock()
defer w.rlock.Unlock()
return w.conn.ReadJSON(v)
}
// Close wraps corresponding method on the websocket but is safe for concurrent calling
func (w *connWrapper) Close() error {
// The Close and WriteControl methods can be called concurrently with all other methods,
// so the mutex is not used here
return w.conn.Close()
}
// New returns a monitoring service ready for stats reporting. // New returns a monitoring service ready for stats reporting.
func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) { func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) {
// Parse the netstats connection url // Parse the netstats connection url
@ -229,16 +275,17 @@ func (s *Service) loop() {
} }
// Establish a websocket connection to the server on any supported URL // Establish a websocket connection to the server on any supported URL
var ( var (
conf *websocket.Config conn *connWrapper
conn *websocket.Conn
err error err error
) )
dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second}
header := make(http.Header)
header.Set("origin", "http://localhost")
for _, url := range urls { for _, url := range urls {
if conf, err = websocket.NewConfig(url, "http://localhost/"); err != nil { c, _, e := dialer.Dial(url, header)
continue err = e
} if err == nil {
conf.Dialer = &net.Dialer{Timeout: 5 * time.Second} conn = newConnectionWrapper(c)
if conn, err = websocket.DialConfig(conf); err == nil {
break break
} }
} }
@ -305,14 +352,29 @@ func (s *Service) loop() {
// from the network socket. If any of them match an active request, it forwards // from the network socket. If any of them match an active request, it forwards
// it, if they themselves are requests it initiates a reply, and lastly it drops // it, if they themselves are requests it initiates a reply, and lastly it drops
// unknown packets. // unknown packets.
func (s *Service) readLoop(conn *websocket.Conn) { func (s *Service) readLoop(conn *connWrapper) {
// If the read loop exists, close the connection // If the read loop exists, close the connection
defer conn.Close() defer conn.Close()
for { for {
// Retrieve the next generic network packet and bail out on error // Retrieve the next generic network packet and bail out on error
var blob json.RawMessage
if err := conn.ReadJSON(&blob); err != nil {
log.Warn("Failed to retrieve stats server message", "err", err)
return
}
// If the network packet is a system ping, respond to it directly
var ping string
if err := json.Unmarshal(blob, &ping); err == nil && strings.HasPrefix(ping, "primus::ping::") {
if err := conn.WriteJSON(strings.Replace(ping, "ping", "pong", -1)); err != nil {
log.Warn("Failed to respond to system ping message", "err", err)
return
}
continue
}
// Not a system ping, try to decode an actual state message
var msg map[string][]interface{} var msg map[string][]interface{}
if err := websocket.JSON.Receive(conn, &msg); err != nil { if err := json.Unmarshal(blob, &msg); err != nil {
log.Warn("Failed to decode stats server message", "err", err) log.Warn("Failed to decode stats server message", "err", err)
return return
} }
@ -396,7 +458,7 @@ type authMsg struct {
} }
// login tries to authorize the client at the remote server. // login tries to authorize the client at the remote server.
func (s *Service) login(conn *websocket.Conn) error { func (s *Service) login(conn *connWrapper) error {
// Construct and send the login authentication // Construct and send the login authentication
infos := s.server.NodeInfo() infos := s.server.NodeInfo()
@ -427,12 +489,12 @@ func (s *Service) login(conn *websocket.Conn) error {
login := map[string][]interface{}{ login := map[string][]interface{}{
"emit": {"hello", auth}, "emit": {"hello", auth},
} }
if err := websocket.JSON.Send(conn, login); err != nil { if err := conn.WriteJSON(login); err != nil {
return err return err
} }
// Retrieve the remote ack or connection termination // Retrieve the remote ack or connection termination
var ack map[string][]string var ack map[string][]string
if err := websocket.JSON.Receive(conn, &ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { if err := conn.ReadJSON(&ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" {
return errors.New("unauthorized") return errors.New("unauthorized")
} }
return nil return nil
@ -441,7 +503,7 @@ func (s *Service) login(conn *websocket.Conn) error {
// report collects all possible data to report and send it to the stats server. // report collects all possible data to report and send it to the stats server.
// This should only be used on reconnects or rarely to avoid overloading the // This should only be used on reconnects or rarely to avoid overloading the
// server. Use the individual methods for reporting subscribed events. // server. Use the individual methods for reporting subscribed events.
func (s *Service) report(conn *websocket.Conn) error { func (s *Service) report(conn *connWrapper) error {
if err := s.reportLatency(conn); err != nil { if err := s.reportLatency(conn); err != nil {
return err return err
} }
@ -459,7 +521,7 @@ func (s *Service) report(conn *websocket.Conn) error {
// reportLatency sends a ping request to the server, measures the RTT time and // reportLatency sends a ping request to the server, measures the RTT time and
// finally sends a latency update. // finally sends a latency update.
func (s *Service) reportLatency(conn *websocket.Conn) error { func (s *Service) reportLatency(conn *connWrapper) error {
// Send the current time to the ethstats server // Send the current time to the ethstats server
start := time.Now() start := time.Now()
@ -469,7 +531,7 @@ func (s *Service) reportLatency(conn *websocket.Conn) error {
"clientTime": start.String(), "clientTime": start.String(),
}}, }},
} }
if err := websocket.JSON.Send(conn, ping); err != nil { if err := conn.WriteJSON(ping); err != nil {
return err return err
} }
// Wait for the pong request to arrive back // Wait for the pong request to arrive back
@ -491,7 +553,7 @@ func (s *Service) reportLatency(conn *websocket.Conn) error {
"latency": latency, "latency": latency,
}}, }},
} }
return websocket.JSON.Send(conn, stats) return conn.WriteJSON(stats)
} }
// blockStats is the information to report about individual blocks. // blockStats is the information to report about individual blocks.
@ -535,7 +597,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
} }
// reportBlock retrieves the current chain head and repors it to the stats server. // reportBlock retrieves the current chain head and repors it to the stats server.
func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error { func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error {
// Gather the block details from the header or block chain // Gather the block details from the header or block chain
details := s.assembleBlockStats(block) details := s.assembleBlockStats(block)
@ -560,11 +622,12 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": {"block", stats}, "emit": {"block", stats},
} }
return websocket.JSON.Send(conn, report)
return conn.WriteJSON(report)
} }
// reportForensics forward the forensics repors it to the stats server. // reportForensics forward the forensics repors it to the stats server.
func (s *Service) reportForensics(conn *websocket.Conn, forensicsProof *types.ForensicProof) error { func (s *Service) reportForensics(conn *connWrapper, forensicsProof *types.ForensicProof) error {
log.Info("Sending Forensics report to ethstats", "ForensicsType", forensicsProof.ForensicsType) log.Info("Sending Forensics report to ethstats", "ForensicsType", forensicsProof.ForensicsType)
stats := map[string]interface{}{ stats := map[string]interface{}{
@ -574,7 +637,7 @@ func (s *Service) reportForensics(conn *websocket.Conn, forensicsProof *types.Fo
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": {"forensics", stats}, "emit": {"forensics", stats},
} }
return websocket.JSON.Send(conn, report) return conn.WriteJSON(report)
} }
// assembleBlockStats retrieves any required metadata to report a single block // assembleBlockStats retrieves any required metadata to report a single block
@ -632,7 +695,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
// reportHistory retrieves the most recent batch of blocks and reports it to the // reportHistory retrieves the most recent batch of blocks and reports it to the
// stats server. // stats server.
func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
// Figure out the indexes that need reporting // Figure out the indexes that need reporting
indexes := make([]uint64, 0, historyUpdateRange) indexes := make([]uint64, 0, historyUpdateRange)
if len(list) > 0 { if len(list) > 0 {
@ -688,7 +751,7 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": {"history", stats}, "emit": {"history", stats},
} }
return websocket.JSON.Send(conn, report) return conn.WriteJSON(report)
} }
// pendStats is the information to report about pending transactions. // pendStats is the information to report about pending transactions.
@ -698,7 +761,7 @@ type pendStats struct {
// reportPending retrieves the current number of pending transactions and reports // reportPending retrieves the current number of pending transactions and reports
// it to the stats server. // it to the stats server.
func (s *Service) reportPending(conn *websocket.Conn) error { func (s *Service) reportPending(conn *connWrapper) error {
// Retrieve the pending count from the local blockchain // Retrieve the pending count from the local blockchain
var pending int var pending int
if s.eth != nil { if s.eth != nil {
@ -718,7 +781,7 @@ func (s *Service) reportPending(conn *websocket.Conn) error {
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": {"pending", stats}, "emit": {"pending", stats},
} }
return websocket.JSON.Send(conn, report) return conn.WriteJSON(report)
} }
// nodeStats is the information to report about the local node. // nodeStats is the information to report about the local node.
@ -734,7 +797,7 @@ type nodeStats struct {
// reportPending retrieves various stats about the node at the networking and // reportPending retrieves various stats about the node at the networking and
// mining layer and reports it to the stats server. // mining layer and reports it to the stats server.
func (s *Service) reportStats(conn *websocket.Conn) error { func (s *Service) reportStats(conn *connWrapper) error {
// Gather the syncing and mining infos from the local miner instance // Gather the syncing and mining infos from the local miner instance
var ( var (
mining bool mining bool
@ -773,5 +836,5 @@ func (s *Service) reportStats(conn *websocket.Conn) error {
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": {"stats", stats}, "emit": {"stats", stats},
} }
return websocket.JSON.Send(conn, report) return conn.WriteJSON(report)
} }

31
go.mod
View file

@ -1,20 +1,18 @@
module github.com/XinFinOrg/XDPoSChain module github.com/XinFinOrg/XDPoSChain
go 1.13 go 1.14
require ( require (
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 bazil.org/fuse v0.0.0-20180421153158-65cc252bf669
github.com/VictoriaMetrics/fastcache v1.5.7 github.com/VictoriaMetrics/fastcache v1.5.7
github.com/aead/siphash v1.0.1 // indirect
github.com/aristanetworks/goarista v0.0.0-20191023202215-f096da5361bb github.com/aristanetworks/goarista v0.0.0-20191023202215-f096da5361bb
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6
github.com/btcsuite/winsvc v1.0.0 // indirect
github.com/cespare/cp v1.1.1 github.com/cespare/cp v1.1.1
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v1.8.0 github.com/deckarep/golang-set v1.8.0
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/edsrzf/mmap-go v1.0.0 github.com/edsrzf/mmap-go v1.0.0
github.com/elastic/gosigar v0.10.5 github.com/elastic/gosigar v0.10.5 // indirect
github.com/ethereum/go-ethereum v1.9.9 github.com/ethereum/go-ethereum v1.9.9
github.com/fatih/color v1.6.0 github.com/fatih/color v1.6.0
github.com/gizak/termui v2.2.0+incompatible github.com/gizak/termui v2.2.0+incompatible
@ -22,46 +20,39 @@ require (
github.com/go-stack/stack v1.8.0 github.com/go-stack/stack v1.8.0
github.com/golang/protobuf v1.3.2 github.com/golang/protobuf v1.3.2
github.com/golang/snappy v0.0.1 github.com/golang/snappy v0.0.1
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989
github.com/hashicorp/golang-lru v0.5.3 github.com/hashicorp/golang-lru v0.5.3
github.com/huin/goupnp v1.0.0 github.com/huin/goupnp v1.0.0
github.com/influxdata/influxdb v1.7.9 github.com/influxdata/influxdb v1.7.9
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 // indirect
github.com/jrick/logrotate v1.0.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/karalabe/hid v1.0.0 github.com/karalabe/hid v1.0.0
github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7 // indirect
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect
github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e // indirect github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e // indirect
github.com/mattn/go-colorable v0.1.0 github.com/maruel/ut v1.0.2 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mattn/go-colorable v0.1.13
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77 // indirect github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77 // indirect
github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c github.com/olekukonko/tablewriter v0.0.5
github.com/olumuyiwadad/XDPoSChain v0.0.0-20210822085431-3aa327803aed
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.9.1
github.com/prometheus/prometheus v1.7.2-0.20170814170113-3101606756c5 github.com/prometheus/prometheus v1.7.2-0.20170814170113-3101606756c5
github.com/rjeczalik/notify v0.9.2 github.com/rjeczalik/notify v0.9.2
github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d
github.com/rs/cors v1.6.0 github.com/rs/cors v1.7.0
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59 golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59
golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2
golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
gopkg.in/urfave/cli.v1 v1.20.0 gopkg.in/urfave/cli.v1 v1.20.0
gotest.tools v2.2.0+incompatible
) )

59
go.sum
View file

@ -24,11 +24,9 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU
github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/XinFinOrg/XDPoSChain v1.5.7 h1:8b5WT3AzYxmDVOni3K+aTj9Fur6Xf0rddDaGjghwjqw=
github.com/XinFinOrg/XDPoSChain v1.5.7/go.mod h1:P2WCRgVo6taq0uP7EMLjuxtGY2ISiIFgZ+ZZ1DzQDxc=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks=
github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA=
@ -41,21 +39,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI=
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btcd v0.20.0-beta h1:DnZGUjFbRkpytojHWwy6nfUSA7vFrzWXDLpFNzt74ZA=
github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU=
github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
@ -89,11 +72,11 @@ github.com/elastic/gosigar v0.10.5 h1:GzPQ+78RaAb4J63unidA/JavQRKrB6s8IOzN6Ib59j
github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
github.com/ethereum/go-ethereum v1.9.9 h1:jnoBvjH8aMH++iH14XmiJdAsnRcmZUM+B5fsnEZBVE0= github.com/ethereum/go-ethereum v1.9.9 h1:jnoBvjH8aMH++iH14XmiJdAsnRcmZUM+B5fsnEZBVE0=
github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo= github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo=
github.com/ethereum/go-ethereum v1.10.10 h1:Ft2GcLQrr2M89l49g9NoqgNtJZ9AahzMb7N6VXKZy5U=
github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU= github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
@ -122,6 +105,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -132,6 +117,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag=
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
@ -144,8 +130,6 @@ github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e/go.mod
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@ -156,9 +140,6 @@ github.com/karalabe/hid v1.0.0 h1:+/CIMNXhSU/zIJgnIvBD2nKHxS/bnRHhhs9xBryLpPo=
github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8= github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7 h1:AYzjK/SHz6m6mg5iuFwkrAhCc14jvCpW9d6frC9iDPE=
github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -168,20 +149,28 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e h1:e2z/lz9pvtRrEOgKWaLW2Dw02Nqd3/fqv0qWTQ8ByZE= github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e h1:e2z/lz9pvtRrEOgKWaLW2Dw02Nqd3/fqv0qWTQ8ByZE=
github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/maruel/ut v1.0.2 h1:mQTlQk3jubTbdTcza+hwoZQWhzcvE4L6K6RTtAFlA1k=
github.com/maruel/ut v1.0.2/go.mod h1:RV8PwPD9dd2KFlnlCc/DB2JVvkXmyaalfc5xvmSrRSs=
github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4=
github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -199,12 +188,14 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk=
github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olumuyiwadad/XDPoSChain v0.0.0-20210822085431-3aa327803aed h1:nshJxH0Tj14Gyj33Hj0tkIINwtuDbLNGoxWYDVIBjxw= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olumuyiwadad/XDPoSChain v0.0.0-20210822085431-3aa327803aed/go.mod h1:9KFeM+HVoAXifmzHfXei4m/kTjGmJPPYDv6JvgD7tR0= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc=
github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw=
@ -218,6 +209,8 @@ github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0je
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -243,8 +236,8 @@ github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa
github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d h1:ouzpe+YhpIfnjR40gSkJHWsvXmB6TiPKqMtMpfyU9DE= github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d h1:ouzpe+YhpIfnjR40gSkJHWsvXmB6TiPKqMtMpfyU9DE=
github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -315,6 +308,8 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE=
golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -323,8 +318,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190912185636-87d9f09c5d89/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190912185636-87d9f09c5d89/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 h1:azkp5oIgy7LNGQ64URezZccjePaEGSYIHEgYTn/bfXI= golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9 h1:m9xhlkk2j+sO9WjAgNfTtl505MN7ZkuW69nOcBlp9qY=
golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -334,6 +329,7 @@ gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPA
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
@ -349,6 +345,7 @@ gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJ
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=

View file

@ -81,8 +81,9 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
} }
// GasPrice returns a suggestion for a gas price. // GasPrice returns a suggestion for a gas price.
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*big.Int, error) { func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
return s.b.SuggestPrice(ctx) price, err := s.b.SuggestPrice(ctx)
return (*hexutil.Big)(price), err
} }
// ProtocolVersion returns the current Ethereum protocol version this node supports // ProtocolVersion returns the current Ethereum protocol version this node supports
@ -511,9 +512,9 @@ func NewPublicBlockChainAPI(b Backend, chainReader consensus.ChainReader) *Publi
} }
// BlockNumber returns the block number of the chain head. // BlockNumber returns the block number of the chain head.
func (s *PublicBlockChainAPI) BlockNumber() *big.Int { func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
return header.Number return hexutil.Uint64(header.Number.Uint64())
} }
// BlockNumber returns the block number of the chain head. // BlockNumber returns the block number of the chain head.
@ -524,13 +525,12 @@ func (s *PublicBlockChainAPI) GetRewardByHash(hash common.Hash) map[string]map[s
// GetBalance returns the amount of wei for the given address in the state of the // GetBalance returns the amount of wei for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed. // block numbers are also allowed.
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr) state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
if state == nil || err != nil { if state == nil || err != nil {
return nil, err return nil, err
} }
b := state.GetBalance(address) return (*hexutil.Big)(state.GetBalance(address)), state.Error()
return b, state.Error()
} }
// GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all // GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all

View file

@ -19,7 +19,6 @@ package node
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -27,6 +26,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
@ -341,7 +342,8 @@ func (n *Node) startIPC(apis []rpc.API) error {
n.log.Error("IPC accept failed", "err", err) n.log.Error("IPC accept failed", "err", err)
continue continue
} }
go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) log.Trace("Accepted RPC connection", "conn", conn.RemoteAddr())
go handler.ServeCodec(rpc.NewCodec(conn), 0)
} }
}() }()
// All listeners booted successfully // All listeners booted successfully

View file

@ -41,7 +41,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"golang.org/x/net/websocket" "github.com/gorilla/websocket"
) )
// ExecAdapter is a NodeAdapter which runs simulation nodes by executing the // ExecAdapter is a NodeAdapter which runs simulation nodes by executing the
@ -288,29 +288,35 @@ func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
// ServeRPC serves RPC requests over the given connection by dialling the // ServeRPC serves RPC requests over the given connection by dialling the
// node's WebSocket address and joining the two connections // node's WebSocket address and joining the two connections
func (n *ExecNode) ServeRPC(clientConn net.Conn) error { func (n *ExecNode) ServeRPC(clientConn *websocket.Conn) error {
conn, err := websocket.Dial(n.wsAddr, "", "http://localhost") conn, _, err := websocket.DefaultDialer.Dial(n.wsAddr, nil)
if err != nil { if err != nil {
return err return err
} }
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
join := func(src, dst net.Conn) { go wsCopy(&wg, conn, clientConn)
defer wg.Done() go wsCopy(&wg, clientConn, conn)
io.Copy(dst, src) wg.Wait()
// close the write end of the destination connection conn.Close()
if cw, ok := dst.(interface { return nil
CloseWrite() error }
}); ok {
cw.CloseWrite() func wsCopy(wg *sync.WaitGroup, src, dst *websocket.Conn) {
} else { defer wg.Done()
dst.Close() for {
msgType, r, err := src.NextReader()
if err != nil {
return
}
w, err := dst.NextWriter(msgType)
if err != nil {
return
}
if _, err = io.Copy(w, r); err != nil {
return
} }
} }
go join(conn, clientConn)
go join(clientConn, conn)
wg.Wait()
return nil
} }
// Snapshots creates snapshots of the services by calling the // Snapshots creates snapshots of the services by calling the

View file

@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket"
) )
// SimAdapter is a NodeAdapter which creates in-memory simulation nodes and // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
@ -182,12 +183,13 @@ func (self *SimNode) Client() (*rpc.Client, error) {
// ServeRPC serves RPC requests over the given connection by creating an // ServeRPC serves RPC requests over the given connection by creating an
// in-memory client to the node's RPC server // in-memory client to the node's RPC server
func (self *SimNode) ServeRPC(conn net.Conn) error { func (self *SimNode) ServeRPC(conn *websocket.Conn) error {
handler, err := self.node.RPCHandler() handler, err := self.node.RPCHandler()
if err != nil { if err != nil {
return err return err
} }
handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) codec := rpc.NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON)
handler.ServeCodec(codec, 0)
return nil return nil
} }

View file

@ -21,7 +21,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"os" "os"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -30,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/gorilla/websocket"
) )
// Node represents a node in a simulation network which is created by a // Node represents a node in a simulation network which is created by a
@ -48,7 +48,7 @@ type Node interface {
Client() (*rpc.Client, error) Client() (*rpc.Client, error)
// ServeRPC serves RPC requests over the given connection // ServeRPC serves RPC requests over the given connection
ServeRPC(net.Conn) error ServeRPC(*websocket.Conn) error
// Start starts the node with the given snapshots // Start starts the node with the given snapshots
Start(snapshots map[string][]byte) error Start(snapshots map[string][]byte) error

View file

@ -34,8 +34,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"golang.org/x/net/websocket"
) )
// DefaultClient is the default simulation API client which expects the API // DefaultClient is the default simulation API client which expects the API
@ -653,16 +653,20 @@ func (s *Server) Options(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
var wsUpgrade = websocket.Upgrader{
CheckOrigin: func(*http.Request) bool { return true },
}
// NodeRPC forwards RPC requests to a node in the network via a WebSocket // NodeRPC forwards RPC requests to a node in the network via a WebSocket
// connection // connection
func (s *Server) NodeRPC(w http.ResponseWriter, req *http.Request) { func (s *Server) NodeRPC(w http.ResponseWriter, req *http.Request) {
node := req.Context().Value("node").(*Node) conn, err := wsUpgrade.Upgrade(w, req, nil)
if err != nil {
handler := func(conn *websocket.Conn) { return
node.ServeRPC(conn)
} }
defer conn.Close()
websocket.Server{Handler: handler}.ServeHTTP(w, req) node := req.Context().Value("node").(*Node)
node.ServeRPC(conn)
} }
// ServeHTTP implements the http.Handler interface by delegating to the // ServeHTTP implements the http.Handler interface by delegating to the

View file

@ -18,17 +18,13 @@ package rpc
import ( import (
"bytes" "bytes"
"container/list"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net"
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -39,14 +35,14 @@ var (
ErrClientQuit = errors.New("client is closed") ErrClientQuit = errors.New("client is closed")
ErrNoResult = errors.New("no result in JSON-RPC response") ErrNoResult = errors.New("no result in JSON-RPC response")
ErrSubscriptionQueueOverflow = errors.New("subscription queue overflow") ErrSubscriptionQueueOverflow = errors.New("subscription queue overflow")
errClientReconnected = errors.New("client reconnected")
errDead = errors.New("connection lost")
) )
const ( const (
// Timeouts // Timeouts
tcpKeepAliveInterval = 30 * time.Second defaultDialTimeout = 10 * time.Second // used if context has no deadline
defaultDialTimeout = 10 * time.Second // used when dialing if the context has no deadline subscribeTimeout = 5 * time.Second // overall timeout eth_subscribe, rpc_modules calls
defaultWriteTimeout = 10 * time.Second // used for calls if the context has no deadline
subscribeTimeout = 5 * time.Second // overall timeout eth_subscribe, rpc_modules calls
) )
const ( const (
@ -60,7 +56,7 @@ const (
// The approach taken here is to maintain a per-subscription linked list buffer // The approach taken here is to maintain a per-subscription linked list buffer
// shrinks on demand. If the buffer reaches the size below, the subscription is // shrinks on demand. If the buffer reaches the size below, the subscription is
// dropped. // dropped.
maxClientSubscriptionBuffer = 8000 maxClientSubscriptionBuffer = 20000
) )
// BatchElem is an element in a batch request. // BatchElem is an element in a batch request.
@ -76,55 +72,57 @@ type BatchElem struct {
Error error Error error
} }
// A value of this type can a JSON-RPC request, notification, successful response or
// error response. Which one it is depends on the fields.
type jsonrpcMessage struct {
Version string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Error *jsonError `json:"error,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
}
func (msg *jsonrpcMessage) isNotification() bool {
return msg.ID == nil && msg.Method != ""
}
func (msg *jsonrpcMessage) isResponse() bool {
return msg.hasValidID() && msg.Method == "" && len(msg.Params) == 0
}
func (msg *jsonrpcMessage) hasValidID() bool {
return len(msg.ID) > 0 && msg.ID[0] != '{' && msg.ID[0] != '['
}
func (msg *jsonrpcMessage) String() string {
b, _ := json.Marshal(msg)
return string(b)
}
// Client represents a connection to an RPC server. // Client represents a connection to an RPC server.
type Client struct { type Client struct {
idCounter uint32 idgen func() ID // for subscriptions
connectFunc func(ctx context.Context) (net.Conn, error) isHTTP bool
isHTTP bool services *serviceRegistry
// writeConn is only safe to access outside dispatch, with the idCounter uint32
// write lock held. The write lock is taken by sending on
// requestOp and released by sending on sendDone. // This function, if non-nil, is called when the connection is lost.
writeConn net.Conn reconnectFunc reconnectFunc
// writeConn is used for writing to the connection on the caller's goroutine. It should
// only be accessed outside of dispatch, with the write lock held. The write lock is
// taken by sending on reqInit and released by sending on reqSent.
writeConn jsonWriter
// for dispatch // for dispatch
close chan struct{} close chan struct{}
didQuit chan struct{} // closed when client quits closing chan struct{} // closed when client is quitting
reconnected chan net.Conn // where write/reconnect sends the new connection didClose chan struct{} // closed when client quits
readErr chan error // errors from read reconnected chan ServerCodec // where write/reconnect sends the new connection
readResp chan []*jsonrpcMessage // valid messages from read readOp chan readOp // read messages
requestOp chan *requestOp // for registering response IDs readErr chan error // errors from read
sendDone chan error // signals write completion, releases write lock reqInit chan *requestOp // register response IDs, takes write lock
respWait map[string]*requestOp // active requests reqSent chan error // signals write completion, releases write lock
subs map[string]*ClientSubscription // active subscriptions reqTimeout chan *requestOp // removes response IDs when call timeout expires
}
type reconnectFunc func(ctx context.Context) (ServerCodec, error)
type clientContextKey struct{}
type clientConn struct {
codec ServerCodec
handler *handler
}
func (c *Client) newClientConn(conn ServerCodec) *clientConn {
ctx := context.WithValue(context.Background(), clientContextKey{}, c)
handler := newHandler(ctx, conn, c.idgen, c.services)
return &clientConn{conn, handler}
}
func (cc *clientConn) close(err error, inflightReq *requestOp) {
cc.handler.close(err, inflightReq)
cc.codec.close()
}
type readOp struct {
msgs []*jsonrpcMessage
batch bool
} }
type requestOp struct { type requestOp struct {
@ -134,9 +132,16 @@ type requestOp struct {
sub *ClientSubscription // only set for EthSubscribe requests sub *ClientSubscription // only set for EthSubscribe requests
} }
func (op *requestOp) wait(ctx context.Context) (*jsonrpcMessage, error) { func (op *requestOp) wait(ctx context.Context, c *Client) (*jsonrpcMessage, error) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
// Send the timeout to dispatch so it can remove the request IDs.
if !c.isHTTP {
select {
case c.reqTimeout <- op:
case <-c.closing:
}
}
return nil, ctx.Err() return nil, ctx.Err()
case resp := <-op.resp: case resp := <-op.resp:
return resp, op.err return resp, op.err
@ -171,6 +176,8 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
return DialHTTP(rawurl) return DialHTTP(rawurl)
case "ws", "wss": case "ws", "wss":
return DialWebsocket(ctx, rawurl, "") return DialWebsocket(ctx, rawurl, "")
case "stdio":
return DialStdIO(ctx)
case "": case "":
return DialIPC(ctx, rawurl) return DialIPC(ctx, rawurl)
default: default:
@ -178,36 +185,57 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
} }
} }
func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) { // Client retrieves the client from the context, if any. This can be used to perform
conn, err := connectFunc(initctx) // 'reverse calls' in a handler method.
func ClientFromContext(ctx context.Context) (*Client, bool) {
client, ok := ctx.Value(clientContextKey{}).(*Client)
return client, ok
}
func newClient(initctx context.Context, connect reconnectFunc) (*Client, error) {
conn, err := connect(initctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, isHTTP := conn.(*httpConn) c := initClient(conn, randomIDGenerator(), new(serviceRegistry))
c.reconnectFunc = connect
return c, nil
}
func initClient(conn ServerCodec, idgen func() ID, services *serviceRegistry) *Client {
_, isHTTP := conn.(*httpConn)
c := &Client{ c := &Client{
writeConn: conn, idgen: idgen,
isHTTP: isHTTP, isHTTP: isHTTP,
connectFunc: connectFunc, services: services,
writeConn: conn,
close: make(chan struct{}), close: make(chan struct{}),
didQuit: make(chan struct{}), closing: make(chan struct{}),
reconnected: make(chan net.Conn), didClose: make(chan struct{}),
reconnected: make(chan ServerCodec),
readOp: make(chan readOp),
readErr: make(chan error), readErr: make(chan error),
readResp: make(chan []*jsonrpcMessage), reqInit: make(chan *requestOp),
requestOp: make(chan *requestOp), reqSent: make(chan error, 1),
sendDone: make(chan error, 1), reqTimeout: make(chan *requestOp),
respWait: make(map[string]*requestOp),
subs: make(map[string]*ClientSubscription),
} }
if !isHTTP { if !isHTTP {
go c.dispatch(conn) go c.dispatch(conn)
} }
return c, nil return c
}
// RegisterName creates a service for the given receiver type under the given name. When no
// methods on the given receiver match the criteria to be either a RPC method or a
// subscription an error is returned. Otherwise a new service is created and added to the
// service collection this client provides to the server.
func (c *Client) RegisterName(name string, receiver interface{}) error {
return c.services.registerName(name, receiver)
} }
func (c *Client) nextID() json.RawMessage { func (c *Client) nextID() json.RawMessage {
id := atomic.AddUint32(&c.idCounter, 1) id := atomic.AddUint32(&c.idCounter, 1)
return []byte(strconv.FormatUint(uint64(id), 10)) return strconv.AppendUint(nil, uint64(id), 10)
} }
// SupportedModules calls the rpc_modules method, retrieving the list of // SupportedModules calls the rpc_modules method, retrieving the list of
@ -227,11 +255,24 @@ func (c *Client) Close() {
} }
select { select {
case c.close <- struct{}{}: case c.close <- struct{}{}:
<-c.didQuit <-c.didClose
case <-c.didQuit: case <-c.didClose:
} }
} }
// SetHeader adds a custom HTTP header to the client's requests.
// This method only works for clients using HTTP, it doesn't have
// any effect for clients using another transport.
func (c *Client) SetHeader(key, value string) {
if !c.isHTTP {
return
}
conn := c.writeConn.(*httpConn)
conn.mu.Lock()
conn.headers.Set(key, value)
conn.mu.Unlock()
}
// Call performs a JSON-RPC call with the given arguments and unmarshals into // Call performs a JSON-RPC call with the given arguments and unmarshals into
// result if no error occurred. // result if no error occurred.
// //
@ -248,6 +289,9 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er
// The result must be a pointer so that package json can unmarshal into it. You // The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored. // can also pass nil, in which case the result is ignored.
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr {
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
}
msg, err := c.newMessage(method, args...) msg, err := c.newMessage(method, args...)
if err != nil { if err != nil {
return err return err
@ -263,8 +307,8 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
return err return err
} }
// dispatch has accepted the request and will close the channel it when it quits. // dispatch has accepted the request and will close the channel when it quits.
switch resp, err := op.wait(ctx); { switch resp, err := op.wait(ctx, c); {
case err != nil: case err != nil:
return err return err
case resp.Error != nil: case resp.Error != nil:
@ -298,7 +342,7 @@ func (c *Client) GetResultCallContext(ctx context.Context, result interface{}, m
} }
// dispatch has accepted the request and will close the channel it when it quits. // dispatch has accepted the request and will close the channel it when it quits.
switch resp, err := op.wait(ctx); { switch resp, err := op.wait(ctx, c); {
case err != nil: case err != nil:
return nil, err return nil, err
case resp.Error != nil: case resp.Error != nil:
@ -356,7 +400,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error {
// Wait for all responses to come back. // Wait for all responses to come back.
for n := 0; n < len(b) && err == nil; n++ { for n := 0; n < len(b) && err == nil; n++ {
var resp *jsonrpcMessage var resp *jsonrpcMessage
resp, err = op.wait(ctx) resp, err = op.wait(ctx, c)
if err != nil { if err != nil {
break break
} }
@ -383,12 +427,28 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error {
return err return err
} }
// Notify sends a notification, i.e. a method call that doesn't expect a response.
func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) error {
op := new(requestOp)
msg, err := c.newMessage(method, args...)
if err != nil {
return err
}
msg.ID = nil
if c.isHTTP {
return c.sendHTTP(ctx, op, msg)
}
return c.send(ctx, op, msg)
}
// EthSubscribe registers a subscripion under the "eth" namespace. // EthSubscribe registers a subscripion under the "eth" namespace.
func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) { func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) {
return c.Subscribe(ctx, "eth", channel, args...) return c.Subscribe(ctx, "eth", channel, args...)
} }
// ShhSubscribe registers a subscripion under the "shh" namespace. // ShhSubscribe registers a subscripion under the "shh" namespace.
// Deprecated: use Subscribe(ctx, "shh", ...).
func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) { func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) {
return c.Subscribe(ctx, "shh", channel, args...) return c.Subscribe(ctx, "shh", channel, args...)
} }
@ -401,7 +461,7 @@ func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...
// The context argument cancels the RPC request that sets up the subscription but has no // The context argument cancels the RPC request that sets up the subscription but has no
// effect on the subscription after Subscribe has returned. // effect on the subscription after Subscribe has returned.
// //
// Slow subscribers will be dropped eventually. Client buffers up to 8000 notifications // Slow subscribers will be dropped eventually. Client buffers up to 20000 notifications
// before considering the subscriber dead. The subscription Err channel will receive // before considering the subscriber dead. The subscription Err channel will receive
// ErrSubscriptionQueueOverflow. Use a sufficiently large buffer on the channel or ensure // ErrSubscriptionQueueOverflow. Use a sufficiently large buffer on the channel or ensure
// that the channel usually has at least one reader to prevent this issue. // that the channel usually has at least one reader to prevent this issue.
@ -433,71 +493,78 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
if err := c.send(ctx, op, msg); err != nil { if err := c.send(ctx, op, msg); err != nil {
return nil, err return nil, err
} }
if _, err := op.wait(ctx); err != nil { if _, err := op.wait(ctx, c); err != nil {
return nil, err return nil, err
} }
return op.sub, nil return op.sub, nil
} }
func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) {
params, err := json.Marshal(paramsIn) msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
if err != nil { if paramsIn != nil { // prevent sending "params":null
return nil, err var err error
if msg.Params, err = json.Marshal(paramsIn); err != nil {
return nil, err
}
} }
return &jsonrpcMessage{Version: "2.0", ID: c.nextID(), Method: method, Params: params}, nil return msg, nil
} }
// send registers op with the dispatch loop, then sends msg on the connection. // send registers op with the dispatch loop, then sends msg on the connection.
// if sending fails, op is deregistered. // if sending fails, op is deregistered.
func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error { func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error {
select { select {
case c.requestOp <- op: case c.reqInit <- op:
log.Trace("", "msg", log.Lazy{Fn: func() string { err := c.write(ctx, msg, false)
return fmt.Sprint("sending ", msg) c.reqSent <- err
}})
err := c.write(ctx, msg)
c.sendDone <- err
return err return err
case <-ctx.Done(): case <-ctx.Done():
// This can happen if the client is overloaded or unable to keep up with // This can happen if the client is overloaded or unable to keep up with
// subscription notifications. // subscription notifications.
return ctx.Err() return ctx.Err()
case <-c.didQuit: case <-c.closing:
return ErrClientQuit return ErrClientQuit
} }
} }
func (c *Client) write(ctx context.Context, msg interface{}) error { func (c *Client) write(ctx context.Context, msg interface{}, retry bool) error {
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultWriteTimeout)
}
// The previous write failed. Try to establish a new connection. // The previous write failed. Try to establish a new connection.
if c.writeConn == nil { if c.writeConn == nil {
if err := c.reconnect(ctx); err != nil { if err := c.reconnect(ctx); err != nil {
return err return err
} }
} }
c.writeConn.SetWriteDeadline(deadline) err := c.writeConn.writeJSON(ctx, msg)
err := json.NewEncoder(c.writeConn).Encode(msg)
if err != nil { if err != nil {
c.writeConn = nil c.writeConn = nil
if !retry {
return c.write(ctx, msg, true)
}
} }
return err return err
} }
func (c *Client) reconnect(ctx context.Context) error { func (c *Client) reconnect(ctx context.Context) error {
newconn, err := c.connectFunc(ctx) if c.reconnectFunc == nil {
return errDead
}
if _, ok := ctx.Deadline(); !ok {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
}
newconn, err := c.reconnectFunc(ctx)
if err != nil { if err != nil {
log.Trace(fmt.Sprintf("reconnect failed: %v", err)) log.Trace("RPC client reconnect failed", "err", err)
return err return err
} }
select { select {
case c.reconnected <- newconn: case c.reconnected <- newconn:
c.writeConn = newconn c.writeConn = newconn
return nil return nil
case <-c.didQuit: case <-c.didClose:
newconn.Close() newconn.close()
return ErrClientQuit return ErrClientQuit
} }
} }
@ -505,321 +572,107 @@ func (c *Client) reconnect(ctx context.Context) error {
// dispatch is the main loop of the client. // dispatch is the main loop of the client.
// It sends read messages to waiting calls to Call and BatchCall // It sends read messages to waiting calls to Call and BatchCall
// and subscription notifications to registered subscriptions. // and subscription notifications to registered subscriptions.
func (c *Client) dispatch(conn net.Conn) { func (c *Client) dispatch(codec ServerCodec) {
// Spawn the initial read loop.
go c.read(conn)
var ( var (
lastOp *requestOp // tracks last send operation lastOp *requestOp // tracks last send operation
requestOpLock = c.requestOp // nil while the send lock is held reqInitLock = c.reqInit // nil while the send lock is held
reading = true // if true, a read loop is running conn = c.newClientConn(codec)
reading = true
) )
defer close(c.didQuit)
defer func() { defer func() {
c.closeRequestOps(ErrClientQuit) close(c.closing)
conn.Close()
if reading { if reading {
// Empty read channels until read is dead. conn.close(ErrClientQuit, nil)
for { c.drainRead()
select {
case <-c.readResp:
case <-c.readErr:
return
}
}
} }
close(c.didClose)
}() }()
// Spawn the initial read loop.
go c.read(codec)
for { for {
select { select {
case <-c.close: case <-c.close:
return return
// Read path. // Read path:
case batch := <-c.readResp: case op := <-c.readOp:
for _, msg := range batch { if op.batch {
switch { conn.handler.handleBatch(op.msgs)
case msg.isNotification(): } else {
log.Trace("", "msg", log.Lazy{Fn: func() string { conn.handler.handleMsg(op.msgs[0])
return fmt.Sprint("<-readResp: notification ", msg)
}})
c.handleNotification(msg)
case msg.isResponse():
log.Trace("", "msg", log.Lazy{Fn: func() string {
return fmt.Sprint("<-readResp: response ", msg)
}})
c.handleResponse(msg)
default:
log.Debug("", "msg", log.Lazy{Fn: func() string {
return fmt.Sprint("<-readResp: dropping weird message", msg)
}})
// TODO: maybe close
}
} }
case err := <-c.readErr: case err := <-c.readErr:
log.Debug(fmt.Sprintf("<-readErr: %v", err)) conn.handler.log.Debug("RPC connection read error", "err", err)
c.closeRequestOps(err) conn.close(err, lastOp)
conn.Close()
reading = false reading = false
case newconn := <-c.reconnected: // Reconnect:
log.Debug(fmt.Sprintf("<-reconnected: (reading=%t) %v", reading, conn.RemoteAddr())) case newcodec := <-c.reconnected:
log.Debug("RPC client reconnected", "reading", reading, "conn", newcodec.remoteAddr())
if reading { if reading {
// Wait for the previous read loop to exit. This is a rare case. // Wait for the previous read loop to exit. This is a rare case which
conn.Close() // happens if this loop isn't notified in time after the connection breaks.
<-c.readErr // In those cases the caller will notice first and reconnect. Closing the
// handler terminates all waiting requests (closing op.resp) except for
// lastOp, which will be transferred to the new handler.
conn.close(errClientReconnected, lastOp)
c.drainRead()
} }
go c.read(newconn) go c.read(newcodec)
reading = true reading = true
conn = newconn conn = c.newClientConn(newcodec)
// Re-register the in-flight request on the new handler
// because that's where it will be sent.
conn.handler.addRequestOp(lastOp)
// Send path. // Send path:
case op := <-requestOpLock: case op := <-reqInitLock:
// Stop listening for further send ops until the current one is done. // Stop listening for further requests until the current one has been sent.
requestOpLock = nil reqInitLock = nil
lastOp = op lastOp = op
for _, id := range op.ids { conn.handler.addRequestOp(op)
c.respWait[string(id)] = op
}
case err := <-c.sendDone: case err := <-c.reqSent:
if err != nil { if err != nil {
// Remove response handlers for the last send. We remove those here // Remove response handlers for the last send. When the read loop
// because the error is already handled in Call or BatchCall. When the // goes down, it will signal all other current operations.
// read loop goes down, it will signal all other current operations. conn.handler.removeRequestOp(lastOp)
for _, id := range lastOp.ids {
delete(c.respWait, string(id))
}
} }
// Listen for send ops again. // Let the next request in.
requestOpLock = c.requestOp reqInitLock = c.reqInit
lastOp = nil lastOp = nil
case op := <-c.reqTimeout:
conn.handler.removeRequestOp(op)
} }
} }
} }
// closeRequestOps unblocks pending send ops and active subscriptions. // drainRead drops read messages until an error occurs.
func (c *Client) closeRequestOps(err error) { func (c *Client) drainRead() {
didClose := make(map[*requestOp]bool)
for id, op := range c.respWait {
// Remove the op so that later calls will not close op.resp again.
delete(c.respWait, id)
if !didClose[op] {
op.err = err
close(op.resp)
didClose[op] = true
}
}
for id, sub := range c.subs {
delete(c.subs, id)
sub.quitWithError(err, false)
}
}
func (c *Client) handleNotification(msg *jsonrpcMessage) {
if !strings.HasSuffix(msg.Method, notificationMethodSuffix) {
log.Debug(fmt.Sprint("dropping non-subscription message: ", msg))
return
}
var subResult struct {
ID string `json:"subscription"`
Result json.RawMessage `json:"result"`
}
if err := json.Unmarshal(msg.Params, &subResult); err != nil {
log.Debug(fmt.Sprint("dropping invalid subscription message: ", msg))
return
}
if c.subs[subResult.ID] != nil {
c.subs[subResult.ID].deliver(subResult.Result)
}
}
func (c *Client) handleResponse(msg *jsonrpcMessage) {
op := c.respWait[string(msg.ID)]
if op == nil {
log.Debug(fmt.Sprintf("unsolicited response %v", msg))
return
}
delete(c.respWait, string(msg.ID))
// For normal responses, just forward the reply to Call/BatchCall.
if op.sub == nil {
op.resp <- msg
return
}
// For subscription responses, start the subscription if the server
// indicates success. EthSubscribe gets unblocked in either case through
// the op.resp channel.
defer close(op.resp)
if msg.Error != nil {
op.err = msg.Error
return
}
if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil {
go op.sub.start()
c.subs[op.sub.subid] = op.sub
}
}
// Reading happens on a dedicated goroutine.
func (c *Client) read(conn net.Conn) error {
var (
buf json.RawMessage
dec = json.NewDecoder(conn)
)
readMessage := func() (rs []*jsonrpcMessage, err error) {
buf = buf[:0]
if err = dec.Decode(&buf); err != nil {
return nil, err
}
if isBatch(buf) {
err = json.Unmarshal(buf, &rs)
} else {
rs = make([]*jsonrpcMessage, 1)
err = json.Unmarshal(buf, &rs[0])
}
return rs, err
}
for { for {
resp, err := readMessage() select {
case <-c.readOp:
case <-c.readErr:
return
}
}
}
// read decodes RPC messages from a codec, feeding them into dispatch.
func (c *Client) read(codec ServerCodec) {
for {
msgs, batch, err := codec.readBatch()
if _, ok := err.(*json.SyntaxError); ok {
codec.writeJSON(context.Background(), errorMessage(&parseError{err.Error()}))
}
if err != nil { if err != nil {
c.readErr <- err c.readErr <- err
return err return
} }
c.readResp <- resp c.readOp <- readOp{msgs, batch}
} }
} }
// Subscriptions.
// A ClientSubscription represents a subscription established through EthSubscribe.
type ClientSubscription struct {
client *Client
etype reflect.Type
channel reflect.Value
namespace string
subid string
in chan json.RawMessage
quitOnce sync.Once // ensures quit is closed once
quit chan struct{} // quit is closed when the subscription exits
errOnce sync.Once // ensures err is closed once
err chan error
}
func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
sub := &ClientSubscription{
client: c,
namespace: namespace,
etype: channel.Type().Elem(),
channel: channel,
quit: make(chan struct{}),
err: make(chan error, 1),
in: make(chan json.RawMessage),
}
return sub
}
// Err returns the subscription error channel. The intended use of Err is to schedule
// resubscription when the client connection is closed unexpectedly.
//
// The error channel receives a value when the subscription has ended due
// to an error. The received error is nil if Close has been called
// on the underlying client and no other error has occurred.
//
// The error channel is closed when Unsubscribe is called on the subscription.
func (sub *ClientSubscription) Err() <-chan error {
return sub.err
}
// Unsubscribe unsubscribes the notification and closes the error channel.
// It can safely be called more than once.
func (sub *ClientSubscription) Unsubscribe() {
sub.quitWithError(nil, true)
sub.errOnce.Do(func() { close(sub.err) })
}
func (sub *ClientSubscription) quitWithError(err error, unsubscribeServer bool) {
sub.quitOnce.Do(func() {
// The dispatch loop won't be able to execute the unsubscribe call
// if it is blocked on deliver. Close sub.quit first because it
// unblocks deliver.
close(sub.quit)
if unsubscribeServer {
sub.requestUnsubscribe()
}
if err != nil {
if err == ErrClientQuit {
err = nil // Adhere to subscription semantics.
}
sub.err <- err
}
})
}
func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
select {
case sub.in <- result:
return true
case <-sub.quit:
return false
}
}
func (sub *ClientSubscription) start() {
sub.quitWithError(sub.forward())
}
func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) {
cases := []reflect.SelectCase{
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)},
{Dir: reflect.SelectSend, Chan: sub.channel},
}
buffer := list.New()
defer buffer.Init()
for {
var chosen int
var recv reflect.Value
if buffer.Len() == 0 {
// Idle, omit send case.
chosen, recv, _ = reflect.Select(cases[:2])
} else {
// Non-empty buffer, send the first queued item.
cases[2].Send = reflect.ValueOf(buffer.Front().Value)
chosen, recv, _ = reflect.Select(cases)
}
switch chosen {
case 0: // <-sub.quit
return nil, false
case 1: // <-sub.in
val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
if err != nil {
return err, true
}
if buffer.Len() == maxClientSubscriptionBuffer {
return ErrSubscriptionQueueOverflow, true
}
buffer.PushBack(val)
case 2: // sub.channel<-
cases[2].Send = reflect.Value{} // Don't hold onto the value.
buffer.Remove(buffer.Front())
}
}
}
func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) {
val := reflect.New(sub.etype)
err := json.Unmarshal(result, val.Interface())
return val.Elem().Interface(), err
}
func (sub *ClientSubscription) requestUnsubscribe() error {
var result interface{}
return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid)
}

View file

@ -19,28 +19,28 @@ package rpc_test
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
// In this example, our client whishes to track the latest 'block number' // In this example, our client wishes to track the latest 'block number'
// known to the server. The server supports two methods: // known to the server. The server supports two methods:
// //
// eth_getBlockByNumber("latest", {}) // eth_getBlockByNumber("latest", {})
// returns the latest block object. // returns the latest block object.
// //
// eth_subscribe("newBlocks") // eth_subscribe("newHeads")
// creates a subscription which fires block objects when new blocks arrive. // creates a subscription which fires block objects when new blocks arrive.
type Block struct { type Block struct {
Number *big.Int Number *hexutil.Big
} }
func ExampleClientSubscription() { func ExampleClientSubscription() {
// Connect the client. // Connect the client.
client, _ := rpc.Dial("ws://127.0.0.1:8485") client, _ := rpc.Dial("ws://127.0.0.1:8545")
subch := make(chan Block) subch := make(chan Block)
// Ensure that subch receives the latest block. // Ensure that subch receives the latest block.
@ -66,7 +66,7 @@ func subscribeBlocks(client *rpc.Client, subch chan Block) {
defer cancel() defer cancel()
// Subscribe to new blocks. // Subscribe to new blocks.
sub, err := client.EthSubscribe(ctx, subch, "newBlocks") sub, err := client.EthSubscribe(ctx, subch, "newHeads")
if err != nil { if err != nil {
fmt.Println("subscribe error:", err) fmt.Println("subscribe error:", err)
return return
@ -75,7 +75,8 @@ func subscribeBlocks(client *rpc.Client, subch chan Block) {
// The connection is established now. // The connection is established now.
// Update the channel with the current block. // Update the channel with the current block.
var lastBlock Block var lastBlock Block
if err := client.CallContext(ctx, &lastBlock, "eth_getBlockByNumber", "latest"); err != nil { err = client.CallContext(ctx, &lastBlock, "eth_getBlockByNumber", "latest", false)
if err != nil {
fmt.Println("can't get latest block:", err) fmt.Println("can't get latest block:", err)
return return
} }

View file

@ -26,6 +26,7 @@ import (
"os" "os"
"reflect" "reflect"
"runtime" "runtime"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -35,36 +36,80 @@ import (
) )
func TestClientRequest(t *testing.T) { func TestClientRequest(t *testing.T) {
server := newTestServer("service", new(Service)) server := newTestServer()
defer server.Stop() defer server.Stop()
client := DialInProc(server) client := DialInProc(server)
defer client.Close() defer client.Close()
var resp Result var resp echoResult
if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil { if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) { if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
t.Errorf("incorrect result %#v", resp) t.Errorf("incorrect result %#v", resp)
} }
} }
func TestClientResponseType(t *testing.T) {
server := newTestServer()
defer server.Stop()
client := DialInProc(server)
defer client.Close()
if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
}
var resultVar echoResult
// Note: passing the var, not a ref
err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
if err == nil {
t.Error("Passing a var as result should be an error")
}
}
// This test checks that server-returned errors with code and data come out of Client.Call.
func TestClientErrorData(t *testing.T) {
server := newTestServer()
defer server.Stop()
client := DialInProc(server)
defer client.Close()
var resp interface{}
err := client.Call(&resp, "test_returnError")
if err == nil {
t.Fatal("expected error")
}
// Check code.
if e, ok := err.(Error); !ok {
t.Fatalf("client did not return rpc.Error, got %#v", e)
} else if e.ErrorCode() != (testError{}.ErrorCode()) {
t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
}
// Check data.
if e, ok := err.(DataError); !ok {
t.Fatalf("client did not return rpc.DataError, got %#v", e)
} else if e.ErrorData() != (testError{}.ErrorData()) {
t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
}
}
func TestClientBatchRequest(t *testing.T) { func TestClientBatchRequest(t *testing.T) {
server := newTestServer("service", new(Service)) server := newTestServer()
defer server.Stop() defer server.Stop()
client := DialInProc(server) client := DialInProc(server)
defer client.Close() defer client.Close()
batch := []BatchElem{ batch := []BatchElem{
{ {
Method: "service_echo", Method: "test_echo",
Args: []interface{}{"hello", 10, &Args{"world"}}, Args: []interface{}{"hello", 10, &echoArgs{"world"}},
Result: new(Result), Result: new(echoResult),
}, },
{ {
Method: "service_echo", Method: "test_echo",
Args: []interface{}{"hello2", 11, &Args{"world"}}, Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
Result: new(Result), Result: new(echoResult),
}, },
{ {
Method: "no_such_method", Method: "no_such_method",
@ -77,20 +122,20 @@ func TestClientBatchRequest(t *testing.T) {
} }
wantResult := []BatchElem{ wantResult := []BatchElem{
{ {
Method: "service_echo", Method: "test_echo",
Args: []interface{}{"hello", 10, &Args{"world"}}, Args: []interface{}{"hello", 10, &echoArgs{"world"}},
Result: &Result{"hello", 10, &Args{"world"}}, Result: &echoResult{"hello", 10, &echoArgs{"world"}},
}, },
{ {
Method: "service_echo", Method: "test_echo",
Args: []interface{}{"hello2", 11, &Args{"world"}}, Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
Result: &Result{"hello2", 11, &Args{"world"}}, Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
}, },
{ {
Method: "no_such_method", Method: "no_such_method",
Args: []interface{}{1, 2, 3}, Args: []interface{}{1, 2, 3},
Result: new(int), Result: new(int),
Error: &jsonError{Code: -32601, Message: "The method no_such_method_ does not exist/is not available"}, Error: &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
}, },
} }
if !reflect.DeepEqual(batch, wantResult) { if !reflect.DeepEqual(batch, wantResult) {
@ -98,6 +143,17 @@ func TestClientBatchRequest(t *testing.T) {
} }
} }
func TestClientNotify(t *testing.T) {
server := newTestServer()
defer server.Stop()
client := DialInProc(server)
defer client.Close()
if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
t.Fatal(err)
}
}
// func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) }
@ -106,7 +162,12 @@ func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) }
// This test checks that requests made through CallContext can be canceled by canceling // This test checks that requests made through CallContext can be canceled by canceling
// the context. // the context.
func testClientCancel(transport string, t *testing.T) { func testClientCancel(transport string, t *testing.T) {
server := newTestServer("service", new(Service)) // These tests take a lot of time, run them all at once.
// You probably want to run with -parallel 1 or comment out
// the call to t.Parallel if you enable the logging.
t.Parallel()
server := newTestServer()
defer server.Stop() defer server.Stop()
// What we want to achieve is that the context gets canceled // What we want to achieve is that the context gets canceled
@ -142,16 +203,11 @@ func testClientCancel(transport string, t *testing.T) {
panic("unknown transport: " + transport) panic("unknown transport: " + transport)
} }
// These tests take a lot of time, run them all at once.
// You probably want to run with -parallel 1 or comment out
// the call to t.Parallel if you enable the logging.
t.Parallel()
// The actual test starts here. // The actual test starts here.
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
nreqs = 10 nreqs = 10
ncallers = 6 ncallers = 10
) )
caller := func(index int) { caller := func(index int) {
defer wg.Done() defer wg.Done()
@ -172,13 +228,16 @@ func testClientCancel(transport string, t *testing.T) {
// deadline. // deadline.
ctx, cancel = context.WithTimeout(context.Background(), timeout) ctx, cancel = context.WithTimeout(context.Background(), timeout)
} }
// Now perform a call with the context. // Now perform a call with the context.
// The key thing here is that no call will ever complete successfully. // The key thing here is that no call will ever complete successfully.
err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout) err := client.CallContext(ctx, nil, "test_block")
if err != nil { switch {
log.Debug(fmt.Sprint("got expected error:", err)) case err == nil:
} else { _, hasDeadline := ctx.Deadline()
t.Errorf("no error for call with %v wait time", timeout) t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline)
// default:
// t.Logf("got expected error with %v wait time: %v", timeout, err)
} }
cancel() cancel()
} }
@ -191,7 +250,7 @@ func testClientCancel(transport string, t *testing.T) {
} }
func TestClientSubscribeInvalidArg(t *testing.T) { func TestClientSubscribeInvalidArg(t *testing.T) {
server := newTestServer("service", new(Service)) server := newTestServer()
defer server.Stop() defer server.Stop()
client := DialInProc(server) client := DialInProc(server)
defer client.Close() defer client.Close()
@ -221,14 +280,14 @@ func TestClientSubscribeInvalidArg(t *testing.T) {
} }
func TestClientSubscribe(t *testing.T) { func TestClientSubscribe(t *testing.T) {
server := newTestServer("eth", new(NotificationTestService)) server := newTestServer()
defer server.Stop() defer server.Stop()
client := DialInProc(server) client := DialInProc(server)
defer client.Close() defer client.Close()
nc := make(chan int) nc := make(chan int)
count := 10 count := 10
sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", count, 0) sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0)
if err != nil { if err != nil {
t.Fatal("can't subscribe:", err) t.Fatal("can't subscribe:", err)
} }
@ -251,58 +310,29 @@ func TestClientSubscribe(t *testing.T) {
} }
} }
func TestClientSubscribeCustomNamespace(t *testing.T) { // In this test, the connection drops while Subscribe is waiting for a response.
namespace := "custom"
server := newTestServer(namespace, new(NotificationTestService))
defer server.Stop()
client := DialInProc(server)
defer client.Close()
nc := make(chan int)
count := 10
sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0)
if err != nil {
t.Fatal("can't subscribe:", err)
}
for i := 0; i < count; i++ {
if val := <-nc; val != i {
t.Fatalf("value mismatch: got %d, want %d", val, i)
}
}
sub.Unsubscribe()
select {
case v := <-nc:
t.Fatal("received value after unsubscribe:", v)
case err := <-sub.Err():
if err != nil {
t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
}
case <-time.After(1 * time.Second):
t.Fatalf("subscription not closed within 1s after unsubscribe")
}
}
// In this test, the connection drops while EthSubscribe is
// waiting for a response.
func TestClientSubscribeClose(t *testing.T) { func TestClientSubscribeClose(t *testing.T) {
service := &NotificationTestService{ server := newTestServer()
service := &notificationTestService{
gotHangSubscriptionReq: make(chan struct{}), gotHangSubscriptionReq: make(chan struct{}),
unblockHangSubscription: make(chan struct{}), unblockHangSubscription: make(chan struct{}),
} }
server := newTestServer("eth", service) if err := server.RegisterName("nftest2", service); err != nil {
t.Fatal(err)
}
defer server.Stop() defer server.Stop()
client := DialInProc(server) client := DialInProc(server)
defer client.Close() defer client.Close()
var ( var (
nc = make(chan int) nc = make(chan int)
errc = make(chan error) errc = make(chan error, 1)
sub *ClientSubscription sub *ClientSubscription
err error err error
) )
go func() { go func() {
sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999) sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999)
errc <- err errc <- err
}() }()
@ -313,20 +343,43 @@ func TestClientSubscribeClose(t *testing.T) {
select { select {
case err := <-errc: case err := <-errc:
if err == nil { if err == nil {
t.Errorf("EthSubscribe returned nil error after Close") t.Errorf("Subscribe returned nil error after Close")
} }
if sub != nil { if sub != nil {
t.Error("EthSubscribe returned non-nil subscription after Close") t.Error("Subscribe returned non-nil subscription after Close")
} }
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
t.Fatalf("EthSubscribe did not return within 1s after Close") t.Fatalf("Subscribe did not return within 1s after Close")
}
}
// This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the
// client hangs during shutdown when Unsubscribe races with Client.Close.
func TestClientCloseUnsubscribeRace(t *testing.T) {
server := newTestServer()
defer server.Stop()
for i := 0; i < 20; i++ {
client := DialInProc(server)
nc := make(chan int)
sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1)
if err != nil {
t.Fatal(err)
}
go client.Close()
go sub.Unsubscribe()
select {
case <-sub.Err():
case <-time.After(5 * time.Second):
t.Fatal("subscription not closed within timeout")
}
} }
} }
// This test checks that Client doesn't lock up when a single subscriber // This test checks that Client doesn't lock up when a single subscriber
// doesn't read subscription events. // doesn't read subscription events.
func TestClientNotificationStorm(t *testing.T) { func TestClientNotificationStorm(t *testing.T) {
server := newTestServer("eth", new(NotificationTestService)) server := newTestServer()
defer server.Stop() defer server.Stop()
doTest := func(count int, wantError bool) { doTest := func(count int, wantError bool) {
@ -338,7 +391,7 @@ func TestClientNotificationStorm(t *testing.T) {
// Subscribe on the server. It will start sending many notifications // Subscribe on the server. It will start sending many notifications
// very quickly. // very quickly.
nc := make(chan int) nc := make(chan int)
sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0) sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0)
if err != nil { if err != nil {
t.Fatal("can't subscribe:", err) t.Fatal("can't subscribe:", err)
} }
@ -360,7 +413,7 @@ func TestClientNotificationStorm(t *testing.T) {
return return
} }
var r int var r int
err := client.CallContext(ctx, &r, "eth_echo", i) err := client.CallContext(ctx, &r, "nftest_echo", i)
if err != nil { if err != nil {
if !wantError { if !wantError {
t.Fatalf("(%d/%d) call error: %v", i, count, err) t.Fatalf("(%d/%d) call error: %v", i, count, err)
@ -368,14 +421,53 @@ func TestClientNotificationStorm(t *testing.T) {
return return
} }
} }
if wantError {
t.Fatalf("didn't get expected error")
}
} }
doTest(8000, false) doTest(8000, false)
doTest(10000, true) doTest(24000, true)
}
func TestClientSetHeader(t *testing.T) {
var gotHeader bool
srv := newTestServer()
httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("test") == "ok" {
gotHeader = true
}
srv.ServeHTTP(w, r)
}))
defer httpsrv.Close()
defer srv.Stop()
client, err := Dial(httpsrv.URL)
if err != nil {
t.Fatal(err)
}
defer client.Close()
client.SetHeader("test", "ok")
if _, err := client.SupportedModules(); err != nil {
t.Fatal(err)
}
if !gotHeader {
t.Fatal("client did not set custom header")
}
// Check that Content-Type can be replaced.
client.SetHeader("content-type", "application/x-garbage")
_, err = client.SupportedModules()
if err == nil {
t.Fatal("no error for invalid content-type header")
} else if !strings.Contains(err.Error(), "Unsupported Media Type") {
t.Fatalf("error is not related to content-type: %q", err)
}
} }
func TestClientHTTP(t *testing.T) { func TestClientHTTP(t *testing.T) {
server := newTestServer("service", new(Service)) server := newTestServer()
defer server.Stop() defer server.Stop()
client, hs := httpTestClient(server, "http", nil) client, hs := httpTestClient(server, "http", nil)
@ -384,16 +476,15 @@ func TestClientHTTP(t *testing.T) {
// Launch concurrent requests. // Launch concurrent requests.
var ( var (
results = make([]Result, 100) results = make([]echoResult, 100)
errc = make(chan error) errc = make(chan error, len(results))
wantResult = Result{"a", 1, new(Args)} wantResult = echoResult{"a", 1, new(echoArgs)}
) )
defer client.Close() defer client.Close()
for i := range results { for i := range results {
i := i i := i
go func() { go func() {
errc <- client.Call(&results[i], "service_echo", errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args)
wantResult.String, wantResult.Int, wantResult.Args)
}() }()
} }
@ -421,16 +512,16 @@ func TestClientHTTP(t *testing.T) {
func TestClientReconnect(t *testing.T) { func TestClientReconnect(t *testing.T) {
startServer := func(addr string) (*Server, net.Listener) { startServer := func(addr string) (*Server, net.Listener) {
srv := newTestServer("service", new(Service)) srv := newTestServer()
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal("can't listen:", err)
} }
go http.Serve(l, srv.WebsocketHandler([]string{"*"})) go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
return srv, l return srv, l
} }
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
defer cancel() defer cancel()
// Start a server and corresponding client. // Start a server and corresponding client.
@ -441,22 +532,23 @@ func TestClientReconnect(t *testing.T) {
} }
// Perform a call. This should work because the server is up. // Perform a call. This should work because the server is up.
var resp Result var resp echoResult
if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Shut down the server and try calling again. It shouldn't work. // Shut down the server and allow for some cool down time so we can listen on the same
// address again.
l1.Close() l1.Close()
s1.Stop() s1.Stop()
if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { time.Sleep(2 * time.Second)
// Try calling again. It shouldn't work.
if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
t.Error("successful call while the server is down") t.Error("successful call while the server is down")
t.Logf("resp: %#v", resp) t.Logf("resp: %#v", resp)
} }
// Allow for some cool down time so we can listen on the same address again.
time.Sleep(2 * time.Second)
// Start it up again and call again. The connection should be reestablished. // Start it up again and call again. The connection should be reestablished.
// We spawn multiple calls here to check whether this hangs somehow. // We spawn multiple calls here to check whether this hangs somehow.
s2, l2 := startServer(l1.Addr().String()) s2, l2 := startServer(l1.Addr().String())
@ -468,8 +560,8 @@ func TestClientReconnect(t *testing.T) {
for i := 0; i < cap(errors); i++ { for i := 0; i < cap(errors); i++ {
go func() { go func() {
<-start <-start
var resp Result var resp echoResult
errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
}() }()
} }
close(start) close(start)
@ -479,20 +571,12 @@ func TestClientReconnect(t *testing.T) {
errcount++ errcount++
} }
} }
t.Log("err:", err) t.Logf("%d errors, last error: %v", errcount, err)
if errcount > 1 { if errcount > 1 {
t.Errorf("expected one error after disconnect, got %d", errcount) t.Errorf("expected one error after disconnect, got %d", errcount)
} }
} }
func newTestServer(serviceName string, service interface{}) *Server {
server := NewServer()
if err := server.RegisterName(serviceName, service); err != nil {
panic(err)
}
return server
}
func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
// Create the HTTP server. // Create the HTTP server.
var hs *httptest.Server var hs *httptest.Server

35
rpc/constants_unix.go Normal file
View file

@ -0,0 +1,35 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package rpc
/*
#include <sys/un.h>
__attribute__((weak))
int max_socket_path_size() {
struct sockaddr_un s;
return sizeof(s.sun_path);
}
*/
import "C"
var (
max_path_size = C.max_socket_path_size()
)

View file

@ -0,0 +1,26 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build !cgo && !windows
// +build !cgo,!windows
package rpc
var (
// On Linux, sun_path is 108 bytes in size
// see http://man7.org/linux/man-pages/man7/unix.7.html
max_path_size = 108
)

View file

@ -15,50 +15,54 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/* /*
Package rpc provides access to the exported methods of an object across a network
or other I/O connection. After creating a server instance objects can be registered, Package rpc implements bi-directional JSON-RPC 2.0 on multiple transports.
making it visible from the outside. Exported methods that follow specific
conventions can be called remotely. It also has support for the publish/subscribe It provides access to the exported methods of an object across a network or other I/O
pattern. connection. After creating a server or client instance, objects can be registered to make
them visible as 'services'. Exported methods that follow specific conventions can be
called remotely. It also has support for the publish/subscribe pattern.
RPC Methods
Methods that satisfy the following criteria are made available for remote access: Methods that satisfy the following criteria are made available for remote access:
- object must be exported
- method must be exported - method must be exported
- method returns 0, 1 (response or error) or 2 (response and error) values - method returns 0, 1 (response or error) or 2 (response and error) values
- method argument(s) must be exported or builtin types
- method returned value(s) must be exported or builtin types
An example method: An example method:
func (s *CalcService) Add(a, b int) (int, error) func (s *CalcService) Add(a, b int) (int, error)
When the returned error isn't nil the returned integer is ignored and the error is When the returned error isn't nil the returned integer is ignored and the error is sent
send back to the client. Otherwise the returned integer is send back to the client. back to the client. Otherwise the returned integer is sent back to the client.
Optional arguments are supported by accepting pointer values as arguments. E.g. Optional arguments are supported by accepting pointer values as arguments. E.g. if we want
if we want to do the addition in an optional finite field we can accept a mod to do the addition in an optional finite field we can accept a mod argument as pointer
argument as pointer value. value.
func (s *CalService) Add(a, b int, mod *int) (int, error) func (s *CalcService) Add(a, b int, mod *int) (int, error)
This RPC method can be called with 2 integers and a null value as third argument. This RPC method can be called with 2 integers and a null value as third argument. In that
In that case the mod argument will be nil. Or it can be called with 3 integers, case the mod argument will be nil. Or it can be called with 3 integers, in that case mod
in that case mod will be pointing to the given third argument. Since the optional will be pointing to the given third argument. Since the optional argument is the last
argument is the last argument the RPC package will also accept 2 integers as argument the RPC package will also accept 2 integers as arguments. It will pass the mod
arguments. It will pass the mod argument as nil to the RPC method. argument as nil to the RPC method.
The server offers the ServeCodec method which accepts a ServerCodec instance. It will The server offers the ServeCodec method which accepts a ServerCodec instance. It will read
read requests from the codec, process the request and sends the response back to the requests from the codec, process the request and sends the response back to the client
client using the codec. The server can execute requests concurrently. Responses using the codec. The server can execute requests concurrently. Responses can be sent back
can be sent back to the client out of order. to the client out of order.
An example server which uses the JSON codec: An example server which uses the JSON codec:
type CalculatorService struct {} type CalculatorService struct {}
func (s *CalculatorService) Add(a, b int) int { func (s *CalculatorService) Add(a, b int) int {
return a + b return a + b
} }
func (s *CalculatorService Div(a, b int) (int, error) { func (s *CalculatorService) Div(a, b int) (int, error) {
if b == 0 { if b == 0 {
return 0, errors.New("divide by zero") return 0, errors.New("divide by zero")
} }
@ -67,32 +71,40 @@ An example server which uses the JSON codec:
calculator := new(CalculatorService) calculator := new(CalculatorService)
server := NewServer() server := NewServer()
server.RegisterName("calculator", calculator") server.RegisterName("calculator", calculator)
l, _ := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: "/tmp/calculator.sock"}) l, _ := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: "/tmp/calculator.sock"})
for { server.ServeListener(l)
c, _ := l.AcceptUnix()
codec := v2.NewJSONCodec(c) Subscriptions
go server.ServeCodec(codec)
}
The package also supports the publish subscribe pattern through the use of subscriptions. The package also supports the publish subscribe pattern through the use of subscriptions.
A method that is considered eligible for notifications must satisfy the following criteria: A method that is considered eligible for notifications must satisfy the following
- object must be exported criteria:
- method must be exported - method must be exported
- first method argument type must be context.Context - first method argument type must be context.Context
- method argument(s) must be exported or builtin types - method must have return types (rpc.Subscription, error)
- method must return the tuple Subscription, error
An example method: An example method:
func (s *BlockChainService) NewBlocks(ctx context.Context) (Subscription, error) {
func (s *BlockChainService) NewBlocks(ctx context.Context) (rpc.Subscription, error) {
... ...
} }
Subscriptions are deleted when: When the service containing the subscription method is registered to the server, for
- the user sends an unsubscribe request example under the "blockchain" namespace, a subscription is created by calling the
- the connection which was used to create the subscription is closed. This can be initiated "blockchain_subscribe" method.
by the client and server. The server will close the connection on an write error or when
the queue of buffered notifications gets too big. Subscriptions are deleted when the user sends an unsubscribe request or when the
connection which was used to create the subscription is closed. This can be initiated by
the client and server. The server will close the connection for any write error.
For more information about subscriptions, see https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB.
Reverse Calls
In any method handler, an instance of rpc.Client can be accessed through the
ClientFromContext method. Using this client instance, server-to-client method calls can be
performed on the RPC connection.
*/ */
package rpc package rpc

52
rpc/endpoints.go Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"net"
"strings"
"github.com/XinFinOrg/XDPoSChain/log"
)
// StartIPCEndpoint starts an IPC endpoint.
func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) {
// Register all the APIs exposed by the services.
var (
handler = NewServer()
regMap = make(map[string]struct{})
registered []string
)
for _, api := range apis {
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
log.Info("IPC registration failed", "namespace", api.Namespace, "error", err)
return nil, nil, err
}
if _, ok := regMap[api.Namespace]; !ok {
registered = append(registered, api.Namespace)
regMap[api.Namespace] = struct{}{}
}
}
log.Debug("IPCs registered", "namespaces", strings.Join(registered, ","))
// All APIs registered, start the IPC listener.
listener, err := ipcListen(ipcEndpoint)
if err != nil {
return nil, nil, err
}
go handler.ServeListener(listener)
return listener, handler, nil
}

View file

@ -18,18 +18,40 @@ package rpc
import "fmt" import "fmt"
// request is for an unknown service var (
type methodNotFoundError struct { _ Error = new(methodNotFoundError)
service string _ Error = new(subscriptionNotFoundError)
method string _ Error = new(parseError)
} _ Error = new(invalidRequestError)
_ Error = new(invalidMessageError)
_ Error = new(invalidParamsError)
)
const defaultErrorCode = -32000
type methodNotFoundError struct{ method string }
func (e *methodNotFoundError) ErrorCode() int { return -32601 } func (e *methodNotFoundError) ErrorCode() int { return -32601 }
func (e *methodNotFoundError) Error() string { func (e *methodNotFoundError) Error() string {
return fmt.Sprintf("The method %s%s%s does not exist/is not available", e.service, serviceMethodSeparator, e.method) return fmt.Sprintf("the method %s does not exist/is not available", e.method)
} }
type subscriptionNotFoundError struct{ namespace, subscription string }
func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 }
func (e *subscriptionNotFoundError) Error() string {
return fmt.Sprintf("no %q subscription in %s namespace", e.subscription, e.namespace)
}
// Invalid JSON was received by the server.
type parseError struct{ message string }
func (e *parseError) ErrorCode() int { return -32700 }
func (e *parseError) Error() string { return e.message }
// received message isn't a valid request // received message isn't a valid request
type invalidRequestError struct{ message string } type invalidRequestError struct{ message string }
@ -50,17 +72,3 @@ type invalidParamsError struct{ message string }
func (e *invalidParamsError) ErrorCode() int { return -32602 } func (e *invalidParamsError) ErrorCode() int { return -32602 }
func (e *invalidParamsError) Error() string { return e.message } func (e *invalidParamsError) Error() string { return e.message }
// logic error, callback returned an error
type callbackError struct{ message string }
func (e *callbackError) ErrorCode() int { return -32000 }
func (e *callbackError) Error() string { return e.message }
// issued when a request is received after the server is issued to stop.
type shutdownError struct{}
func (e *shutdownError) ErrorCode() int { return -32000 }
func (e *shutdownError) Error() string { return "server is shutting down" }

416
rpc/handler.go Normal file
View file

@ -0,0 +1,416 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"context"
"encoding/json"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/XinFinOrg/XDPoSChain/log"
)
// handler handles JSON-RPC messages. There is one handler per connection. Note that
// handler is not safe for concurrent use. Message handling never blocks indefinitely
// because RPCs are processed on background goroutines launched by handler.
//
// The entry points for incoming messages are:
//
// h.handleMsg(message)
// h.handleBatch(message)
//
// Outgoing calls use the requestOp struct. Register the request before sending it
// on the connection:
//
// op := &requestOp{ids: ...}
// h.addRequestOp(op)
//
// Now send the request, then wait for the reply to be delivered through handleMsg:
//
// if err := op.wait(...); err != nil {
// h.removeRequestOp(op) // timeout, etc.
// }
type handler struct {
reg *serviceRegistry
unsubscribeCb *callback
idgen func() ID // subscription ID generator
respWait map[string]*requestOp // active client requests
clientSubs map[string]*ClientSubscription // active client subscriptions
callWG sync.WaitGroup // pending call goroutines
rootCtx context.Context // canceled by close()
cancelRoot func() // cancel function for rootCtx
conn jsonWriter // where responses will be sent
log log.Logger
allowSubscribe bool
subLock sync.Mutex
serverSubs map[ID]*Subscription
}
type callProc struct {
ctx context.Context
notifiers []*Notifier
}
func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry) *handler {
rootCtx, cancelRoot := context.WithCancel(connCtx)
h := &handler{
reg: reg,
idgen: idgen,
conn: conn,
respWait: make(map[string]*requestOp),
clientSubs: make(map[string]*ClientSubscription),
rootCtx: rootCtx,
cancelRoot: cancelRoot,
allowSubscribe: true,
serverSubs: make(map[ID]*Subscription),
log: log.Root(),
}
if conn.remoteAddr() != "" {
h.log = h.log.New("conn", conn.remoteAddr())
}
h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe))
return h
}
// handleBatch executes all messages in a batch and returns the responses.
func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
// Emit error response for empty batches:
if len(msgs) == 0 {
h.startCallProc(func(cp *callProc) {
h.conn.writeJSON(cp.ctx, errorMessage(&invalidRequestError{"empty batch"}))
})
return
}
// Handle non-call messages first:
calls := make([]*jsonrpcMessage, 0, len(msgs))
for _, msg := range msgs {
if handled := h.handleImmediate(msg); !handled {
calls = append(calls, msg)
}
}
if len(calls) == 0 {
return
}
// Process calls on a goroutine because they may block indefinitely:
h.startCallProc(func(cp *callProc) {
answers := make([]*jsonrpcMessage, 0, len(msgs))
for _, msg := range calls {
if answer := h.handleCallMsg(cp, msg); answer != nil {
answers = append(answers, answer)
}
}
h.addSubscriptions(cp.notifiers)
if len(answers) > 0 {
h.conn.writeJSON(cp.ctx, answers)
}
for _, n := range cp.notifiers {
n.activate()
}
})
}
// handleMsg handles a single message.
func (h *handler) handleMsg(msg *jsonrpcMessage) {
if ok := h.handleImmediate(msg); ok {
return
}
h.startCallProc(func(cp *callProc) {
answer := h.handleCallMsg(cp, msg)
h.addSubscriptions(cp.notifiers)
if answer != nil {
h.conn.writeJSON(cp.ctx, answer)
}
for _, n := range cp.notifiers {
n.activate()
}
})
}
// close cancels all requests except for inflightReq and waits for
// call goroutines to shut down.
func (h *handler) close(err error, inflightReq *requestOp) {
h.cancelAllRequests(err, inflightReq)
h.callWG.Wait()
h.cancelRoot()
h.cancelServerSubscriptions(err)
}
// addRequestOp registers a request operation.
func (h *handler) addRequestOp(op *requestOp) {
for _, id := range op.ids {
h.respWait[string(id)] = op
}
}
// removeRequestOps stops waiting for the given request IDs.
func (h *handler) removeRequestOp(op *requestOp) {
for _, id := range op.ids {
delete(h.respWait, string(id))
}
}
// cancelAllRequests unblocks and removes pending requests and active subscriptions.
func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) {
didClose := make(map[*requestOp]bool)
if inflightReq != nil {
didClose[inflightReq] = true
}
for id, op := range h.respWait {
// Remove the op so that later calls will not close op.resp again.
delete(h.respWait, id)
if !didClose[op] {
op.err = err
close(op.resp)
didClose[op] = true
}
}
for id, sub := range h.clientSubs {
delete(h.clientSubs, id)
sub.quitWithError(false, err)
}
}
func (h *handler) addSubscriptions(nn []*Notifier) {
h.subLock.Lock()
defer h.subLock.Unlock()
for _, n := range nn {
if sub := n.takeSubscription(); sub != nil {
h.serverSubs[sub.ID] = sub
}
}
}
// cancelServerSubscriptions removes all subscriptions and closes their error channels.
func (h *handler) cancelServerSubscriptions(err error) {
h.subLock.Lock()
defer h.subLock.Unlock()
for id, s := range h.serverSubs {
s.err <- err
close(s.err)
delete(h.serverSubs, id)
}
}
// startCallProc runs fn in a new goroutine and starts tracking it in the h.calls wait group.
func (h *handler) startCallProc(fn func(*callProc)) {
h.callWG.Add(1)
go func() {
ctx, cancel := context.WithCancel(h.rootCtx)
defer h.callWG.Done()
defer cancel()
fn(&callProc{ctx: ctx})
}()
}
// handleImmediate executes non-call messages. It returns false if the message is a
// call or requires a reply.
func (h *handler) handleImmediate(msg *jsonrpcMessage) bool {
start := time.Now()
switch {
case msg.isNotification():
if strings.HasSuffix(msg.Method, notificationMethodSuffix) {
h.handleSubscriptionResult(msg)
return true
}
return false
case msg.isResponse():
h.handleResponse(msg)
h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "t", time.Since(start))
return true
default:
return false
}
}
// handleSubscriptionResult processes subscription notifications.
func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) {
var result subscriptionResult
if err := json.Unmarshal(msg.Params, &result); err != nil {
h.log.Debug("Dropping invalid subscription message")
return
}
if h.clientSubs[result.ID] != nil {
h.clientSubs[result.ID].deliver(result.Result)
}
}
// handleResponse processes method call responses.
func (h *handler) handleResponse(msg *jsonrpcMessage) {
op := h.respWait[string(msg.ID)]
if op == nil {
h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID})
return
}
delete(h.respWait, string(msg.ID))
// For normal responses, just forward the reply to Call/BatchCall.
if op.sub == nil {
op.resp <- msg
return
}
// For subscription responses, start the subscription if the server
// indicates success. EthSubscribe gets unblocked in either case through
// the op.resp channel.
defer close(op.resp)
if msg.Error != nil {
op.err = msg.Error
return
}
if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil {
go op.sub.start()
h.clientSubs[op.sub.subid] = op.sub
}
}
// handleCallMsg executes a call message and returns the answer.
func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
start := time.Now()
switch {
case msg.isNotification():
h.handleCall(ctx, msg)
h.log.Debug("Served "+msg.Method, "t", time.Since(start))
return nil
case msg.isCall():
resp := h.handleCall(ctx, msg)
var ctx []interface{}
ctx = append(ctx, "reqid", idForLog{msg.ID}, "t", time.Since(start))
if resp.Error != nil {
ctx = append(ctx, "err", resp.Error.Message)
if resp.Error.Data != nil {
ctx = append(ctx, "errdata", resp.Error.Data)
}
h.log.Warn("Served "+msg.Method, ctx...)
} else {
h.log.Debug("Served "+msg.Method, ctx...)
}
return resp
case msg.hasValidID():
return msg.errorResponse(&invalidRequestError{"invalid request"})
default:
return errorMessage(&invalidRequestError{"invalid request"})
}
}
// handleCall processes method calls.
func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
if msg.isSubscribe() {
return h.handleSubscribe(cp, msg)
}
var callb *callback
if msg.isUnsubscribe() {
callb = h.unsubscribeCb
} else {
callb = h.reg.callback(msg.Method)
}
if callb == nil {
return msg.errorResponse(&methodNotFoundError{method: msg.Method})
}
args, err := parsePositionalArguments(msg.Params, callb.argTypes)
if err != nil {
return msg.errorResponse(&invalidParamsError{err.Error()})
}
start := time.Now()
answer := h.runMethod(cp.ctx, msg, callb, args)
// Collect the statistics for RPC calls if metrics is enabled.
// We only care about pure rpc call. Filter out subscription.
if callb != h.unsubscribeCb {
rpcRequestGauge.Inc(1)
if answer.Error != nil {
failedReqeustGauge.Inc(1)
} else {
successfulRequestGauge.Inc(1)
}
rpcServingTimer.UpdateSince(start)
newRPCServingTimer(msg.Method, answer.Error == nil).UpdateSince(start)
}
return answer
}
// handleSubscribe processes *_subscribe method calls.
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
if !h.allowSubscribe {
return msg.errorResponse(ErrNotificationsUnsupported)
}
// Subscription method name is first argument.
name, err := parseSubscriptionName(msg.Params)
if err != nil {
return msg.errorResponse(&invalidParamsError{err.Error()})
}
namespace := msg.namespace()
callb := h.reg.subscription(namespace, name)
if callb == nil {
return msg.errorResponse(&subscriptionNotFoundError{namespace, name})
}
// Parse subscription name arg too, but remove it before calling the callback.
argTypes := append([]reflect.Type{stringType}, callb.argTypes...)
args, err := parsePositionalArguments(msg.Params, argTypes)
if err != nil {
return msg.errorResponse(&invalidParamsError{err.Error()})
}
args = args[1:]
// Install notifier in context so the subscription handler can find it.
n := &Notifier{h: h, namespace: namespace}
cp.notifiers = append(cp.notifiers, n)
ctx := context.WithValue(cp.ctx, notifierKey{}, n)
return h.runMethod(ctx, msg, callb, args)
}
// runMethod runs the Go callback for an RPC method.
func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *callback, args []reflect.Value) *jsonrpcMessage {
result, err := callb.call(ctx, msg.Method, args)
if err != nil {
return msg.errorResponse(err)
}
return msg.response(result)
}
// unsubscribe is the callback function for all *_unsubscribe calls.
func (h *handler) unsubscribe(ctx context.Context, id ID) (bool, error) {
h.subLock.Lock()
defer h.subLock.Unlock()
s := h.serverSubs[id]
if s == nil {
return false, ErrSubscriptionNotFound
}
close(s.err)
delete(h.serverSubs, id)
return true, nil
}
type idForLog struct{ json.RawMessage }
func (id idForLog) String() string {
if s, err := strconv.Unquote(string(id.RawMessage)); err == nil {
return s
}
return string(id.RawMessage)
}

View file

@ -27,6 +27,7 @@ import (
"mime" "mime"
"net" "net"
"net/http" "net/http"
"net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -35,50 +36,97 @@ import (
) )
const ( const (
maxRequestContentLength = 1024 * 1024 * 5
contentType = "application/json" contentType = "application/json"
maxRequestContentLength = 1024 * 128
) )
var nullAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:0") // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"}
type httpConn struct { type httpConn struct {
client *http.Client client *http.Client
req *http.Request url string
closeOnce sync.Once closeOnce sync.Once
closed chan struct{} closeCh chan interface{}
mu sync.Mutex // protects headers
headers http.Header
} }
// httpConn is treated specially by Client. // httpConn is treated specially by Client.
func (hc *httpConn) LocalAddr() net.Addr { return nullAddr } func (hc *httpConn) writeJSON(context.Context, interface{}) error {
func (hc *httpConn) RemoteAddr() net.Addr { return nullAddr } panic("writeJSON called on httpConn")
func (hc *httpConn) SetReadDeadline(time.Time) error { return nil }
func (hc *httpConn) SetWriteDeadline(time.Time) error { return nil }
func (hc *httpConn) SetDeadline(time.Time) error { return nil }
func (hc *httpConn) Write([]byte) (int, error) { panic("Write called") }
func (hc *httpConn) Read(b []byte) (int, error) {
<-hc.closed
return 0, io.EOF
} }
func (hc *httpConn) Close() error { func (hc *httpConn) remoteAddr() string {
hc.closeOnce.Do(func() { close(hc.closed) }) return hc.url
return nil }
func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) {
<-hc.closeCh
return nil, false, io.EOF
}
func (hc *httpConn) close() {
hc.closeOnce.Do(func() { close(hc.closeCh) })
}
func (hc *httpConn) closed() <-chan interface{} {
return hc.closeCh
}
// HTTPTimeouts represents the configuration params for the HTTP RPC server.
type HTTPTimeouts struct {
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout time.Duration
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, ReadHeaderTimeout is used.
IdleTimeout time.Duration
}
// DefaultHTTPTimeouts represents the default timeout values used if further
// configuration is not provided.
var DefaultHTTPTimeouts = HTTPTimeouts{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
} }
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
// using the provided HTTP Client. // using the provided HTTP Client.
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
req, err := http.NewRequest(http.MethodPost, endpoint, nil) // Sanity check URL so we don't end up with a client that will fail every request.
_, err := url.Parse(endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", contentType)
req.Header.Set("Accept", contentType)
initctx := context.Background() initctx := context.Background()
return newClient(initctx, func(context.Context) (net.Conn, error) { headers := make(http.Header, 2)
return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil headers.Set("accept", contentType)
headers.Set("content-type", contentType)
return newClient(initctx, func(context.Context) (ServerCodec, error) {
hc := &httpConn{
client: client,
headers: headers,
url: endpoint,
closeCh: make(chan interface{}),
}
return hc, nil
}) })
} }
@ -90,10 +138,19 @@ func DialHTTP(endpoint string) (*Client, error) {
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
hc := c.writeConn.(*httpConn) hc := c.writeConn.(*httpConn)
respBody, err := hc.doRequest(ctx, msg) respBody, err := hc.doRequest(ctx, msg)
if respBody != nil {
defer respBody.Close()
}
if err != nil { if err != nil {
if respBody != nil {
buf := new(bytes.Buffer)
if _, err2 := buf.ReadFrom(respBody); err2 == nil {
return fmt.Errorf("%v: %v", err, buf.String())
}
}
return err return err
} }
defer respBody.Close()
var respmsg jsonrpcMessage var respmsg jsonrpcMessage
if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
return err return err
@ -124,28 +181,52 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
if err != nil { if err != nil {
return nil, err return nil, err
} }
req := hc.req.WithContext(ctx) req, err := http.NewRequestWithContext(ctx, "POST", hc.url, ioutil.NopCloser(bytes.NewReader(body)))
req.Body = ioutil.NopCloser(bytes.NewReader(body)) if err != nil {
return nil, err
}
req.ContentLength = int64(len(body)) req.ContentLength = int64(len(body))
// set headers
hc.mu.Lock()
req.Header = hc.headers.Clone()
hc.mu.Unlock()
// do request
resp, err := hc.client.Do(req) resp, err := hc.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return resp.Body, errors.New(resp.Status)
}
return resp.Body, nil return resp.Body, nil
} }
// httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method. // httpServerConn turns a HTTP connection into a Conn.
type httpReadWriteNopCloser struct { type httpServerConn struct {
io.Reader io.Reader
io.Writer io.Writer
r *http.Request
} }
// Close does nothing and returns always nil func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
func (t *httpReadWriteNopCloser) Close() error { body := io.LimitReader(r.Body, maxRequestContentLength)
return nil conn := &httpServerConn{Reader: body, Writer: w, r: r}
return NewCodec(conn)
} }
// Close does nothing and always returns nil.
func (t *httpServerConn) Close() error { return nil }
// RemoteAddr returns the peer address of the underlying connection.
func (t *httpServerConn) RemoteAddr() string {
return t.r.RemoteAddr
}
// SetWriteDeadline does nothing and always returns nil.
func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil }
// NewHTTPServer creates a new HTTP RPC server around an API provider. // NewHTTPServer creates a new HTTP RPC server around an API provider.
// //
// Deprecated: Server implements http.Handler // Deprecated: Server implements http.Handler
@ -162,24 +243,34 @@ func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server {
} }
// ServeHTTP serves JSON-RPC requests over HTTP. // ServeHTTP serves JSON-RPC requests over HTTP.
func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Permit dumb empty requests for remote health-checks (AWS) // Permit dumb empty requests for remote health-checks (AWS)
if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" {
w.WriteHeader(http.StatusOK)
return return
} }
if code, err := validateRequest(r); err != nil { if code, err := validateRequest(r); err != nil {
http.Error(w, err.Error(), code) http.Error(w, err.Error(), code)
return return
} }
// All checks passed, create a codec that reads direct from the request body // All checks passed, create a codec that reads directly from the request body
// untilEOF and writes the response to w and order the server to process a // until EOF, writes the response to w, and orders the server to process a
// single request. // single request.
body := io.LimitReader(r.Body, maxRequestContentLength) ctx := r.Context()
codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) ctx = context.WithValue(ctx, "remote", r.RemoteAddr)
defer codec.Close() ctx = context.WithValue(ctx, "scheme", r.Proto)
ctx = context.WithValue(ctx, "local", r.Host)
if ua := r.Header.Get("User-Agent"); ua != "" {
ctx = context.WithValue(ctx, "User-Agent", ua)
}
if origin := r.Header.Get("Origin"); origin != "" {
ctx = context.WithValue(ctx, "Origin", origin)
}
w.Header().Set("content-type", contentType) w.Header().Set("content-type", contentType)
srv.ServeSingleRequest(codec, OptionMethodInvocation) codec := newHTTPServerConn(r, w)
defer codec.close()
s.serveSingleRequest(ctx, codec)
} }
// validateRequest returns a non-zero response code and error message if the // validateRequest returns a non-zero response code and error message if the
@ -192,12 +283,21 @@ func validateRequest(r *http.Request) (int, error) {
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
return http.StatusRequestEntityTooLarge, err return http.StatusRequestEntityTooLarge, err
} }
mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")) // Allow OPTIONS (regardless of content-type)
if r.Method != http.MethodOptions && (err != nil || mt != contentType) { if r.Method == http.MethodOptions {
err := fmt.Errorf("invalid content type, only %s is supported", contentType) return 0, nil
return http.StatusUnsupportedMediaType, err
} }
return 0, nil // Check content-type
if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil {
for _, accepted := range acceptedContentTypes {
if accepted == mt {
return 0, nil
}
}
}
// Invalid content-type
err := fmt.Errorf("invalid content type, only %s is supported", contentType)
return http.StatusUnsupportedMediaType, err
} }
func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {
@ -223,6 +323,14 @@ type virtualHostHandler struct {
next http.Handler next http.Handler
} }
func newVHostHandler(vhosts []string, next http.Handler) http.Handler {
vhostMap := make(map[string]struct{})
for _, allowedHost := range vhosts {
vhostMap[strings.ToLower(allowedHost)] = struct{}{}
}
return &virtualHostHandler{vhostMap, next}
}
// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// if r.Host is not set, we can continue serving since a browser would set the Host header // if r.Host is not set, we can continue serving since a browser would set the Host header
@ -252,11 +360,3 @@ func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
http.Error(w, "invalid host specified", http.StatusForbidden) http.Error(w, "invalid host specified", http.StatusForbidden)
} }
func newVHostHandler(vhosts []string, next http.Handler) http.Handler {
vhostMap := make(map[string]struct{})
for _, allowedHost := range vhosts {
vhostMap[strings.ToLower(allowedHost)] = struct{}{}
}
return &virtualHostHandler{vhostMap, next}
}

View file

@ -23,32 +23,103 @@ import (
"testing" "testing"
) )
func confirmStatusCode(t *testing.T, got, want int) {
t.Helper()
if got == want {
return
}
if gotName := http.StatusText(got); len(gotName) > 0 {
if wantName := http.StatusText(want); len(wantName) > 0 {
t.Fatalf("response status code: got %d (%s), want %d (%s)", got, gotName, want, wantName)
}
}
t.Fatalf("response status code: got %d, want %d", got, want)
}
func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
t.Helper()
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
if len(contentType) > 0 {
request.Header.Set("Content-Type", contentType)
}
code, err := validateRequest(request)
if code == 0 {
if err != nil {
t.Errorf("validation: got error %v, expected nil", err)
}
} else if err == nil {
t.Errorf("validation: code %d: got nil, expected error", code)
}
confirmStatusCode(t, code, expectedStatusCode)
}
func TestHTTPErrorResponseWithDelete(t *testing.T) { func TestHTTPErrorResponseWithDelete(t *testing.T) {
testHTTPErrorResponse(t, http.MethodDelete, contentType, "", http.StatusMethodNotAllowed) confirmRequestValidationCode(t, http.MethodDelete, contentType, "", http.StatusMethodNotAllowed)
} }
func TestHTTPErrorResponseWithPut(t *testing.T) { func TestHTTPErrorResponseWithPut(t *testing.T) {
testHTTPErrorResponse(t, http.MethodPut, contentType, "", http.StatusMethodNotAllowed) confirmRequestValidationCode(t, http.MethodPut, contentType, "", http.StatusMethodNotAllowed)
} }
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) { func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
body := make([]rune, maxRequestContentLength+1) body := make([]rune, maxRequestContentLength+1)
testHTTPErrorResponse(t, confirmRequestValidationCode(t,
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge) http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
} }
func TestHTTPErrorResponseWithEmptyContentType(t *testing.T) { func TestHTTPErrorResponseWithEmptyContentType(t *testing.T) {
testHTTPErrorResponse(t, http.MethodPost, "", "", http.StatusUnsupportedMediaType) confirmRequestValidationCode(t, http.MethodPost, "", "", http.StatusUnsupportedMediaType)
} }
func TestHTTPErrorResponseWithValidRequest(t *testing.T) { func TestHTTPErrorResponseWithValidRequest(t *testing.T) {
testHTTPErrorResponse(t, http.MethodPost, contentType, "", 0) confirmRequestValidationCode(t, http.MethodPost, contentType, "", 0)
} }
func testHTTPErrorResponse(t *testing.T, method, contentType, body string, expected int) { func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body)) t.Helper()
request.Header.Set("content-type", contentType) s := Server{}
if code, _ := validateRequest(request); code != expected { ts := httptest.NewServer(&s)
t.Fatalf("response code should be %d not %d", expected, code) defer ts.Close()
request, err := http.NewRequest(method, ts.URL, strings.NewReader(body))
if err != nil {
t.Fatalf("failed to create a valid HTTP request: %v", err)
}
if len(contentType) > 0 {
request.Header.Set("Content-Type", contentType)
}
resp, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatalf("request failed: %v", err)
}
confirmStatusCode(t, resp.StatusCode, expectedStatusCode)
}
func TestHTTPResponseWithEmptyGet(t *testing.T) {
confirmHTTPRequestYieldsStatusCode(t, http.MethodGet, "", "", http.StatusOK)
}
// This checks that maxRequestContentLength is not applied to the response of a request.
func TestHTTPRespBodyUnlimited(t *testing.T) {
const respLength = maxRequestContentLength * 3
s := NewServer()
defer s.Stop()
s.RegisterName("test", largeRespService{respLength})
ts := httptest.NewServer(s)
defer ts.Close()
c, err := DialHTTP(ts.URL)
if err != nil {
t.Fatal(err)
}
defer c.Close()
var r string
if err := c.Call(&r, "test_largeResp"); err != nil {
t.Fatal(err)
}
if len(r) != respLength {
t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
} }
} }

View file

@ -21,13 +21,13 @@ import (
"net" "net"
) )
// NewInProcClient attaches an in-process connection to the given RPC server. // DialInProc attaches an in-process connection to the given RPC server.
func DialInProc(handler *Server) *Client { func DialInProc(handler *Server) *Client {
initctx := context.Background() initctx := context.Background()
c, _ := newClient(initctx, func(context.Context) (net.Conn, error) { c, _ := newClient(initctx, func(context.Context) (ServerCodec, error) {
p1, p2 := net.Pipe() p1, p2 := net.Pipe()
go handler.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions) go handler.ServeCodec(NewCodec(p1), 0)
return p2, nil return NewCodec(p2), nil
}) })
return c return c
} }

View file

@ -18,10 +18,10 @@ package rpc
import ( import (
"context" "context"
"fmt"
"net" "net"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil"
) )
// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on // CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on
@ -31,14 +31,17 @@ func CreateIPCListener(endpoint string) (net.Listener, error) {
} }
// ServeListener accepts connections on l, serving JSON-RPC on them. // ServeListener accepts connections on l, serving JSON-RPC on them.
func (srv *Server) ServeListener(l net.Listener) error { func (s *Server) ServeListener(l net.Listener) error {
for { for {
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if netutil.IsTemporaryError(err) {
log.Warn("RPC accept error", "err", err)
continue
} else if err != nil {
return err return err
} }
log.Trace(fmt.Sprint("accepted conn", conn.RemoteAddr())) log.Trace("Accepted RPC connection", "conn", conn.RemoteAddr())
go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) go s.ServeCodec(NewCodec(conn), 0)
} }
} }
@ -49,7 +52,11 @@ func (srv *Server) ServeListener(l net.Listener) error {
// The context is used for the initial connection establishment. It does not // The context is used for the initial connection establishment. It does not
// affect subsequent interactions with the client. // affect subsequent interactions with the client.
func DialIPC(ctx context.Context, endpoint string) (*Client, error) { func DialIPC(ctx context.Context, endpoint string) (*Client, error) {
return newClient(ctx, func(ctx context.Context) (net.Conn, error) { return newClient(ctx, func(ctx context.Context) (ServerCodec, error) {
return newIPCConnection(ctx, endpoint) conn, err := newIPCConnection(ctx, endpoint)
if err != nil {
return nil, err
}
return NewCodec(conn), err
}) })
} }

38
rpc/ipc_js.go Normal file
View file

@ -0,0 +1,38 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build js
// +build js
package rpc
import (
"context"
"errors"
"net"
)
var errNotSupported = errors.New("rpc: not supported")
// ipcListen will create a named pipe on the given endpoint.
func ipcListen(endpoint string) (net.Listener, error) {
return nil, errNotSupported
}
// newIPCConnection will connect to a named pipe with the given endpoint as name.
func newIPCConnection(ctx context.Context, endpoint string) (net.Conn, error) {
return nil, errNotSupported
}

View file

@ -14,19 +14,28 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package rpc package rpc
import ( import (
"context" "context"
"fmt"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"github.com/XinFinOrg/XDPoSChain/log"
) )
// ipcListen will create a Unix socket on the given endpoint. // ipcListen will create a Unix socket on the given endpoint.
func ipcListen(endpoint string) (net.Listener, error) { func ipcListen(endpoint string) (net.Listener, error) {
if len(endpoint) > int(max_path_size) {
log.Warn(fmt.Sprintf("The ipc endpoint is longer than %d characters. ", max_path_size),
"endpoint", endpoint)
}
// Ensure the IPC path exists and remove any previous leftover // Ensure the IPC path exists and remove any previous leftover
if err := os.MkdirAll(filepath.Dir(endpoint), 0751); err != nil { if err := os.MkdirAll(filepath.Dir(endpoint), 0751); err != nil {
return nil, err return nil, err
@ -42,5 +51,5 @@ func ipcListen(endpoint string) (net.Listener, error) {
// newIPCConnection will connect to a Unix socket on the given endpoint. // newIPCConnection will connect to a Unix socket on the given endpoint.
func newIPCConnection(ctx context.Context, endpoint string) (net.Conn, error) { func newIPCConnection(ctx context.Context, endpoint string) (net.Conn, error) {
return dialContext(ctx, "unix", endpoint) return new(net.Dialer).DialContext(ctx, "unix", endpoint)
} }

View file

@ -18,36 +18,108 @@ package rpc
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
"strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/XinFinOrg/XDPoSChain/log"
) )
const ( const (
jsonrpcVersion = "2.0" vsn = "2.0"
serviceMethodSeparator = "_" serviceMethodSeparator = "_"
subscribeMethodSuffix = "_subscribe" subscribeMethodSuffix = "_subscribe"
unsubscribeMethodSuffix = "_unsubscribe" unsubscribeMethodSuffix = "_unsubscribe"
notificationMethodSuffix = "_subscription" notificationMethodSuffix = "_subscription"
defaultWriteTimeout = 10 * time.Second // used if context has no deadline
) )
type jsonRequest struct { var null = json.RawMessage("null")
Method string `json:"method"`
Version string `json:"jsonrpc"` type subscriptionResult struct {
Id json.RawMessage `json:"id,omitempty"` ID string `json:"subscription"`
Payload json.RawMessage `json:"params,omitempty"` Result json.RawMessage `json:"result,omitempty"`
} }
type jsonSuccessResponse struct { // A value of this type can a JSON-RPC request, notification, successful response or
Version string `json:"jsonrpc"` // error response. Which one it is depends on the fields.
Id interface{} `json:"id,omitempty"` type jsonrpcMessage struct {
Result interface{} `json:"result"` Version string `json:"jsonrpc,omitempty"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Error *jsonError `json:"error,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
}
func (msg *jsonrpcMessage) isNotification() bool {
return msg.ID == nil && msg.Method != ""
}
func (msg *jsonrpcMessage) isCall() bool {
return msg.hasValidID() && msg.Method != ""
}
func (msg *jsonrpcMessage) isResponse() bool {
return msg.hasValidID() && msg.Method == "" && msg.Params == nil && (msg.Result != nil || msg.Error != nil)
}
func (msg *jsonrpcMessage) hasValidID() bool {
return len(msg.ID) > 0 && msg.ID[0] != '{' && msg.ID[0] != '['
}
func (msg *jsonrpcMessage) isSubscribe() bool {
return strings.HasSuffix(msg.Method, subscribeMethodSuffix)
}
func (msg *jsonrpcMessage) isUnsubscribe() bool {
return strings.HasSuffix(msg.Method, unsubscribeMethodSuffix)
}
func (msg *jsonrpcMessage) namespace() string {
elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2)
return elem[0]
}
func (msg *jsonrpcMessage) String() string {
b, _ := json.Marshal(msg)
return string(b)
}
func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage {
resp := errorMessage(err)
resp.ID = msg.ID
return resp
}
func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage {
enc, err := json.Marshal(result)
if err != nil {
// TODO: wrap with 'internal server error'
return msg.errorResponse(err)
}
return &jsonrpcMessage{Version: vsn, ID: msg.ID, Result: enc}
}
func errorMessage(err error) *jsonrpcMessage {
msg := &jsonrpcMessage{Version: vsn, ID: null, Error: &jsonError{
Code: defaultErrorCode,
Message: err.Error(),
}}
ec, ok := err.(Error)
if ok {
msg.Error.Code = ec.ErrorCode()
}
de, ok := err.(DataError)
if ok {
msg.Error.Data = de.ErrorData()
}
return msg
} }
type jsonError struct { type jsonError struct {
@ -56,35 +128,6 @@ type jsonError struct {
Data interface{} `json:"data,omitempty"` Data interface{} `json:"data,omitempty"`
} }
type jsonErrResponse struct {
Version string `json:"jsonrpc"`
Id interface{} `json:"id,omitempty"`
Error jsonError `json:"error"`
}
type jsonSubscription struct {
Subscription string `json:"subscription"`
Result interface{} `json:"result,omitempty"`
}
type jsonNotification struct {
Version string `json:"jsonrpc"`
Method string `json:"method"`
Params jsonSubscription `json:"params"`
}
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It
// also has support for parsing arguments and serializing (result) objects.
type jsonCodec struct {
closer sync.Once // close closed channel once
closed chan interface{} // closed on Close
decMu sync.Mutex // guards the decoder
decode func(v interface{}) error // decoder to allow multiple transports
encMu sync.Mutex // guards the encoder
encode func(v interface{}) error // encoder to allow multiple transports
rw io.ReadWriteCloser // connection
}
func (err *jsonError) Error() string { func (err *jsonError) Error() string {
if err.Message == "" { if err.Message == "" {
return fmt.Sprintf("json-rpc error %d", err.Code) return fmt.Sprintf("json-rpc error %d", err.Code)
@ -96,34 +139,134 @@ func (err *jsonError) ErrorCode() int {
return err.Code return err.Code
} }
// NewCodec creates a new RPC server codec with support for JSON-RPC 2.0 based func (err *jsonError) ErrorData() interface{} {
// on explicitly given encoding and decoding methods. return err.Data
func NewCodec(rwc io.ReadWriteCloser, encode, decode func(v interface{}) error) ServerCodec {
return &jsonCodec{
closed: make(chan interface{}),
encode: encode,
decode: decode,
rw: rwc,
}
} }
// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0. // Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec.
func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec { type Conn interface {
enc := json.NewEncoder(rwc) io.ReadWriteCloser
dec := json.NewDecoder(rwc) SetWriteDeadline(time.Time) error
dec.UseNumber() }
return &jsonCodec{ type deadlineCloser interface {
closed: make(chan interface{}), io.Closer
encode: enc.Encode, SetWriteDeadline(time.Time) error
decode: dec.Decode, }
rw: rwc,
// ConnRemoteAddr wraps the RemoteAddr operation, which returns a description
// of the peer address of a connection. If a Conn also implements ConnRemoteAddr, this
// description is used in log messages.
type ConnRemoteAddr interface {
RemoteAddr() string
}
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has
// support for parsing arguments and serializing (result) objects.
type jsonCodec struct {
remote string
closer sync.Once // close closed channel once
closeCh chan interface{} // closed on Close
decode func(v interface{}) error // decoder to allow multiple transports
encMu sync.Mutex // guards the encoder
encode func(v interface{}) error // encoder to allow multiple transports
conn deadlineCloser
}
// NewFuncCodec creates a codec which uses the given functions to read and write. If conn
// implements ConnRemoteAddr, log messages will use it to include the remote address of
// the connection.
func NewFuncCodec(conn deadlineCloser, encode, decode func(v interface{}) error) ServerCodec {
codec := &jsonCodec{
closeCh: make(chan interface{}),
encode: encode,
decode: decode,
conn: conn,
} }
if ra, ok := conn.(ConnRemoteAddr); ok {
codec.remote = ra.RemoteAddr()
}
return codec
}
// NewCodec creates a codec on the given connection. If conn implements ConnRemoteAddr, log
// messages will use it to include the remote address of the connection.
func NewCodec(conn Conn) ServerCodec {
enc := json.NewEncoder(conn)
dec := json.NewDecoder(conn)
dec.UseNumber()
return NewFuncCodec(conn, enc.Encode, dec.Decode)
}
func (c *jsonCodec) remoteAddr() string {
return c.remote
}
func (c *jsonCodec) readBatch() (messages []*jsonrpcMessage, batch bool, err error) {
// Decode the next JSON object in the input stream.
// This verifies basic syntax, etc.
var rawmsg json.RawMessage
if err := c.decode(&rawmsg); err != nil {
return nil, false, err
}
messages, batch = parseMessage(rawmsg)
for i, msg := range messages {
if msg == nil {
// Message is JSON 'null'. Replace with zero value so it
// will be treated like any other invalid message.
messages[i] = new(jsonrpcMessage)
}
}
return messages, batch, nil
}
func (c *jsonCodec) writeJSON(ctx context.Context, v interface{}) error {
c.encMu.Lock()
defer c.encMu.Unlock()
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultWriteTimeout)
}
c.conn.SetWriteDeadline(deadline)
return c.encode(v)
}
func (c *jsonCodec) close() {
c.closer.Do(func() {
close(c.closeCh)
c.conn.Close()
})
}
// Closed returns a channel which will be closed when Close is called
func (c *jsonCodec) closed() <-chan interface{} {
return c.closeCh
}
// parseMessage parses raw bytes as a (batch of) JSON-RPC message(s). There are no error
// checks in this function because the raw message has already been syntax-checked when it
// is called. Any non-JSON-RPC messages in the input return the zero value of
// jsonrpcMessage.
func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) {
if !isBatch(raw) {
msgs := []*jsonrpcMessage{{}}
json.Unmarshal(raw, &msgs[0])
return msgs, false
}
dec := json.NewDecoder(bytes.NewReader(raw))
dec.Token() // skip '['
var msgs []*jsonrpcMessage
for dec.More() {
msgs = append(msgs, new(jsonrpcMessage))
dec.Decode(&msgs[len(msgs)-1])
}
return msgs, true
} }
// isBatch returns true when the first non-whitespace characters is '[' // isBatch returns true when the first non-whitespace characters is '['
func isBatch(msg json.RawMessage) bool { func isBatch(raw json.RawMessage) bool {
for _, c := range msg { for _, c := range raw {
// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
continue continue
@ -133,239 +276,67 @@ func isBatch(msg json.RawMessage) bool {
return false return false
} }
// ReadRequestHeaders will read new requests without parsing the arguments. It will
// return a collection of requests, an indication if these requests are in batch
// form or an error when the incoming message could not be read/parsed.
func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, Error) {
c.decMu.Lock()
defer c.decMu.Unlock()
var incomingMsg json.RawMessage
if err := c.decode(&incomingMsg); err != nil {
return nil, false, &invalidRequestError{err.Error()}
}
if isBatch(incomingMsg) {
return parseBatchRequest(incomingMsg)
}
return parseRequest(incomingMsg)
}
// checkReqId returns an error when the given reqId isn't valid for RPC method calls.
// valid id's are strings, numbers or null
func checkReqId(reqId json.RawMessage) error {
if len(reqId) == 0 {
return fmt.Errorf("missing request id")
}
if _, err := strconv.ParseFloat(string(reqId), 64); err == nil {
return nil
}
var str string
if err := json.Unmarshal(reqId, &str); err == nil {
return nil
}
return fmt.Errorf("invalid request id")
}
// parseRequest will parse a single request from the given RawMessage. It will return
// the parsed request, an indication if the request was a batch or an error when
// the request could not be parsed.
func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
var in jsonRequest
if err := json.Unmarshal(incomingMsg, &in); err != nil {
return nil, false, &invalidMessageError{err.Error()}
}
if err := checkReqId(in.Id); err != nil {
return nil, false, &invalidMessageError{err.Error()}
}
// subscribe are special, they will always use `subscribeMethod` as first param in the payload
if strings.HasSuffix(in.Method, subscribeMethodSuffix) {
reqs := []rpcRequest{{id: &in.Id, isPubSub: true}}
if len(in.Payload) > 0 {
// first param must be subscription name
var subscribeMethod [1]string
if err := json.Unmarshal(in.Payload, &subscribeMethod); err != nil {
log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err))
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
}
reqs[0].service, reqs[0].method = strings.TrimSuffix(in.Method, subscribeMethodSuffix), subscribeMethod[0]
reqs[0].params = in.Payload
return reqs, false, nil
}
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
}
if strings.HasSuffix(in.Method, unsubscribeMethodSuffix) {
return []rpcRequest{{id: &in.Id, isPubSub: true,
method: in.Method, params: in.Payload}}, false, nil
}
elems := strings.Split(in.Method, serviceMethodSeparator)
if len(elems) != 2 {
return nil, false, &methodNotFoundError{in.Method, ""}
}
// regular RPC call
if len(in.Payload) == 0 {
return []rpcRequest{{service: elems[0], method: elems[1], id: &in.Id}}, false, nil
}
return []rpcRequest{{service: elems[0], method: elems[1], id: &in.Id, params: in.Payload}}, false, nil
}
// parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication
// if the request was a batch or an error when the request could not be read.
func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
var in []jsonRequest
if err := json.Unmarshal(incomingMsg, &in); err != nil {
return nil, false, &invalidMessageError{err.Error()}
}
requests := make([]rpcRequest, len(in))
for i, r := range in {
if err := checkReqId(r.Id); err != nil {
return nil, false, &invalidMessageError{err.Error()}
}
id := &in[i].Id
// subscribe are special, they will always use `subscriptionMethod` as first param in the payload
if strings.HasSuffix(r.Method, subscribeMethodSuffix) {
requests[i] = rpcRequest{id: id, isPubSub: true}
if len(r.Payload) > 0 {
// first param must be subscription name
var subscribeMethod [1]string
if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil {
log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err))
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
}
requests[i].service, requests[i].method = strings.TrimSuffix(r.Method, subscribeMethodSuffix), subscribeMethod[0]
requests[i].params = r.Payload
continue
}
return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"}
}
if strings.HasSuffix(r.Method, unsubscribeMethodSuffix) {
requests[i] = rpcRequest{id: id, isPubSub: true, method: r.Method, params: r.Payload}
continue
}
if len(r.Payload) == 0 {
requests[i] = rpcRequest{id: id, params: nil}
} else {
requests[i] = rpcRequest{id: id, params: r.Payload}
}
if elem := strings.Split(r.Method, serviceMethodSeparator); len(elem) == 2 {
requests[i].service, requests[i].method = elem[0], elem[1]
} else {
requests[i].err = &methodNotFoundError{r.Method, ""}
}
}
return requests, true, nil
}
// ParseRequestArguments tries to parse the given params (json.RawMessage) with the given
// types. It returns the parsed values or an error when the parsing failed.
func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, Error) {
if args, ok := params.(json.RawMessage); !ok {
return nil, &invalidParamsError{"Invalid params supplied"}
} else {
return parsePositionalArguments(args, argTypes)
}
}
// parsePositionalArguments tries to parse the given args to an array of values with the // parsePositionalArguments tries to parse the given args to an array of values with the
// given types. It returns the parsed values or an error when the args could not be // given types. It returns the parsed values or an error when the args could not be
// parsed. Missing optional arguments are returned as reflect.Zero values. // parsed. Missing optional arguments are returned as reflect.Zero values.
func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, Error) { func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) {
// Read beginning of the args array.
dec := json.NewDecoder(bytes.NewReader(rawArgs)) dec := json.NewDecoder(bytes.NewReader(rawArgs))
if tok, _ := dec.Token(); tok != json.Delim('[') { var args []reflect.Value
return nil, &invalidParamsError{"non-array args"} tok, err := dec.Token()
} switch {
// Read args. case err == io.EOF || tok == nil && err == nil:
args := make([]reflect.Value, 0, len(types)) // "params" is optional and may be empty. Also allow "params":null even though it's
for i := 0; dec.More(); i++ { // not in the spec because our own client used to send it.
if i >= len(types) { case err != nil:
return nil, &invalidParamsError{fmt.Sprintf("too many arguments, want at most %d", len(types))} return nil, err
case tok == json.Delim('['):
// Read argument array.
if args, err = parseArgumentArray(dec, types); err != nil {
return nil, err
} }
argval := reflect.New(types[i]) default:
if err := dec.Decode(argval.Interface()); err != nil { return nil, errors.New("non-array args")
return nil, &invalidParamsError{fmt.Sprintf("invalid argument %d: %v", i, err)}
}
if argval.IsNil() && types[i].Kind() != reflect.Ptr {
return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)}
}
args = append(args, argval.Elem())
}
// Read end of args array.
if _, err := dec.Token(); err != nil {
return nil, &invalidParamsError{err.Error()}
} }
// Set any missing args to nil. // Set any missing args to nil.
for i := len(args); i < len(types); i++ { for i := len(args); i < len(types); i++ {
if types[i].Kind() != reflect.Ptr { if types[i].Kind() != reflect.Ptr {
return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)} return nil, fmt.Errorf("missing value for required argument %d", i)
} }
args = append(args, reflect.Zero(types[i])) args = append(args, reflect.Zero(types[i]))
} }
return args, nil return args, nil
} }
// CreateResponse will create a JSON-RPC success response with the given id and reply as result. func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) {
func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} { args := make([]reflect.Value, 0, len(types))
if isHexNum(reflect.TypeOf(reply)) { for i := 0; dec.More(); i++ {
return &jsonSuccessResponse{Version: jsonrpcVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} if i >= len(types) {
return args, fmt.Errorf("too many arguments, want at most %d", len(types))
}
argval := reflect.New(types[i])
if err := dec.Decode(argval.Interface()); err != nil {
return args, fmt.Errorf("invalid argument %d: %v", i, err)
}
if argval.IsNil() && types[i].Kind() != reflect.Ptr {
return args, fmt.Errorf("missing value for required argument %d", i)
}
args = append(args, argval.Elem())
} }
return &jsonSuccessResponse{Version: jsonrpcVersion, Id: id, Result: reply} // Read end of args array.
_, err := dec.Token()
return args, err
} }
// CreateErrorResponse will create a JSON-RPC error response with the given id and error. // parseSubscriptionName extracts the subscription name from an encoded argument array.
func (c *jsonCodec) CreateErrorResponse(id interface{}, err Error) interface{} { func parseSubscriptionName(rawArgs json.RawMessage) (string, error) {
return &jsonErrResponse{Version: jsonrpcVersion, Id: id, Error: jsonError{Code: err.ErrorCode(), Message: err.Error()}} dec := json.NewDecoder(bytes.NewReader(rawArgs))
} if tok, _ := dec.Token(); tok != json.Delim('[') {
return "", errors.New("non-array args")
// CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error.
// info is optional and contains additional information about the error. When an empty string is passed it is ignored.
func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{} {
return &jsonErrResponse{Version: jsonrpcVersion, Id: id,
Error: jsonError{Code: err.ErrorCode(), Message: err.Error(), Data: info}}
}
// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params.
func (c *jsonCodec) CreateNotification(subid, namespace string, event interface{}) interface{} {
if isHexNum(reflect.TypeOf(event)) {
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}}
} }
v, _ := dec.Token()
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix, method, ok := v.(string)
Params: jsonSubscription{Subscription: subid, Result: event}} if !ok {
} return "", errors.New("expected subscription name as first argument")
}
// Write message to client return method, nil
func (c *jsonCodec) Write(res interface{}) error {
c.encMu.Lock()
defer c.encMu.Unlock()
return c.encode(res)
}
// Close the underlying connection
func (c *jsonCodec) Close() {
c.closer.Do(func() {
close(c.closed)
c.rw.Close()
})
}
// Closed returns a channel which will be closed when Close is called
func (c *jsonCodec) Closed() <-chan interface{} {
return c.closed
} }

View file

@ -1,178 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"bufio"
"bytes"
"encoding/json"
"reflect"
"strconv"
"testing"
)
type RWC struct {
*bufio.ReadWriter
}
func (rwc *RWC) Close() error {
return nil
}
func TestJSONRequestParsing(t *testing.T) {
server := NewServer()
service := new(Service)
if err := server.RegisterName("calc", service); err != nil {
t.Fatalf("%v", err)
}
req := bytes.NewBufferString(`{"id": 1234, "jsonrpc": "2.0", "method": "calc_add", "params": [11, 22]}`)
var str string
reply := bytes.NewBufferString(str)
rw := &RWC{bufio.NewReadWriter(bufio.NewReader(req), bufio.NewWriter(reply))}
codec := NewJSONCodec(rw)
requests, batch, err := codec.ReadRequestHeaders()
if err != nil {
t.Fatalf("%v", err)
}
if batch {
t.Fatalf("Request isn't a batch")
}
if len(requests) != 1 {
t.Fatalf("Expected 1 request but got %d requests - %v", len(requests), requests)
}
if requests[0].service != "calc" {
t.Fatalf("Expected service 'calc' but got '%s'", requests[0].service)
}
if requests[0].method != "add" {
t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method)
}
if rawId, ok := requests[0].id.(*json.RawMessage); ok {
id, e := strconv.ParseInt(string(*rawId), 0, 64)
if e != nil {
t.Fatalf("%v", e)
}
if id != 1234 {
t.Fatalf("Expected id 1234 but got %d", id)
}
} else {
t.Fatalf("invalid request, expected *json.RawMesage got %T", requests[0].id)
}
var arg int
args := []reflect.Type{reflect.TypeOf(arg), reflect.TypeOf(arg)}
v, err := codec.ParseRequestArguments(args, requests[0].params)
if err != nil {
t.Fatalf("%v", err)
}
if len(v) != 2 {
t.Fatalf("Expected 2 argument values, got %d", len(v))
}
if v[0].Int() != 11 || v[1].Int() != 22 {
t.Fatalf("expected %d == 11 && %d == 22", v[0].Int(), v[1].Int())
}
}
func TestJSONRequestParamsParsing(t *testing.T) {
var (
stringT = reflect.TypeOf("")
intT = reflect.TypeOf(0)
intPtrT = reflect.TypeOf(new(int))
stringV = reflect.ValueOf("abc")
i = 1
intV = reflect.ValueOf(i)
intPtrV = reflect.ValueOf(&i)
)
var validTests = []struct {
input string
argTypes []reflect.Type
expected []reflect.Value
}{
{`[]`, []reflect.Type{}, []reflect.Value{}},
{`[]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}},
{`[1]`, []reflect.Type{intT}, []reflect.Value{intV}},
{`[1,"abc"]`, []reflect.Type{intT, stringT}, []reflect.Value{intV, stringV}},
{`[null]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}},
{`[null,"abc"]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}},
{`[null,"abc",null]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}},
}
codec := jsonCodec{}
for _, test := range validTests {
params := (json.RawMessage)([]byte(test.input))
args, err := codec.ParseRequestArguments(test.argTypes, params)
if err != nil {
t.Fatal(err)
}
var match []interface{}
json.Unmarshal([]byte(test.input), &match)
if len(args) != len(test.argTypes) {
t.Fatalf("expected %d parsed args, got %d", len(test.argTypes), len(args))
}
for i, arg := range args {
expected := test.expected[i]
if arg.Kind() != expected.Kind() {
t.Errorf("expected type for param %d in %s", i, test.input)
}
if arg.Kind() == reflect.Int && arg.Int() != expected.Int() {
t.Errorf("expected int(%d), got int(%d) in %s", expected.Int(), arg.Int(), test.input)
}
if arg.Kind() == reflect.String && arg.String() != expected.String() {
t.Errorf("expected string(%s), got string(%s) in %s", expected.String(), arg.String(), test.input)
}
}
}
var invalidTests = []struct {
input string
argTypes []reflect.Type
}{
{`[]`, []reflect.Type{intT}},
{`[null]`, []reflect.Type{intT}},
{`[1]`, []reflect.Type{stringT}},
{`[1,2]`, []reflect.Type{stringT}},
{`["abc", null]`, []reflect.Type{stringT, intT}},
}
for i, test := range invalidTests {
if _, err := codec.ParseRequestArguments(test.argTypes, test.input); err == nil {
t.Errorf("expected test %d - %s to fail", i, test.input)
}
}
}

39
rpc/metrics.go Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"fmt"
"github.com/ethereum/go-ethereum/metrics"
)
var (
rpcRequestGauge = metrics.NewRegisteredGauge("rpc/requests", nil)
successfulRequestGauge = metrics.NewRegisteredGauge("rpc/success", nil)
failedReqeustGauge = metrics.NewRegisteredGauge("rpc/failure", nil)
rpcServingTimer = metrics.NewRegisteredTimer("rpc/duration/all", nil)
)
func newRPCServingTimer(method string, valid bool) metrics.Timer {
flag := "success"
if !valid {
flag = "failure"
}
m := fmt.Sprintf("rpc/duration/%s/%s", method, flag)
return metrics.GetOrRegisterTimer(m, nil)
}

View file

@ -18,11 +18,7 @@ package rpc
import ( import (
"context" "context"
"fmt" "io"
"reflect"
"runtime"
"strings"
"sync"
"sync/atomic" "sync/atomic"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -31,33 +27,107 @@ import (
const MetadataApi = "rpc" const MetadataApi = "rpc"
// CodecOption specifies which type of messages this codec supports // CodecOption specifies which type of messages a codec supports.
//
// Deprecated: this option is no longer honored by Server.
type CodecOption int type CodecOption int
const ( const (
// OptionMethodInvocation is an indication that the codec supports RPC method calls // OptionMethodInvocation is an indication that the codec supports RPC method calls
OptionMethodInvocation CodecOption = 1 << iota OptionMethodInvocation CodecOption = 1 << iota
// OptionSubscriptions is an indication that the codec suports RPC notifications // OptionSubscriptions is an indication that the codec supports RPC notifications
OptionSubscriptions = 1 << iota // support pub sub OptionSubscriptions = 1 << iota // support pub sub
) )
// NewServer will create a new server instance with no registered handlers. // Server is an RPC server.
func NewServer() *Server { type Server struct {
server := &Server{ services serviceRegistry
services: make(serviceRegistry), idgen func() ID
codecs: mapset.NewSet(), run int32
run: 1, codecs mapset.Set
} }
// register a default service which will provide meta information about the RPC service such as the services and // NewServer creates a new server instance with no registered handlers.
// methods it offers. func NewServer() *Server {
server := &Server{idgen: randomIDGenerator(), codecs: mapset.NewSet(), run: 1}
// Register the default service providing meta information about the RPC service such
// as the services and methods it offers.
rpcService := &RPCService{server} rpcService := &RPCService{server}
server.RegisterName(MetadataApi, rpcService) server.RegisterName(MetadataApi, rpcService)
return server return server
} }
// RegisterName creates a service for the given receiver type under the given name. When no
// methods on the given receiver match the criteria to be either a RPC method or a
// subscription an error is returned. Otherwise a new service is created and added to the
// service collection this server provides to clients.
func (s *Server) RegisterName(name string, receiver interface{}) error {
return s.services.registerName(name, receiver)
}
// ServeCodec reads incoming requests from codec, calls the appropriate callback and writes
// the response back using the given codec. It will block until the codec is closed or the
// server is stopped. In either case the codec is closed.
//
// Note that codec options are no longer supported.
func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) {
defer codec.close()
// Don't serve if server is stopped.
if atomic.LoadInt32(&s.run) == 0 {
return
}
// Add the codec to the set so it can be closed by Stop.
s.codecs.Add(codec)
defer s.codecs.Remove(codec)
c := initClient(codec, s.idgen, &s.services)
<-codec.closed()
c.Close()
}
// serveSingleRequest reads and processes a single RPC request from the given codec. This
// is used to serve HTTP connections. Subscriptions and reverse calls are not allowed in
// this mode.
func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) {
// Don't serve if server is stopped.
if atomic.LoadInt32(&s.run) == 0 {
return
}
h := newHandler(ctx, codec, s.idgen, &s.services)
h.allowSubscribe = false
defer h.close(io.EOF, nil)
reqs, batch, err := codec.readBatch()
if err != nil {
if err != io.EOF {
codec.writeJSON(ctx, errorMessage(&invalidMessageError{"parse error"}))
}
return
}
if batch {
h.handleBatch(reqs)
} else {
h.handleMsg(reqs[0])
}
}
// Stop stops reading new requests, waits for stopPendingRequestTimeout to allow pending
// requests to finish, then closes all codecs which will cancel pending requests and
// subscriptions.
func (s *Server) Stop() {
if atomic.CompareAndSwapInt32(&s.run, 1, 0) {
log.Debug("RPC server shutting down")
s.codecs.Each(func(c interface{}) bool {
c.(ServerCodec).close()
return true
})
}
}
// RPCService gives meta information about the server. // RPCService gives meta information about the server.
// e.g. gives information about the loaded modules. // e.g. gives information about the loaded modules.
type RPCService struct { type RPCService struct {
@ -66,380 +136,12 @@ type RPCService struct {
// Modules returns the list of RPC services with their version number // Modules returns the list of RPC services with their version number
func (s *RPCService) Modules() map[string]string { func (s *RPCService) Modules() map[string]string {
s.server.services.mu.Lock()
defer s.server.services.mu.Unlock()
modules := make(map[string]string) modules := make(map[string]string)
for name := range s.server.services { for name := range s.server.services.services {
modules[name] = "1.0" modules[name] = "1.0"
} }
return modules return modules
} }
// RegisterName will create a service for the given rcvr type under the given name. When no methods on the given rcvr
// match the criteria to be either a RPC method or a subscription an error is returned. Otherwise a new service is
// created and added to the service collection this server instance serves.
func (s *Server) RegisterName(name string, rcvr interface{}) error {
if s.services == nil {
s.services = make(serviceRegistry)
}
svc := new(service)
svc.typ = reflect.TypeOf(rcvr)
rcvrVal := reflect.ValueOf(rcvr)
if name == "" {
return fmt.Errorf("no service name for type %s", svc.typ.String())
}
if !isExported(reflect.Indirect(rcvrVal).Type().Name()) {
return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name())
}
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
// already a previous service register under given sname, merge methods/subscriptions
if regsvc, present := s.services[name]; present {
if len(methods) == 0 && len(subscriptions) == 0 {
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
}
for _, m := range methods {
regsvc.callbacks[formatName(m.method.Name)] = m
}
for _, s := range subscriptions {
regsvc.subscriptions[formatName(s.method.Name)] = s
}
return nil
}
svc.name = name
svc.callbacks, svc.subscriptions = methods, subscriptions
if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 {
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
}
s.services[svc.name] = svc
return nil
}
// serveRequest will reads requests from the codec, calls the RPC callback and
// writes the response to the given codec.
//
// If singleShot is true it will process a single request, otherwise it will handle
// requests until the codec returns an error when reading a request (in most cases
// an EOF). It executes requests in parallel when singleShot is false.
func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption) error {
var pend sync.WaitGroup
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Error(fmt.Sprintf("RPC serveRequest %s\n", string(buf)))
}
s.codecsMu.Lock()
s.codecs.Remove(codec)
s.codecsMu.Unlock()
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// if the codec supports notification include a notifier that callbacks can use
// to send notification to clients. It is thight to the codec/connection. If the
// connection is closed the notifier will stop and cancels all active subscriptions.
if options&OptionSubscriptions == OptionSubscriptions {
ctx = context.WithValue(ctx, notifierKey{}, newNotifier(codec))
}
s.codecsMu.Lock()
if atomic.LoadInt32(&s.run) != 1 { // server stopped
s.codecsMu.Unlock()
return &shutdownError{}
}
s.codecs.Add(codec)
s.codecsMu.Unlock()
// test if the server is ordered to stop
for atomic.LoadInt32(&s.run) == 1 {
reqs, batch, err := s.readRequest(codec)
if err != nil {
// If a parsing error occurred, send an error
if err.Error() != "EOF" {
log.Debug(fmt.Sprintf("read error %v\n", err))
codec.Write(codec.CreateErrorResponse(nil, err))
}
// Error or end of stream, wait for requests and tear down
pend.Wait()
return nil
}
// check if server is ordered to shutdown and return an error
// telling the client that his request failed.
if atomic.LoadInt32(&s.run) != 1 {
err = &shutdownError{}
if batch {
resps := make([]interface{}, len(reqs))
for i, r := range reqs {
resps[i] = codec.CreateErrorResponse(&r.id, err)
}
codec.Write(resps)
} else {
codec.Write(codec.CreateErrorResponse(&reqs[0].id, err))
}
return nil
}
// If a single shot request is executing, run and return immediately
if singleShot {
if batch {
s.execBatch(ctx, codec, reqs)
} else {
s.exec(ctx, codec, reqs[0])
}
return nil
}
// For multi-shot connections, start a goroutine to serve and loop back
pend.Add(1)
go func(reqs []*serverRequest, batch bool) {
defer pend.Done()
if batch {
s.execBatch(ctx, codec, reqs)
} else {
s.exec(ctx, codec, reqs[0])
}
}(reqs, batch)
}
return nil
}
// ServeCodec reads incoming requests from codec, calls the appropriate callback and writes the
// response back using the given codec. It will block until the codec is closed or the server is
// stopped. In either case the codec is closed.
func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) {
defer codec.Close()
s.serveRequest(codec, false, options)
}
// ServeSingleRequest reads and processes a single RPC request from the given codec. It will not
// close the codec unless a non-recoverable error has occurred. Note, this method will return after
// a single request has been processed!
func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption) {
s.serveRequest(codec, true, options)
}
// Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish,
// close all codecs which will cancel pending requests/subscriptions.
func (s *Server) Stop() {
if atomic.CompareAndSwapInt32(&s.run, 1, 0) {
log.Debug("RPC Server shutdown initiatied")
s.codecsMu.Lock()
defer s.codecsMu.Unlock()
s.codecs.Each(func(c interface{}) bool {
c.(ServerCodec).Close()
return true
})
}
}
// createSubscription will call the subscription callback and returns the subscription id or error.
func (s *Server) createSubscription(ctx context.Context, c ServerCodec, req *serverRequest) (ID, error) {
// subscription have as first argument the context following optional arguments
args := []reflect.Value{req.callb.rcvr, reflect.ValueOf(ctx)}
args = append(args, req.args...)
reply := req.callb.method.Func.Call(args)
if !reply[1].IsNil() { // subscription creation failed
return "", reply[1].Interface().(error)
}
return reply[0].Interface().(*Subscription).ID, nil
}
// handle executes a request and returns the response from the callback.
func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) (interface{}, func()) {
if req.err != nil {
return codec.CreateErrorResponse(&req.id, req.err), nil
}
if req.isUnsubscribe { // cancel subscription, first param must be the subscription id
if len(req.args) >= 1 && req.args[0].Kind() == reflect.String {
notifier, supported := NotifierFromContext(ctx)
if !supported { // interface doesn't support subscriptions (e.g. http)
return codec.CreateErrorResponse(&req.id, &callbackError{ErrNotificationsUnsupported.Error()}), nil
}
subid := ID(req.args[0].String())
if err := notifier.unsubscribe(subid); err != nil {
return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil
}
return codec.CreateResponse(req.id, true), nil
}
return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as first argument"}), nil
}
if req.callb.isSubscribe {
subid, err := s.createSubscription(ctx, codec, req)
if err != nil {
return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil
}
// active the subscription after the sub id was successfully sent to the client
activateSub := func() {
notifier, _ := NotifierFromContext(ctx)
notifier.activate(subid, req.svcname)
}
return codec.CreateResponse(req.id, subid), activateSub
}
// regular RPC call, prepare arguments
if len(req.args) != len(req.callb.argTypes) {
rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d",
req.svcname, serviceMethodSeparator, req.callb.method.Name,
len(req.callb.argTypes), len(req.args))}
return codec.CreateErrorResponse(&req.id, rpcErr), nil
}
arguments := []reflect.Value{req.callb.rcvr}
if req.callb.hasCtx {
arguments = append(arguments, reflect.ValueOf(ctx))
}
if len(req.args) > 0 {
arguments = append(arguments, req.args...)
}
// execute RPC method and return result
reply := req.callb.method.Func.Call(arguments)
if len(reply) == 0 {
return codec.CreateResponse(req.id, nil), nil
}
if req.callb.errPos >= 0 { // test if method returned an error
if !reply[req.callb.errPos].IsNil() {
e := reply[req.callb.errPos].Interface().(error)
res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()})
return res, nil
}
}
return codec.CreateResponse(req.id, reply[0].Interface()), nil
}
// exec executes the given request and writes the result back using the codec.
func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest) {
var response interface{}
var callback func()
if req.err != nil {
response = codec.CreateErrorResponse(&req.id, req.err)
} else {
response, callback = s.handle(ctx, codec, req)
}
if err := codec.Write(response); err != nil {
log.Error(fmt.Sprintf("RPC exec %v\n", err))
codec.Close()
}
// when request was a subscribe request this allows these subscriptions to be actived
if callback != nil {
callback()
}
}
// execBatch executes the given requests and writes the result back using the codec.
// It will only write the response back when the last request is processed.
func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*serverRequest) {
responses := make([]interface{}, len(requests))
var callbacks []func()
for i, req := range requests {
if req.err != nil {
responses[i] = codec.CreateErrorResponse(&req.id, req.err)
} else {
var callback func()
if responses[i], callback = s.handle(ctx, codec, req); callback != nil {
callbacks = append(callbacks, callback)
}
}
}
if err := codec.Write(responses); err != nil {
log.Error(fmt.Sprintf("RPC execBacth %v\n", err))
codec.Close()
}
// when request holds one of more subscribe requests this allows these subscriptions to be activated
for _, c := range callbacks {
c()
}
}
// readRequest requests the next (batch) request from the codec. It will return the collection
// of requests, an indication if the request was a batch, the invalid request identifier and an
// error when the request could not be read/parsed.
func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error) {
reqs, batch, err := codec.ReadRequestHeaders()
if err != nil {
return nil, batch, err
}
requests := make([]*serverRequest, len(reqs))
// verify requests
for i, r := range reqs {
var ok bool
var svc *service
if r.err != nil {
requests[i] = &serverRequest{id: r.id, err: r.err}
continue
}
if r.isPubSub && strings.HasSuffix(r.method, unsubscribeMethodSuffix) {
requests[i] = &serverRequest{id: r.id, isUnsubscribe: true}
argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg
if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil {
requests[i].args = args
} else {
requests[i].err = &invalidParamsError{err.Error()}
}
continue
}
if svc, ok = s.services[r.service]; !ok { // rpc method isn't available
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}}
continue
}
if r.isPubSub { // eth_subscribe, r.method contains the subscription method name
if callb, ok := svc.subscriptions[r.method]; ok {
requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb}
if r.params != nil && len(callb.argTypes) > 0 {
argTypes := []reflect.Type{reflect.TypeOf("")}
argTypes = append(argTypes, callb.argTypes...)
if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil {
requests[i].args = args[1:] // first one is service.method name which isn't an actual argument
} else {
requests[i].err = &invalidParamsError{err.Error()}
}
}
} else {
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}}
}
continue
}
if callb, ok := svc.callbacks[r.method]; ok { // lookup RPC method
requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb}
if r.params != nil && len(callb.argTypes) > 0 {
if args, err := codec.ParseRequestArguments(callb.argTypes, r.params); err == nil {
requests[i].args = args
} else {
requests[i].err = &invalidParamsError{err.Error()}
}
}
continue
}
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}}
}
return requests, batch, nil
}

View file

@ -17,146 +17,136 @@
package rpc package rpc
import ( import (
"context" "bufio"
"encoding/json" "bytes"
"io"
"io/ioutil"
"net" "net"
"reflect" "path/filepath"
"strings"
"testing" "testing"
"time" "time"
) )
type Service struct{}
type Args struct {
S string
}
func (s *Service) NoArgsRets() {
}
type Result struct {
String string
Int int
Args *Args
}
func (s *Service) Echo(str string, i int, args *Args) Result {
return Result{str, i, args}
}
func (s *Service) EchoWithCtx(ctx context.Context, str string, i int, args *Args) Result {
return Result{str, i, args}
}
func (s *Service) Sleep(ctx context.Context, duration time.Duration) {
select {
case <-time.After(duration):
case <-ctx.Done():
}
}
func (s *Service) Rets() (string, error) {
return "", nil
}
func (s *Service) InvalidRets1() (error, string) {
return nil, ""
}
func (s *Service) InvalidRets2() (string, string) {
return "", ""
}
func (s *Service) InvalidRets3() (string, string, error) {
return "", "", nil
}
func (s *Service) Subscription(ctx context.Context) (*Subscription, error) {
return nil, nil
}
func TestServerRegisterName(t *testing.T) { func TestServerRegisterName(t *testing.T) {
server := NewServer() server := NewServer()
service := new(Service) service := new(testService)
if err := server.RegisterName("calc", service); err != nil {
t.Fatalf("%v", err)
}
if len(server.services) != 2 {
t.Fatalf("Expected 2 service entries, got %d", len(server.services))
}
svc, ok := server.services["calc"]
if !ok {
t.Fatalf("Expected service calc to be registered")
}
if len(svc.callbacks) != 5 {
t.Errorf("Expected 5 callbacks for service 'calc', got %d", len(svc.callbacks))
}
if len(svc.subscriptions) != 1 {
t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions))
}
}
func testServerMethodExecution(t *testing.T, method string) {
server := NewServer()
service := new(Service)
if err := server.RegisterName("test", service); err != nil { if err := server.RegisterName("test", service); err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
stringArg := "string arg" if len(server.services.services) != 2 {
intArg := 1122 t.Fatalf("Expected 2 service entries, got %d", len(server.services.services))
argsArg := &Args{"abcde"} }
params := []interface{}{stringArg, intArg, argsArg}
request := map[string]interface{}{ svc, ok := server.services.services["test"]
"id": 12345, if !ok {
"method": "test_" + method, t.Fatalf("Expected service calc to be registered")
"version": "2.0", }
"params": params,
wantCallbacks := 9
if len(svc.callbacks) != wantCallbacks {
t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks))
}
}
func TestServer(t *testing.T) {
files, err := ioutil.ReadDir("testdata")
if err != nil {
t.Fatal("where'd my testdata go?")
}
for _, f := range files {
if f.IsDir() || strings.HasPrefix(f.Name(), ".") {
continue
}
path := filepath.Join("testdata", f.Name())
name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
t.Run(name, func(t *testing.T) {
runTestScript(t, path)
})
}
}
func runTestScript(t *testing.T, file string) {
server := newTestServer()
content, err := ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
} }
clientConn, serverConn := net.Pipe() clientConn, serverConn := net.Pipe()
defer clientConn.Close() defer clientConn.Close()
go server.ServeCodec(NewCodec(serverConn), 0)
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation) readbuf := bufio.NewReader(clientConn)
for _, line := range strings.Split(string(content), "\n") {
out := json.NewEncoder(clientConn) line = strings.TrimSpace(line)
in := json.NewDecoder(clientConn) switch {
case len(line) == 0 || strings.HasPrefix(line, "//"):
if err := out.Encode(request); err != nil { // skip comments, blank lines
t.Fatal(err) continue
} case strings.HasPrefix(line, "--> "):
t.Log(line)
response := jsonSuccessResponse{Result: &Result{}} // write to connection
if err := in.Decode(&response); err != nil { clientConn.SetWriteDeadline(time.Now().Add(5 * time.Second))
t.Fatal(err) if _, err := io.WriteString(clientConn, line[4:]+"\n"); err != nil {
} t.Fatalf("write error: %v", err)
}
if result, ok := response.Result.(*Result); ok { case strings.HasPrefix(line, "<-- "):
if result.String != stringArg { t.Log(line)
t.Errorf("expected %s, got : %s\n", stringArg, result.String) want := line[4:]
// read line from connection and compare text
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
sent, err := readbuf.ReadString('\n')
if err != nil {
t.Fatalf("read error: %v", err)
}
sent = strings.TrimRight(sent, "\r\n")
if sent != want {
t.Errorf("wrong line from server\ngot: %s\nwant: %s", sent, want)
}
default:
panic("invalid line in test script: " + line)
} }
if result.Int != intArg {
t.Errorf("expected %d, got %d\n", intArg, result.Int)
}
if !reflect.DeepEqual(result.Args, argsArg) {
t.Errorf("expected %v, got %v\n", argsArg, result)
}
} else {
t.Fatalf("invalid response: expected *Result - got: %T", response.Result)
} }
} }
func TestServerMethodExecution(t *testing.T) { // This test checks that responses are delivered for very short-lived connections that
testServerMethodExecution(t, "echo") // only carry a single request.
} func TestServerShortLivedConn(t *testing.T) {
server := newTestServer()
defer server.Stop()
func TestServerMethodWithCtx(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0")
testServerMethodExecution(t, "echoWithCtx") if err != nil {
t.Fatal("can't listen:", err)
}
defer listener.Close()
go server.ServeListener(listener)
var (
request = `{"jsonrpc":"2.0","id":1,"method":"rpc_modules"}` + "\n"
wantResp = `{"jsonrpc":"2.0","id":1,"result":{"nftest":"1.0","rpc":"1.0","test":"1.0"}}` + "\n"
deadline = time.Now().Add(10 * time.Second)
)
for i := 0; i < 20; i++ {
conn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatal("can't dial:", err)
}
defer conn.Close()
conn.SetDeadline(deadline)
// Write the request, then half-close the connection so the server stops reading.
conn.Write([]byte(request))
conn.(*net.TCPConn).CloseWrite()
// Now try to get the response.
buf := make([]byte, 2000)
n, err := conn.Read(buf)
if err != nil {
t.Fatal("read error:", err)
}
if !bytes.Equal(buf[:n], []byte(wantResp)) {
t.Fatalf("wrong response: %s", buf[:n])
}
}
} }

261
rpc/service.go Normal file
View file

@ -0,0 +1,261 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"context"
"errors"
"fmt"
"reflect"
"runtime"
"strings"
"sync"
"unicode"
"github.com/XinFinOrg/XDPoSChain/log"
)
var (
contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
errorType = reflect.TypeOf((*error)(nil)).Elem()
subscriptionType = reflect.TypeOf(Subscription{})
stringType = reflect.TypeOf("")
)
type serviceRegistry struct {
mu sync.Mutex
services map[string]service
}
// service represents a registered object.
type service struct {
name string // name for service
callbacks map[string]*callback // registered handlers
subscriptions map[string]*callback // available subscriptions/notifications
}
// callback is a method callback which was registered in the server
type callback struct {
fn reflect.Value // the function
rcvr reflect.Value // receiver object of method, set if fn is method
argTypes []reflect.Type // input argument types
hasCtx bool // method's first argument is a context (not included in argTypes)
errPos int // err return idx, of -1 when method cannot return error
isSubscribe bool // true if this is a subscription callback
}
func (r *serviceRegistry) registerName(name string, rcvr interface{}) error {
rcvrVal := reflect.ValueOf(rcvr)
if name == "" {
return fmt.Errorf("no service name for type %s", rcvrVal.Type().String())
}
callbacks := suitableCallbacks(rcvrVal)
if len(callbacks) == 0 {
return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
}
r.mu.Lock()
defer r.mu.Unlock()
if r.services == nil {
r.services = make(map[string]service)
}
svc, ok := r.services[name]
if !ok {
svc = service{
name: name,
callbacks: make(map[string]*callback),
subscriptions: make(map[string]*callback),
}
r.services[name] = svc
}
for name, cb := range callbacks {
if cb.isSubscribe {
svc.subscriptions[name] = cb
} else {
svc.callbacks[name] = cb
}
}
return nil
}
// callback returns the callback corresponding to the given RPC method name.
func (r *serviceRegistry) callback(method string) *callback {
elem := strings.SplitN(method, serviceMethodSeparator, 2)
if len(elem) != 2 {
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
return r.services[elem[0]].callbacks[elem[1]]
}
// subscription returns a subscription callback in the given service.
func (r *serviceRegistry) subscription(service, name string) *callback {
r.mu.Lock()
defer r.mu.Unlock()
return r.services[service].subscriptions[name]
}
// suitableCallbacks iterates over the methods of the given type. It determines if a method
// satisfies the criteria for a RPC callback or a subscription callback and adds it to the
// collection of callbacks. See server documentation for a summary of these criteria.
func suitableCallbacks(receiver reflect.Value) map[string]*callback {
typ := receiver.Type()
callbacks := make(map[string]*callback)
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
if method.PkgPath != "" {
continue // method not exported
}
cb := newCallback(receiver, method.Func)
if cb == nil {
continue // function invalid
}
name := formatName(method.Name)
callbacks[name] = cb
}
return callbacks
}
// newCallback turns fn (a function) into a callback object. It returns nil if the function
// is unsuitable as an RPC callback.
func newCallback(receiver, fn reflect.Value) *callback {
fntype := fn.Type()
c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
// Determine parameter types. They must all be exported or builtin types.
c.makeArgTypes()
// Verify return types. The function must return at most one error
// and/or one other non-error value.
outs := make([]reflect.Type, fntype.NumOut())
for i := 0; i < fntype.NumOut(); i++ {
outs[i] = fntype.Out(i)
}
if len(outs) > 2 {
return nil
}
// If an error is returned, it must be the last returned value.
switch {
case len(outs) == 1 && isErrorType(outs[0]):
c.errPos = 0
case len(outs) == 2:
if isErrorType(outs[0]) || !isErrorType(outs[1]) {
return nil
}
c.errPos = 1
}
return c
}
// makeArgTypes composes the argTypes list.
func (c *callback) makeArgTypes() {
fntype := c.fn.Type()
// Skip receiver and context.Context parameter (if present).
firstArg := 0
if c.rcvr.IsValid() {
firstArg++
}
if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType {
c.hasCtx = true
firstArg++
}
// Add all remaining parameters.
c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg)
for i := firstArg; i < fntype.NumIn(); i++ {
c.argTypes[i-firstArg] = fntype.In(i)
}
}
// call invokes the callback.
func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) {
// Create the argument slice.
fullargs := make([]reflect.Value, 0, 2+len(args))
if c.rcvr.IsValid() {
fullargs = append(fullargs, c.rcvr)
}
if c.hasCtx {
fullargs = append(fullargs, reflect.ValueOf(ctx))
}
fullargs = append(fullargs, args...)
// Catch panic while running the callback.
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf))
errRes = errors.New("method handler crashed")
}
}()
// Run the callback.
results := c.fn.Call(fullargs)
if len(results) == 0 {
return nil, nil
}
if c.errPos >= 0 && !results[c.errPos].IsNil() {
// Method has returned non-nil error value.
err := results[c.errPos].Interface().(error)
return reflect.Value{}, err
}
return results[0].Interface(), nil
}
// Is t context.Context or *context.Context?
func isContextType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t == contextType
}
// Does t satisfy the error interface?
func isErrorType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Implements(errorType)
}
// Is t Subscription or *Subscription?
func isSubscriptionType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t == subscriptionType
}
// isPubSub tests whether the given method has as as first argument a context.Context and
// returns the pair (Subscription, error).
func isPubSub(methodType reflect.Type) bool {
// numIn(0) is the receiver type
if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
return false
}
return isContextType(methodType.In(1)) &&
isSubscriptionType(methodType.Out(0)) &&
isErrorType(methodType.Out(1))
}
// formatName converts to first character of name to lowercase.
func formatName(name string) string {
ret := []rune(name)
if len(ret) > 0 {
ret[0] = unicode.ToLower(ret[0])
}
return string(ret)
}

66
rpc/stdio.go Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"context"
"errors"
"io"
"net"
"os"
"time"
)
// DialStdIO creates a client on stdin/stdout.
func DialStdIO(ctx context.Context) (*Client, error) {
return DialIO(ctx, os.Stdin, os.Stdout)
}
// DialIO creates a client which uses the given IO channels
func DialIO(ctx context.Context, in io.Reader, out io.Writer) (*Client, error) {
return newClient(ctx, func(_ context.Context) (ServerCodec, error) {
return NewCodec(stdioConn{
in: in,
out: out,
}), nil
})
}
type stdioConn struct {
in io.Reader
out io.Writer
}
func (io stdioConn) Read(b []byte) (n int, err error) {
return io.in.Read(b)
}
func (io stdioConn) Write(b []byte) (n int, err error) {
return io.out.Write(b)
}
func (io stdioConn) Close() error {
return nil
}
func (io stdioConn) RemoteAddr() string {
return "/dev/stdin"
}
func (io stdioConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

View file

@ -17,9 +17,18 @@
package rpc package rpc
import ( import (
"container/list"
"context" "context"
crand "crypto/rand"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors" "errors"
"math/rand"
"reflect"
"strings"
"sync" "sync"
"time"
) )
var ( var (
@ -29,10 +38,151 @@ var (
ErrSubscriptionNotFound = errors.New("subscription not found") ErrSubscriptionNotFound = errors.New("subscription not found")
) )
var globalGen = randomIDGenerator()
// ID defines a pseudo random number that is used to identify RPC subscriptions. // ID defines a pseudo random number that is used to identify RPC subscriptions.
type ID string type ID string
// a Subscription is created by a notifier and tight to that notifier. The client can use // NewID returns a new, random ID.
func NewID() ID {
return globalGen()
}
// randomIDGenerator returns a function generates a random IDs.
func randomIDGenerator() func() ID {
var buf = make([]byte, 8)
var seed int64
if _, err := crand.Read(buf); err == nil {
seed = int64(binary.BigEndian.Uint64(buf))
} else {
seed = int64(time.Now().Nanosecond())
}
var (
mu sync.Mutex
rng = rand.New(rand.NewSource(seed))
)
return func() ID {
mu.Lock()
defer mu.Unlock()
id := make([]byte, 16)
rng.Read(id)
return encodeID(id)
}
}
func encodeID(b []byte) ID {
id := hex.EncodeToString(b)
id = strings.TrimLeft(id, "0")
if id == "" {
id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0.
}
return ID("0x" + id)
}
type notifierKey struct{}
// NotifierFromContext returns the Notifier value stored in ctx, if any.
func NotifierFromContext(ctx context.Context) (*Notifier, bool) {
n, ok := ctx.Value(notifierKey{}).(*Notifier)
return n, ok
}
// Notifier is tied to a RPC connection that supports subscriptions.
// Server callbacks use the notifier to send notifications.
type Notifier struct {
h *handler
namespace string
mu sync.Mutex
sub *Subscription
buffer []json.RawMessage
callReturned bool
activated bool
}
// CreateSubscription returns a new subscription that is coupled to the
// RPC connection. By default subscriptions are inactive and notifications
// are dropped until the subscription is marked as active. This is done
// by the RPC server after the subscription ID is send to the client.
func (n *Notifier) CreateSubscription() *Subscription {
n.mu.Lock()
defer n.mu.Unlock()
if n.sub != nil {
panic("can't create multiple subscriptions with Notifier")
} else if n.callReturned {
panic("can't create subscription after subscribe call has returned")
}
n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)}
return n.sub
}
// Notify sends a notification to the client with the given data as payload.
// If an error occurs the RPC connection is closed and the error is returned.
func (n *Notifier) Notify(id ID, data interface{}) error {
enc, err := json.Marshal(data)
if err != nil {
return err
}
n.mu.Lock()
defer n.mu.Unlock()
if n.sub == nil {
panic("can't Notify before subscription is created")
} else if n.sub.ID != id {
panic("Notify with wrong ID")
}
if n.activated {
return n.send(n.sub, enc)
}
n.buffer = append(n.buffer, enc)
return nil
}
// Closed returns a channel that is closed when the RPC connection is closed.
// Deprecated: use subscription error channel
func (n *Notifier) Closed() <-chan interface{} {
return n.h.conn.closed()
}
// takeSubscription returns the subscription (if one has been created). No subscription can
// be created after this call.
func (n *Notifier) takeSubscription() *Subscription {
n.mu.Lock()
defer n.mu.Unlock()
n.callReturned = true
return n.sub
}
// activate is called after the subscription ID was sent to client. Notifications are
// buffered before activation. This prevents notifications being sent to the client before
// the subscription ID is sent to the client.
func (n *Notifier) activate() error {
n.mu.Lock()
defer n.mu.Unlock()
for _, data := range n.buffer {
if err := n.send(n.sub, data); err != nil {
return err
}
}
n.activated = true
return nil
}
func (n *Notifier) send(sub *Subscription, data json.RawMessage) error {
params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
ctx := context.Background()
return n.h.conn.writeJSON(ctx, &jsonrpcMessage{
Version: vsn,
Method: n.namespace + notificationMethodSuffix,
Params: params,
})
}
// A Subscription is created by a notifier and tied to that notifier. The client can use
// this subscription to wait for an unsubscribe request for the client, see Err(). // this subscription to wait for an unsubscribe request for the client, see Err().
type Subscription struct { type Subscription struct {
ID ID ID ID
@ -45,91 +195,136 @@ func (s *Subscription) Err() <-chan error {
return s.err return s.err
} }
// notifierKey is used to store a notifier within the connection context. // MarshalJSON marshals a subscription as its ID.
type notifierKey struct{} func (s *Subscription) MarshalJSON() ([]byte, error) {
return json.Marshal(s.ID)
// Notifier is tight to a RPC connection that supports subscriptions.
// Server callbacks use the notifier to send notifications.
type Notifier struct {
codec ServerCodec
subMu sync.RWMutex // guards active and inactive maps
active map[ID]*Subscription
inactive map[ID]*Subscription
} }
// newNotifier creates a new notifier that can be used to send subscription // ClientSubscription is a subscription established through the Client's Subscribe or
// notifications to the client. // EthSubscribe methods.
func newNotifier(codec ServerCodec) *Notifier { type ClientSubscription struct {
return &Notifier{ client *Client
codec: codec, etype reflect.Type
active: make(map[ID]*Subscription), channel reflect.Value
inactive: make(map[ID]*Subscription), namespace string
subid string
in chan json.RawMessage
quitOnce sync.Once // ensures quit is closed once
quit chan struct{} // quit is closed when the subscription exits
errOnce sync.Once // ensures err is closed once
err chan error
}
func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
sub := &ClientSubscription{
client: c,
namespace: namespace,
etype: channel.Type().Elem(),
channel: channel,
quit: make(chan struct{}),
err: make(chan error, 1),
in: make(chan json.RawMessage),
}
return sub
}
// Err returns the subscription error channel. The intended use of Err is to schedule
// resubscription when the client connection is closed unexpectedly.
//
// The error channel receives a value when the subscription has ended due
// to an error. The received error is nil if Close has been called
// on the underlying client and no other error has occurred.
//
// The error channel is closed when Unsubscribe is called on the subscription.
func (sub *ClientSubscription) Err() <-chan error {
return sub.err
}
// Unsubscribe unsubscribes the notification and closes the error channel.
// It can safely be called more than once.
func (sub *ClientSubscription) Unsubscribe() {
sub.quitWithError(true, nil)
sub.errOnce.Do(func() { close(sub.err) })
}
func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) {
sub.quitOnce.Do(func() {
// The dispatch loop won't be able to execute the unsubscribe call
// if it is blocked on deliver. Close sub.quit first because it
// unblocks deliver.
close(sub.quit)
if unsubscribeServer {
sub.requestUnsubscribe()
}
if err != nil {
if err == ErrClientQuit {
err = nil // Adhere to subscription semantics.
}
sub.err <- err
}
})
}
func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
select {
case sub.in <- result:
return true
case <-sub.quit:
return false
} }
} }
// NotifierFromContext returns the Notifier value stored in ctx, if any. func (sub *ClientSubscription) start() {
func NotifierFromContext(ctx context.Context) (*Notifier, bool) { sub.quitWithError(sub.forward())
n, ok := ctx.Value(notifierKey{}).(*Notifier)
return n, ok
} }
// CreateSubscription returns a new subscription that is coupled to the func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
// RPC connection. By default subscriptions are inactive and notifications cases := []reflect.SelectCase{
// are dropped until the subscription is marked as active. This is done {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
// by the RPC server after the subscription ID is send to the client. {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)},
func (n *Notifier) CreateSubscription() *Subscription { {Dir: reflect.SelectSend, Chan: sub.channel},
s := &Subscription{ID: NewID(), err: make(chan error)} }
n.subMu.Lock() buffer := list.New()
n.inactive[s.ID] = s defer buffer.Init()
n.subMu.Unlock() for {
return s var chosen int
} var recv reflect.Value
if buffer.Len() == 0 {
// Idle, omit send case.
chosen, recv, _ = reflect.Select(cases[:2])
} else {
// Non-empty buffer, send the first queued item.
cases[2].Send = reflect.ValueOf(buffer.Front().Value)
chosen, recv, _ = reflect.Select(cases)
}
// Notify sends a notification to the client with the given data as payload. switch chosen {
// If an error occurs the RPC connection is closed and the error is returned. case 0: // <-sub.quit
func (n *Notifier) Notify(id ID, data interface{}) error { return false, nil
n.subMu.RLock() case 1: // <-sub.in
defer n.subMu.RUnlock() val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
if err != nil {
sub, active := n.active[id] return true, err
if active { }
notification := n.codec.CreateNotification(string(id), sub.namespace, data) if buffer.Len() == maxClientSubscriptionBuffer {
if err := n.codec.Write(notification); err != nil { return true, ErrSubscriptionQueueOverflow
n.codec.Close() }
return err buffer.PushBack(val)
case 2: // sub.channel<-
cases[2].Send = reflect.Value{} // Don't hold onto the value.
buffer.Remove(buffer.Front())
} }
} }
return nil
} }
// Closed returns a channel that is closed when the RPC connection is closed. func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) {
func (n *Notifier) Closed() <-chan interface{} { val := reflect.New(sub.etype)
return n.codec.Closed() err := json.Unmarshal(result, val.Interface())
return val.Elem().Interface(), err
} }
// unsubscribe a subscription. func (sub *ClientSubscription) requestUnsubscribe() error {
// If the subscription could not be found ErrSubscriptionNotFound is returned. var result interface{}
func (n *Notifier) unsubscribe(id ID) error { return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid)
n.subMu.Lock()
defer n.subMu.Unlock()
if s, found := n.active[id]; found {
close(s.err)
delete(n.active, id)
return nil
}
return ErrSubscriptionNotFound
}
// activate enables a subscription. Until a subscription is enabled all
// notifications are dropped. This method is called by the RPC server after
// the subscription ID was sent to client. This prevents notifications being
// send to the client before the subscription ID is send to the client.
func (n *Notifier) activate(id ID, namespace string) {
n.subMu.Lock()
defer n.subMu.Unlock()
if sub, found := n.inactive[id]; found {
sub.namespace = namespace
n.active[id] = sub
delete(n.inactive, id)
}
} }

View file

@ -17,314 +17,204 @@
package rpc package rpc
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"sync" "strings"
"testing" "testing"
"time" "time"
) )
type NotificationTestService struct { func TestNewID2(t *testing.T) {
mu sync.Mutex hexchars := "0123456789ABCDEFabcdef"
unsubscribed bool for i := 0; i < 100; i++ {
id := string(NewID())
if !strings.HasPrefix(id, "0x") {
t.Fatalf("invalid ID prefix, want '0x...', got %s", id)
}
gotHangSubscriptionReq chan struct{} id = id[2:]
unblockHangSubscription chan struct{} if len(id) == 0 || len(id) > 32 {
} t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id))
}
func (s *NotificationTestService) Echo(i int) int { for i := 0; i < len(id); i++ {
return i if strings.IndexByte(hexchars, id[i]) == -1 {
} t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i])
func (s *NotificationTestService) wasUnsubCallbackCalled() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.unsubscribed
}
func (s *NotificationTestService) Unsubscribe(subid string) {
s.mu.Lock()
s.unsubscribed = true
s.mu.Unlock()
}
func (s *NotificationTestService) SomeSubscription(ctx context.Context, n, val int) (*Subscription, error) {
notifier, supported := NotifierFromContext(ctx)
if !supported {
return nil, ErrNotificationsUnsupported
}
// by explicitly creating an subscription we make sure that the subscription id is send back to the client
// before the first subscription.Notify is called. Otherwise the events might be send before the response
// for the eth_subscribe method.
subscription := notifier.CreateSubscription()
go func() {
// test expects n events, if we begin sending event immediately some events
// will probably be dropped since the subscription ID might not be send to
// the client.
time.Sleep(5 * time.Second)
for i := 0; i < n; i++ {
if err := notifier.Notify(subscription.ID, val+i); err != nil {
return
} }
} }
select {
case <-notifier.Closed():
s.mu.Lock()
s.unsubscribed = true
s.mu.Unlock()
case <-subscription.Err():
s.mu.Lock()
s.unsubscribed = true
s.mu.Unlock()
}
}()
return subscription, nil
}
// HangSubscription blocks on s.unblockHangSubscription before
// sending anything.
func (s *NotificationTestService) HangSubscription(ctx context.Context, val int) (*Subscription, error) {
notifier, supported := NotifierFromContext(ctx)
if !supported {
return nil, ErrNotificationsUnsupported
}
s.gotHangSubscriptionReq <- struct{}{}
<-s.unblockHangSubscription
subscription := notifier.CreateSubscription()
go func() {
notifier.Notify(subscription.ID, val)
}()
return subscription, nil
}
func TestNotifications(t *testing.T) {
server := NewServer()
service := &NotificationTestService{}
if err := server.RegisterName("eth", service); err != nil {
t.Fatalf("unable to register test service %v", err)
}
clientConn, serverConn := net.Pipe()
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions)
out := json.NewEncoder(clientConn)
in := json.NewDecoder(clientConn)
n := 5
val := 12345
request := map[string]interface{}{
"id": 1,
"method": "eth_subscribe",
"version": "2.0",
"params": []interface{}{"someSubscription", n, val},
}
// create subscription
if err := out.Encode(request); err != nil {
t.Fatal(err)
}
var subid string
response := jsonSuccessResponse{Result: subid}
if err := in.Decode(&response); err != nil {
t.Fatal(err)
}
var ok bool
if _, ok = response.Result.(string); !ok {
t.Fatalf("expected subscription id, got %T", response.Result)
}
for i := 0; i < n; i++ {
var notification jsonNotification
if err := in.Decode(&notification); err != nil {
t.Fatalf("%v", err)
}
if int(notification.Params.Result.(float64)) != val+i {
t.Fatalf("expected %d, got %d", val+i, notification.Params.Result)
}
}
clientConn.Close() // causes notification unsubscribe callback to be called
time.Sleep(1 * time.Second)
if !service.wasUnsubCallbackCalled() {
t.Error("unsubscribe callback not called after closing connection")
} }
} }
func waitForMessages(t *testing.T, in *json.Decoder, successes chan<- jsonSuccessResponse, func TestSubscriptions(t *testing.T) {
failures chan<- jsonErrResponse, notifications chan<- jsonNotification, errors chan<- error) {
// read and parse server messages
for {
var rmsg json.RawMessage
if err := in.Decode(&rmsg); err != nil {
return
}
var responses []map[string]interface{}
if rmsg[0] == '[' {
if err := json.Unmarshal(rmsg, &responses); err != nil {
errors <- fmt.Errorf("Received invalid message: %s", rmsg)
return
}
} else {
var msg map[string]interface{}
if err := json.Unmarshal(rmsg, &msg); err != nil {
errors <- fmt.Errorf("Received invalid message: %s", rmsg)
return
}
responses = append(responses, msg)
}
for _, msg := range responses {
// determine what kind of msg was received and broadcast
// it to over the corresponding channel
if _, found := msg["result"]; found {
successes <- jsonSuccessResponse{
Version: msg["jsonrpc"].(string),
Id: msg["id"],
Result: msg["result"],
}
continue
}
if _, found := msg["error"]; found {
params := msg["params"].(map[string]interface{})
failures <- jsonErrResponse{
Version: msg["jsonrpc"].(string),
Id: msg["id"],
Error: jsonError{int(params["subscription"].(float64)), params["message"].(string), params["data"]},
}
continue
}
if _, found := msg["params"]; found {
params := msg["params"].(map[string]interface{})
notifications <- jsonNotification{
Version: msg["jsonrpc"].(string),
Method: msg["method"].(string),
Params: jsonSubscription{params["subscription"].(string), params["result"]},
}
continue
}
errors <- fmt.Errorf("Received invalid message: %s", msg)
}
}
}
// TestSubscriptionMultipleNamespaces ensures that subscriptions can exists
// for multiple different namespaces.
func TestSubscriptionMultipleNamespaces(t *testing.T) {
var ( var (
namespaces = []string{"eth", "shh", "bzz"} namespaces = []string{"eth", "shh", "bzz"}
service = &notificationTestService{}
subCount = len(namespaces)
notificationCount = 3
server = NewServer() server = NewServer()
service = NotificationTestService{}
clientConn, serverConn = net.Pipe() clientConn, serverConn = net.Pipe()
out = json.NewEncoder(clientConn)
out = json.NewEncoder(clientConn) in = json.NewDecoder(clientConn)
in = json.NewDecoder(clientConn) successes = make(chan subConfirmation)
successes = make(chan jsonSuccessResponse) notifications = make(chan subscriptionResult)
failures = make(chan jsonErrResponse) errors = make(chan error, subCount*notificationCount+1)
notifications = make(chan jsonNotification)
errors = make(chan error, 10)
) )
// setup and start server // setup and start server
for _, namespace := range namespaces { for _, namespace := range namespaces {
if err := server.RegisterName(namespace, &service); err != nil { if err := server.RegisterName(namespace, service); err != nil {
t.Fatalf("unable to register test service %v", err) t.Fatalf("unable to register test service %v", err)
} }
} }
go server.ServeCodec(NewCodec(serverConn), 0)
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions)
defer server.Stop() defer server.Stop()
// wait for message and write them to the given channels // wait for message and write them to the given channels
go waitForMessages(t, in, successes, failures, notifications, errors) go waitForMessages(in, successes, notifications, errors)
// create subscriptions one by one // create subscriptions one by one
n := 3
for i, namespace := range namespaces { for i, namespace := range namespaces {
request := map[string]interface{}{ request := map[string]interface{}{
"id": i, "id": i,
"method": fmt.Sprintf("%s_subscribe", namespace), "method": fmt.Sprintf("%s_subscribe", namespace),
"version": "2.0", "version": "2.0",
"params": []interface{}{"someSubscription", n, i}, "params": []interface{}{"someSubscription", notificationCount, i},
} }
if err := out.Encode(&request); err != nil { if err := out.Encode(&request); err != nil {
t.Fatalf("Could not create subscription: %v", err) t.Fatalf("Could not create subscription: %v", err)
} }
} }
// create all subscriptions in 1 batch
var requests []interface{}
for i, namespace := range namespaces {
requests = append(requests, map[string]interface{}{
"id": i,
"method": fmt.Sprintf("%s_subscribe", namespace),
"version": "2.0",
"params": []interface{}{"someSubscription", n, i},
})
}
if err := out.Encode(&requests); err != nil {
t.Fatalf("Could not create subscription in batch form: %v", err)
}
timeout := time.After(30 * time.Second) timeout := time.After(30 * time.Second)
subids := make(map[string]string, 2*len(namespaces)) subids := make(map[string]string, subCount)
count := make(map[string]int, 2*len(namespaces)) count := make(map[string]int, subCount)
allReceived := func() bool {
for { done := len(count) == subCount
done := true for _, c := range count {
for id := range count { if c < notificationCount {
if count, found := count[id]; !found || count < (2*n) {
done = false done = false
} }
} }
return done
if done && len(count) == len(namespaces) { }
break for !allReceived() {
}
select { select {
case confirmation := <-successes: // subscription created
subids[namespaces[confirmation.reqid]] = string(confirmation.subid)
case notification := <-notifications:
count[notification.ID]++
case err := <-errors: case err := <-errors:
t.Fatal(err) t.Fatal(err)
case suc := <-successes: // subscription created
subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string)
case failure := <-failures:
t.Errorf("received error: %v", failure.Error)
case notification := <-notifications:
if cnt, found := count[notification.Params.Subscription]; found {
count[notification.Params.Subscription] = cnt + 1
} else {
count[notification.Params.Subscription] = 1
}
case <-timeout: case <-timeout:
for _, namespace := range namespaces { for _, namespace := range namespaces {
subid, found := subids[namespace] subid, found := subids[namespace]
if !found { if !found {
t.Errorf("Subscription for '%s' not created", namespace) t.Errorf("subscription for %q not created", namespace)
continue continue
} }
if count, found := count[subid]; !found || count < n { if count, found := count[subid]; !found || count < notificationCount {
t.Errorf("Didn't receive all notifications (%d<%d) in time for namespace '%s'", count, n, namespace) t.Errorf("didn't receive all notifications (%d<%d) in time for namespace %q", count, notificationCount, namespace)
} }
} }
return t.Fatal("timed out")
} }
} }
} }
// This test checks that unsubscribing works.
func TestServerUnsubscribe(t *testing.T) {
p1, p2 := net.Pipe()
defer p2.Close()
// Start the server.
server := newTestServer()
service := &notificationTestService{unsubscribed: make(chan string, 1)}
server.RegisterName("nftest2", service)
go server.ServeCodec(NewCodec(p1), 0)
// Subscribe.
p2.SetDeadline(time.Now().Add(10 * time.Second))
p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`))
// Handle received messages.
var (
resps = make(chan subConfirmation)
notifications = make(chan subscriptionResult)
errors = make(chan error, 1)
)
go waitForMessages(json.NewDecoder(p2), resps, notifications, errors)
// Receive the subscription ID.
var sub subConfirmation
select {
case sub = <-resps:
case err := <-errors:
t.Fatal(err)
}
// Unsubscribe and check that it is handled on the server side.
p2.Write([]byte(`{"jsonrpc":"2.0","method":"nftest2_unsubscribe","params":["` + sub.subid + `"]}`))
for {
select {
case id := <-service.unsubscribed:
if id != string(sub.subid) {
t.Errorf("wrong subscription ID unsubscribed")
}
return
case err := <-errors:
t.Fatal(err)
case <-notifications:
// drop notifications
}
}
}
type subConfirmation struct {
reqid int
subid ID
}
// waitForMessages reads RPC messages from 'in' and dispatches them into the given channels.
// It stops if there is an error.
func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) {
for {
resp, notification, err := readAndValidateMessage(in)
if err != nil {
errors <- err
return
} else if resp != nil {
successes <- *resp
} else {
notifications <- *notification
}
}
}
func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionResult, error) {
var msg jsonrpcMessage
if err := in.Decode(&msg); err != nil {
return nil, nil, fmt.Errorf("decode error: %v", err)
}
switch {
case msg.isNotification():
var res subscriptionResult
if err := json.Unmarshal(msg.Params, &res); err != nil {
return nil, nil, fmt.Errorf("invalid subscription result: %v", err)
}
return nil, &res, nil
case msg.isResponse():
var c subConfirmation
if msg.Error != nil {
return nil, nil, msg.Error
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
return nil, nil, fmt.Errorf("invalid response: %v", err)
} else {
json.Unmarshal(msg.ID, &c.reqid)
return &c, nil, nil
}
default:
return nil, nil, fmt.Errorf("unrecognized message: %v", msg)
}
}

7
rpc/testdata/invalid-badid.js vendored Normal file
View file

@ -0,0 +1,7 @@
// This test checks processing of messages with invalid ID.
--> {"id":[],"method":"test_foo"}
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}
--> {"id":{},"method":"test_foo"}
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}

17
rpc/testdata/invalid-batch.js vendored Normal file
View file

@ -0,0 +1,17 @@
// This test checks the behavior of batches with invalid elements.
// Empty batches are not allowed. Batches may contain junk.
--> []
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"empty batch"}}
--> [1]
<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}]
--> [1,2,3]
<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}]
--> [null]
<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}]
--> [{"jsonrpc":"2.0","id":1,"method":"test_echo","params":["foo",1]},55,{"jsonrpc":"2.0","id":2,"method":"unknown_method"},{"foo":"bar"}]
<-- [{"jsonrpc":"2.0","id":1,"result":{"String":"foo","Int":1,"Args":null}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"the method unknown_method does not exist/is not available"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}]

7
rpc/testdata/invalid-idonly.js vendored Normal file
View file

@ -0,0 +1,7 @@
// This test checks processing of messages that contain just the ID and nothing else.
--> {"id":1}
<-- {"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"invalid request"}}
--> {"jsonrpc":"2.0","id":1}
<-- {"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"invalid request"}}

7
rpc/testdata/invalid-nonobj.js vendored Normal file
View file

@ -0,0 +1,7 @@
// This test checks behavior for invalid requests.
--> 1
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}
--> null
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}

5
rpc/testdata/invalid-syntax.json vendored Normal file
View file

@ -0,0 +1,5 @@
// This test checks that an error is written for invalid JSON requests.
--> 'f
<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"invalid character '\\'' looking for beginning of value"}}

8
rpc/testdata/reqresp-batch.js vendored Normal file
View file

@ -0,0 +1,8 @@
// There is no response for all-notification batches.
--> [{"jsonrpc":"2.0","method":"test_echo","params":["x",99]}]
// This test checks regular batch calls.
--> [{"jsonrpc":"2.0","id":2,"method":"test_echo","params":[]}, {"jsonrpc":"2.0","id": 3,"method":"test_echo","params":["x",3]}]
<-- [{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 0"}},{"jsonrpc":"2.0","id":3,"result":{"String":"x","Int":3,"Args":null}}]

16
rpc/testdata/reqresp-echo.js vendored Normal file
View file

@ -0,0 +1,16 @@
// This test calls the test_echo method.
--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": []}
<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 0"}}
--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x"]}
<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 1"}}
--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x", 3]}
<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":null}}
--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x", 3, {"S": "foo"}]}
<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":{"S":"foo"}}}
--> {"jsonrpc": "2.0", "id": 2, "method": "test_echoWithCtx", "params": ["x", 3, {"S": "foo"}]}
<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":{"S":"foo"}}}

5
rpc/testdata/reqresp-namedparam.js vendored Normal file
View file

@ -0,0 +1,5 @@
// This test checks that an error response is sent for calls
// with named parameters.
--> {"jsonrpc":"2.0","method":"test_echo","params":{"int":23},"id":3}
<-- {"jsonrpc":"2.0","id":3,"error":{"code":-32602,"message":"non-array args"}}

4
rpc/testdata/reqresp-noargsrets.js vendored Normal file
View file

@ -0,0 +1,4 @@
// This test calls the test_noArgsRets method.
--> {"jsonrpc": "2.0", "id": "foo", "method": "test_noArgsRets", "params": []}
<-- {"jsonrpc":"2.0","id":"foo","result":null}

4
rpc/testdata/reqresp-nomethod.js vendored Normal file
View file

@ -0,0 +1,4 @@
// This test calls a method that doesn't exist.
--> {"jsonrpc": "2.0", "id": 2, "method": "invalid_method", "params": [2, 3]}
<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"the method invalid_method does not exist/is not available"}}

4
rpc/testdata/reqresp-noparam.js vendored Normal file
View file

@ -0,0 +1,4 @@
// This test checks that calls with no parameters work.
--> {"jsonrpc":"2.0","method":"test_noArgsRets","id":3}
<-- {"jsonrpc":"2.0","id":3,"result":null}

4
rpc/testdata/reqresp-paramsnull.js vendored Normal file
View file

@ -0,0 +1,4 @@
// This test checks that calls with "params":null work.
--> {"jsonrpc":"2.0","method":"test_noArgsRets","params":null,"id":3}
<-- {"jsonrpc":"2.0","id":3,"result":null}

6
rpc/testdata/revcall.js vendored Normal file
View file

@ -0,0 +1,6 @@
// This test checks reverse calls.
--> {"jsonrpc":"2.0","id":2,"method":"test_callMeBack","params":["foo",[1]]}
<-- {"jsonrpc":"2.0","id":1,"method":"foo","params":[1]}
--> {"jsonrpc":"2.0","id":1,"result":"my result"}
<-- {"jsonrpc":"2.0","id":2,"result":"my result"}

7
rpc/testdata/revcall2.js vendored Normal file
View file

@ -0,0 +1,7 @@
// This test checks reverse calls.
--> {"jsonrpc":"2.0","id":2,"method":"test_callMeBackLater","params":["foo",[1]]}
<-- {"jsonrpc":"2.0","id":2,"result":null}
<-- {"jsonrpc":"2.0","id":1,"method":"foo","params":[1]}
--> {"jsonrpc":"2.0","id":1,"result":"my result"}

12
rpc/testdata/subscription.js vendored Normal file
View file

@ -0,0 +1,12 @@
// This test checks basic subscription support.
--> {"jsonrpc":"2.0","id":1,"method":"nftest_subscribe","params":["someSubscription",5,1]}
<-- {"jsonrpc":"2.0","id":1,"result":"0x1"}
<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":1}}
<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":2}}
<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":3}}
<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":4}}
<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":5}}
--> {"jsonrpc":"2.0","id":2,"method":"nftest_echo","params":[11]}
<-- {"jsonrpc":"2.0","id":2,"result":11}

206
rpc/testservice_test.go Normal file
View file

@ -0,0 +1,206 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"context"
"encoding/binary"
"errors"
"strings"
"sync"
"time"
)
func newTestServer() *Server {
server := NewServer()
server.idgen = sequentialIDGenerator()
if err := server.RegisterName("test", new(testService)); err != nil {
panic(err)
}
if err := server.RegisterName("nftest", new(notificationTestService)); err != nil {
panic(err)
}
return server
}
func sequentialIDGenerator() func() ID {
var (
mu sync.Mutex
counter uint64
)
return func() ID {
mu.Lock()
defer mu.Unlock()
counter++
id := make([]byte, 8)
binary.BigEndian.PutUint64(id, counter)
return encodeID(id)
}
}
type testService struct{}
type echoArgs struct {
S string
}
type echoResult struct {
String string
Int int
Args *echoArgs
}
type testError struct{}
func (testError) Error() string { return "testError" }
func (testError) ErrorCode() int { return 444 }
func (testError) ErrorData() interface{} { return "testError data" }
func (s *testService) NoArgsRets() {}
func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
return echoResult{str, i, args}
}
func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *echoArgs) echoResult {
return echoResult{str, i, args}
}
func (s *testService) Sleep(ctx context.Context, duration time.Duration) {
time.Sleep(duration)
}
func (s *testService) Block(ctx context.Context) error {
<-ctx.Done()
return errors.New("context canceled in testservice_block")
}
func (s *testService) Rets() (string, error) {
return "", nil
}
//lint:ignore ST1008 returns error first on purpose.
func (s *testService) InvalidRets1() (error, string) {
return nil, ""
}
func (s *testService) InvalidRets2() (string, string) {
return "", ""
}
func (s *testService) InvalidRets3() (string, string, error) {
return "", "", nil
}
func (s *testService) ReturnError() error {
return testError{}
}
func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) {
c, ok := ClientFromContext(ctx)
if !ok {
return nil, errors.New("no client")
}
var result interface{}
err := c.Call(&result, method, args...)
return result, err
}
func (s *testService) CallMeBackLater(ctx context.Context, method string, args []interface{}) error {
c, ok := ClientFromContext(ctx)
if !ok {
return errors.New("no client")
}
go func() {
<-ctx.Done()
var result interface{}
c.Call(&result, method, args...)
}()
return nil
}
func (s *testService) Subscription(ctx context.Context) (*Subscription, error) {
return nil, nil
}
type notificationTestService struct {
unsubscribed chan string
gotHangSubscriptionReq chan struct{}
unblockHangSubscription chan struct{}
}
func (s *notificationTestService) Echo(i int) int {
return i
}
func (s *notificationTestService) Unsubscribe(subid string) {
if s.unsubscribed != nil {
s.unsubscribed <- subid
}
}
func (s *notificationTestService) SomeSubscription(ctx context.Context, n, val int) (*Subscription, error) {
notifier, supported := NotifierFromContext(ctx)
if !supported {
return nil, ErrNotificationsUnsupported
}
// By explicitly creating an subscription we make sure that the subscription id is send
// back to the client before the first subscription.Notify is called. Otherwise the
// events might be send before the response for the *_subscribe method.
subscription := notifier.CreateSubscription()
go func() {
for i := 0; i < n; i++ {
if err := notifier.Notify(subscription.ID, val+i); err != nil {
return
}
}
select {
case <-notifier.Closed():
case <-subscription.Err():
}
if s.unsubscribed != nil {
s.unsubscribed <- string(subscription.ID)
}
}()
return subscription, nil
}
// HangSubscription blocks on s.unblockHangSubscription before sending anything.
func (s *notificationTestService) HangSubscription(ctx context.Context, val int) (*Subscription, error) {
notifier, supported := NotifierFromContext(ctx)
if !supported {
return nil, ErrNotificationsUnsupported
}
s.gotHangSubscriptionReq <- struct{}{}
<-s.unblockHangSubscription
subscription := notifier.CreateSubscription()
go func() {
notifier.Notify(subscription.ID, val)
}()
return subscription, nil
}
// largeRespService generates arbitrary-size JSON responses.
type largeRespService struct {
length int
}
func (x largeRespService) LargeResp() string {
return strings.Repeat("x", x.length)
}

View file

@ -17,14 +17,14 @@
package rpc package rpc
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"math" "math"
"reflect"
"strings" "strings"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
mapset "github.com/deckarep/golang-set"
) )
// API describes the set of methods offered over the RPC interface // API describes the set of methods offered over the RPC interface
@ -35,85 +35,35 @@ type API struct {
Public bool // indication if the methods must be considered safe for public use Public bool // indication if the methods must be considered safe for public use
} }
// callback is a method callback which was registered in the server
type callback struct {
rcvr reflect.Value // receiver of method
method reflect.Method // callback
argTypes []reflect.Type // input argument types
hasCtx bool // method's first argument is a context (not included in argTypes)
errPos int // err return idx, of -1 when method cannot return error
isSubscribe bool // indication if the callback is a subscription
}
// service represents a registered object
type service struct {
name string // name for service
typ reflect.Type // receiver type
callbacks callbacks // registered handlers
subscriptions subscriptions // available subscriptions/notifications
}
// serverRequest is an incoming request
type serverRequest struct {
id interface{}
svcname string
callb *callback
args []reflect.Value
isUnsubscribe bool
err Error
}
type serviceRegistry map[string]*service // collection of services
type callbacks map[string]*callback // collection of RPC callbacks
type subscriptions map[string]*callback // collection of subscription callbacks
// Server represents a RPC server
type Server struct {
services serviceRegistry
run int32
codecsMu sync.Mutex
codecs mapset.Set
}
// rpcRequest represents a raw incoming RPC request
type rpcRequest struct {
service string
method string
id interface{}
isPubSub bool
params interface{}
err Error // invalid batch element
}
// Error wraps RPC errors, which contain an error code in addition to the message. // Error wraps RPC errors, which contain an error code in addition to the message.
type Error interface { type Error interface {
Error() string // returns the message Error() string // returns the message
ErrorCode() int // returns the code ErrorCode() int // returns the code
} }
// A DataError contains some data in addition to the error message.
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
}
// ServerCodec implements reading, parsing and writing RPC messages for the server side of // ServerCodec implements reading, parsing and writing RPC messages for the server side of
// a RPC session. Implementations must be go-routine safe since the codec can be called in // a RPC session. Implementations must be go-routine safe since the codec can be called in
// multiple go-routines concurrently. // multiple go-routines concurrently.
type ServerCodec interface { type ServerCodec interface {
// Read next request readBatch() (msgs []*jsonrpcMessage, isBatch bool, err error)
ReadRequestHeaders() ([]rpcRequest, bool, Error) close()
// Parse request argument to the given types jsonWriter
ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, Error) }
// Assemble success response, expects response id and payload
CreateResponse(id interface{}, reply interface{}) interface{} // jsonWriter can write JSON messages to its underlying connection.
// Assemble error response, expects response id and error // Implementations must be safe for concurrent use.
CreateErrorResponse(id interface{}, err Error) interface{} type jsonWriter interface {
// Assemble error response with extra information about the error through info writeJSON(context.Context, interface{}) error
CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{} // Closed returns a channel which is closed when the connection is closed.
// Create notification response closed() <-chan interface{}
CreateNotification(id, namespace string, event interface{}) interface{} // RemoteAddr returns the peer address of the connection.
// Write msg to client. remoteAddr() string
Write(msg interface{}) error
// Close underlying data stream
Close()
// Closed when underlying connection is closed
Closed() <-chan interface{}
} }
type BlockNumber int64 type BlockNumber int64
@ -134,7 +84,11 @@ const (
// - an invalid block number error when the given argument isn't a known strings // - an invalid block number error when the given argument isn't a known strings
// - an out of range error when the given block number is either too little or too large // - an out of range error when the given block number is either too little or too large
func (bn *BlockNumber) UnmarshalJSON(data []byte) error { func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
input := trimData(data) input := strings.TrimSpace(string(data))
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {
input = input[1 : len(input)-1]
}
switch input { switch input {
case "earliest": case "earliest":
*bn = EarliestBlockNumber *bn = EarliestBlockNumber
@ -155,9 +109,8 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
return err return err
} }
if blckNum > math.MaxInt64 { if blckNum > math.MaxInt64 {
return fmt.Errorf("Blocknumber too high") return fmt.Errorf("block number larger than int64")
} }
*bn = BlockNumber(blckNum) *bn = BlockNumber(blckNum)
return nil return nil
} }
@ -189,6 +142,101 @@ func (e EpochNumber) Int64() int64 {
return (int64)(e) return (int64)(e)
} }
type BlockNumberOrHash struct {
BlockNumber *BlockNumber `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
RequireCanonical bool `json:"requireCanonical,omitempty"`
}
func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
type erased BlockNumberOrHash
e := erased{}
err := json.Unmarshal(data, &e)
if err == nil {
if e.BlockNumber != nil && e.BlockHash != nil {
return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
}
bnh.BlockNumber = e.BlockNumber
bnh.BlockHash = e.BlockHash
bnh.RequireCanonical = e.RequireCanonical
return nil
}
var input string
err = json.Unmarshal(data, &input)
if err != nil {
return err
}
switch input {
case "earliest":
bn := EarliestBlockNumber
bnh.BlockNumber = &bn
return nil
case "latest":
bn := LatestBlockNumber
bnh.BlockNumber = &bn
return nil
case "pending":
bn := PendingBlockNumber
bnh.BlockNumber = &bn
return nil
case "committed":
bn := CommittedBlockNumber
bnh.BlockNumber = &bn
return nil
default:
if len(input) == 66 {
hash := common.Hash{}
err := hash.UnmarshalText([]byte(input))
if err != nil {
return err
}
bnh.BlockHash = &hash
return nil
} else {
blckNum, err := hexutil.DecodeUint64(input)
if err != nil {
return err
}
if blckNum > math.MaxInt64 {
return fmt.Errorf("blocknumber too high")
}
bn := BlockNumber(blckNum)
bnh.BlockNumber = &bn
return nil
}
}
}
func (bnh *BlockNumberOrHash) Number() (BlockNumber, bool) {
if bnh.BlockNumber != nil {
return *bnh.BlockNumber, true
}
return BlockNumber(0), false
}
func (bnh *BlockNumberOrHash) Hash() (common.Hash, bool) {
if bnh.BlockHash != nil {
return *bnh.BlockHash, true
}
return common.Hash{}, false
}
func BlockNumberOrHashWithNumber(blockNr BlockNumber) BlockNumberOrHash {
return BlockNumberOrHash{
BlockNumber: &blockNr,
BlockHash: nil,
RequireCanonical: false,
}
}
func BlockNumberOrHashWithHash(hash common.Hash, canonical bool) BlockNumberOrHash {
return BlockNumberOrHash{
BlockNumber: nil,
BlockHash: &hash,
RequireCanonical: canonical,
}
}
func trimData(data []byte) string { func trimData(data []byte) string {
input := strings.TrimSpace(string(data)) input := strings.TrimSpace(string(data))
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {

View file

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
) )
@ -43,10 +44,9 @@ func TestBlockNumberJSONUnmarshal(t *testing.T) {
11: {`"pending"`, false, PendingBlockNumber}, 11: {`"pending"`, false, PendingBlockNumber},
12: {`"latest"`, false, LatestBlockNumber}, 12: {`"latest"`, false, LatestBlockNumber},
13: {`"earliest"`, false, EarliestBlockNumber}, 13: {`"earliest"`, false, EarliestBlockNumber},
14: {`"committed"`, false, CommittedBlockNumber}, 14: {`someString`, true, BlockNumber(0)},
15: {`someString`, true, BlockNumber(0)}, 15: {`""`, true, BlockNumber(0)},
16: {`""`, true, BlockNumber(0)}, 16: {``, true, BlockNumber(0)},
17: {``, true, BlockNumber(0)},
} }
for i, test := range tests { for i, test := range tests {
@ -65,3 +65,60 @@ func TestBlockNumberJSONUnmarshal(t *testing.T) {
} }
} }
} }
func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) {
tests := []struct {
input string
mustFail bool
expected BlockNumberOrHash
}{
0: {`"0x"`, true, BlockNumberOrHash{}},
1: {`"0x0"`, false, BlockNumberOrHashWithNumber(0)},
2: {`"0X1"`, false, BlockNumberOrHashWithNumber(1)},
3: {`"0x00"`, true, BlockNumberOrHash{}},
4: {`"0x01"`, true, BlockNumberOrHash{}},
5: {`"0x1"`, false, BlockNumberOrHashWithNumber(1)},
6: {`"0x12"`, false, BlockNumberOrHashWithNumber(18)},
7: {`"0x7fffffffffffffff"`, false, BlockNumberOrHashWithNumber(math.MaxInt64)},
8: {`"0x8000000000000000"`, true, BlockNumberOrHash{}},
9: {"0", true, BlockNumberOrHash{}},
10: {`"ff"`, true, BlockNumberOrHash{}},
11: {`"pending"`, false, BlockNumberOrHashWithNumber(PendingBlockNumber)},
12: {`"latest"`, false, BlockNumberOrHashWithNumber(LatestBlockNumber)},
13: {`"earliest"`, false, BlockNumberOrHashWithNumber(EarliestBlockNumber)},
14: {`someString`, true, BlockNumberOrHash{}},
15: {`""`, true, BlockNumberOrHash{}},
16: {``, true, BlockNumberOrHash{}},
17: {`"0x0000000000000000000000000000000000000000000000000000000000000000"`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
18: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
19: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","requireCanonical":false}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
20: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","requireCanonical":true}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), true)},
21: {`{"blockNumber":"0x1"}`, false, BlockNumberOrHashWithNumber(1)},
22: {`{"blockNumber":"pending"}`, false, BlockNumberOrHashWithNumber(PendingBlockNumber)},
23: {`{"blockNumber":"latest"}`, false, BlockNumberOrHashWithNumber(LatestBlockNumber)},
24: {`{"blockNumber":"earliest"}`, false, BlockNumberOrHashWithNumber(EarliestBlockNumber)},
25: {`{"blockNumber":"0x1", "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`, true, BlockNumberOrHash{}},
}
for i, test := range tests {
var bnh BlockNumberOrHash
err := json.Unmarshal([]byte(test.input), &bnh)
if test.mustFail && err == nil {
t.Errorf("Test %d should fail", i)
continue
}
if !test.mustFail && err != nil {
t.Errorf("Test %d should pass but got err: %v", i, err)
continue
}
hash, hashOk := bnh.Hash()
expectedHash, expectedHashOk := test.expected.Hash()
num, numOk := bnh.Number()
expectedNum, expectedNumOk := test.expected.Number()
if bnh.RequireCanonical != test.expected.RequireCanonical ||
hash != expectedHash || hashOk != expectedHashOk ||
num != expectedNum || numOk != expectedNumOk {
t.Errorf("Test %d got unexpected value, want %v, got %v", i, test.expected, bnh)
}
}
}

View file

@ -1,241 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"bufio"
"context"
crand "crypto/rand"
"encoding/binary"
"encoding/hex"
"math/big"
"math/rand"
"reflect"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
)
var (
subscriptionIDGenMu sync.Mutex
subscriptionIDGen = idGenerator()
)
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
// isContextType returns an indication if the given t is of context.Context or *context.Context type
func isContextType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t == contextType
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// Implements this type the error interface
func isErrorType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Implements(errorType)
}
var subscriptionType = reflect.TypeOf((*Subscription)(nil)).Elem()
// isSubscriptionType returns an indication if the given t is of Subscription or *Subscription type
func isSubscriptionType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t == subscriptionType
}
// isPubSub tests whether the given method has as as first argument a context.Context
// and returns the pair (Subscription, error)
func isPubSub(methodType reflect.Type) bool {
// numIn(0) is the receiver type
if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
return false
}
return isContextType(methodType.In(1)) &&
isSubscriptionType(methodType.Out(0)) &&
isErrorType(methodType.Out(1))
}
// formatName will convert to first character to lower case
func formatName(name string) string {
ret := []rune(name)
if len(ret) > 0 {
ret[0] = unicode.ToLower(ret[0])
}
return string(ret)
}
var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
// Indication if this type should be serialized in hex
func isHexNum(t reflect.Type) bool {
if t == nil {
return false
}
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t == bigIntType
}
// suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria
// for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server
// documentation for a summary of these criteria.
func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) {
callbacks := make(callbacks)
subscriptions := make(subscriptions)
METHODS:
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
mtype := method.Type
mname := formatName(method.Name)
if method.PkgPath != "" { // method must be exported
continue
}
var h callback
h.isSubscribe = isPubSub(mtype)
h.rcvr = rcvr
h.method = method
h.errPos = -1
firstArg := 1
numIn := mtype.NumIn()
if numIn >= 2 && mtype.In(1) == contextType {
h.hasCtx = true
firstArg = 2
}
if h.isSubscribe {
h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type
for i := firstArg; i < numIn; i++ {
argType := mtype.In(i)
if isExportedOrBuiltinType(argType) {
h.argTypes[i-firstArg] = argType
} else {
continue METHODS
}
}
subscriptions[mname] = &h
continue METHODS
}
// determine method arguments, ignore first arg since it's the receiver type
// Arguments must be exported or builtin types
h.argTypes = make([]reflect.Type, numIn-firstArg)
for i := firstArg; i < numIn; i++ {
argType := mtype.In(i)
if !isExportedOrBuiltinType(argType) {
continue METHODS
}
h.argTypes[i-firstArg] = argType
}
// check that all returned values are exported or builtin types
for i := 0; i < mtype.NumOut(); i++ {
if !isExportedOrBuiltinType(mtype.Out(i)) {
continue METHODS
}
}
// when a method returns an error it must be the last returned value
h.errPos = -1
for i := 0; i < mtype.NumOut(); i++ {
if isErrorType(mtype.Out(i)) {
h.errPos = i
break
}
}
if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 {
continue METHODS
}
switch mtype.NumOut() {
case 0, 1, 2:
if mtype.NumOut() == 2 && h.errPos == -1 { // method must one return value and 1 error
continue METHODS
}
callbacks[mname] = &h
}
}
return callbacks, subscriptions
}
// idGenerator helper utility that generates a (pseudo) random sequence of
// bytes that are used to generate identifiers.
func idGenerator() *rand.Rand {
if seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader)); err == nil {
return rand.New(rand.NewSource(seed))
}
return rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
}
// NewID generates a identifier that can be used as an identifier in the RPC interface.
// e.g. filter and subscription identifier.
func NewID() ID {
subscriptionIDGenMu.Lock()
defer subscriptionIDGenMu.Unlock()
id := make([]byte, 16)
for i := 0; i < len(id); i += 7 {
val := subscriptionIDGen.Int63()
for j := 0; i+j < len(id) && j < 7; j++ {
id[i+j] = byte(val)
val >>= 8
}
}
rpcId := hex.EncodeToString(id)
// rpc ID's are RPC quantities, no leading zero's and 0 is 0x0
rpcId = strings.TrimLeft(rpcId, "0")
if rpcId == "" {
rpcId = "0"
}
return ID("0x" + rpcId)
}

View file

@ -17,60 +17,51 @@
package rpc package rpc
import ( import (
"bytes"
"context" "context"
"crypto/tls" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
mapset "github.com/deckarep/golang-set" mapset "github.com/deckarep/golang-set"
"golang.org/x/net/websocket" "github.com/gorilla/websocket"
) )
// websocketJSONCodec is a custom JSON codec with payload size enforcement and const (
// special number parsing. wsReadBuffer = 1024
var websocketJSONCodec = websocket.Codec{ wsWriteBuffer = 1024
// Marshal is the stock JSON marshaller used by the websocket library too. wsPingInterval = 60 * time.Second
Marshal: func(v interface{}) ([]byte, byte, error) { wsPingWriteTimeout = 5 * time.Second
msg, err := json.Marshal(v) wsMessageSizeLimit = 15 * 1024 * 1024
return msg, websocket.TextFrame, err )
},
// Unmarshal is a specialized unmarshaller to properly convert numbers.
Unmarshal: func(msg []byte, payloadType byte, v interface{}) error {
dec := json.NewDecoder(bytes.NewReader(msg))
dec.UseNumber()
return dec.Decode(v) var wsBufferPool = new(sync.Pool)
},
}
// WebsocketHandler returns a handler that serves JSON-RPC to WebSocket connections. // WebsocketHandler returns a handler that serves JSON-RPC to WebSocket connections.
// //
// allowedOrigins should be a comma-separated list of allowed origin URLs. // allowedOrigins should be a comma-separated list of allowed origin URLs.
// To allow connections with any origin, pass "*". // To allow connections with any origin, pass "*".
func (srv *Server) WebsocketHandler(allowedOrigins []string) http.Handler { func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler {
return websocket.Server{ var upgrader = websocket.Upgrader{
Handshake: wsHandshakeValidator(allowedOrigins), ReadBufferSize: wsReadBuffer,
Handler: func(conn *websocket.Conn) { WriteBufferSize: wsWriteBuffer,
// Create a custom encode/decode pair to enforce payload size and number encoding WriteBufferPool: wsBufferPool,
conn.MaxPayloadBytes = maxRequestContentLength CheckOrigin: wsHandshakeValidator(allowedOrigins),
encoder := func(v interface{}) error {
return websocketJSONCodec.Send(conn, v)
}
decoder := func(v interface{}) error {
return websocketJSONCodec.Receive(conn, v)
}
srv.ServeCodec(NewCodec(conn, encoder, decoder), OptionMethodInvocation|OptionSubscriptions)
},
} }
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Debug("WebSocket upgrade failed", "err", err)
return
}
codec := newWebsocketCodec(conn)
s.ServeCodec(codec, 0)
})
} }
// NewWSServer creates a new websocket RPC server around an API provider. // NewWSServer creates a new websocket RPC server around an API provider.
@ -83,7 +74,7 @@ func NewWSServer(allowedOrigins []string, srv *Server) *http.Server {
// wsHandshakeValidator returns a handler that verifies the origin during the // wsHandshakeValidator returns a handler that verifies the origin during the
// websocket upgrade process. When a '*' is specified as an allowed origins all // websocket upgrade process. When a '*' is specified as an allowed origins all
// connections are accepted. // connections are accepted.
func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http.Request) error { func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool {
origins := mapset.NewSet() origins := mapset.NewSet()
allowAllOrigins := false allowAllOrigins := false
@ -92,104 +83,219 @@ func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http
allowAllOrigins = true allowAllOrigins = true
} }
if origin != "" { if origin != "" {
origins.Add(strings.ToLower(origin)) origins.Add(origin)
} }
} }
// allow localhost if no allowedOrigins are specified. // allow localhost if no allowedOrigins are specified.
if len(origins.ToSlice()) == 0 { if len(origins.ToSlice()) == 0 {
origins.Add("http://localhost") origins.Add("http://localhost")
if hostname, err := os.Hostname(); err == nil { if hostname, err := os.Hostname(); err == nil {
origins.Add("http://" + strings.ToLower(hostname)) origins.Add("http://" + hostname)
} }
} }
log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice()))
log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v\n", origins.ToSlice())) f := func(req *http.Request) bool {
// Skip origin verification if no Origin header is present. The origin check
f := func(cfg *websocket.Config, req *http.Request) error { // is supposed to protect against browser based attacks. Browsers always set
origin := strings.ToLower(req.Header.Get("Origin")) // Origin. Non-browser software can put anything in origin and checking it doesn't
if allowAllOrigins || origins.Contains(origin) { // provide additional security.
return nil if _, ok := req.Header["Origin"]; !ok {
return true
} }
log.Warn(fmt.Sprintf("origin '%s' not allowed on WS-RPC interface\n", origin)) // Verify origin against whitelist.
return fmt.Errorf("origin %s not allowed", origin) origin := strings.ToLower(req.Header.Get("Origin"))
if allowAllOrigins || originIsAllowed(origins, origin) {
return true
}
log.Warn("Rejected WebSocket connection", "origin", origin)
return false
} }
return f return f
} }
type wsHandshakeError struct {
err error
status string
}
func (e wsHandshakeError) Error() string {
s := e.err.Error()
if e.status != "" {
s += " (HTTP status " + e.status + ")"
}
return s
}
func originIsAllowed(allowedOrigins mapset.Set, browserOrigin string) bool {
it := allowedOrigins.Iterator()
for origin := range it.C {
if ruleAllowsOrigin(origin.(string), browserOrigin) {
return true
}
}
return false
}
func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool {
var (
allowedScheme, allowedHostname, allowedPort string
browserScheme, browserHostname, browserPort string
err error
)
allowedScheme, allowedHostname, allowedPort, err = parseOriginURL(allowedOrigin)
if err != nil {
log.Warn("Error parsing allowed origin specification", "spec", allowedOrigin, "error", err)
return false
}
browserScheme, browserHostname, browserPort, err = parseOriginURL(browserOrigin)
if err != nil {
log.Warn("Error parsing browser 'Origin' field", "Origin", browserOrigin, "error", err)
return false
}
if allowedScheme != "" && allowedScheme != browserScheme {
return false
}
if allowedHostname != "" && allowedHostname != browserHostname {
return false
}
if allowedPort != "" && allowedPort != browserPort {
return false
}
return true
}
func parseOriginURL(origin string) (string, string, string, error) {
parsedURL, err := url.Parse(strings.ToLower(origin))
if err != nil {
return "", "", "", err
}
var scheme, hostname, port string
if strings.Contains(origin, "://") {
scheme = parsedURL.Scheme
hostname = parsedURL.Hostname()
port = parsedURL.Port()
} else {
scheme = ""
hostname = parsedURL.Scheme
port = parsedURL.Opaque
if hostname == "" {
hostname = origin
}
}
return scheme, hostname, port, nil
}
// DialWebsocketWithDialer creates a new RPC client that communicates with a JSON-RPC server
// that is listening on the given endpoint using the provided dialer.
func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) {
endpoint, header, err := wsClientHeaders(endpoint, origin)
if err != nil {
return nil, err
}
return newClient(ctx, func(ctx context.Context) (ServerCodec, error) {
conn, resp, err := dialer.DialContext(ctx, endpoint, header)
if err != nil {
hErr := wsHandshakeError{err: err}
if resp != nil {
hErr.status = resp.Status
}
return nil, hErr
}
return newWebsocketCodec(conn), nil
})
}
// DialWebsocket creates a new RPC client that communicates with a JSON-RPC server // DialWebsocket creates a new RPC client that communicates with a JSON-RPC server
// that is listening on the given endpoint. // that is listening on the given endpoint.
// //
// The context is used for the initial connection establishment. It does not // The context is used for the initial connection establishment. It does not
// affect subsequent interactions with the client. // affect subsequent interactions with the client.
func DialWebsocket(ctx context.Context, endpoint, origin string) (*Client, error) { func DialWebsocket(ctx context.Context, endpoint, origin string) (*Client, error) {
if origin == "" { dialer := websocket.Dialer{
var err error ReadBufferSize: wsReadBuffer,
if origin, err = os.Hostname(); err != nil { WriteBufferSize: wsWriteBuffer,
return nil, err WriteBufferPool: wsBufferPool,
} }
if strings.HasPrefix(endpoint, "wss") { return DialWebsocketWithDialer(ctx, endpoint, origin, dialer)
origin = "https://" + strings.ToLower(origin) }
} else {
origin = "http://" + strings.ToLower(origin) func wsClientHeaders(endpoint, origin string) (string, http.Header, error) {
endpointURL, err := url.Parse(endpoint)
if err != nil {
return endpoint, nil, err
}
header := make(http.Header)
if origin != "" {
header.Add("origin", origin)
}
if endpointURL.User != nil {
b64auth := base64.StdEncoding.EncodeToString([]byte(endpointURL.User.String()))
header.Add("authorization", "Basic "+b64auth)
endpointURL.User = nil
}
return endpointURL.String(), header, nil
}
type websocketCodec struct {
*jsonCodec
conn *websocket.Conn
wg sync.WaitGroup
pingReset chan struct{}
}
func newWebsocketCodec(conn *websocket.Conn) ServerCodec {
conn.SetReadLimit(wsMessageSizeLimit)
wc := &websocketCodec{
jsonCodec: NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON).(*jsonCodec),
conn: conn,
pingReset: make(chan struct{}, 1),
}
wc.wg.Add(1)
go wc.pingLoop()
return wc
}
func (wc *websocketCodec) close() {
wc.jsonCodec.close()
wc.wg.Wait()
}
func (wc *websocketCodec) writeJSON(ctx context.Context, v interface{}) error {
err := wc.jsonCodec.writeJSON(ctx, v)
if err == nil {
// Notify pingLoop to delay the next idle ping.
select {
case wc.pingReset <- struct{}{}:
default:
} }
} }
config, err := websocket.NewConfig(endpoint, origin) return err
if err != nil {
return nil, err
}
return newClient(ctx, func(ctx context.Context) (net.Conn, error) {
return wsDialContext(ctx, config)
})
} }
func wsDialContext(ctx context.Context, config *websocket.Config) (*websocket.Conn, error) { // pingLoop sends periodic ping frames when the connection is idle.
var conn net.Conn func (wc *websocketCodec) pingLoop() {
var err error var timer = time.NewTimer(wsPingInterval)
switch config.Location.Scheme { defer wc.wg.Done()
case "ws": defer timer.Stop()
conn, err = dialContext(ctx, "tcp", wsDialAddress(config.Location))
case "wss":
dialer := contextDialer(ctx)
conn, err = tls.DialWithDialer(dialer, "tcp", wsDialAddress(config.Location), config.TlsConfig)
default:
err = websocket.ErrBadScheme
}
if err != nil {
return nil, err
}
ws, err := websocket.NewClient(config, conn)
if err != nil {
conn.Close()
return nil, err
}
return ws, err
}
var wsPortMap = map[string]string{"ws": "80", "wss": "443"} for {
select {
func wsDialAddress(location *url.URL) string { case <-wc.closed():
if _, ok := wsPortMap[location.Scheme]; ok { return
if _, _, err := net.SplitHostPort(location.Host); err != nil { case <-wc.pingReset:
return net.JoinHostPort(location.Host, wsPortMap[location.Scheme]) if !timer.Stop() {
<-timer.C
}
timer.Reset(wsPingInterval)
case <-timer.C:
wc.jsonCodec.encMu.Lock()
wc.conn.SetWriteDeadline(time.Now().Add(wsPingWriteTimeout))
wc.conn.WriteMessage(websocket.PingMessage, nil)
wc.jsonCodec.encMu.Unlock()
timer.Reset(wsPingInterval)
} }
} }
return location.Host
}
func dialContext(ctx context.Context, network, addr string) (net.Conn, error) {
d := &net.Dialer{KeepAlive: tcpKeepAliveInterval}
return d.DialContext(ctx, network, addr)
}
func contextDialer(ctx context.Context) *net.Dialer {
dialer := &net.Dialer{Cancel: ctx.Done(), KeepAlive: tcpKeepAliveInterval}
if deadline, ok := ctx.Deadline(); ok {
dialer.Deadline = deadline
} else {
dialer.Deadline = time.Now().Add(defaultDialTimeout)
}
return dialer
} }

288
rpc/websocket_test.go Normal file
View file

@ -0,0 +1,288 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"context"
"net"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
)
func TestWebsocketClientHeaders(t *testing.T) {
t.Parallel()
endpoint, header, err := wsClientHeaders("wss://testuser:test-PASS_01@example.com:1234", "https://example.com")
if err != nil {
t.Fatalf("wsGetConfig failed: %s", err)
}
if endpoint != "wss://example.com:1234" {
t.Fatal("User should have been stripped from the URL")
}
if header.Get("authorization") != "Basic dGVzdHVzZXI6dGVzdC1QQVNTXzAx" {
t.Fatal("Basic auth header is incorrect")
}
if header.Get("origin") != "https://example.com" {
t.Fatal("Origin not set")
}
}
// This test checks that the server rejects connections from disallowed origins.
func TestWebsocketOriginCheck(t *testing.T) {
t.Parallel()
var (
srv = newTestServer()
httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"http://example.com"}))
wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
)
defer srv.Stop()
defer httpsrv.Close()
client, err := DialWebsocket(context.Background(), wsURL, "http://ekzample.com")
if err == nil {
client.Close()
t.Fatal("no error for wrong origin")
}
wantErr := wsHandshakeError{websocket.ErrBadHandshake, "403 Forbidden"}
if !reflect.DeepEqual(err, wantErr) {
t.Fatalf("wrong error for wrong origin: %q", err)
}
// Connections without origin header should work.
client, err = DialWebsocket(context.Background(), wsURL, "")
if err != nil {
t.Fatal("error for empty origin")
}
client.Close()
}
// This test checks whether calls exceeding the request size limit are rejected.
func TestWebsocketLargeCall(t *testing.T) {
t.Parallel()
var (
srv = newTestServer()
httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"*"}))
wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
)
defer srv.Stop()
defer httpsrv.Close()
client, err := DialWebsocket(context.Background(), wsURL, "")
if err != nil {
t.Fatalf("can't dial: %v", err)
}
defer client.Close()
// This call sends slightly less than the limit and should work.
var result echoResult
arg := strings.Repeat("x", maxRequestContentLength-200)
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
t.Fatalf("valid call didn't work: %v", err)
}
if result.String != arg {
t.Fatal("wrong string echoed")
}
// This call sends twice the allowed size and shouldn't work.
arg = strings.Repeat("x", maxRequestContentLength*2)
err = client.Call(&result, "test_echo", arg)
if err == nil {
t.Fatal("no error for too large call")
}
}
// This test checks that client handles WebSocket ping frames correctly.
func TestClientWebsocketPing(t *testing.T) {
t.Parallel()
var (
sendPing = make(chan struct{})
server = wsPingTestServer(t, sendPing)
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
)
defer cancel()
defer server.Shutdown(ctx)
client, err := DialContext(ctx, "ws://"+server.Addr)
if err != nil {
t.Fatalf("client dial error: %v", err)
}
resultChan := make(chan int)
sub, err := client.EthSubscribe(ctx, resultChan, "foo")
if err != nil {
t.Fatalf("client subscribe error: %v", err)
}
// Wait for the context's deadline to be reached before proceeding.
// This is important for reproducing https://github.com/ethereum/go-ethereum/issues/19798
<-ctx.Done()
close(sendPing)
// Wait for the subscription result.
timeout := time.NewTimer(5 * time.Second)
defer timeout.Stop()
for {
select {
case err := <-sub.Err():
t.Error("client subscription error:", err)
case result := <-resultChan:
t.Log("client got result:", result)
return
case <-timeout.C:
t.Error("didn't get any result within the test timeout")
return
}
}
}
// This checks that the websocket transport can deal with large messages.
func TestClientWebsocketLargeMessage(t *testing.T) {
var (
srv = NewServer()
httpsrv = httptest.NewServer(srv.WebsocketHandler(nil))
wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
)
defer srv.Stop()
defer httpsrv.Close()
respLength := wsMessageSizeLimit - 50
srv.RegisterName("test", largeRespService{respLength})
c, err := DialWebsocket(context.Background(), wsURL, "")
if err != nil {
t.Fatal(err)
}
var r string
if err := c.Call(&r, "test_largeResp"); err != nil {
t.Fatal("call failed:", err)
}
if len(r) != respLength {
t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
}
}
// wsPingTestServer runs a WebSocket server which accepts a single subscription request.
// When a value arrives on sendPing, the server sends a ping frame, waits for a matching
// pong and finally delivers a single subscription result.
func wsPingTestServer(t *testing.T, sendPing <-chan struct{}) *http.Server {
var srv http.Server
shutdown := make(chan struct{})
srv.RegisterOnShutdown(func() {
close(shutdown)
})
srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Upgrade to WebSocket.
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
t.Errorf("server WS upgrade error: %v", err)
return
}
defer conn.Close()
// Handle the connection.
wsPingTestHandler(t, conn, shutdown, sendPing)
})
// Start the server.
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal("can't listen:", err)
}
srv.Addr = listener.Addr().String()
go srv.Serve(listener)
return &srv
}
func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <-chan struct{}) {
// Canned responses for the eth_subscribe call in TestClientWebsocketPing.
const (
subResp = `{"jsonrpc":"2.0","id":1,"result":"0x00"}`
subNotify = `{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":1}}`
)
// Handle subscribe request.
if _, _, err := conn.ReadMessage(); err != nil {
t.Errorf("server read error: %v", err)
return
}
if err := conn.WriteMessage(websocket.TextMessage, []byte(subResp)); err != nil {
t.Errorf("server write error: %v", err)
return
}
// Read from the connection to process control messages.
var pongCh = make(chan string)
conn.SetPongHandler(func(d string) error {
t.Logf("server got pong: %q", d)
pongCh <- d
return nil
})
go func() {
for {
typ, msg, err := conn.ReadMessage()
if err != nil {
return
}
t.Logf("server got message (%d): %q", typ, msg)
}
}()
// Write messages.
var (
wantPong string
timer = time.NewTimer(0)
)
defer timer.Stop()
<-timer.C
for {
select {
case _, open := <-sendPing:
if !open {
sendPing = nil
}
t.Logf("server sending ping")
conn.WriteMessage(websocket.PingMessage, []byte("ping"))
wantPong = "ping"
case data := <-pongCh:
if wantPong == "" {
t.Errorf("unexpected pong")
} else if data != wantPong {
t.Errorf("got pong with wrong data %q", data)
}
wantPong = ""
timer.Reset(200 * time.Millisecond)
case <-timer.C:
t.Logf("server sending response")
conn.WriteMessage(websocket.TextMessage, []byte(subNotify))
case <-shutdown:
conn.Close()
return
}
}
}