forked from cerc-io/plugeth
rpc: various fixes/enhancements
rpc: be less restrictive on the request id rpc: improved documentation console: upgrade web3.js to version 0.16.0 rpc: cache http connections rpc: rename wsDomains parameter to wsOrigins
This commit is contained in:
parent
7e02105672
commit
aa9fff3e68
@ -237,7 +237,7 @@ func (js *jsre) apiBindings() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load only supported API's in javascript runtime
|
// load only supported API's in javascript runtime
|
||||||
shortcuts := "var eth = web3.eth; "
|
shortcuts := "var eth = web3.eth; var personal = web3.personal; "
|
||||||
for _, apiName := range apiNames {
|
for _, apiName := range apiNames {
|
||||||
if apiName == "web3" || apiName == "rpc" {
|
if apiName == "web3" || apiName == "rpc" {
|
||||||
continue // manually mapped or ignore
|
continue // manually mapped or ignore
|
||||||
|
@ -326,7 +326,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
|||||||
utils.WSListenAddrFlag,
|
utils.WSListenAddrFlag,
|
||||||
utils.WSPortFlag,
|
utils.WSPortFlag,
|
||||||
utils.WSApiFlag,
|
utils.WSApiFlag,
|
||||||
utils.WSAllowedDomainsFlag,
|
utils.WSAllowedOriginsFlag,
|
||||||
utils.IPCDisabledFlag,
|
utils.IPCDisabledFlag,
|
||||||
utils.IPCApiFlag,
|
utils.IPCApiFlag,
|
||||||
utils.IPCPathFlag,
|
utils.IPCPathFlag,
|
||||||
|
@ -94,7 +94,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||||||
utils.WSListenAddrFlag,
|
utils.WSListenAddrFlag,
|
||||||
utils.WSPortFlag,
|
utils.WSPortFlag,
|
||||||
utils.WSApiFlag,
|
utils.WSApiFlag,
|
||||||
utils.WSAllowedDomainsFlag,
|
utils.WSAllowedOriginsFlag,
|
||||||
utils.IPCDisabledFlag,
|
utils.IPCDisabledFlag,
|
||||||
utils.IPCApiFlag,
|
utils.IPCApiFlag,
|
||||||
utils.IPCPathFlag,
|
utils.IPCPathFlag,
|
||||||
|
@ -287,9 +287,9 @@ var (
|
|||||||
Usage: "API's offered over the WS-RPC interface",
|
Usage: "API's offered over the WS-RPC interface",
|
||||||
Value: rpc.DefaultHTTPApis,
|
Value: rpc.DefaultHTTPApis,
|
||||||
}
|
}
|
||||||
WSAllowedDomainsFlag = cli.StringFlag{
|
WSAllowedOriginsFlag = cli.StringFlag{
|
||||||
Name: "wsdomains",
|
Name: "wsorigins",
|
||||||
Usage: "Domains from which to accept websockets requests (can be spoofed)",
|
Usage: "Origins from which to accept websockets requests",
|
||||||
Value: "",
|
Value: "",
|
||||||
}
|
}
|
||||||
ExecFlag = cli.StringFlag{
|
ExecFlag = cli.StringFlag{
|
||||||
@ -655,7 +655,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
|||||||
HTTPModules: strings.Split(ctx.GlobalString(RPCApiFlag.Name), ","),
|
HTTPModules: strings.Split(ctx.GlobalString(RPCApiFlag.Name), ","),
|
||||||
WSHost: MakeWSRpcHost(ctx),
|
WSHost: MakeWSRpcHost(ctx),
|
||||||
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
||||||
WSDomains: ctx.GlobalString(WSAllowedDomainsFlag.Name),
|
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
|
||||||
WSModules: strings.Split(ctx.GlobalString(WSApiFlag.Name), ","),
|
WSModules: strings.Split(ctx.GlobalString(WSApiFlag.Name), ","),
|
||||||
}
|
}
|
||||||
// Configure the Ethereum service
|
// Configure the Ethereum service
|
||||||
|
@ -37,7 +37,8 @@ func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth {
|
|||||||
return &Jeth{re, client}
|
return &Jeth{re, client}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) {
|
// err returns an error object for the given error code and message.
|
||||||
|
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
|
||||||
m := rpc.JSONErrResponse{
|
m := rpc.JSONErrResponse{
|
||||||
Version: "2.0",
|
Version: "2.0",
|
||||||
Id: id,
|
Id: id,
|
||||||
@ -56,44 +57,50 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre
|
// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre.
|
||||||
|
// It will need the public address for the account to unlock as first argument.
|
||||||
|
// The second argument is an optional string with the password. If not given the user is prompted for the password.
|
||||||
|
// The third argument is an optional integer which specifies for how long the account will be unlocked (in seconds).
|
||||||
func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
|
func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
|
||||||
var account, passwd string
|
var account, passwd otto.Value
|
||||||
timeout := int64(300)
|
duration := otto.NullValue()
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if len(call.ArgumentList) == 0 {
|
if !call.Argument(0).IsString() {
|
||||||
fmt.Println("expected address of account to unlock")
|
fmt.Println("first argument must be the account to unlock")
|
||||||
return otto.FalseValue()
|
return otto.FalseValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(call.ArgumentList) >= 1 {
|
account = call.Argument(0)
|
||||||
if accountExport, err := call.Argument(0).Export(); err == nil {
|
|
||||||
if account, ok = accountExport.(string); ok {
|
// if password is not given or as null value -> ask user for password
|
||||||
if len(call.ArgumentList) == 1 {
|
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
||||||
fmt.Printf("Unlock account %s\n", account)
|
fmt.Printf("Unlock account %s\n", account)
|
||||||
passwd, err = PromptPassword("Passphrase: ", true)
|
if password, err := PromptPassword("Passphrase: ", true); err == nil {
|
||||||
if err != nil {
|
passwd, _ = otto.ToValue(password)
|
||||||
return otto.FalseValue()
|
} else {
|
||||||
}
|
throwJSExeception(err.Error())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if len(call.ArgumentList) >= 2 {
|
if !call.Argument(1).IsString() {
|
||||||
if passwdExport, err := call.Argument(1).Export(); err == nil {
|
throwJSExeception("password must be a string")
|
||||||
passwd, _ = passwdExport.(string)
|
|
||||||
}
|
}
|
||||||
|
passwd = call.Argument(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(call.ArgumentList) >= 3 {
|
// third argument is the duration how long the account must be unlocked.
|
||||||
if timeoutExport, err := call.Argument(2).Export(); err == nil {
|
// verify that its a number.
|
||||||
timeout, _ = timeoutExport.(int64)
|
if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() {
|
||||||
|
if !call.Argument(2).IsNumber() {
|
||||||
|
throwJSExeception("unlock duration must be a number")
|
||||||
}
|
}
|
||||||
|
duration = call.Argument(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, timeout); err == nil {
|
// jeth.unlockAccount will send the request to the backend.
|
||||||
|
if val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration); err == nil {
|
||||||
return val
|
return val
|
||||||
|
} else {
|
||||||
|
throwJSExeception(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return otto.FalseValue()
|
return otto.FalseValue()
|
||||||
@ -134,19 +141,31 @@ func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
|||||||
return otto.FalseValue()
|
return otto.FalseValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send will serialize the first argument, send it to the node and returns the response.
|
||||||
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
reqif, err := call.Argument(0).Export()
|
// verify we got a batch request (array) or a single request (object)
|
||||||
if err != nil {
|
ro := call.Argument(0).Object()
|
||||||
return self.err(call, -32700, err.Error(), nil)
|
if ro == nil || (ro.Class() != "Array" && ro.Class() != "Object") {
|
||||||
|
throwJSExeception("Internal Error: request must be an object or array")
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonreq, err := json.Marshal(reqif)
|
// convert otto vm arguments to go values by JSON serialising and parsing.
|
||||||
|
data, err := call.Otto.Call("JSON.stringify", nil, ro)
|
||||||
|
if err != nil {
|
||||||
|
throwJSExeception(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonreq, _ := data.ToString()
|
||||||
|
|
||||||
|
// parse arguments to JSON rpc requests, either to an array (batch) or to a single request.
|
||||||
var reqs []rpc.JSONRequest
|
var reqs []rpc.JSONRequest
|
||||||
batch := true
|
batch := true
|
||||||
err = json.Unmarshal(jsonreq, &reqs)
|
if err = json.Unmarshal([]byte(jsonreq), &reqs); err != nil {
|
||||||
if err != nil {
|
// single request?
|
||||||
reqs = make([]rpc.JSONRequest, 1)
|
reqs = make([]rpc.JSONRequest, 1)
|
||||||
err = json.Unmarshal(jsonreq, &reqs[0])
|
if err = json.Unmarshal([]byte(jsonreq), &reqs[0]); err != nil {
|
||||||
|
throwJSExeception("invalid request")
|
||||||
|
}
|
||||||
batch = false
|
batch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,47 +173,50 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
|||||||
call.Otto.Run("var ret_response = new Array(response_len);")
|
call.Otto.Run("var ret_response = new Array(response_len);")
|
||||||
|
|
||||||
for i, req := range reqs {
|
for i, req := range reqs {
|
||||||
err := self.client.Send(&req)
|
if err := self.client.Send(&req); err != nil {
|
||||||
if err != nil {
|
|
||||||
return self.err(call, -32603, err.Error(), req.Id)
|
return self.err(call, -32603, err.Error(), req.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]interface{})
|
||||||
err = self.client.Recv(&result)
|
if err = self.client.Recv(&result); err != nil {
|
||||||
if err != nil {
|
|
||||||
return self.err(call, -32603, err.Error(), req.Id)
|
return self.err(call, -32603, err.Error(), req.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isSuccessResponse := result["result"]
|
|
||||||
_, isErrorResponse := result["error"]
|
|
||||||
if !isSuccessResponse && !isErrorResponse {
|
|
||||||
return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
|
|
||||||
}
|
|
||||||
|
|
||||||
id, _ := result["id"]
|
id, _ := result["id"]
|
||||||
call.Otto.Set("ret_id", id)
|
|
||||||
|
|
||||||
jsonver, _ := result["jsonrpc"]
|
jsonver, _ := result["jsonrpc"]
|
||||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
|
||||||
|
|
||||||
var payload []byte
|
call.Otto.Set("ret_id", id)
|
||||||
if isSuccessResponse {
|
call.Otto.Set("ret_jsonrpc", jsonver)
|
||||||
payload, _ = json.Marshal(result["result"])
|
|
||||||
} else if isErrorResponse {
|
|
||||||
payload, _ = json.Marshal(result["error"])
|
|
||||||
}
|
|
||||||
call.Otto.Set("ret_result", string(payload))
|
|
||||||
call.Otto.Set("response_idx", i)
|
call.Otto.Set("response_idx", i)
|
||||||
|
|
||||||
response, err = call.Otto.Run(`
|
// call was successful
|
||||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
if res, ok := result["result"]; ok {
|
||||||
`)
|
payload, _ := json.Marshal(res)
|
||||||
|
call.Otto.Set("ret_result", string(payload))
|
||||||
|
response, err = call.Otto.Run(`
|
||||||
|
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
||||||
|
`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// request returned an error
|
||||||
|
if res, ok := result["error"]; ok {
|
||||||
|
payload, _ := json.Marshal(res)
|
||||||
|
call.Otto.Set("ret_result", string(payload))
|
||||||
|
response, err = call.Otto.Run(`
|
||||||
|
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) };
|
||||||
|
`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !batch {
|
if !batch {
|
||||||
call.Otto.Run("ret_response = ret_response[0];")
|
call.Otto.Run("ret_response = ret_response[0];")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if a callback was given execute it.
|
||||||
if call.Argument(1).IsObject() {
|
if call.Argument(1).IsObject() {
|
||||||
call.Otto.Set("callback", call.Argument(1))
|
call.Otto.Set("callback", call.Argument(1))
|
||||||
call.Otto.Run(`
|
call.Otto.Run(`
|
||||||
@ -207,53 +229,6 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// handleRequest will handle user agent requests by interacting with the user and sending
|
|
||||||
// the user response back to the geth service
|
|
||||||
func (self *Jeth) handleRequest(req *shared.Request) bool {
|
|
||||||
var err error
|
|
||||||
var args []interface{}
|
|
||||||
if err = json.Unmarshal(req.Params, &args); err != nil {
|
|
||||||
glog.V(logger.Info).Infof("Unable to parse agent request - %v\n", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch req.Method {
|
|
||||||
case useragent.AskPasswordMethod:
|
|
||||||
return self.askPassword(req.Id, req.Jsonrpc, args)
|
|
||||||
case useragent.ConfirmTransactionMethod:
|
|
||||||
return self.confirmTransaction(req.Id, req.Jsonrpc, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// askPassword will ask the user to supply the password for a given account
|
|
||||||
func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{}) bool {
|
|
||||||
var err error
|
|
||||||
var passwd string
|
|
||||||
if len(args) >= 1 {
|
|
||||||
if account, ok := args[0].(string); ok {
|
|
||||||
fmt.Printf("Unlock account %s\n", account)
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
passwd, err = PromptPassword("Passphrase: ", true)
|
|
||||||
|
|
||||||
if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil {
|
|
||||||
glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []interface{}) bool {
|
|
||||||
// Accept all tx which are send from this console
|
|
||||||
return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error.
|
// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error.
|
||||||
func throwJSExeception(msg interface{}) otto.Value {
|
func throwJSExeception(msg interface{}) otto.Value {
|
||||||
p, _ := otto.ToValue(msg)
|
p, _ := otto.ToValue(msg)
|
||||||
|
60
eth/api.go
60
eth/api.go
@ -25,6 +25,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -96,8 +97,8 @@ type PublicEthereumAPI struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicEthereumAPI creates a new Ethereum protocol API.
|
// NewPublicEthereumAPI creates a new Ethereum protocol API.
|
||||||
func NewPublicEthereumAPI(e *Ethereum) *PublicEthereumAPI {
|
func NewPublicEthereumAPI(e *Ethereum, gpo *GasPriceOracle) *PublicEthereumAPI {
|
||||||
return &PublicEthereumAPI{e, NewGasPriceOracle(e)}
|
return &PublicEthereumAPI{e, gpo}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GasPrice returns a suggestion for a gas price.
|
// GasPrice returns a suggestion for a gas price.
|
||||||
@ -108,11 +109,7 @@ func (s *PublicEthereumAPI) GasPrice() *big.Int {
|
|||||||
// GetCompilers returns the collection of available smart contract compilers
|
// GetCompilers returns the collection of available smart contract compilers
|
||||||
func (s *PublicEthereumAPI) GetCompilers() ([]string, error) {
|
func (s *PublicEthereumAPI) GetCompilers() ([]string, error) {
|
||||||
solc, err := s.e.Solc()
|
solc, err := s.e.Solc()
|
||||||
if err != nil {
|
if err != nil && solc != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if solc != nil {
|
|
||||||
return []string{"Solidity"}, nil
|
return []string{"Solidity"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,9 +237,15 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
|||||||
return &PrivateMinerAPI{e: e}
|
return &PrivateMinerAPI{e: e}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the miner with the given number of threads
|
// Start the miner with the given number of threads. If threads is nil the number of
|
||||||
func (s *PrivateMinerAPI) Start(threads rpc.HexNumber) (bool, error) {
|
// workers started is equal to the number of logical CPU's that are usable by this process.
|
||||||
|
func (s *PrivateMinerAPI) Start(threads *rpc.HexNumber) (bool, error) {
|
||||||
s.e.StartAutoDAG()
|
s.e.StartAutoDAG()
|
||||||
|
|
||||||
|
if threads == nil {
|
||||||
|
threads = rpc.NewHexNumber(runtime.NumCPU())
|
||||||
|
}
|
||||||
|
|
||||||
err := s.e.StartMining(threads.Int(), "")
|
err := s.e.StartMining(threads.Int(), "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -265,7 +268,7 @@ func (s *PrivateMinerAPI) SetExtra(extra string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetGasPrice sets the minimum accepted gas price for the miner.
|
// SetGasPrice sets the minimum accepted gas price for the miner.
|
||||||
func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.Number) bool {
|
func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.HexNumber) bool {
|
||||||
s.e.Miner().SetGasPrice(gasPrice.BigInt())
|
s.e.Miner().SetGasPrice(gasPrice.BigInt())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -440,10 +443,15 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
|
|||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockAccount will unlock the account associated with the given address with the given password for duration seconds.
|
// UnlockAccount will unlock the account associated with the given address with
|
||||||
// It returns an indication if the action was successful.
|
// the given password for duration seconds. If duration is nil it will use a
|
||||||
func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration int) bool {
|
// default of 300 seconds. It returns an indication if the account was unlocked.
|
||||||
if err := s.am.TimedUnlock(addr, password, time.Duration(duration)*time.Second); err != nil {
|
func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *rpc.HexNumber) bool {
|
||||||
|
if duration == nil {
|
||||||
|
duration = rpc.NewHexNumber(300)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.am.TimedUnlock(addr, password, time.Duration(duration.Int())*time.Second); err != nil {
|
||||||
glog.V(logger.Info).Infof("%v\n", err)
|
glog.V(logger.Info).Infof("%v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -458,7 +466,7 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
|
|||||||
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
||||||
// It offers only methods that operate on public data that is freely available to anyone.
|
// It offers only methods that operate on public data that is freely available to anyone.
|
||||||
type PublicBlockChainAPI struct {
|
type PublicBlockChainAPI struct {
|
||||||
config *core.ChainConfig
|
config *core.ChainConfig
|
||||||
bc *core.BlockChain
|
bc *core.BlockChain
|
||||||
chainDb ethdb.Database
|
chainDb ethdb.Database
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
@ -466,10 +474,11 @@ type PublicBlockChainAPI struct {
|
|||||||
newBlockSubscriptions map[string]func(core.ChainEvent) error // callbacks for new block subscriptions
|
newBlockSubscriptions map[string]func(core.ChainEvent) error // callbacks for new block subscriptions
|
||||||
am *accounts.Manager
|
am *accounts.Manager
|
||||||
miner *miner.Miner
|
miner *miner.Miner
|
||||||
|
gpo *GasPriceOracle
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicBlockChainAPI creates a new Etheruem blockchain API.
|
// NewPublicBlockChainAPI creates a new Etheruem blockchain API.
|
||||||
func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI {
|
func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, gpo *GasPriceOracle, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI {
|
||||||
api := &PublicBlockChainAPI{
|
api := &PublicBlockChainAPI{
|
||||||
config: config,
|
config: config,
|
||||||
bc: bc,
|
bc: bc,
|
||||||
@ -478,6 +487,7 @@ func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *mi
|
|||||||
eventMux: eventMux,
|
eventMux: eventMux,
|
||||||
am: am,
|
am: am,
|
||||||
newBlockSubscriptions: make(map[string]func(core.ChainEvent) error),
|
newBlockSubscriptions: make(map[string]func(core.ChainEvent) error),
|
||||||
|
gpo: gpo,
|
||||||
}
|
}
|
||||||
|
|
||||||
go api.subscriptionLoop()
|
go api.subscriptionLoop()
|
||||||
@ -674,8 +684,8 @@ func (m callmsg) Data() []byte { return m.data }
|
|||||||
type CallArgs struct {
|
type CallArgs struct {
|
||||||
From common.Address `json:"from"`
|
From common.Address `json:"from"`
|
||||||
To *common.Address `json:"to"`
|
To *common.Address `json:"to"`
|
||||||
Gas rpc.HexNumber `json:"gas"`
|
Gas *rpc.HexNumber `json:"gas"`
|
||||||
GasPrice rpc.HexNumber `json:"gasPrice"`
|
GasPrice *rpc.HexNumber `json:"gasPrice"`
|
||||||
Value rpc.HexNumber `json:"value"`
|
Value rpc.HexNumber `json:"value"`
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
}
|
}
|
||||||
@ -711,11 +721,11 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
|
|||||||
value: args.Value.BigInt(),
|
value: args.Value.BigInt(),
|
||||||
data: common.FromHex(args.Data),
|
data: common.FromHex(args.Data),
|
||||||
}
|
}
|
||||||
if msg.gas.Cmp(common.Big0) == 0 {
|
if msg.gas == nil {
|
||||||
msg.gas = big.NewInt(50000000)
|
msg.gas = big.NewInt(50000000)
|
||||||
}
|
}
|
||||||
if msg.gasPrice.Cmp(common.Big0) == 0 {
|
if msg.gasPrice == nil {
|
||||||
msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon)
|
msg.gasPrice = s.gpo.SuggestPrice()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the call and return
|
// Execute the call and return
|
||||||
@ -882,10 +892,10 @@ type PublicTransactionPoolAPI struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
|
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
|
||||||
func NewPublicTransactionPoolAPI(e *Ethereum) *PublicTransactionPoolAPI {
|
func NewPublicTransactionPoolAPI(e *Ethereum, gpo *GasPriceOracle) *PublicTransactionPoolAPI {
|
||||||
api := &PublicTransactionPoolAPI{
|
api := &PublicTransactionPoolAPI{
|
||||||
eventMux: e.EventMux(),
|
eventMux: e.EventMux(),
|
||||||
gpo: NewGasPriceOracle(e),
|
gpo: gpo,
|
||||||
chainDb: e.ChainDb(),
|
chainDb: e.ChainDb(),
|
||||||
bc: e.BlockChain(),
|
bc: e.BlockChain(),
|
||||||
am: e.AccountManager(),
|
am: e.AccountManager(),
|
||||||
@ -1306,7 +1316,7 @@ func newTx(t *types.Transaction) *Tx {
|
|||||||
// SignTransaction will sign the given transaction with the from account.
|
// SignTransaction will sign the given transaction with the from account.
|
||||||
// The node needs to have the private key of the account corresponding with
|
// The node needs to have the private key of the account corresponding with
|
||||||
// the given from address and it needs to be unlocked.
|
// the given from address and it needs to be unlocked.
|
||||||
func (s *PublicTransactionPoolAPI) SignTransaction(args *SignTransactionArgs) (*SignTransactionResult, error) {
|
func (s *PublicTransactionPoolAPI) SignTransaction(args SignTransactionArgs) (*SignTransactionResult, error) {
|
||||||
if args.Gas == nil {
|
if args.Gas == nil {
|
||||||
args.Gas = rpc.NewHexNumber(defaultGas)
|
args.Gas = rpc.NewHexNumber(defaultGas)
|
||||||
}
|
}
|
||||||
@ -1397,7 +1407,7 @@ func (s *PublicTransactionPoolAPI) NewPendingTransactions(ctx context.Context) (
|
|||||||
|
|
||||||
// Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the
|
// Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the
|
||||||
// pool and reinsert it with the new gas price and limit.
|
// pool and reinsert it with the new gas price and limit.
|
||||||
func (s *PublicTransactionPoolAPI) Resend(tx *Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) {
|
func (s *PublicTransactionPoolAPI) Resend(tx Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) {
|
||||||
|
|
||||||
pending := s.txPool.GetTransactions()
|
pending := s.txPool.GetTransactions()
|
||||||
for _, p := range pending {
|
for _, p := range pending {
|
||||||
|
@ -272,11 +272,14 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||||||
// APIs returns the collection of RPC services the ethereum package offers.
|
// APIs returns the collection of RPC services the ethereum package offers.
|
||||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||||
func (s *Ethereum) APIs() []rpc.API {
|
func (s *Ethereum) APIs() []rpc.API {
|
||||||
|
// share gas price oracle in API's
|
||||||
|
gpo := NewGasPriceOracle(s)
|
||||||
|
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPublicEthereumAPI(s),
|
Service: NewPublicEthereumAPI(s, gpo),
|
||||||
Public: true,
|
Public: true,
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
@ -291,12 +294,12 @@ func (s *Ethereum) APIs() []rpc.API {
|
|||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPublicBlockChainAPI(s.chainConfig, s.BlockChain(), s.Miner(), s.ChainDb(), s.EventMux(), s.AccountManager()),
|
Service: NewPublicBlockChainAPI(s.chainConfig, s.BlockChain(), s.Miner(), s.ChainDb(), gpo, s.EventMux(), s.AccountManager()),
|
||||||
Public: true,
|
Public: true,
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPublicTransactionPoolAPI(s),
|
Service: NewPublicTransactionPoolAPI(s, gpo),
|
||||||
Public: true,
|
Public: true,
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
|
3116
jsre/ethereum_js.go
3116
jsre/ethereum_js.go
File diff suppressed because it is too large
Load Diff
47
node/api.go
47
node/api.go
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,14 +59,33 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartRPC starts the HTTP RPC API server.
|
// StartRPC starts the HTTP RPC API server.
|
||||||
func (api *PrivateAdminAPI) StartRPC(host string, port int, cors string, apis string) (bool, error) {
|
func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *string, apis *string) (bool, error) {
|
||||||
api.node.lock.Lock()
|
api.node.lock.Lock()
|
||||||
defer api.node.lock.Unlock()
|
defer api.node.lock.Unlock()
|
||||||
|
|
||||||
if api.node.httpHandler != nil {
|
if api.node.httpHandler != nil {
|
||||||
return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
|
return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
|
||||||
}
|
}
|
||||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", host, port), api.node.rpcAPIs, strings.Split(apis, ","), cors); err != nil {
|
|
||||||
|
if host == nil {
|
||||||
|
host = &api.node.httpHost
|
||||||
|
}
|
||||||
|
if port == nil {
|
||||||
|
port = rpc.NewHexNumber(api.node.httpPort)
|
||||||
|
}
|
||||||
|
if cors == nil {
|
||||||
|
cors = &api.node.httpCors
|
||||||
|
}
|
||||||
|
|
||||||
|
modules := api.node.httpWhitelist
|
||||||
|
if apis != nil {
|
||||||
|
modules = nil
|
||||||
|
for _, m := range strings.Split(*apis, ",") {
|
||||||
|
modules = append(modules, strings.TrimSpace(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *cors); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -84,14 +104,33 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartWS starts the websocket RPC API server.
|
// StartWS starts the websocket RPC API server.
|
||||||
func (api *PrivateAdminAPI) StartWS(host string, port int, cors string, apis string) (bool, error) {
|
func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOrigins *string, apis *string) (bool, error) {
|
||||||
api.node.lock.Lock()
|
api.node.lock.Lock()
|
||||||
defer api.node.lock.Unlock()
|
defer api.node.lock.Unlock()
|
||||||
|
|
||||||
if api.node.wsHandler != nil {
|
if api.node.wsHandler != nil {
|
||||||
return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
|
return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
|
||||||
}
|
}
|
||||||
if err := api.node.startWS(fmt.Sprintf("%s:%d", host, port), api.node.rpcAPIs, strings.Split(apis, ","), cors); err != nil {
|
|
||||||
|
if host == nil {
|
||||||
|
host = &api.node.wsHost
|
||||||
|
}
|
||||||
|
if port == nil {
|
||||||
|
port = rpc.NewHexNumber(api.node.wsPort)
|
||||||
|
}
|
||||||
|
if allowedOrigins == nil {
|
||||||
|
allowedOrigins = &api.node.wsOrigins
|
||||||
|
}
|
||||||
|
|
||||||
|
modules := api.node.wsWhitelist
|
||||||
|
if apis != nil {
|
||||||
|
modules = nil
|
||||||
|
for _, m := range strings.Split(*apis, ",") {
|
||||||
|
modules = append(modules, strings.TrimSpace(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *allowedOrigins); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -127,10 +127,10 @@ type Config struct {
|
|||||||
// ephemeral nodes).
|
// ephemeral nodes).
|
||||||
WSPort int
|
WSPort int
|
||||||
|
|
||||||
// WSDomains is the list of domain to accept websocket requests from. Please be
|
// WSOrigins is the list of domain to accept websocket requests from. Please be
|
||||||
// aware that the server can only act upon the HTTP request the client sends and
|
// aware that the server can only act upon the HTTP request the client sends and
|
||||||
// cannot verify the validity of the request header.
|
// cannot verify the validity of the request header.
|
||||||
WSDomains string
|
WSOrigins string
|
||||||
|
|
||||||
// WSModules is a list of API modules to expose via the websocket RPC interface.
|
// WSModules is a list of API modules to expose via the websocket RPC interface.
|
||||||
// If the module list is empty, all RPC API endpoints designated public will be
|
// If the module list is empty, all RPC API endpoints designated public will be
|
||||||
|
20
node/node.go
20
node/node.go
@ -62,15 +62,19 @@ type Node struct {
|
|||||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||||
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
|
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
|
||||||
|
|
||||||
|
httpHost string // HTTP hostname
|
||||||
|
httpPort int // HTTP post
|
||||||
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
|
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
|
||||||
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
||||||
httpCors string // HTTP RPC Cross-Origin Resource Sharing header
|
httpCors string // HTTP RPC Cross-Origin Resource Sharing header
|
||||||
httpListener net.Listener // HTTP RPC listener socket to server API requests
|
httpListener net.Listener // HTTP RPC listener socket to server API requests
|
||||||
httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
|
httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
|
||||||
|
|
||||||
|
wsHost string // Websocket host
|
||||||
|
wsPort int // Websocket post
|
||||||
wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
|
wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
|
||||||
wsWhitelist []string // Websocket RPC modules to allow through this endpoint
|
wsWhitelist []string // Websocket RPC modules to allow through this endpoint
|
||||||
wsDomains string // Websocket RPC allowed origin domains
|
wsOrigins string // Websocket RPC allowed origin domains
|
||||||
wsListener net.Listener // Websocket RPC listener socket to server API requests
|
wsListener net.Listener // Websocket RPC listener socket to server API requests
|
||||||
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
||||||
|
|
||||||
@ -110,12 +114,16 @@ func New(conf *Config) (*Node, error) {
|
|||||||
},
|
},
|
||||||
serviceFuncs: []ServiceConstructor{},
|
serviceFuncs: []ServiceConstructor{},
|
||||||
ipcEndpoint: conf.IPCEndpoint(),
|
ipcEndpoint: conf.IPCEndpoint(),
|
||||||
|
httpHost: conf.HTTPHost,
|
||||||
|
httpPort: conf.HTTPPort,
|
||||||
httpEndpoint: conf.HTTPEndpoint(),
|
httpEndpoint: conf.HTTPEndpoint(),
|
||||||
httpWhitelist: conf.HTTPModules,
|
httpWhitelist: conf.HTTPModules,
|
||||||
httpCors: conf.HTTPCors,
|
httpCors: conf.HTTPCors,
|
||||||
|
wsHost: conf.WSHost,
|
||||||
|
wsPort: conf.WSPort,
|
||||||
wsEndpoint: conf.WSEndpoint(),
|
wsEndpoint: conf.WSEndpoint(),
|
||||||
wsWhitelist: conf.WSModules,
|
wsWhitelist: conf.WSModules,
|
||||||
wsDomains: conf.WSDomains,
|
wsOrigins: conf.WSOrigins,
|
||||||
eventmux: new(event.TypeMux),
|
eventmux: new(event.TypeMux),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -231,7 +239,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
|||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsDomains); err != nil {
|
if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsOrigins); err != nil {
|
||||||
n.stopHTTP()
|
n.stopHTTP()
|
||||||
n.stopIPC()
|
n.stopIPC()
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
@ -383,7 +391,7 @@ func (n *Node) stopHTTP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startWS initializes and starts the websocket RPC endpoint.
|
// startWS initializes and starts the websocket RPC endpoint.
|
||||||
func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, cors string) error {
|
func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins string) error {
|
||||||
// Short circuit if the WS endpoint isn't being exposed
|
// Short circuit if the WS endpoint isn't being exposed
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -411,14 +419,14 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, cors s
|
|||||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go rpc.NewWSServer(cors, handler).Serve(listener)
|
go rpc.NewWSServer(wsOrigins, handler).Serve(listener)
|
||||||
glog.V(logger.Info).Infof("WebSocket endpoint opened: ws://%s", endpoint)
|
glog.V(logger.Info).Infof("WebSocket endpoint opened: ws://%s", endpoint)
|
||||||
|
|
||||||
// All listeners booted successfully
|
// All listeners booted successfully
|
||||||
n.wsEndpoint = endpoint
|
n.wsEndpoint = endpoint
|
||||||
n.wsListener = listener
|
n.wsListener = listener
|
||||||
n.wsHandler = handler
|
n.wsHandler = handler
|
||||||
n.wsDomains = cors
|
n.wsOrigins = wsOrigins
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -554,7 +554,7 @@ func TestAPIGather(t *testing.T) {
|
|||||||
{"multi.v2.nested_theOneMethod", "multi.v2.nested"},
|
{"multi.v2.nested_theOneMethod", "multi.v2.nested"},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
if err := client.Send(rpc.JSONRequest{Id: new(int64), Version: "2.0", Method: test.Method}); err != nil {
|
if err := client.Send(rpc.JSONRequest{Id: []byte("1"), Version: "2.0", Method: test.Method}); err != nil {
|
||||||
t.Fatalf("test %d: failed to send API request: %v", i, err)
|
t.Fatalf("test %d: failed to send API request: %v", i, err)
|
||||||
}
|
}
|
||||||
reply := new(rpc.JSONSuccessResponse)
|
reply := new(rpc.JSONSuccessResponse)
|
||||||
|
14
rpc/doc.go
14
rpc/doc.go
@ -29,11 +29,23 @@ Methods that satisfy the following criteria are made available for remote access
|
|||||||
- method returned value(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) Div(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
|
||||||
send back to the client. Otherwise the returned integer is send back to the client.
|
send back to the client. Otherwise the returned integer is send back to the client.
|
||||||
|
|
||||||
|
Optional arguments are supported by accepting pointer values as arguments. E.g.
|
||||||
|
if we want to do the addition in an optional finite field we can accept a mod
|
||||||
|
argument as pointer value.
|
||||||
|
|
||||||
|
func (s *CalService) Add(a, b int, mod *int) (int, error)
|
||||||
|
|
||||||
|
This RPC method can be called with 2 integers and a null value as third argument.
|
||||||
|
In that case the mod argument will be nil. Or it can be called with 3 integers,
|
||||||
|
in that case mod will be pointing to the given third argument. Since the optional
|
||||||
|
argument is the last argument the RPC package will also accept 2 integers as
|
||||||
|
arguments. It will pass the mod 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 requests from the codec, process the request and sends the response back to the
|
read requests from the codec, process the request and sends the response back to the
|
||||||
client using the codec. The server can execute requests concurrently. Responses
|
client using the codec. The server can execute requests concurrently. Responses
|
||||||
|
20
rpc/http.go
20
rpc/http.go
@ -20,13 +20,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +35,9 @@ const (
|
|||||||
|
|
||||||
// httpClient connects to a geth RPC server over HTTP.
|
// httpClient connects to a geth RPC server over HTTP.
|
||||||
type httpClient struct {
|
type httpClient struct {
|
||||||
endpoint *url.URL // HTTP-RPC server endpoint
|
endpoint *url.URL // HTTP-RPC server endpoint
|
||||||
lastRes []byte // HTTP requests are synchronous, store last response
|
httpClient http.Client // reuse connection
|
||||||
|
lastRes []byte // HTTP requests are synchronous, store last response
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPClient create a new RPC clients that connection to a geth RPC server
|
// NewHTTPClient create a new RPC clients that connection to a geth RPC server
|
||||||
@ -57,30 +57,22 @@ func (client *httpClient) Send(msg interface{}) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
client.lastRes = nil
|
client.lastRes = nil
|
||||||
|
|
||||||
if body, err = json.Marshal(msg); err != nil {
|
if body, err = json.Marshal(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("POST", client.endpoint.String(), bytes.NewBuffer(body))
|
resp, err := client.httpClient.Post(client.endpoint.String(), "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
httpClient := http.Client{}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
client.lastRes, err = ioutil.ReadAll(resp.Body)
|
client.lastRes, err = ioutil.ReadAll(resp.Body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unable to handle request")
|
return fmt.Errorf("request failed: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recv will try to deserialize the last received response into the given msg.
|
// Recv will try to deserialize the last received response into the given msg.
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/microsoft/go-winio"
|
winio "github.com/microsoft/go-winio"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ipcListen will create a named pipe on the given endpoint.
|
// ipcListen will create a named pipe on the given endpoint.
|
||||||
|
@ -19,48 +19,15 @@ package rpc
|
|||||||
var (
|
var (
|
||||||
// Holds geth specific RPC extends which can be used to extend web3
|
// Holds geth specific RPC extends which can be used to extend web3
|
||||||
WEB3Extensions = map[string]string{
|
WEB3Extensions = map[string]string{
|
||||||
"personal": Personal_JS,
|
"txpool": TxPool_JS,
|
||||||
"txpool": TxPool_JS,
|
"admin": Admin_JS,
|
||||||
"admin": Admin_JS,
|
"eth": Eth_JS,
|
||||||
"eth": Eth_JS,
|
"miner": Miner_JS,
|
||||||
"miner": Miner_JS,
|
"debug": Debug_JS,
|
||||||
"debug": Debug_JS,
|
"net": Net_JS,
|
||||||
"net": Net_JS,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const Personal_JS = `
|
|
||||||
web3._extend({
|
|
||||||
property: 'personal',
|
|
||||||
methods:
|
|
||||||
[
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'newAccount',
|
|
||||||
call: 'personal_newAccount',
|
|
||||||
params: 1,
|
|
||||||
outputFormatter: web3._extend.utils.toAddress
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'unlockAccount',
|
|
||||||
call: 'personal_unlockAccount',
|
|
||||||
params: 3,
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'lockAccount',
|
|
||||||
call: 'personal_lockAccount',
|
|
||||||
params: 1
|
|
||||||
})
|
|
||||||
],
|
|
||||||
properties:
|
|
||||||
[
|
|
||||||
new web3._extend.Property({
|
|
||||||
name: 'listAccounts',
|
|
||||||
getter: 'personal_listAccounts'
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
`
|
|
||||||
|
|
||||||
const TxPool_JS = `
|
const TxPool_JS = `
|
||||||
web3._extend({
|
web3._extend({
|
||||||
property: 'txpool',
|
property: 'txpool',
|
||||||
@ -124,22 +91,22 @@ web3._extend({
|
|||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'startRPC',
|
name: 'startRPC',
|
||||||
call: 'admin_startRPC',
|
call: 'admin_startRPC',
|
||||||
params: 4
|
params: 4,
|
||||||
|
inputFormatter: [null, null, null, null]
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'stopRPC',
|
name: 'stopRPC',
|
||||||
call: 'admin_stopRPC',
|
call: 'admin_stopRPC'
|
||||||
params: 0
|
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'startWS',
|
name: 'startWS',
|
||||||
call: 'admin_startWS',
|
call: 'admin_startWS',
|
||||||
params: 4
|
params: 4,
|
||||||
|
inputFormatter: [null, null, null, null]
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'stopWS',
|
name: 'stopWS',
|
||||||
call: 'admin_stopWS',
|
call: 'admin_stopWS'
|
||||||
params: 0
|
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'setGlobalRegistrar',
|
name: 'setGlobalRegistrar',
|
||||||
@ -219,7 +186,7 @@ web3._extend({
|
|||||||
name: 'sign',
|
name: 'sign',
|
||||||
call: 'eth_sign',
|
call: 'eth_sign',
|
||||||
params: 2,
|
params: 2,
|
||||||
inputFormatter: [web3._extend.utils.toAddress, null]
|
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null]
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'resend',
|
name: 'resend',
|
||||||
@ -414,19 +381,18 @@ web3._extend({
|
|||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'start',
|
name: 'start',
|
||||||
call: 'miner_start',
|
call: 'miner_start',
|
||||||
params: 1
|
params: 1,
|
||||||
|
inputFormatter: [null]
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'stop',
|
name: 'stop',
|
||||||
call: 'miner_stop',
|
call: 'miner_stop'
|
||||||
params: 1
|
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'setEtherbase',
|
name: 'setEtherbase',
|
||||||
call: 'miner_setEtherbase',
|
call: 'miner_setEtherbase',
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [web3._extend.formatters.formatInputInt],
|
inputFormatter: [web3._extend.formatters.inputAddressFormatter]
|
||||||
outputFormatter: web3._extend.formatters.formatOutputBool
|
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'setExtra',
|
name: 'setExtra',
|
||||||
|
171
rpc/json.go
171
rpc/json.go
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -40,14 +41,14 @@ const (
|
|||||||
type JSONRequest struct {
|
type JSONRequest struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Version string `json:"jsonrpc"`
|
Version string `json:"jsonrpc"`
|
||||||
Id *int64 `json:"id,omitempty"`
|
Id json.RawMessage `json:"id,omitempty"`
|
||||||
Payload json.RawMessage `json:"params,omitempty"`
|
Payload json.RawMessage `json:"params,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON-RPC response
|
// JSON-RPC response
|
||||||
type JSONSuccessResponse struct {
|
type JSONSuccessResponse struct {
|
||||||
Version string `json:"jsonrpc"`
|
Version string `json:"jsonrpc"`
|
||||||
Id int64 `json:"id"`
|
Id interface{} `json:"id,omitempty"`
|
||||||
Result interface{} `json:"result"`
|
Result interface{} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +61,9 @@ type JSONError struct {
|
|||||||
|
|
||||||
// JSON-RPC error response
|
// JSON-RPC error response
|
||||||
type JSONErrResponse struct {
|
type JSONErrResponse struct {
|
||||||
Version string `json:"jsonrpc"`
|
Version string `json:"jsonrpc"`
|
||||||
Id *int64 `json:"id,omitempty"`
|
Id interface{} `json:"id,omitempty"`
|
||||||
Error JSONError `json:"error"`
|
Error JSONError `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON-RPC notification payload
|
// JSON-RPC notification payload
|
||||||
@ -78,16 +79,16 @@ type jsonNotification struct {
|
|||||||
Params jsonSubscription `json:"params"`
|
Params jsonSubscription `json:"params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has support for parsing arguments
|
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It
|
||||||
// and serializing (result) objects.
|
// also has support for parsing arguments and serializing (result) objects.
|
||||||
type jsonCodec struct {
|
type jsonCodec struct {
|
||||||
closed chan interface{}
|
closer sync.Once // close closed channel once
|
||||||
closer sync.Once
|
closed chan interface{} // closed on Close
|
||||||
d *json.Decoder
|
decMu sync.Mutex // guards d
|
||||||
muEncoder sync.Mutex
|
d *json.Decoder // decodes incoming requests
|
||||||
e *json.Encoder
|
encMu sync.Mutex // guards e
|
||||||
req JSONRequest
|
e *json.Encoder // encodes responses
|
||||||
rw io.ReadWriteCloser
|
rw io.ReadWriteCloser // connection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0
|
// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0
|
||||||
@ -109,9 +110,13 @@ 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
|
// ReadRequestHeaders will read new requests without parsing the arguments. It will
|
||||||
// indication if these requests are in batch form or an error when the incoming message could not be read/parsed.
|
// 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, RPCError) {
|
func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
|
||||||
|
c.decMu.Lock()
|
||||||
|
defer c.decMu.Unlock()
|
||||||
|
|
||||||
var incomingMsg json.RawMessage
|
var incomingMsg json.RawMessage
|
||||||
if err := c.d.Decode(&incomingMsg); err != nil {
|
if err := c.d.Decode(&incomingMsg); err != nil {
|
||||||
return nil, false, &invalidRequestError{err.Error()}
|
return nil, false, &invalidRequestError{err.Error()}
|
||||||
@ -124,21 +129,38 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
|
|||||||
return parseRequest(incomingMsg)
|
return parseRequest(incomingMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication
|
// checkReqId returns an error when the given reqId isn't valid for RPC method calls.
|
||||||
// if the request was a batch or an error when the request could not be parsed.
|
// 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, RPCError) {
|
func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
|
||||||
var in JSONRequest
|
var in JSONRequest
|
||||||
if err := json.Unmarshal(incomingMsg, &in); err != nil {
|
if err := json.Unmarshal(incomingMsg, &in); err != nil {
|
||||||
return nil, false, &invalidMessageError{err.Error()}
|
return nil, false, &invalidMessageError{err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.Id == nil {
|
if err := checkReqId(in.Id); err != nil {
|
||||||
return nil, false, &invalidMessageError{"Server cannot handle notifications"}
|
return nil, false, &invalidMessageError{err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribe are special, they will always use `subscribeMethod` as service method
|
// subscribe are special, they will always use `subscribeMethod` as first param in the payload
|
||||||
if in.Method == subscribeMethod {
|
if in.Method == subscribeMethod {
|
||||||
reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}}
|
reqs := []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true}}
|
||||||
if len(in.Payload) > 0 {
|
if len(in.Payload) > 0 {
|
||||||
// first param must be subscription name
|
// first param must be subscription name
|
||||||
var subscribeMethod [1]string
|
var subscribeMethod [1]string
|
||||||
@ -156,7 +178,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if in.Method == unsubscribeMethod {
|
if in.Method == unsubscribeMethod {
|
||||||
return []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true,
|
return []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true,
|
||||||
method: unsubscribeMethod, params: in.Payload}}, false, nil
|
method: unsubscribeMethod, params: in.Payload}}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,10 +189,10 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(in.Payload) == 0 {
|
if len(in.Payload) == 0 {
|
||||||
return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id}}, false, nil
|
return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: &in.Id}}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id, params: in.Payload}}, false, nil
|
return []rpcRequest{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
|
// parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication
|
||||||
@ -183,14 +205,17 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro
|
|||||||
|
|
||||||
requests := make([]rpcRequest, len(in))
|
requests := make([]rpcRequest, len(in))
|
||||||
for i, r := range in {
|
for i, r := range in {
|
||||||
if r.Id == nil {
|
if err := checkReqId(r.Id); err != nil {
|
||||||
return nil, true, &invalidMessageError{"Server cannot handle notifications"}
|
return nil, false, &invalidMessageError{err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (un)subscribe are special, they will always use the same service.method
|
id := &in[i].Id
|
||||||
|
|
||||||
|
// subscribe are special, they will always use `subscribeMethod` as first param in the payload
|
||||||
if r.Method == subscribeMethod {
|
if r.Method == subscribeMethod {
|
||||||
requests[i] = rpcRequest{id: *r.Id, isPubSub: true}
|
requests[i] = rpcRequest{id: id, isPubSub: true}
|
||||||
if len(r.Payload) > 0 {
|
if len(r.Payload) > 0 {
|
||||||
|
// first param must be subscription name
|
||||||
var subscribeMethod [1]string
|
var subscribeMethod [1]string
|
||||||
if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil {
|
if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil {
|
||||||
glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err)
|
glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err)
|
||||||
@ -207,7 +232,7 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == unsubscribeMethod {
|
if r.Method == unsubscribeMethod {
|
||||||
requests[i] = rpcRequest{id: *r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload}
|
requests[i] = rpcRequest{id: id, isPubSub: true, method: unsubscribeMethod, params: r.Payload}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,9 +242,9 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Payload) == 0 {
|
if len(r.Payload) == 0 {
|
||||||
requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: nil}
|
requests[i] = rpcRequest{service: elems[0], method: elems[1], id: id, params: nil}
|
||||||
} else {
|
} else {
|
||||||
requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: r.Payload}
|
requests[i] = rpcRequest{service: elems[0], method: elems[1], id: id, params: r.Payload}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,58 +261,38 @@ func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func countArguments(args json.RawMessage) (int, error) {
|
// parsePositionalArguments tries to parse the given args to an array of values with the given types.
|
||||||
var cnt []interface{}
|
// It returns the parsed values or an error when the args could not be parsed. Missing optional arguments
|
||||||
if err := json.Unmarshal(args, &cnt); err != nil {
|
// are returned as reflect.Zero values.
|
||||||
return -1, nil
|
func parsePositionalArguments(args json.RawMessage, callbackArgs []reflect.Type) ([]reflect.Value, RPCError) {
|
||||||
}
|
params := make([]interface{}, 0, len(callbackArgs))
|
||||||
return len(cnt), nil
|
for _, t := range callbackArgs {
|
||||||
}
|
params = append(params, reflect.New(t).Interface())
|
||||||
|
|
||||||
// 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 parsed.
|
|
||||||
func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([]reflect.Value, RPCError) {
|
|
||||||
argValues := make([]reflect.Value, len(argTypes))
|
|
||||||
params := make([]interface{}, len(argTypes))
|
|
||||||
|
|
||||||
n, err := countArguments(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &invalidParamsError{err.Error()}
|
|
||||||
}
|
|
||||||
if n != len(argTypes) {
|
|
||||||
return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, t := range argTypes {
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
// values must be pointers for the Unmarshal method, reflect.
|
|
||||||
// Dereference otherwise reflect.New would create **SomeType
|
|
||||||
argValues[i] = reflect.New(t.Elem())
|
|
||||||
params[i] = argValues[i].Interface()
|
|
||||||
|
|
||||||
// when not specified blockNumbers are by default latest (-1)
|
|
||||||
if blockNumber, ok := params[i].(*BlockNumber); ok {
|
|
||||||
*blockNumber = BlockNumber(-1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
argValues[i] = reflect.New(t)
|
|
||||||
params[i] = argValues[i].Interface()
|
|
||||||
|
|
||||||
// when not specified blockNumbers are by default latest (-1)
|
|
||||||
if blockNumber, ok := params[i].(*BlockNumber); ok {
|
|
||||||
*blockNumber = BlockNumber(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(args, ¶ms); err != nil {
|
if err := json.Unmarshal(args, ¶ms); err != nil {
|
||||||
return nil, &invalidParamsError{err.Error()}
|
return nil, &invalidParamsError{err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert pointers back to values where necessary
|
if len(params) > len(callbackArgs) {
|
||||||
for i, a := range argValues {
|
return nil, &invalidParamsError{fmt.Sprintf("too many params, want %d got %d", len(callbackArgs), len(params))}
|
||||||
if a.Kind() != argTypes[i].Kind() {
|
}
|
||||||
argValues[i] = reflect.Indirect(argValues[i])
|
|
||||||
|
// assume missing params are null values
|
||||||
|
for i := len(params); i < len(callbackArgs); i++ {
|
||||||
|
params = append(params, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
argValues := make([]reflect.Value, len(params))
|
||||||
|
for i, p := range params {
|
||||||
|
// verify that JSON null values are only supplied for optional arguments (ptr types)
|
||||||
|
if p == nil && callbackArgs[i].Kind() != reflect.Ptr {
|
||||||
|
return nil, &invalidParamsError{fmt.Sprintf("invalid or missing value for params[%d]", i)}
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
argValues[i] = reflect.Zero(callbackArgs[i])
|
||||||
|
} else { // deref pointers values creates previously with reflect.New
|
||||||
|
argValues[i] = reflect.ValueOf(p).Elem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +300,7 @@ func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateResponse will create a JSON-RPC success response with the given id and reply as result.
|
// CreateResponse will create a JSON-RPC success response with the given id and reply as result.
|
||||||
func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} {
|
func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} {
|
||||||
if isHexNum(reflect.TypeOf(reply)) {
|
if isHexNum(reflect.TypeOf(reply)) {
|
||||||
return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)}
|
return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)}
|
||||||
}
|
}
|
||||||
@ -303,13 +308,13 @@ func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateErrorResponse will create a JSON-RPC error response with the given id and error.
|
// CreateErrorResponse will create a JSON-RPC error response with the given id and error.
|
||||||
func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} {
|
func (c *jsonCodec) CreateErrorResponse(id interface{}, err RPCError) interface{} {
|
||||||
return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}}
|
return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error.
|
// 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.
|
// info is optional and contains additional information about the error. When an empty string is passed it is ignored.
|
||||||
func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} {
|
func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} {
|
||||||
return &JSONErrResponse{Version: jsonRPCVersion, Id: id,
|
return &JSONErrResponse{Version: jsonRPCVersion, Id: id,
|
||||||
Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}}
|
Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}}
|
||||||
}
|
}
|
||||||
@ -327,8 +332,8 @@ func (c *jsonCodec) CreateNotification(subid string, event interface{}) interfac
|
|||||||
|
|
||||||
// Write message to client
|
// Write message to client
|
||||||
func (c *jsonCodec) Write(res interface{}) error {
|
func (c *jsonCodec) Write(res interface{}) error {
|
||||||
c.muEncoder.Lock()
|
c.encMu.Lock()
|
||||||
defer c.muEncoder.Unlock()
|
defer c.encMu.Unlock()
|
||||||
|
|
||||||
return c.e.Encode(res)
|
return c.e.Encode(res)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ package rpc
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,8 +53,16 @@ func TestJSONRequestParsing(t *testing.T) {
|
|||||||
t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method)
|
t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method)
|
||||||
}
|
}
|
||||||
|
|
||||||
if requests[0].id != 1234 {
|
if rawId, ok := requests[0].id.(*json.RawMessage); ok {
|
||||||
t.Fatalf("Expected id 1234 but got %d", requests[0].id)
|
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 %s", id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("invalid request, expected *json.RawMesage got %T", requests[0].id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var arg int
|
var arg int
|
||||||
@ -71,3 +81,82 @@ func TestJSONRequestParsing(t *testing.T) {
|
|||||||
t.Fatalf("expected %d == 11 && %d == 22", v[0].Int(), v[1].Int())
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,10 +18,9 @@ package rpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
@ -69,10 +68,6 @@ func (s *Service) Subscription(ctx context.Context) (Subscription, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SubsriptionWithArgs(ctx context.Context, a, b int) (Subscription, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerRegisterName(t *testing.T) {
|
func TestServerRegisterName(t *testing.T) {
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
service := new(Service)
|
service := new(Service)
|
||||||
@ -94,182 +89,67 @@ func TestServerRegisterName(t *testing.T) {
|
|||||||
t.Errorf("Expected 4 callbacks for service 'calc', got %d", len(svc.callbacks))
|
t.Errorf("Expected 4 callbacks for service 'calc', got %d", len(svc.callbacks))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(svc.subscriptions) != 2 {
|
if len(svc.subscriptions) != 1 {
|
||||||
t.Errorf("Expected 2 subscriptions for service 'calc', got %d", len(svc.subscriptions))
|
t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dummy codec used for testing RPC method execution
|
func testServerMethodExecution(t *testing.T, method string) {
|
||||||
type ServerTestCodec struct {
|
server := NewServer()
|
||||||
counter int
|
service := new(Service)
|
||||||
input []byte
|
|
||||||
output string
|
|
||||||
closer chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
|
if err := server.RegisterName("test", service); err != nil {
|
||||||
c.counter += 1
|
t.Fatalf("%v", err)
|
||||||
|
|
||||||
if c.counter == 1 {
|
|
||||||
var req JSONRequest
|
|
||||||
json.Unmarshal(c.input, &req)
|
|
||||||
return []rpcRequest{rpcRequest{id: *req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// requests are executes in parallel, wait a bit before returning an error so that the previous request has time to
|
stringArg := "string arg"
|
||||||
// be executed
|
intArg := 1122
|
||||||
timer := time.NewTimer(time.Duration(2) * time.Second)
|
argsArg := &Args{"abcde"}
|
||||||
<-timer.C
|
params := []interface{}{stringArg, intArg, argsArg}
|
||||||
|
|
||||||
return nil, false, &invalidRequestError{"connection closed"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) ParseRequestArguments(argTypes []reflect.Type, payload interface{}) ([]reflect.Value, RPCError) {
|
|
||||||
|
|
||||||
args, _ := payload.(json.RawMessage)
|
|
||||||
|
|
||||||
argValues := make([]reflect.Value, len(argTypes))
|
|
||||||
params := make([]interface{}, len(argTypes))
|
|
||||||
|
|
||||||
n, err := countArguments(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &invalidParamsError{err.Error()}
|
|
||||||
}
|
|
||||||
if n != len(argTypes) {
|
|
||||||
return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)}
|
|
||||||
|
|
||||||
|
request := map[string]interface{}{
|
||||||
|
"id": 12345,
|
||||||
|
"method": "test_" + method,
|
||||||
|
"version": "2.0",
|
||||||
|
"params": params,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range argTypes {
|
clientConn, serverConn := net.Pipe()
|
||||||
if t.Kind() == reflect.Ptr {
|
defer clientConn.Close()
|
||||||
// values must be pointers for the Unmarshal method, reflect.
|
|
||||||
// Dereference otherwise reflect.New would create **SomeType
|
|
||||||
argValues[i] = reflect.New(t.Elem())
|
|
||||||
params[i] = argValues[i].Interface()
|
|
||||||
|
|
||||||
// when not specified blockNumbers are by default latest (-1)
|
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation)
|
||||||
if blockNumber, ok := params[i].(*BlockNumber); ok {
|
|
||||||
*blockNumber = BlockNumber(-1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
argValues[i] = reflect.New(t)
|
|
||||||
params[i] = argValues[i].Interface()
|
|
||||||
|
|
||||||
// when not specified blockNumbers are by default latest (-1)
|
out := json.NewEncoder(clientConn)
|
||||||
if blockNumber, ok := params[i].(*BlockNumber); ok {
|
in := json.NewDecoder(clientConn)
|
||||||
*blockNumber = BlockNumber(-1)
|
|
||||||
}
|
if err := out.Encode(request); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := JSONSuccessResponse{Result: &Result{}}
|
||||||
|
if err := in.Decode(&response); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, ok := response.Result.(*Result); ok {
|
||||||
|
if result.String != stringArg {
|
||||||
|
t.Errorf("expected %s, got : %s\n", stringArg, result.String)
|
||||||
}
|
}
|
||||||
}
|
if result.Int != intArg {
|
||||||
|
t.Errorf("expected %d, got %d\n", intArg, result.Int)
|
||||||
if err := json.Unmarshal(args, ¶ms); err != nil {
|
|
||||||
return nil, &invalidParamsError{err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert pointers back to values where necessary
|
|
||||||
for i, a := range argValues {
|
|
||||||
if a.Kind() != argTypes[i].Kind() {
|
|
||||||
argValues[i] = reflect.Indirect(argValues[i])
|
|
||||||
}
|
}
|
||||||
}
|
if !reflect.DeepEqual(result.Args, argsArg) {
|
||||||
|
t.Errorf("expected %v, got %v\n", argsArg, result)
|
||||||
return argValues, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) CreateResponse(id int64, reply interface{}) interface{} {
|
|
||||||
return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) CreateErrorResponse(id *int64, err RPCError) interface{} {
|
|
||||||
return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} {
|
|
||||||
return &JSONErrResponse{Version: jsonRPCVersion, Id: id,
|
|
||||||
Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) CreateNotification(subid string, event interface{}) interface{} {
|
|
||||||
return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod,
|
|
||||||
Params: jsonSubscription{Subscription: subid, Result: event}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) Write(msg interface{}) error {
|
|
||||||
if len(c.output) == 0 { // only capture first response
|
|
||||||
if o, err := json.Marshal(msg); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
c.output = string(o)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("invalid response: expected *Result - got: %T", response.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) Close() {
|
|
||||||
close(c.closer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerTestCodec) Closed() <-chan interface{} {
|
|
||||||
return c.closer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerMethodExecution(t *testing.T) {
|
func TestServerMethodExecution(t *testing.T) {
|
||||||
server := NewServer()
|
testServerMethodExecution(t, "echo")
|
||||||
service := new(Service)
|
|
||||||
|
|
||||||
if err := server.RegisterName("test", service); err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int64(12345)
|
|
||||||
req := JSONRequest{
|
|
||||||
Method: "echo",
|
|
||||||
Version: "2.0",
|
|
||||||
Id: &id,
|
|
||||||
}
|
|
||||||
args := []interface{}{"string arg", 1122, &Args{"qwerty"}}
|
|
||||||
req.Payload, _ = json.Marshal(&args)
|
|
||||||
|
|
||||||
input, _ := json.Marshal(&req)
|
|
||||||
codec := &ServerTestCodec{input: input, closer: make(chan interface{})}
|
|
||||||
go server.ServeCodec(codec, OptionMethodInvocation)
|
|
||||||
|
|
||||||
<-codec.closer
|
|
||||||
|
|
||||||
expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}`
|
|
||||||
|
|
||||||
if expected != codec.output {
|
|
||||||
t.Fatalf("expected %s, got %s\n", expected, codec.output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerMethodWithCtx(t *testing.T) {
|
func TestServerMethodWithCtx(t *testing.T) {
|
||||||
server := NewServer()
|
testServerMethodExecution(t, "echoWithCtx")
|
||||||
service := new(Service)
|
|
||||||
|
|
||||||
if err := server.RegisterName("test", service); err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int64(12345)
|
|
||||||
req := JSONRequest{
|
|
||||||
Method: "echoWithCtx",
|
|
||||||
Version: "2.0",
|
|
||||||
Id: &id,
|
|
||||||
}
|
|
||||||
args := []interface{}{"string arg", 1122, &Args{"qwerty"}}
|
|
||||||
req.Payload, _ = json.Marshal(&args)
|
|
||||||
|
|
||||||
input, _ := json.Marshal(&req)
|
|
||||||
codec := &ServerTestCodec{input: input, closer: make(chan interface{})}
|
|
||||||
go server.ServeCodec(codec, OptionMethodInvocation)
|
|
||||||
|
|
||||||
<-codec.closer
|
|
||||||
|
|
||||||
expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}`
|
|
||||||
|
|
||||||
if expected != codec.output {
|
|
||||||
t.Fatalf("expected %s, got %s\n", expected, codec.output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
51
rpc/types.go
51
rpc/types.go
@ -56,7 +56,7 @@ type service struct {
|
|||||||
|
|
||||||
// serverRequest is an incoming request
|
// serverRequest is an incoming request
|
||||||
type serverRequest struct {
|
type serverRequest struct {
|
||||||
id int64
|
id interface{}
|
||||||
svcname string
|
svcname string
|
||||||
rcvr reflect.Value
|
rcvr reflect.Value
|
||||||
callb *callback
|
callb *callback
|
||||||
@ -85,7 +85,7 @@ type Server struct {
|
|||||||
type rpcRequest struct {
|
type rpcRequest struct {
|
||||||
service string
|
service string
|
||||||
method string
|
method string
|
||||||
id int64
|
id interface{}
|
||||||
isPubSub bool
|
isPubSub bool
|
||||||
params interface{}
|
params interface{}
|
||||||
}
|
}
|
||||||
@ -106,12 +106,12 @@ type ServerCodec interface {
|
|||||||
ReadRequestHeaders() ([]rpcRequest, bool, RPCError)
|
ReadRequestHeaders() ([]rpcRequest, bool, RPCError)
|
||||||
// Parse request argument to the given types
|
// Parse request argument to the given types
|
||||||
ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError)
|
ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError)
|
||||||
// Assemble success response
|
// Assemble success response, expects response id and payload
|
||||||
CreateResponse(int64, interface{}) interface{}
|
CreateResponse(interface{}, interface{}) interface{}
|
||||||
// Assemble error response
|
// Assemble error response, expects response id and error
|
||||||
CreateErrorResponse(*int64, RPCError) interface{}
|
CreateErrorResponse(interface{}, RPCError) interface{}
|
||||||
// Assemble error response with extra information about the error through info
|
// Assemble error response with extra information about the error through info
|
||||||
CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{}
|
CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{}
|
||||||
// Create notification response
|
// Create notification response
|
||||||
CreateNotification(string, interface{}) interface{}
|
CreateNotification(string, interface{}) interface{}
|
||||||
// Write msg to client.
|
// Write msg to client.
|
||||||
@ -207,43 +207,6 @@ func (h *HexNumber) BigInt() *big.Int {
|
|||||||
return (*big.Int)(h)
|
return (*big.Int)(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Number int64
|
|
||||||
|
|
||||||
func (n *Number) UnmarshalJSON(data []byte) error {
|
|
||||||
input := strings.TrimSpace(string(data))
|
|
||||||
|
|
||||||
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {
|
|
||||||
input = input[1 : len(input)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input) == 0 {
|
|
||||||
*n = Number(latestBlockNumber.Int64())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
in := new(big.Int)
|
|
||||||
_, ok := in.SetString(input, 0)
|
|
||||||
|
|
||||||
if !ok { // test if user supplied string tag
|
|
||||||
return fmt.Errorf(`invalid number %s`, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Cmp(earliestBlockNumber) >= 0 && in.Cmp(maxBlockNumber) <= 0 {
|
|
||||||
*n = Number(in.Int64())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("blocknumber not in range [%d, %d]", earliestBlockNumber, maxBlockNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Number) Int64() int64 {
|
|
||||||
return *(*int64)(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Number) BigInt() *big.Int {
|
|
||||||
return big.NewInt(n.Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pendingBlockNumber = big.NewInt(-2)
|
pendingBlockNumber = big.NewInt(-2)
|
||||||
latestBlockNumber = big.NewInt(-1)
|
latestBlockNumber = big.NewInt(-1)
|
||||||
|
@ -232,7 +232,7 @@ func newSubscriptionID() (string, error) {
|
|||||||
// on which the given client connects.
|
// on which the given client connects.
|
||||||
func SupportedModules(client Client) (map[string]string, error) {
|
func SupportedModules(client Client) (map[string]string, error) {
|
||||||
req := JSONRequest{
|
req := JSONRequest{
|
||||||
Id: new(int64),
|
Id: []byte("1"),
|
||||||
Version: "2.0",
|
Version: "2.0",
|
||||||
Method: "rpc_modules",
|
Method: "rpc_modules",
|
||||||
}
|
}
|
||||||
|
@ -88,10 +88,10 @@ func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWSServer creates a new websocket RPC server around an API provider.
|
// NewWSServer creates a new websocket RPC server around an API provider.
|
||||||
func NewWSServer(cors string, handler *Server) *http.Server {
|
func NewWSServer(allowedOrigins string, handler *Server) *http.Server {
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Handler: websocket.Server{
|
Handler: websocket.Server{
|
||||||
Handshake: wsHandshakeValidator(strings.Split(cors, ",")),
|
Handshake: wsHandshakeValidator(strings.Split(allowedOrigins, ",")),
|
||||||
Handler: func(conn *websocket.Conn) {
|
Handler: func(conn *websocket.Conn) {
|
||||||
handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}),
|
handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}),
|
||||||
OptionMethodInvocation|OptionSubscriptions)
|
OptionMethodInvocation|OptionSubscriptions)
|
||||||
|
@ -84,7 +84,7 @@ type NewFilterArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
|
// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
|
||||||
func (s *PublicWhisperAPI) NewFilter(args *NewFilterArgs) (*rpc.HexNumber, error) {
|
func (s *PublicWhisperAPI) NewFilter(args NewFilterArgs) (*rpc.HexNumber, error) {
|
||||||
if s.w == nil {
|
if s.w == nil {
|
||||||
return nil, whisperOffLineErr
|
return nil, whisperOffLineErr
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ type PostArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Post injects a message into the whisper network for distribution.
|
// Post injects a message into the whisper network for distribution.
|
||||||
func (s *PublicWhisperAPI) Post(args *PostArgs) (bool, error) {
|
func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) {
|
||||||
if s.w == nil {
|
if s.w == nil {
|
||||||
return false, whisperOffLineErr
|
return false, whisperOffLineErr
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user