From 8fd243ee2303946dd520dd87513bbe0ec4184301 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Tue, 17 Mar 2015 17:38:05 -0400 Subject: [PATCH 01/10] Add JSON RPC batch support http://www.jsonrpc.org/specification#batch --- rpc/http.go | 85 ++++++++++++++++++++++++++++++++--------------------- rpc/util.go | 20 +------------ 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index 8dcd55ad19..971032d713 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -1,6 +1,8 @@ package rpc import ( + "encoding/json" + "io/ioutil" "net/http" "github.com/ethereum/go-ethereum/logger" @@ -16,54 +18,71 @@ const ( // JSONRPC returns a handler that implements the Ethereum JSON-RPC API. func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { - var json JsonWrapper + var jsw JsonWrapper api := NewEthereumApi(pipe, dataDir) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // TODO this needs to be configurable w.Header().Set("Access-Control-Allow-Origin", "*") - rpchttplogger.DebugDetailln("Handling request") - + // Limit request size to resist DoS if req.ContentLength > maxSizeReqLength { jsonerr := &RpcErrorObject{-32700, "Request too large"} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + jsw.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) return } - reqParsed, reqerr := json.ParseRequestBody(req) - switch reqerr.(type) { - case nil: - break - case *DecodeParamError, *InsufficientParamsError, *ValidationError: - jsonerr := &RpcErrorObject{-32602, reqerr.Error()} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) - return - default: - jsonerr := &RpcErrorObject{-32700, "Could not parse request"} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + jsonerr := &RpcErrorObject{-32700, "Could not read request body"} + jsw.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + } + + // Try to parse the request as a single + var reqSingle RpcRequest + if err := json.Unmarshal(body, &reqSingle); err == nil { + response := RpcResponse(api, &reqSingle) + jsw.Send(w, &response) return } - var response interface{} - reserr := api.GetRequestReply(&reqParsed, &response) - switch reserr.(type) { - case nil: - break - case *NotImplementedError: - jsonerr := &RpcErrorObject{-32601, reserr.Error()} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) - return - case *DecodeParamError, *InsufficientParamsError, *ValidationError: - jsonerr := &RpcErrorObject{-32602, reserr.Error()} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) - return - default: - jsonerr := &RpcErrorObject{-32603, reserr.Error()} - json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) + // Try to parse the request to batch + var reqBatch []RpcRequest + if err := json.Unmarshal(body, &reqBatch); err == nil { + // Build response batch + resBatch := make([]*interface{}, len(reqBatch)) + for i, request := range reqBatch { + response := RpcResponse(api, &request) + resBatch[i] = response + } + jsw.Send(w, resBatch) return } - rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) - json.Send(w, &RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) + // Not a batch or single request, error + jsonerr := &RpcErrorObject{-32600, "Could not decode request"} + jsw.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) }) } + +func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} { + var reply, response interface{} + reserr := api.GetRequestReply(request, &reply) + switch reserr.(type) { + case nil: + response = &RpcSuccessResponse{JsonRpc: jsonrpcver, ID: request.ID, Result: reply} + case *NotImplementedError: + jsonerr := &RpcErrorObject{-32601, reserr.Error()} + response = &RpcErrorResponse{JsonRpc: jsonrpcver, ID: request.ID, Error: jsonerr} + case *DecodeParamError, *InsufficientParamsError, *ValidationError: + jsonerr := &RpcErrorObject{-32602, reserr.Error()} + response = &RpcErrorResponse{JsonRpc: jsonrpcver, ID: request.ID, Error: jsonerr} + default: + jsonerr := &RpcErrorObject{-32603, reserr.Error()} + response = &RpcErrorResponse{JsonRpc: jsonrpcver, ID: request.ID, Error: jsonerr} + } + + rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) + return &response +} diff --git a/rpc/util.go b/rpc/util.go index e5610dc2c2..6eb1faefb6 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -21,7 +21,7 @@ import ( "fmt" "io" "math/big" - "net/http" + // "net/http" "reflect" "time" @@ -106,24 +106,6 @@ func (self JsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) return writer.Write(payload) } -func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) { - var reqParsed RpcRequest - - // Convert JSON to native types - d := json.NewDecoder(req.Body) - defer req.Body.Close() - err := d.Decode(&reqParsed) - - if err != nil { - rpclogger.Errorln("Error decoding JSON: ", err) - return reqParsed, err - } - - rpclogger.DebugDetailf("Parsed request: %s", reqParsed) - - return reqParsed, nil -} - func toHex(b []byte) string { hex := common.Bytes2Hex(b) // Prefer output of "0x0" instead of "0x" From 67c9d9c2fa6d1cb74188dccf7e25422db72983c8 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Tue, 17 Mar 2015 20:14:19 -0400 Subject: [PATCH 02/10] Remove JsonWrapper --- rpc/http.go | 25 +++++++++++++++++++------ rpc/util.go | 16 ---------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index 0fe634dee2..5f2445e6c2 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -2,6 +2,7 @@ package rpc import ( "encoding/json" + "io" "io/ioutil" "net/http" @@ -18,7 +19,6 @@ const ( // JSONRPC returns a handler that implements the Ethereum JSON-RPC API. func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { - var jsw JsonWrapper api := NewEthereumApi(pipe, dataDir) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { @@ -28,22 +28,23 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { // Limit request size to resist DoS if req.ContentLength > maxSizeReqLength { jsonerr := &RpcErrorObject{-32700, "Request too large"} - jsw.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) return } + // Read request body defer req.Body.Close() body, err := ioutil.ReadAll(req.Body) if err != nil { jsonerr := &RpcErrorObject{-32700, "Could not read request body"} - jsw.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) } // Try to parse the request as a single var reqSingle RpcRequest if err := json.Unmarshal(body, &reqSingle); err == nil { response := RpcResponse(api, &reqSingle) - jsw.Send(w, &response) + Send(w, &response) return } @@ -56,13 +57,13 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { response := RpcResponse(api, &request) resBatch[i] = response } - jsw.Send(w, resBatch) + Send(w, resBatch) return } // Not a batch or single request, error jsonerr := &RpcErrorObject{-32600, "Could not decode request"} - jsw.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) }) } @@ -86,3 +87,15 @@ func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} { rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) return &response } + +func Send(writer io.Writer, v interface{}) (n int, err error) { + var payload []byte + payload, err = json.MarshalIndent(v, "", "\t") + if err != nil { + rpclogger.Fatalln("Error marshalling JSON", err) + return 0, err + } + rpclogger.DebugDetailf("Sending payload: %s", payload) + + return writer.Write(payload) +} diff --git a/rpc/util.go b/rpc/util.go index 6eb1faefb6..413ec48878 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -19,9 +19,7 @@ package rpc import ( "encoding/json" "fmt" - "io" "math/big" - // "net/http" "reflect" "time" @@ -33,8 +31,6 @@ import ( var rpclogger = logger.NewLogger("RPC") -type JsonWrapper struct{} - // Unmarshal state is a helper method which has the ability to decode messsages // that use the `defaultBlock` (https://github.com/ethereum/wiki/wiki/JSON-RPC#the-default-block-parameter) // For example a `call`: [{to: "0x....", data:"0x..."}, "latest"]. The first argument is the transaction @@ -94,18 +90,6 @@ func UnmarshalRawMessages(b []byte, iface interface{}, number *int64) (err error return nil } -func (self JsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) { - var payload []byte - payload, err = json.MarshalIndent(v, "", "\t") - if err != nil { - rpclogger.Fatalln("Error marshalling JSON", err) - return 0, err - } - rpclogger.DebugDetailf("Sending payload: %s", payload) - - return writer.Write(payload) -} - func toHex(b []byte) string { hex := common.Bytes2Hex(b) // Prefer output of "0x0" instead of "0x" From 7c9bc8517d92cd7fc75e0ea7c3eff16546a82702 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 11:08:49 -0400 Subject: [PATCH 03/10] Remove RpcServer --- rpc/util.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rpc/util.go b/rpc/util.go index 3209c4540e..230d0dcf1f 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -94,11 +94,6 @@ func i2hex(n int) string { return common.ToHex(big.NewInt(int64(n)).Bytes()) } -type RpcServer interface { - Start() - Stop() -} - type Log struct { Address string `json:"address"` Topic []string `json:"topic"` From 60c43d195289e65af2146d8655350b7f7dca66b7 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 11:10:08 -0400 Subject: [PATCH 04/10] Remove i2hex --- rpc/api.go | 4 ++-- rpc/util.go | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/rpc/api.go b/rpc/api.go index 57075bffc3..6154a0b286 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -161,7 +161,7 @@ func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) erro id = self.filterManager.InstallFilter(filter) self.logs[id] = &logFilter{timeout: time.Now()} - *reply = i2hex(id) + *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) return nil } @@ -198,7 +198,7 @@ func (self *EthereumApi) NewFilterString(args *FilterStringArgs, reply *interfac id = self.filterManager.InstallFilter(filter) self.logs[id] = &logFilter{timeout: time.Now()} - *reply = i2hex(id) + *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) return nil } diff --git a/rpc/util.go b/rpc/util.go index 230d0dcf1f..3e2792c075 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -19,7 +19,6 @@ package rpc import ( "encoding/json" "fmt" - "math/big" "reflect" "time" @@ -90,10 +89,6 @@ func UnmarshalRawMessages(b []byte, iface interface{}, number *int64) (err error return nil } -func i2hex(n int) string { - return common.ToHex(big.NewInt(int64(n)).Bytes()) -} - type Log struct { Address string `json:"address"` Topic []string `json:"topic"` From 7cbcd81ddce566d6d2eba0685e14f07a50683603 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 15:42:57 -0400 Subject: [PATCH 05/10] Remove UnmarshalRawMessages --- rpc/args.go | 187 +++++++++++++++++++++++++++++++++++----------------- rpc/util.go | 62 ----------------- 2 files changed, 125 insertions(+), 124 deletions(-) diff --git a/rpc/args.go b/rpc/args.go index fee44c4e09..86ffd0c3ae 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -8,10 +8,18 @@ import ( "github.com/ethereum/go-ethereum/common" ) -func blockNumber(raw json.RawMessage, number *int64) (err error) { - var str string - if err = json.Unmarshal(raw, &str); err != nil { - return NewDecodeParamError(err.Error()) +func blockAge(raw interface{}, number *int64) (err error) { + // Parse as integer + num, ok := raw.(int64) + if ok { + *number = num + return nil + } + + // Parse as string/hexstring + str, ok := raw.(string) + if !ok { + return NewDecodeParamError("BlockNumber is not a string") } switch str { @@ -22,6 +30,7 @@ func blockNumber(raw json.RawMessage, number *int64) (err error) { default: *number = common.String2Big(str).Int64() } + return nil } @@ -95,17 +104,43 @@ type NewTxArgs struct { } func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { - var obj struct{ From, To, Value, Gas, GasPrice, Data string } - if err = UnmarshalRawMessages(b, &obj, &args.BlockNumber); err != nil { - return err + var obj []json.RawMessage + var ext struct{ From, To, Value, Gas, GasPrice, Data string } + + // Decode byte slice to array of RawMessages + if err := json.Unmarshal(b, &obj); err != nil { + return NewDecodeParamError(err.Error()) } - args.From = obj.From - args.To = obj.To - args.Value = common.Big(obj.Value) - args.Gas = common.Big(obj.Gas) - args.GasPrice = common.Big(obj.GasPrice) - args.Data = obj.Data + // Check for sufficient params + if len(obj) < 1 { + return NewInsufficientParamsError(len(obj), 1) + } + + // Decode 0th RawMessage to temporary struct + if err := json.Unmarshal(obj[0], &ext); err != nil { + return NewDecodeParamError(err.Error()) + } + + // var ok bool + args.From = ext.From + args.To = ext.To + args.Value = common.String2Big(ext.Value) + args.Gas = common.String2Big(ext.Gas) + args.GasPrice = common.String2Big(ext.GasPrice) + args.Data = ext.Data + + // Check for optional BlockNumber param + if len(obj) > 1 { + var raw interface{} + if err = json.Unmarshal(obj[1], &raw); err != nil { + return NewDecodeParamError(err.Error()) + } + + if err := blockAge(raw, &args.BlockNumber); err != nil { + return err + } + } return nil } @@ -116,10 +151,27 @@ type GetStorageArgs struct { } func (args *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { - if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { return NewDecodeParamError(err.Error()) } + if len(obj) < 1 { + return NewInsufficientParamsError(len(obj), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return NewDecodeParamError("Address is not a string") + } + args.Address = addstr + + if len(obj) > 1 { + if err := blockAge(obj[1], &args.BlockNumber); err != nil { + return err + } + } + return nil } @@ -137,16 +189,32 @@ type GetStorageAtArgs struct { } func (args *GetStorageAtArgs) UnmarshalJSON(b []byte) (err error) { - var obj []string - if err = UnmarshalRawMessages(b, &obj, &args.BlockNumber); err != nil { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { return NewDecodeParamError(err.Error()) } + if len(obj) < 2 { return NewInsufficientParamsError(len(obj), 2) } - args.Address = obj[0] - args.Key = obj[1] + addstr, ok := obj[0].(string) + if !ok { + return NewDecodeParamError("Address is not a string") + } + args.Address = addstr + + keystr, ok := obj[1].(string) + if !ok { + return NewDecodeParamError("Key is not a string") + } + args.Key = keystr + + if len(obj) > 2 { + if err := blockAge(obj[2], &args.BlockNumber); err != nil { + return err + } + } return nil } @@ -168,10 +236,27 @@ type GetTxCountArgs struct { } func (args *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { - if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { return NewDecodeParamError(err.Error()) } + if len(obj) < 1 { + return NewInsufficientParamsError(len(obj), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return NewDecodeParamError("Address is not a string") + } + args.Address = addstr + + if len(obj) > 1 { + if err := blockAge(obj[1], &args.BlockNumber); err != nil { + return err + } + } + return nil } @@ -189,8 +274,7 @@ type GetBalanceArgs struct { func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { var obj []interface{} - r := bytes.NewReader(b) - if err := json.NewDecoder(r).Decode(&obj); err != nil { + if err := json.Unmarshal(b, &obj); err != nil { return NewDecodeParamError(err.Error()) } @@ -205,17 +289,11 @@ func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { args.Address = addstr if len(obj) > 1 { - if obj[1].(string) == "latest" { - args.BlockNumber = -1 - } else { - args.BlockNumber = common.Big(obj[1].(string)).Int64() + if err := blockAge(obj[1], &args.BlockNumber); err != nil { + return err } } - // if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { - // return NewDecodeParamError(err.Error()) - // } - return nil } @@ -232,10 +310,27 @@ type GetDataArgs struct { } func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) { - if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { return NewDecodeParamError(err.Error()) } + if len(obj) < 1 { + return NewInsufficientParamsError(len(obj), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return NewDecodeParamError("Address is not a string") + } + args.Address = addstr + + if len(obj) > 1 { + if err := blockAge(obj[1], &args.BlockNumber); err != nil { + return err + } + } + return nil } @@ -392,10 +487,6 @@ func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) { return nil } -// type FilterChangedArgs struct { -// n int -// } - type DbArgs struct { Database string Key string @@ -578,31 +669,3 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { return nil } - -// func (req *RpcRequest) ToRegisterArgs() (string, error) { -// if len(req.Params) < 1 { -// return "", errArguments -// } - -// var args string -// err := json.Unmarshal(req.Params, &args) -// if err != nil { -// return "", err -// } - -// return args, nil -// } - -// func (req *RpcRequest) ToWatchTxArgs() (string, error) { -// if len(req.Params) < 1 { -// return "", errArguments -// } - -// var args string -// err := json.Unmarshal(req.Params, &args) -// if err != nil { -// return "", err -// } - -// return args, nil -// } diff --git a/rpc/util.go b/rpc/util.go index 3e2792c075..0798ae1d22 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -17,9 +17,6 @@ package rpc import ( - "encoding/json" - "fmt" - "reflect" "time" "github.com/ethereum/go-ethereum/common" @@ -30,65 +27,6 @@ import ( var rpclogger = logger.NewLogger("RPC") -// Unmarshal state is a helper method which has the ability to decode messsages -// that use the `defaultBlock` (https://github.com/ethereum/wiki/wiki/JSON-RPC#the-default-block-parameter) -// For example a `call`: [{to: "0x....", data:"0x..."}, "latest"]. The first argument is the transaction -// message and the second one refers to the block height (or state) to which to apply this `call`. -func UnmarshalRawMessages(b []byte, iface interface{}, number *int64) (err error) { - var data []json.RawMessage - if err = json.Unmarshal(b, &data); err != nil && len(data) == 0 { - return NewDecodeParamError(err.Error()) - } - - // Hrm... Occurs when no params - if len(data) == 0 { - return NewDecodeParamError("No data") - } - - // Number index determines the index in the array for a possible block number - numberIndex := 0 - - value := reflect.ValueOf(iface) - rvalue := reflect.Indirect(value) - - switch rvalue.Kind() { - case reflect.Slice: - // This is a bit of a cheat, but `data` is expected to be larger than 2 if iface is a slice - if number != nil { - numberIndex = len(data) - 1 - } else { - numberIndex = len(data) - } - - slice := reflect.MakeSlice(rvalue.Type(), numberIndex, numberIndex) - for i, raw := range data[0:numberIndex] { - v := slice.Index(i).Interface() - if err = json.Unmarshal(raw, &v); err != nil { - fmt.Println(err, v) - return err - } - slice.Index(i).Set(reflect.ValueOf(v)) - } - reflect.Indirect(rvalue).Set(slice) //value.Set(slice) - case reflect.Struct: - fallthrough - default: - if err = json.Unmarshal(data[0], iface); err != nil { - return NewDecodeParamError(err.Error()) - } - numberIndex = 1 - } - - // <0 index means out of bound for block number - if numberIndex >= 0 && len(data) > numberIndex { - if err = blockNumber(data[numberIndex], number); err != nil { - return NewDecodeParamError(err.Error()) - } - } - - return nil -} - type Log struct { Address string `json:"address"` Topic []string `json:"topic"` From e02eedb43e4e59ae06b5560e3a5f5b7dafe33c04 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 15:48:34 -0400 Subject: [PATCH 06/10] BlockNumber as int fix --- rpc/args.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/args.go b/rpc/args.go index 86ffd0c3ae..992ea1eede 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -10,9 +10,9 @@ import ( func blockAge(raw interface{}, number *int64) (err error) { // Parse as integer - num, ok := raw.(int64) + num, ok := raw.(float64) if ok { - *number = num + *number = int64(num) return nil } From 0685810ec604134bdf5b4ea555722909f72a7f57 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 15:48:40 -0400 Subject: [PATCH 07/10] Tests --- rpc/args_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/rpc/args_test.go b/rpc/args_test.go index 61b9dad25e..872c535fae 100644 --- a/rpc/args_test.go +++ b/rpc/args_test.go @@ -43,6 +43,30 @@ func TestGetBalanceArgs(t *testing.T) { } } +func TestGetBalanceArgsLatest(t *testing.T) { + input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` + expected := new(GetBalanceArgs) + expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + expected.BlockNumber = -1 + + args := new(GetBalanceArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if args.Address != expected.Address { + t.Errorf("Address should be %v but is %v", expected.Address, args.Address) + } + + if args.BlockNumber != expected.BlockNumber { + t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) + } +} + func TestGetBalanceEmptyArgs(t *testing.T) { input := `[]` @@ -120,7 +144,8 @@ func TestNewTxArgs(t *testing.T) { "gas": "0x76c0", "gasPrice": "0x9184e72a000", "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}]` + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, + "0x10"]` expected := new(NewTxArgs) expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675" @@ -128,6 +153,7 @@ func TestNewTxArgs(t *testing.T) { expected.GasPrice = big.NewInt(10000000000000) expected.Value = big.NewInt(10000000000000) expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + expected.BlockNumber = big.NewInt(16).Int64() args := new(NewTxArgs) if err := json.Unmarshal([]byte(input), &args); err != nil { @@ -157,6 +183,30 @@ func TestNewTxArgs(t *testing.T) { if expected.Data != args.Data { t.Errorf("Data shoud be %#v but is %#v", expected.Data, args.Data) } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestNewTxArgsBlockInt(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}, 5]` + expected := new(NewTxArgs) + expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" + expected.BlockNumber = big.NewInt(5).Int64() + + args := new(NewTxArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.From != args.From { + t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } } func TestNewTxArgsEmpty(t *testing.T) { From 8b20c3cc976a149c64ef0ac2cabbe49e93ba739d Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Wed, 18 Mar 2015 20:30:09 -0400 Subject: [PATCH 08/10] Validate NewTx From field is not blank --- rpc/api.go | 5 +++++ rpc/args.go | 7 +++++++ rpc/args_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/rpc/api.go b/rpc/api.go index 6154a0b286..5d4e235d91 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -257,6 +257,11 @@ func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) (err error) p.register[ags.From] = append(p.register[args.From], args) } */ + + if err := args.requirements(); err != nil { + return err + } + // TODO: align default values to have the same type, e.g. not depend on // common.Value conversions later on if args.Gas.Cmp(big.NewInt(0)) == 0 { diff --git a/rpc/args.go b/rpc/args.go index 992ea1eede..ab1c405850 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -145,6 +145,13 @@ func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { return nil } +func (args *NewTxArgs) requirements() error { + if len(args.From) == 0 { + return NewValidationError("From", "Is required") + } + return nil +} + type GetStorageArgs struct { Address string BlockNumber int64 diff --git a/rpc/args_test.go b/rpc/args_test.go index 872c535fae..0d8dc4085f 100644 --- a/rpc/args_test.go +++ b/rpc/args_test.go @@ -219,6 +219,34 @@ func TestNewTxArgsEmpty(t *testing.T) { } } +func TestNewTxArgsReqs(t *testing.T) { + args := new(NewTxArgs) + args.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" + + err := args.requirements() + switch err.(type) { + case nil: + break + default: + t.Errorf("Get %T", err) + } +} + +func TestNewTxArgsReqsFromBlank(t *testing.T) { + args := new(NewTxArgs) + args.From = "" + + err := args.requirements() + switch err.(type) { + case nil: + t.Error("Expected error but didn't get one") + case *ValidationError: + break + default: + t.Error("Wrong type of error") + } +} + func TestGetStorageArgs(t *testing.T) { input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` expected := new(GetStorageArgs) From b52807f0c0ff1b89cc1d7172ce1b768a34d02b00 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 19 Mar 2015 15:36:00 +0100 Subject: [PATCH 09/10] Removed frontier in version :-( --- cmd/ethereum/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 7524af4b2c..38c04ec9d8 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -42,7 +42,7 @@ import ( const ( ClientIdentifier = "Ethereum(G)" - Version = "Frontier - 0.9.1" + Version = "0.9.1" ) var ( From 14a2f42f3700640f191e0095b50a266d2a919b38 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 19 Mar 2015 16:19:54 +0100 Subject: [PATCH 10/10] fixed chain event. Closes #529 --- core/block_processor.go | 20 ++++++++------------ core/chain_makers.go | 4 ++-- core/chain_manager.go | 10 +++++----- core/events.go | 25 +++++++++++++++++++++---- core/filter.go | 4 ++-- core/types/common.go | 8 ++++++-- event/filter/eth_filter.go | 6 +++--- rpc/api.go | 15 +++++++++------ 8 files changed, 56 insertions(+), 36 deletions(-) diff --git a/core/block_processor.go b/core/block_processor.go index f67d6d006b..62c9c92a66 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" @@ -17,10 +17,6 @@ import ( "gopkg.in/fatih/set.v0" ) -type PendingBlockEvent struct { - Block *types.Block -} - var statelogger = logger.NewLogger("BLOCK") type BlockProcessor struct { @@ -137,7 +133,7 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state block.Header().GasUsed = totalUsedGas if transientProcess { - go self.eventMux.Post(PendingBlockEvent{block}) + go self.eventMux.Post(PendingBlockEvent{block, statedb.Logs()}) } return receipts, handled, unhandled, erroneous, err @@ -146,25 +142,25 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state // Process block will attempt to process the given block's transactions and applies them // on top of the block's parent state (given it exists) and will return wether it was // successful or not. -func (sm *BlockProcessor) Process(block *types.Block) (td *big.Int, err error) { +func (sm *BlockProcessor) Process(block *types.Block) (td *big.Int, logs state.Logs, err error) { // Processing a blocks may never happen simultaneously sm.mutex.Lock() defer sm.mutex.Unlock() header := block.Header() if sm.bc.HasBlock(header.Hash()) { - return nil, &KnownBlockError{header.Number, header.Hash()} + return nil, nil, &KnownBlockError{header.Number, header.Hash()} } if !sm.bc.HasBlock(header.ParentHash) { - return nil, ParentError(header.ParentHash) + return nil, nil, ParentError(header.ParentHash) } parent := sm.bc.GetBlock(header.ParentHash) return sm.processWithParent(block, parent) } -func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big.Int, err error) { +func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big.Int, logs state.Logs, err error) { sm.lastAttemptedBlock = block // Create a new state based on the parent's root (e.g., create copy) @@ -177,7 +173,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big // There can be at most two uncles if len(block.Uncles()) > 2 { - return nil, ValidationError("Block can only contain one uncle (contained %v)", len(block.Uncles())) + return nil, nil, ValidationError("Block can only contain one uncle (contained %v)", len(block.Uncles())) } receipts, err := sm.TransitionState(state, parent, block, false) @@ -236,7 +232,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big chainlogger.Infof("processed block #%d (%x...)\n", header.Number, block.Hash()[0:4]) - return td, nil + return td, state.Logs(), nil } // Validates the current block. Returns an error if the block was invalid, diff --git a/core/chain_makers.go b/core/chain_makers.go index 59c297dbe7..d800dee34d 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -4,8 +4,8 @@ import ( "fmt" "math/big" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/state" @@ -93,7 +93,7 @@ func makeChain(bman *BlockProcessor, parent *types.Block, max int, db common.Dat blocks := make(types.Blocks, max) for i := 0; i < max; i++ { block := makeBlock(bman, parent, i, db, seed) - td, err := bman.processWithParent(block, parent) + td, _, err := bman.processWithParent(block, parent) if err != nil { fmt.Println("process with parent failed", err) panic(err) diff --git a/core/chain_manager.go b/core/chain_manager.go index ff91b04271..88518e62b4 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -6,8 +6,8 @@ import ( "math/big" "sync" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/rlp" @@ -411,7 +411,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { for i, block := range chain { // Call in to the block processor and check for errors. It's likely that if one block fails // all others will fail too (unless a known block is returned). - td, err := self.processor.Process(block) + td, logs, err := self.processor.Process(block) if err != nil { if IsKnownBlockErr(err) { continue @@ -437,7 +437,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, common.Big1)) < 0 { chainlogger.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, block.Hash()[:4], td, cblock.Header().Number, cblock.Hash()[:4], self.td) - queue[i] = ChainSplitEvent{block} + queue[i] = ChainSplitEvent{block, logs} queueEvent.splitCount++ } @@ -456,10 +456,10 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { self.setTransState(state.New(block.Root(), self.stateDb)) self.setTxState(state.New(block.Root(), self.stateDb)) - queue[i] = ChainEvent{block} + queue[i] = ChainEvent{block, logs} queueEvent.canonicalCount++ } else { - queue[i] = ChainSideEvent{block} + queue[i] = ChainSideEvent{block, logs} queueEvent.sideCount++ } } diff --git a/core/events.go b/core/events.go index 23678ef600..8c5fb592a6 100644 --- a/core/events.go +++ b/core/events.go @@ -1,6 +1,9 @@ package core -import "github.com/ethereum/go-ethereum/core/types" +import ( + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/state" +) // TxPreEvent is posted when a transaction enters the transaction pool. type TxPreEvent struct{ Tx *types.Transaction } @@ -15,11 +18,25 @@ type NewBlockEvent struct{ Block *types.Block } type NewMinedBlockEvent struct{ Block *types.Block } // ChainSplit is posted when a new head is detected -type ChainSplitEvent struct{ Block *types.Block } +type ChainSplitEvent struct { + Block *types.Block + Logs state.Logs +} -type ChainEvent struct{ Block *types.Block } +type ChainEvent struct { + Block *types.Block + Logs state.Logs +} -type ChainSideEvent struct{ Block *types.Block } +type ChainSideEvent struct { + Block *types.Block + Logs state.Logs +} + +type PendingBlockEvent struct { + Block *types.Block + Logs state.Logs +} type ChainHeadEvent struct{ Block *types.Block } diff --git a/core/filter.go b/core/filter.go index d58aa8d7cb..487e82902e 100644 --- a/core/filter.go +++ b/core/filter.go @@ -33,8 +33,8 @@ type Filter struct { max int topics [][][]byte - BlockCallback func(*types.Block) - PendingCallback func(*types.Block) + BlockCallback func(*types.Block, state.Logs) + PendingCallback func(*types.Block, state.Logs) LogsCallback func(state.Logs) } diff --git a/core/types/common.go b/core/types/common.go index 7953749594..def7936a50 100644 --- a/core/types/common.go +++ b/core/types/common.go @@ -1,7 +1,11 @@ package types -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/state" +) type BlockProcessor interface { - Process(*Block) (*big.Int, error) + Process(*Block) (*big.Int, state.Logs, error) } diff --git a/event/filter/eth_filter.go b/event/filter/eth_filter.go index 4ba66a7e07..cb75d7e1a0 100644 --- a/event/filter/eth_filter.go +++ b/event/filter/eth_filter.go @@ -63,7 +63,7 @@ func (self *FilterManager) filterLoop() { // Subscribe to events events := self.eventMux.Subscribe( core.PendingBlockEvent{}, - //core.ChainEvent{}, + core.ChainEvent{}, state.Logs(nil)) out: @@ -77,7 +77,7 @@ out: self.filterMu.RLock() for _, filter := range self.filters { if filter.BlockCallback != nil { - filter.BlockCallback(event.Block) + filter.BlockCallback(event.Block, event.Logs) } } self.filterMu.RUnlock() @@ -86,7 +86,7 @@ out: self.filterMu.RLock() for _, filter := range self.filters { if filter.PendingCallback != nil { - filter.PendingCallback(event.Block) + filter.PendingCallback(event.Block, event.Logs) } } self.filterMu.RUnlock() diff --git a/rpc/api.go b/rpc/api.go index 5d4e235d91..a0c12f8379 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -86,7 +86,7 @@ func (self *EthereumApi) getStateWithNum(num int64) *xeth.State { } func (self *EthereumApi) start() { - timer := time.NewTicker(filterTickerTime) + timer := time.NewTicker(2 * time.Second) done: for { select { @@ -94,20 +94,20 @@ done: self.logMut.Lock() self.messagesMut.Lock() for id, filter := range self.logs { - if time.Since(filter.timeout) > 20*time.Second { + if time.Since(filter.timeout) > filterTickerTime { self.filterManager.UninstallFilter(id) delete(self.logs, id) } } for id, filter := range self.messages { - if time.Since(filter.timeout) > 20*time.Second { + if time.Since(filter.timeout) > filterTickerTime { self.xeth().Whisper().Unwatch(id) delete(self.messages, id) } } - self.logMut.Unlock() self.messagesMut.Unlock() + self.logMut.Unlock() case <-self.quit: break done } @@ -180,10 +180,13 @@ func (self *EthereumApi) NewFilterString(args *FilterStringArgs, reply *interfac var id int filter := core.NewFilter(self.xeth().Backend()) - callback := func(block *types.Block) { + callback := func(block *types.Block, logs state.Logs) { self.logMut.Lock() defer self.logMut.Unlock() + for _, log := range logs { + self.logs[id].add(log) + } self.logs[id].add(&state.StateLog{}) } @@ -483,7 +486,7 @@ func (p *EthereumApi) GetBlockUncleCountByNumber(blocknum int64) (int64, error) func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error { // Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC - rpclogger.Debugf("%s %s", req.Method, req.Params) + rpclogger.Infof("%s %s", req.Method, req.Params) switch req.Method { case "web3_sha3": args := new(Sha3Args)