Merge pull request #2359 from bas-vk/rpc-optional-args
rpc: several fixes and support for optional arguments
This commit is contained in:
commit
8627680e24
@ -238,7 +238,7 @@ func (js *jsre) apiBindings() error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if apiName == "web3" || apiName == "rpc" {
|
||||
continue // manually mapped or ignore
|
||||
|
@ -326,7 +326,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
utils.WSListenAddrFlag,
|
||||
utils.WSPortFlag,
|
||||
utils.WSApiFlag,
|
||||
utils.WSAllowedDomainsFlag,
|
||||
utils.WSAllowedOriginsFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCApiFlag,
|
||||
utils.IPCPathFlag,
|
||||
|
@ -94,7 +94,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.WSListenAddrFlag,
|
||||
utils.WSPortFlag,
|
||||
utils.WSApiFlag,
|
||||
utils.WSAllowedDomainsFlag,
|
||||
utils.WSAllowedOriginsFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCApiFlag,
|
||||
utils.IPCPathFlag,
|
||||
|
@ -287,9 +287,9 @@ var (
|
||||
Usage: "API's offered over the WS-RPC interface",
|
||||
Value: rpc.DefaultHTTPApis,
|
||||
}
|
||||
WSAllowedDomainsFlag = cli.StringFlag{
|
||||
Name: "wsdomains",
|
||||
Usage: "Domains from which to accept websockets requests (can be spoofed)",
|
||||
WSAllowedOriginsFlag = cli.StringFlag{
|
||||
Name: "wsorigins",
|
||||
Usage: "Origins from which to accept websockets requests",
|
||||
Value: "",
|
||||
}
|
||||
ExecFlag = cli.StringFlag{
|
||||
@ -659,7 +659,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||
HTTPModules: strings.Split(ctx.GlobalString(RPCApiFlag.Name), ","),
|
||||
WSHost: MakeWSRpcHost(ctx),
|
||||
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
||||
WSDomains: ctx.GlobalString(WSAllowedDomainsFlag.Name),
|
||||
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
|
||||
WSModules: strings.Split(ctx.GlobalString(WSApiFlag.Name), ","),
|
||||
}
|
||||
// Configure the Ethereum service
|
||||
|
@ -37,7 +37,8 @@ func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth {
|
||||
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{
|
||||
Version: "2.0",
|
||||
Id: id,
|
||||
@ -56,44 +57,50 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (
|
||||
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) {
|
||||
var account, passwd string
|
||||
timeout := int64(300)
|
||||
var ok bool
|
||||
var account, passwd otto.Value
|
||||
duration := otto.NullValue()
|
||||
|
||||
if len(call.ArgumentList) == 0 {
|
||||
fmt.Println("expected address of account to unlock")
|
||||
if !call.Argument(0).IsString() {
|
||||
fmt.Println("first argument must be the account to unlock")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
if len(call.ArgumentList) >= 1 {
|
||||
if accountExport, err := call.Argument(0).Export(); err == nil {
|
||||
if account, ok = accountExport.(string); ok {
|
||||
if len(call.ArgumentList) == 1 {
|
||||
fmt.Printf("Unlock account %s\n", account)
|
||||
passwd, err = PromptPassword("Passphrase: ", true)
|
||||
if err != nil {
|
||||
return otto.FalseValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
account = call.Argument(0)
|
||||
|
||||
// if password is not given or as null value -> ask user for password
|
||||
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
||||
fmt.Printf("Unlock account %s\n", account)
|
||||
if password, err := PromptPassword("Passphrase: ", true); err == nil {
|
||||
passwd, _ = otto.ToValue(password)
|
||||
} else {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
}
|
||||
if len(call.ArgumentList) >= 2 {
|
||||
if passwdExport, err := call.Argument(1).Export(); err == nil {
|
||||
passwd, _ = passwdExport.(string)
|
||||
} else {
|
||||
if !call.Argument(1).IsString() {
|
||||
throwJSExeception("password must be a string")
|
||||
}
|
||||
passwd = call.Argument(1)
|
||||
}
|
||||
|
||||
if len(call.ArgumentList) >= 3 {
|
||||
if timeoutExport, err := call.Argument(2).Export(); err == nil {
|
||||
timeout, _ = timeoutExport.(int64)
|
||||
// third argument is the duration how long the account must be unlocked.
|
||||
// verify that its a number.
|
||||
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
|
||||
} else {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
|
||||
return otto.FalseValue()
|
||||
@ -134,19 +141,31 @@ func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
||||
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) {
|
||||
reqif, err := call.Argument(0).Export()
|
||||
if err != nil {
|
||||
return self.err(call, -32700, err.Error(), nil)
|
||||
// verify we got a batch request (array) or a single request (object)
|
||||
ro := call.Argument(0).Object()
|
||||
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
|
||||
batch := true
|
||||
err = json.Unmarshal(jsonreq, &reqs)
|
||||
if err != nil {
|
||||
if err = json.Unmarshal([]byte(jsonreq), &reqs); err != nil {
|
||||
// single request?
|
||||
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
|
||||
}
|
||||
|
||||
@ -154,47 +173,50 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
||||
call.Otto.Run("var ret_response = new Array(response_len);")
|
||||
|
||||
for i, req := range reqs {
|
||||
err := self.client.Send(&req)
|
||||
if err != nil {
|
||||
if err := self.client.Send(&req); err != nil {
|
||||
return self.err(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
err = self.client.Recv(&result)
|
||||
if err != nil {
|
||||
if err = self.client.Recv(&result); err != nil {
|
||||
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"]
|
||||
call.Otto.Set("ret_id", id)
|
||||
|
||||
jsonver, _ := result["jsonrpc"]
|
||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
||||
|
||||
var payload []byte
|
||||
if isSuccessResponse {
|
||||
payload, _ = json.Marshal(result["result"])
|
||||
} else if isErrorResponse {
|
||||
payload, _ = json.Marshal(result["error"])
|
||||
}
|
||||
call.Otto.Set("ret_result", string(payload))
|
||||
call.Otto.Set("ret_id", id)
|
||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
||||
call.Otto.Set("response_idx", i)
|
||||
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
||||
`)
|
||||
// call was successful
|
||||
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 {
|
||||
call.Otto.Run("ret_response = ret_response[0];")
|
||||
}
|
||||
|
||||
// if a callback was given execute it.
|
||||
if call.Argument(1).IsObject() {
|
||||
call.Otto.Set("callback", call.Argument(1))
|
||||
call.Otto.Run(`
|
||||
@ -207,53 +229,6 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
||||
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.
|
||||
func throwJSExeception(msg interface{}) otto.Value {
|
||||
p, _ := otto.ToValue(msg)
|
||||
|
60
eth/api.go
60
eth/api.go
@ -25,6 +25,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -96,8 +97,8 @@ type PublicEthereumAPI struct {
|
||||
}
|
||||
|
||||
// NewPublicEthereumAPI creates a new Ethereum protocol API.
|
||||
func NewPublicEthereumAPI(e *Ethereum) *PublicEthereumAPI {
|
||||
return &PublicEthereumAPI{e, NewGasPriceOracle(e)}
|
||||
func NewPublicEthereumAPI(e *Ethereum, gpo *GasPriceOracle) *PublicEthereumAPI {
|
||||
return &PublicEthereumAPI{e, gpo}
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *PublicEthereumAPI) GetCompilers() ([]string, error) {
|
||||
solc, err := s.e.Solc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if solc != nil {
|
||||
if err != nil && solc != nil {
|
||||
return []string{"Solidity"}, nil
|
||||
}
|
||||
|
||||
@ -240,9 +237,15 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
||||
return &PrivateMinerAPI{e: e}
|
||||
}
|
||||
|
||||
// Start the miner with the given number of threads
|
||||
func (s *PrivateMinerAPI) Start(threads rpc.HexNumber) (bool, error) {
|
||||
// Start the miner with the given number of threads. If threads is nil the number of
|
||||
// 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()
|
||||
|
||||
if threads == nil {
|
||||
threads = rpc.NewHexNumber(runtime.NumCPU())
|
||||
}
|
||||
|
||||
err := s.e.StartMining(threads.Int(), "")
|
||||
if err == 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.
|
||||
func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.Number) bool {
|
||||
func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.HexNumber) bool {
|
||||
s.e.Miner().SetGasPrice(gasPrice.BigInt())
|
||||
return true
|
||||
}
|
||||
@ -440,10 +443,15 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
// UnlockAccount will unlock the account associated with the given address with the given password for duration seconds.
|
||||
// It returns an indication if the action was successful.
|
||||
func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration int) bool {
|
||||
if err := s.am.TimedUnlock(addr, password, time.Duration(duration)*time.Second); err != nil {
|
||||
// UnlockAccount will unlock the account associated with the given address with
|
||||
// the given password for duration seconds. If duration is nil it will use a
|
||||
// default of 300 seconds. It returns an indication if the account was unlocked.
|
||||
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)
|
||||
return false
|
||||
}
|
||||
@ -458,7 +466,7 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
|
||||
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
||||
// It offers only methods that operate on public data that is freely available to anyone.
|
||||
type PublicBlockChainAPI struct {
|
||||
config *core.ChainConfig
|
||||
config *core.ChainConfig
|
||||
bc *core.BlockChain
|
||||
chainDb ethdb.Database
|
||||
eventMux *event.TypeMux
|
||||
@ -466,10 +474,11 @@ type PublicBlockChainAPI struct {
|
||||
newBlockSubscriptions map[string]func(core.ChainEvent) error // callbacks for new block subscriptions
|
||||
am *accounts.Manager
|
||||
miner *miner.Miner
|
||||
gpo *GasPriceOracle
|
||||
}
|
||||
|
||||
// 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{
|
||||
config: config,
|
||||
bc: bc,
|
||||
@ -478,6 +487,7 @@ func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *mi
|
||||
eventMux: eventMux,
|
||||
am: am,
|
||||
newBlockSubscriptions: make(map[string]func(core.ChainEvent) error),
|
||||
gpo: gpo,
|
||||
}
|
||||
|
||||
go api.subscriptionLoop()
|
||||
@ -674,8 +684,8 @@ func (m callmsg) Data() []byte { return m.data }
|
||||
type CallArgs struct {
|
||||
From common.Address `json:"from"`
|
||||
To *common.Address `json:"to"`
|
||||
Gas rpc.HexNumber `json:"gas"`
|
||||
GasPrice rpc.HexNumber `json:"gasPrice"`
|
||||
Gas *rpc.HexNumber `json:"gas"`
|
||||
GasPrice *rpc.HexNumber `json:"gasPrice"`
|
||||
Value rpc.HexNumber `json:"value"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
@ -711,11 +721,11 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
|
||||
value: args.Value.BigInt(),
|
||||
data: common.FromHex(args.Data),
|
||||
}
|
||||
if msg.gas.Cmp(common.Big0) == 0 {
|
||||
if msg.gas == nil {
|
||||
msg.gas = big.NewInt(50000000)
|
||||
}
|
||||
if msg.gasPrice.Cmp(common.Big0) == 0 {
|
||||
msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon)
|
||||
if msg.gasPrice == nil {
|
||||
msg.gasPrice = s.gpo.SuggestPrice()
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewPublicTransactionPoolAPI(e *Ethereum) *PublicTransactionPoolAPI {
|
||||
func NewPublicTransactionPoolAPI(e *Ethereum, gpo *GasPriceOracle) *PublicTransactionPoolAPI {
|
||||
api := &PublicTransactionPoolAPI{
|
||||
eventMux: e.EventMux(),
|
||||
gpo: NewGasPriceOracle(e),
|
||||
gpo: gpo,
|
||||
chainDb: e.ChainDb(),
|
||||
bc: e.BlockChain(),
|
||||
am: e.AccountManager(),
|
||||
@ -1306,7 +1316,7 @@ func newTx(t *types.Transaction) *Tx {
|
||||
// SignTransaction will sign the given transaction with the from account.
|
||||
// The node needs to have the private key of the account corresponding with
|
||||
// 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 {
|
||||
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
|
||||
// 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()
|
||||
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.
|
||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||
func (s *Ethereum) APIs() []rpc.API {
|
||||
// share gas price oracle in API's
|
||||
gpo := NewGasPriceOracle(s)
|
||||
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: NewPublicEthereumAPI(s),
|
||||
Service: NewPublicEthereumAPI(s, gpo),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
@ -291,12 +294,12 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
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,
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: NewPublicTransactionPoolAPI(s),
|
||||
Service: NewPublicTransactionPoolAPI(s, gpo),
|
||||
Public: true,
|
||||
}, {
|
||||
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/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
@ -58,14 +59,33 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.httpHandler != nil {
|
||||
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 true, nil
|
||||
@ -84,14 +104,33 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) {
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.wsHandler != nil {
|
||||
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 true, nil
|
||||
|
@ -127,10 +127,10 @@ type Config struct {
|
||||
// ephemeral nodes).
|
||||
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
|
||||
// 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.
|
||||
// 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
|
||||
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)
|
||||
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
||||
httpCors string // HTTP RPC Cross-Origin Resource Sharing header
|
||||
httpListener net.Listener // HTTP RPC listener socket to server 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)
|
||||
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
|
||||
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
||||
|
||||
@ -110,12 +114,16 @@ func New(conf *Config) (*Node, error) {
|
||||
},
|
||||
serviceFuncs: []ServiceConstructor{},
|
||||
ipcEndpoint: conf.IPCEndpoint(),
|
||||
httpHost: conf.HTTPHost,
|
||||
httpPort: conf.HTTPPort,
|
||||
httpEndpoint: conf.HTTPEndpoint(),
|
||||
httpWhitelist: conf.HTTPModules,
|
||||
httpCors: conf.HTTPCors,
|
||||
wsHost: conf.WSHost,
|
||||
wsPort: conf.WSPort,
|
||||
wsEndpoint: conf.WSEndpoint(),
|
||||
wsWhitelist: conf.WSModules,
|
||||
wsDomains: conf.WSDomains,
|
||||
wsOrigins: conf.WSOrigins,
|
||||
eventmux: new(event.TypeMux),
|
||||
}, nil
|
||||
}
|
||||
@ -231,7 +239,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
||||
n.stopInProc()
|
||||
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.stopIPC()
|
||||
n.stopInProc()
|
||||
@ -383,7 +391,7 @@ func (n *Node) stopHTTP() {
|
||||
}
|
||||
|
||||
// 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
|
||||
if endpoint == "" {
|
||||
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 {
|
||||
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)
|
||||
|
||||
// All listeners booted successfully
|
||||
n.wsEndpoint = endpoint
|
||||
n.wsListener = listener
|
||||
n.wsHandler = handler
|
||||
n.wsDomains = cors
|
||||
n.wsOrigins = wsOrigins
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -554,7 +554,7 @@ func TestAPIGather(t *testing.T) {
|
||||
{"multi.v2.nested_theOneMethod", "multi.v2.nested"},
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
20
rpc/http.go
20
rpc/http.go
@ -20,13 +20,12 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
@ -36,8 +35,9 @@ const (
|
||||
|
||||
// httpClient connects to a geth RPC server over HTTP.
|
||||
type httpClient struct {
|
||||
endpoint *url.URL // HTTP-RPC server endpoint
|
||||
lastRes []byte // HTTP requests are synchronous, store last response
|
||||
endpoint *url.URL // HTTP-RPC server endpoint
|
||||
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
|
||||
@ -57,30 +57,22 @@ func (client *httpClient) Send(msg interface{}) error {
|
||||
var err error
|
||||
|
||||
client.lastRes = nil
|
||||
|
||||
if body, err = json.Marshal(msg); err != nil {
|
||||
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 {
|
||||
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()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
client.lastRes, err = ioutil.ReadAll(resp.Body)
|
||||
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.
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/go-winio"
|
||||
winio "github.com/microsoft/go-winio"
|
||||
)
|
||||
|
||||
// ipcListen will create a named pipe on the given endpoint.
|
||||
|
@ -19,48 +19,15 @@ package rpc
|
||||
var (
|
||||
// Holds geth specific RPC extends which can be used to extend web3
|
||||
WEB3Extensions = map[string]string{
|
||||
"personal": Personal_JS,
|
||||
"txpool": TxPool_JS,
|
||||
"admin": Admin_JS,
|
||||
"eth": Eth_JS,
|
||||
"miner": Miner_JS,
|
||||
"debug": Debug_JS,
|
||||
"net": Net_JS,
|
||||
"txpool": TxPool_JS,
|
||||
"admin": Admin_JS,
|
||||
"eth": Eth_JS,
|
||||
"miner": Miner_JS,
|
||||
"debug": Debug_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 = `
|
||||
web3._extend({
|
||||
property: 'txpool',
|
||||
@ -124,22 +91,22 @@ web3._extend({
|
||||
new web3._extend.Method({
|
||||
name: 'startRPC',
|
||||
call: 'admin_startRPC',
|
||||
params: 4
|
||||
params: 4,
|
||||
inputFormatter: [null, null, null, null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'stopRPC',
|
||||
call: 'admin_stopRPC',
|
||||
params: 0
|
||||
call: 'admin_stopRPC'
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'startWS',
|
||||
call: 'admin_startWS',
|
||||
params: 4
|
||||
params: 4,
|
||||
inputFormatter: [null, null, null, null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'stopWS',
|
||||
call: 'admin_stopWS',
|
||||
params: 0
|
||||
call: 'admin_stopWS'
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setGlobalRegistrar',
|
||||
@ -219,7 +186,7 @@ web3._extend({
|
||||
name: 'sign',
|
||||
call: 'eth_sign',
|
||||
params: 2,
|
||||
inputFormatter: [web3._extend.utils.toAddress, null]
|
||||
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'resend',
|
||||
@ -422,19 +389,18 @@ web3._extend({
|
||||
new web3._extend.Method({
|
||||
name: 'start',
|
||||
call: 'miner_start',
|
||||
params: 1
|
||||
params: 1,
|
||||
inputFormatter: [null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'stop',
|
||||
call: 'miner_stop',
|
||||
params: 1
|
||||
call: 'miner_stop'
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setEtherbase',
|
||||
call: 'miner_setEtherbase',
|
||||
params: 1,
|
||||
inputFormatter: [web3._extend.formatters.formatInputInt],
|
||||
outputFormatter: web3._extend.formatters.formatOutputBool
|
||||
inputFormatter: [web3._extend.formatters.inputAddressFormatter]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setExtra',
|
||||
|
171
rpc/json.go
171
rpc/json.go
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -40,14 +41,14 @@ const (
|
||||
type JSONRequest struct {
|
||||
Method string `json:"method"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
Id json.RawMessage `json:"id,omitempty"`
|
||||
Payload json.RawMessage `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// JSON-RPC response
|
||||
type JSONSuccessResponse struct {
|
||||
Version string `json:"jsonrpc"`
|
||||
Id int64 `json:"id"`
|
||||
Id interface{} `json:"id,omitempty"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
@ -60,9 +61,9 @@ type JSONError struct {
|
||||
|
||||
// JSON-RPC error response
|
||||
type JSONErrResponse struct {
|
||||
Version string `json:"jsonrpc"`
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
Error JSONError `json:"error"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Id interface{} `json:"id,omitempty"`
|
||||
Error JSONError `json:"error"`
|
||||
}
|
||||
|
||||
// JSON-RPC notification payload
|
||||
@ -78,16 +79,16 @@ type jsonNotification struct {
|
||||
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.
|
||||
// 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 {
|
||||
closed chan interface{}
|
||||
closer sync.Once
|
||||
d *json.Decoder
|
||||
muEncoder sync.Mutex
|
||||
e *json.Encoder
|
||||
req JSONRequest
|
||||
rw io.ReadWriteCloser
|
||||
closer sync.Once // close closed channel once
|
||||
closed chan interface{} // closed on Close
|
||||
decMu sync.Mutex // guards d
|
||||
d *json.Decoder // decodes incoming requests
|
||||
encMu sync.Mutex // guards e
|
||||
e *json.Encoder // encodes responses
|
||||
rw io.ReadWriteCloser // connection
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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, RPCError) {
|
||||
c.decMu.Lock()
|
||||
defer c.decMu.Unlock()
|
||||
|
||||
var incomingMsg json.RawMessage
|
||||
if err := c.d.Decode(&incomingMsg); err != nil {
|
||||
return nil, false, &invalidRequestError{err.Error()}
|
||||
@ -124,21 +129,38 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
|
||||
return parseRequest(incomingMsg)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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, RPCError) {
|
||||
var in JSONRequest
|
||||
if err := json.Unmarshal(incomingMsg, &in); err != nil {
|
||||
return nil, false, &invalidMessageError{err.Error()}
|
||||
}
|
||||
|
||||
if in.Id == nil {
|
||||
return nil, false, &invalidMessageError{"Server cannot handle notifications"}
|
||||
if err := checkReqId(in.Id); err != nil {
|
||||
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 {
|
||||
reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}}
|
||||
reqs := []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true}}
|
||||
if len(in.Payload) > 0 {
|
||||
// first param must be subscription name
|
||||
var subscribeMethod [1]string
|
||||
@ -156,7 +178,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -167,10 +189,10 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
|
||||
}
|
||||
|
||||
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
|
||||
@ -183,14 +205,17 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro
|
||||
|
||||
requests := make([]rpcRequest, len(in))
|
||||
for i, r := range in {
|
||||
if r.Id == nil {
|
||||
return nil, true, &invalidMessageError{"Server cannot handle notifications"}
|
||||
if err := checkReqId(r.Id); err != nil {
|
||||
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 {
|
||||
requests[i] = rpcRequest{id: *r.Id, isPubSub: true}
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -217,9 +242,9 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
var cnt []interface{}
|
||||
if err := json.Unmarshal(args, &cnt); err != nil {
|
||||
return -1, nil
|
||||
}
|
||||
return len(cnt), nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// 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. Missing optional arguments
|
||||
// are returned as reflect.Zero values.
|
||||
func parsePositionalArguments(args json.RawMessage, callbackArgs []reflect.Type) ([]reflect.Value, RPCError) {
|
||||
params := make([]interface{}, 0, len(callbackArgs))
|
||||
for _, t := range callbackArgs {
|
||||
params = append(params, reflect.New(t).Interface())
|
||||
}
|
||||
|
||||
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 len(params) > len(callbackArgs) {
|
||||
return nil, &invalidParamsError{fmt.Sprintf("too many params, want %d got %d", len(callbackArgs), len(params))}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} {
|
||||
func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} {
|
||||
if isHexNum(reflect.TypeOf(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.
|
||||
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()}}
|
||||
}
|
||||
|
||||
// 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 *int64, err RPCError, info interface{}) interface{} {
|
||||
func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} {
|
||||
return &JSONErrResponse{Version: jsonRPCVersion, Id: id,
|
||||
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
|
||||
func (c *jsonCodec) Write(res interface{}) error {
|
||||
c.muEncoder.Lock()
|
||||
defer c.muEncoder.Unlock()
|
||||
c.encMu.Lock()
|
||||
defer c.encMu.Unlock()
|
||||
|
||||
return c.e.Encode(res)
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package rpc
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -51,8 +53,16 @@ func TestJSONRequestParsing(t *testing.T) {
|
||||
t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method)
|
||||
}
|
||||
|
||||
if requests[0].id != 1234 {
|
||||
t.Fatalf("Expected id 1234 but got %d", requests[0].id)
|
||||
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 %s", id)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("invalid request, expected *json.RawMesage got %T", requests[0].id)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@ -69,10 +68,6 @@ func (s *Service) Subscription(ctx context.Context) (Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Service) SubsriptionWithArgs(ctx context.Context, a, b int) (Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestServerRegisterName(t *testing.T) {
|
||||
server := NewServer()
|
||||
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))
|
||||
}
|
||||
|
||||
if len(svc.subscriptions) != 2 {
|
||||
t.Errorf("Expected 2 subscriptions for service 'calc', got %d", len(svc.subscriptions))
|
||||
if len(svc.subscriptions) != 1 {
|
||||
t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions))
|
||||
}
|
||||
}
|
||||
|
||||
// dummy codec used for testing RPC method execution
|
||||
type ServerTestCodec struct {
|
||||
counter int
|
||||
input []byte
|
||||
output string
|
||||
closer chan interface{}
|
||||
}
|
||||
func testServerMethodExecution(t *testing.T, method string) {
|
||||
server := NewServer()
|
||||
service := new(Service)
|
||||
|
||||
func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
|
||||
c.counter += 1
|
||||
|
||||
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
|
||||
if err := server.RegisterName("test", service); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
// requests are executes in parallel, wait a bit before returning an error so that the previous request has time to
|
||||
// be executed
|
||||
timer := time.NewTimer(time.Duration(2) * time.Second)
|
||||
<-timer.C
|
||||
|
||||
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)}
|
||||
stringArg := "string arg"
|
||||
intArg := 1122
|
||||
argsArg := &Args{"abcde"}
|
||||
params := []interface{}{stringArg, intArg, argsArg}
|
||||
|
||||
request := map[string]interface{}{
|
||||
"id": 12345,
|
||||
"method": "test_" + method,
|
||||
"version": "2.0",
|
||||
"params": params,
|
||||
}
|
||||
|
||||
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()
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
|
||||
// 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()
|
||||
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation)
|
||||
|
||||
// when not specified blockNumbers are by default latest (-1)
|
||||
if blockNumber, ok := params[i].(*BlockNumber); ok {
|
||||
*blockNumber = BlockNumber(-1)
|
||||
}
|
||||
out := json.NewEncoder(clientConn)
|
||||
in := json.NewDecoder(clientConn)
|
||||
|
||||
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 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 result.Int != intArg {
|
||||
t.Errorf("expected %d, got %d\n", intArg, result.Int)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServerTestCodec) Close() {
|
||||
close(c.closer)
|
||||
}
|
||||
|
||||
func (c *ServerTestCodec) Closed() <-chan interface{} {
|
||||
return c.closer
|
||||
}
|
||||
|
||||
func TestServerMethodExecution(t *testing.T) {
|
||||
server := NewServer()
|
||||
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)
|
||||
}
|
||||
testServerMethodExecution(t, "echo")
|
||||
}
|
||||
|
||||
func TestServerMethodWithCtx(t *testing.T) {
|
||||
server := NewServer()
|
||||
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)
|
||||
}
|
||||
testServerMethodExecution(t, "echoWithCtx")
|
||||
}
|
||||
|
51
rpc/types.go
51
rpc/types.go
@ -56,7 +56,7 @@ type service struct {
|
||||
|
||||
// serverRequest is an incoming request
|
||||
type serverRequest struct {
|
||||
id int64
|
||||
id interface{}
|
||||
svcname string
|
||||
rcvr reflect.Value
|
||||
callb *callback
|
||||
@ -85,7 +85,7 @@ type Server struct {
|
||||
type rpcRequest struct {
|
||||
service string
|
||||
method string
|
||||
id int64
|
||||
id interface{}
|
||||
isPubSub bool
|
||||
params interface{}
|
||||
}
|
||||
@ -106,12 +106,12 @@ type ServerCodec interface {
|
||||
ReadRequestHeaders() ([]rpcRequest, bool, RPCError)
|
||||
// Parse request argument to the given types
|
||||
ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError)
|
||||
// Assemble success response
|
||||
CreateResponse(int64, interface{}) interface{}
|
||||
// Assemble error response
|
||||
CreateErrorResponse(*int64, RPCError) interface{}
|
||||
// Assemble success response, expects response id and payload
|
||||
CreateResponse(interface{}, interface{}) interface{}
|
||||
// Assemble error response, expects response id and error
|
||||
CreateErrorResponse(interface{}, RPCError) interface{}
|
||||
// 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
|
||||
CreateNotification(string, interface{}) interface{}
|
||||
// Write msg to client.
|
||||
@ -207,43 +207,6 @@ func (h *HexNumber) BigInt() *big.Int {
|
||||
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 (
|
||||
pendingBlockNumber = big.NewInt(-2)
|
||||
latestBlockNumber = big.NewInt(-1)
|
||||
|
@ -232,7 +232,7 @@ func newSubscriptionID() (string, error) {
|
||||
// on which the given client connects.
|
||||
func SupportedModules(client Client) (map[string]string, error) {
|
||||
req := JSONRequest{
|
||||
Id: new(int64),
|
||||
Id: []byte("1"),
|
||||
Version: "2.0",
|
||||
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.
|
||||
func NewWSServer(cors string, handler *Server) *http.Server {
|
||||
func NewWSServer(allowedOrigins string, handler *Server) *http.Server {
|
||||
return &http.Server{
|
||||
Handler: websocket.Server{
|
||||
Handshake: wsHandshakeValidator(strings.Split(cors, ",")),
|
||||
Handshake: wsHandshakeValidator(strings.Split(allowedOrigins, ",")),
|
||||
Handler: func(conn *websocket.Conn) {
|
||||
handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}),
|
||||
OptionMethodInvocation|OptionSubscriptions)
|
||||
|
@ -84,7 +84,7 @@ type NewFilterArgs struct {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, whisperOffLineErr
|
||||
}
|
||||
@ -171,7 +171,7 @@ type PostArgs struct {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false, whisperOffLineErr
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user