diff --git a/pkg/network/rpc.go b/pkg/network/rpc.go new file mode 100644 index 000000000..c1156fa65 --- /dev/null +++ b/pkg/network/rpc.go @@ -0,0 +1,129 @@ +package network + +import ( + "encoding/json" + "fmt" + "net/http" +) + +const ( + rpcPortMainNet = 20332 + rpcPortTestNet = 10332 + rpcVersion = "2.0" + + // error response messages + methodNotFound = "Method not found" + parseError = "Parse error" +) + +// Each NEO node has a set of optional APIs for accessing blockchain +// data and making things easier for development of blockchain apps. +// APIs are provided via JSON-RPC , comm at bottom layer is with http/https protocol. + +// listenHTTP creates an ingress bridge from the outside world to the passed +// server, by installing handlers for all the necessary RPCs to the passed mux. +func listenHTTP(s *Server, port int) { + api := &API{s} + p := fmt.Sprintf(":%d", port) + s.logger.Printf("serving RPC on %d", port) + s.logger.Printf("%s", http.ListenAndServe(p, api)) +} + +// API serves JSON-RPC. +type API struct { + s *Server +} + +func (s *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Official nodes respond a parse error if the method is not POST. + // Instead of returning a decent response for this, let's do the same. + if r.Method != "POST" { + writeError(w, 0, 0, parseError) + } + + var req Request + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(w, 0, 0, parseError) + return + } + defer r.Body.Close() + + if req.Version != rpcVersion { + writeJSON(w, http.StatusBadRequest, nil) + return + } + + switch req.Method { + case "getconnectioncount": + if err := s.getConnectionCount(w, &req); err != nil { + writeError(w, 0, 0, parseError) + return + } + case "getblockcount": + case "getbestblockhash": + default: + writeError(w, 0, 0, methodNotFound) + } +} + +// This is an Example on how we could handle incomming RPC requests. +func (s *API) getConnectionCount(w http.ResponseWriter, req *Request) error { + count := s.s.peerCount() + + resp := ConnectionCountResponse{ + Version: rpcVersion, + Result: count, + ID: 1, + } + + return writeJSON(w, http.StatusOK, resp) +} + +// writeError returns a JSON error with given parameters. All error HTTP +// status codes are 200. According to the official API. +func writeError(w http.ResponseWriter, id, code int, msg string) error { + resp := RequestError{ + Version: rpcVersion, + ID: id, + Error: Error{ + Code: code, + Message: msg, + }, + } + + return writeJSON(w, http.StatusOK, resp) +} + +func writeJSON(w http.ResponseWriter, status int, v interface{}) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + return json.NewEncoder(w).Encode(v) +} + +// Request is an object received through JSON-RPC from the client. +type Request struct { + Version string `json:"jsonrpc"` + Method string `json:"method"` + Params []string `json:"params"` + ID int `json:"id"` +} + +// ConnectionCountResponse .. +type ConnectionCountResponse struct { + Version string `json:"jsonrpc"` + Result int `json:"result"` + ID int `json:"id"` +} + +// RequestError .. +type RequestError struct { + Version string `json:"jsonrpc"` + ID int `json:"id"` + Error Error `json:"error"` +} + +// Error holds information about an RCP error. +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +}