diff options
Diffstat (limited to 'rpc')
-rw-r--r-- | rpc/api.go | 24 | ||||
-rw-r--r-- | rpc/args.go | 87 | ||||
-rw-r--r-- | rpc/args_test.go | 277 | ||||
-rw-r--r-- | rpc/http.go | 30 | ||||
-rw-r--r-- | rpc/messages.go | 93 | ||||
-rw-r--r-- | rpc/responses.go | 312 | ||||
-rw-r--r-- | rpc/responses_test.go | 175 |
7 files changed, 697 insertions, 301 deletions
diff --git a/rpc/api.go b/rpc/api.go index 502079177..c046c22fe 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -54,7 +54,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err case "net_peerCount": v := api.xeth().PeerCount() *reply = common.ToHex(big.NewInt(int64(v)).Bytes()) - case "eth_version": + case "eth_protocolVersion": *reply = api.xeth().EthVersion() case "eth_coinbase": // TODO handling of empty coinbase due to lack of accounts @@ -159,7 +159,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err } *reply = v case "eth_call": - args := new(NewTxArgs) + args := new(CallArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } @@ -199,9 +199,13 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err args := new(HashIndexArgs) if err := json.Unmarshal(req.Params, &args); err != nil { } - tx := api.xeth().EthTransactionByHash(args.Hash) + tx, bhash, bnum, txi := api.xeth().EthTransactionByHash(args.Hash) if tx != nil { - *reply = NewTransactionRes(tx) + v := NewTransactionRes(tx) + v.BlockHash = newHexData(bhash) + v.BlockNumber = newHexNum(bnum) + v.TxIndex = newHexNum(txi) + *reply = v } case "eth_getTransactionByBlockHashAndIndex": args := new(HashIndexArgs) @@ -213,7 +217,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err br := NewBlockRes(block) br.fullTx = true - if args.Index > int64(len(br.Transactions)) || args.Index < 0 { + if args.Index >= int64(len(br.Transactions)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } *reply = br.Transactions[args.Index] @@ -227,7 +231,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err v := NewBlockRes(block) v.fullTx = true - if args.Index > int64(len(v.Transactions)) || args.Index < 0 { + if args.Index >= int64(len(v.Transactions)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } *reply = v.Transactions[args.Index] @@ -239,12 +243,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err br := NewBlockRes(api.xeth().EthBlockByHash(args.Hash)) - if args.Index > int64(len(br.Uncles)) || args.Index < 0 { + if args.Index >= int64(len(br.Uncles)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } uhash := br.Uncles[args.Index] - uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.Hex())) + uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.String())) *reply = uncle case "eth_getUncleByBlockNumberAndIndex": @@ -257,12 +261,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err v := NewBlockRes(block) v.fullTx = true - if args.Index > int64(len(v.Uncles)) || args.Index < 0 { + if args.Index >= int64(len(v.Uncles)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } uhash := v.Uncles[args.Index] - uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.Hex())) + uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.String())) *reply = uncle case "eth_getCompilers": diff --git a/rpc/args.go b/rpc/args.go index 25a6c7a4f..dd013147d 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -238,6 +238,93 @@ func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { return nil } +type CallArgs struct { + From string + To string + Value *big.Int + Gas *big.Int + GasPrice *big.Int + Data string + + BlockNumber int64 +} + +func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { + var obj []json.RawMessage + var ext struct { + From string + To string + Value interface{} + Gas interface{} + GasPrice interface{} + Data string + } + + // Decode byte slice to array of RawMessages + if err := json.Unmarshal(b, &obj); err != nil { + return NewDecodeParamError(err.Error()) + } + + // 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()) + } + + if len(ext.From) == 0 { + return NewValidationError("from", "is required") + } + args.From = ext.From + + if len(ext.To) == 0 { + return NewValidationError("to", "is required") + } + args.To = ext.To + + var num int64 + if ext.Value == nil { + num = int64(0) + } else { + if err := numString(ext.Value, &num); err != nil { + return err + } + } + args.Value = big.NewInt(num) + + if ext.Gas == nil { + num = int64(0) + } else { + if err := numString(ext.Gas, &num); err != nil { + return err + } + } + args.Gas = big.NewInt(num) + + if ext.GasPrice == nil { + num = int64(0) + } else { + if err := numString(ext.GasPrice, &num); err != nil { + return err + } + } + args.GasPrice = big.NewInt(num) + + args.Data = ext.Data + + // Check for optional BlockNumber param + if len(obj) > 1 { + if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil { + return err + } + } + + return nil +} + type GetStorageArgs struct { Address string BlockNumber int64 diff --git a/rpc/args_test.go b/rpc/args_test.go index 602631b67..3635882c0 100644 --- a/rpc/args_test.go +++ b/rpc/args_test.go @@ -531,14 +531,275 @@ func TestNewTxArgsFromEmpty(t *testing.T) { input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` args := new(NewTxArgs) - err := json.Unmarshal([]byte(input), &args) - switch err.(type) { - case nil: - t.Error("Expected error but didn't get one") - case *ValidationError: - break - default: - t.Errorf("Expected *rpc.ValidationError, but got %T with message `%s`", err, err.Error()) + str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgs(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, + "0x10"]` + expected := new(CallArgs) + expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" + expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675" + expected.Gas = big.NewInt(30400) + expected.GasPrice = big.NewInt(10000000000000) + expected.Value = big.NewInt(10000000000000) + expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + expected.BlockNumber = big.NewInt(16).Int64() + + args := new(CallArgs) + 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.To != args.To { + t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) + } + + if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { + t.Errorf("Gas shoud be %#v but is %#v", expected.Gas.Bytes(), args.Gas.Bytes()) + } + + if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { + t.Errorf("GasPrice shoud be %#v but is %#v", expected.GasPrice, args.GasPrice) + } + + if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { + t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) + } + + 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 TestCallArgsInt(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": 100, + "gasPrice": 50, + "value": 8765456789, + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, + 5]` + expected := new(CallArgs) + expected.Gas = big.NewInt(100) + expected.GasPrice = big.NewInt(50) + expected.Value = big.NewInt(8765456789) + expected.BlockNumber = int64(5) + + args := new(CallArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { + t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) + } + + if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { + t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) + } + + if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { + t.Errorf("Value shoud be %v but is %v", expected.Value, args.Value) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %v but is %v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestCallArgsBlockBool(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, + false]` + + args := new(CallArgs) + str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsGasInvalid(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": false, + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsGaspriceInvalid(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": false, + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsValueInvalid(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": false, + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsGasMissing(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + expected := new(CallArgs) + expected.Gas = big.NewInt(0) + + if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { + t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) + } + +} + +func TestCallArgsBlockGaspriceMissing(t *testing.T) { + input := `[{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + expected := new(CallArgs) + expected.GasPrice = big.NewInt(0) + + if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { + t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) + } +} + +func TestCallArgsValueMissing(t *testing.T) { + input := `[{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }]` + + args := new(CallArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + expected := new(CallArgs) + expected.Value = big.NewInt(int64(0)) + + if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { + t.Errorf("GasPrice shoud be %v but is %v", expected.Value, args.Value) + } +} + +func TestCallArgsEmpty(t *testing.T) { + input := `[]` + + args := new(CallArgs) + str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsInvalid(t *testing.T) { + input := `{}` + + args := new(CallArgs) + str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} +func TestCallArgsNotStrings(t *testing.T) { + input := `[{"from":6}]` + + args := new(CallArgs) + str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsFromEmpty(t *testing.T) { + input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` + + args := new(CallArgs) + str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) + } +} + +func TestCallArgsToEmpty(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` + + args := new(CallArgs) + str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) + if len(str) > 0 { + t.Error(str) } } diff --git a/rpc/http.go b/rpc/http.go index 919c567bd..f15d557ad 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -2,12 +2,15 @@ package rpc import ( "encoding/json" + "fmt" "io" "io/ioutil" + "net" "net/http" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/xeth" + "github.com/rs/cors" ) var rpclogger = logger.NewLogger("RPC") @@ -17,13 +20,36 @@ const ( maxSizeReqLength = 1024 * 1024 // 1MB ) +func Start(pipe *xeth.XEth, config RpcConfig) error { + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort)) + if err != nil { + rpclogger.Errorf("Can't listen on %s:%d: %v", config.ListenAddress, config.ListenPort, err) + return err + } + + var handler http.Handler + if len(config.CorsDomain) > 0 { + var opts cors.Options + opts.AllowedMethods = []string{"POST"} + opts.AllowedOrigins = []string{config.CorsDomain} + + c := cors.New(opts) + handler = c.Handler(JSONRPC(pipe)) + } else { + handler = JSONRPC(pipe) + } + + go http.Serve(l, handler) + + return nil +} + // JSONRPC returns a handler that implements the Ethereum JSON-RPC API. func JSONRPC(pipe *xeth.XEth) http.Handler { api := NewEthereumApi(pipe) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // TODO this needs to be configurable - w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Content-Type", "application/json") // Limit request size to resist DoS if req.ContentLength > maxSizeReqLength { diff --git a/rpc/messages.go b/rpc/messages.go index 5c498234f..3c011cd93 100644 --- a/rpc/messages.go +++ b/rpc/messages.go @@ -19,8 +19,101 @@ package rpc import ( "encoding/json" "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" ) +type hexdata struct { + data []byte +} + +func (d *hexdata) String() string { + return "0x" + common.Bytes2Hex(d.data) +} + +func (d *hexdata) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +func (d *hexdata) UnmarshalJSON(b []byte) (err error) { + d.data = common.FromHex(string(b)) + return nil +} + +func newHexData(input interface{}) *hexdata { + d := new(hexdata) + + switch input := input.(type) { + case []byte: + d.data = input + case common.Hash: + d.data = input.Bytes() + case *common.Hash: + d.data = input.Bytes() + case common.Address: + d.data = input.Bytes() + case *common.Address: + d.data = input.Bytes() + case *big.Int: + d.data = input.Bytes() + case int64: + d.data = big.NewInt(input).Bytes() + case uint64: + d.data = big.NewInt(int64(input)).Bytes() + case int: + d.data = big.NewInt(int64(input)).Bytes() + case uint: + d.data = big.NewInt(int64(input)).Bytes() + case string: // hexstring + d.data = common.Big(input).Bytes() + default: + d.data = nil + } + + return d +} + +type hexnum struct { + data []byte +} + +func (d *hexnum) String() string { + // Get hex string from bytes + out := common.Bytes2Hex(d.data) + // Trim leading 0s + out = strings.Trim(out, "0") + // Output "0x0" when value is 0 + if len(out) == 0 { + out = "0" + } + return "0x" + out +} + +func (d *hexnum) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +func (d *hexnum) UnmarshalJSON(b []byte) (err error) { + d.data = common.FromHex(string(b)) + return nil +} + +func newHexNum(input interface{}) *hexnum { + d := new(hexnum) + + d.data = newHexData(input).data + + return d +} + +type RpcConfig struct { + ListenAddress string + ListenPort uint + CorsDomain string +} + type InvalidTypeError struct { method string msg string diff --git a/rpc/responses.go b/rpc/responses.go index 9767cac3b..3e9293fbb 100644 --- a/rpc/responses.go +++ b/rpc/responses.go @@ -1,11 +1,6 @@ package rpc import ( - "encoding/json" - // "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" ) @@ -13,84 +8,25 @@ import ( type BlockRes struct { fullTx bool - BlockNumber *big.Int `json:"number"` - BlockHash common.Hash `json:"hash"` - ParentHash common.Hash `json:"parentHash"` - Nonce [8]byte `json:"nonce"` - Sha3Uncles common.Hash `json:"sha3Uncles"` - LogsBloom types.Bloom `json:"logsBloom"` - TransactionRoot common.Hash `json:"transactionRoot"` - StateRoot common.Hash `json:"stateRoot"` - Miner common.Address `json:"miner"` - Difficulty *big.Int `json:"difficulty"` - TotalDifficulty *big.Int `json:"totalDifficulty"` - Size *big.Int `json:"size"` - ExtraData []byte `json:"extraData"` - GasLimit *big.Int `json:"gasLimit"` - MinGasPrice int64 `json:"minGasPrice"` - GasUsed *big.Int `json:"gasUsed"` - UnixTimestamp int64 `json:"timestamp"` + BlockNumber *hexnum `json:"number"` + BlockHash *hexdata `json:"hash"` + ParentHash *hexdata `json:"parentHash"` + Nonce *hexnum `json:"nonce"` + Sha3Uncles *hexdata `json:"sha3Uncles"` + LogsBloom *hexdata `json:"logsBloom"` + TransactionRoot *hexdata `json:"transactionRoot"` + StateRoot *hexdata `json:"stateRoot"` + Miner *hexdata `json:"miner"` + Difficulty *hexnum `json:"difficulty"` + TotalDifficulty *hexnum `json:"totalDifficulty"` + Size *hexnum `json:"size"` + ExtraData *hexdata `json:"extraData"` + GasLimit *hexnum `json:"gasLimit"` + MinGasPrice *hexnum `json:"minGasPrice"` + GasUsed *hexnum `json:"gasUsed"` + UnixTimestamp *hexnum `json:"timestamp"` Transactions []*TransactionRes `json:"transactions"` - Uncles []common.Hash `json:"uncles"` -} - -func (b *BlockRes) MarshalJSON() ([]byte, error) { - var ext struct { - BlockNumber string `json:"number"` - BlockHash string `json:"hash"` - ParentHash string `json:"parentHash"` - Nonce string `json:"nonce"` - Sha3Uncles string `json:"sha3Uncles"` - LogsBloom string `json:"logsBloom"` - TransactionRoot string `json:"transactionRoot"` - StateRoot string `json:"stateRoot"` - Miner string `json:"miner"` - Difficulty string `json:"difficulty"` - TotalDifficulty string `json:"totalDifficulty"` - Size string `json:"size"` - ExtraData string `json:"extraData"` - GasLimit string `json:"gasLimit"` - MinGasPrice string `json:"minGasPrice"` - GasUsed string `json:"gasUsed"` - UnixTimestamp string `json:"timestamp"` - Transactions []interface{} `json:"transactions"` - Uncles []string `json:"uncles"` - } - - // convert strict types to hexified strings - ext.BlockNumber = common.ToHex(b.BlockNumber.Bytes()) - ext.BlockHash = b.BlockHash.Hex() - ext.ParentHash = b.ParentHash.Hex() - ext.Nonce = common.ToHex(b.Nonce[:]) - ext.Sha3Uncles = b.Sha3Uncles.Hex() - ext.LogsBloom = common.ToHex(b.LogsBloom[:]) - ext.TransactionRoot = b.TransactionRoot.Hex() - ext.StateRoot = b.StateRoot.Hex() - ext.Miner = b.Miner.Hex() - ext.Difficulty = common.ToHex(b.Difficulty.Bytes()) - ext.TotalDifficulty = common.ToHex(b.TotalDifficulty.Bytes()) - ext.Size = common.ToHex(b.Size.Bytes()) - ext.ExtraData = common.ToHex(b.ExtraData) - ext.GasLimit = common.ToHex(b.GasLimit.Bytes()) - // ext.MinGasPrice = common.ToHex(big.NewInt(b.MinGasPrice).Bytes()) - ext.GasUsed = common.ToHex(b.GasUsed.Bytes()) - ext.UnixTimestamp = common.ToHex(big.NewInt(b.UnixTimestamp).Bytes()) - ext.Transactions = make([]interface{}, len(b.Transactions)) - if b.fullTx { - for i, tx := range b.Transactions { - ext.Transactions[i] = tx - } - } else { - for i, tx := range b.Transactions { - ext.Transactions[i] = tx.Hash.Hex() - } - } - ext.Uncles = make([]string, len(b.Uncles)) - for i, v := range b.Uncles { - ext.Uncles[i] = v.Hex() - } - - return json.Marshal(ext) + Uncles []*hexdata `json:"uncles"` } func NewBlockRes(block *types.Block) *BlockRes { @@ -99,158 +35,116 @@ func NewBlockRes(block *types.Block) *BlockRes { } res := new(BlockRes) - res.BlockNumber = block.Number() - res.BlockHash = block.Hash() - res.ParentHash = block.ParentHash() - res.Nonce = block.Header().Nonce - res.Sha3Uncles = block.Header().UncleHash - res.LogsBloom = block.Bloom() - res.TransactionRoot = block.Header().TxHash - res.StateRoot = block.Root() - res.Miner = block.Header().Coinbase - res.Difficulty = block.Difficulty() - res.TotalDifficulty = block.Td - res.Size = big.NewInt(int64(block.Size())) - res.ExtraData = []byte(block.Header().Extra) - res.GasLimit = block.GasLimit() + res.BlockNumber = newHexNum(block.Number()) + res.BlockHash = newHexData(block.Hash()) + res.ParentHash = newHexData(block.ParentHash()) + res.Nonce = newHexNum(block.Header().Nonce) + res.Sha3Uncles = newHexData(block.Header().UncleHash) + res.LogsBloom = newHexData(block.Bloom()) + res.TransactionRoot = newHexData(block.Header().TxHash) + res.StateRoot = newHexData(block.Root()) + res.Miner = newHexData(block.Header().Coinbase) + res.Difficulty = newHexNum(block.Difficulty()) + res.TotalDifficulty = newHexNum(block.Td) + res.Size = newHexNum(block.Size()) + res.ExtraData = newHexData(block.Header().Extra) + res.GasLimit = newHexNum(block.GasLimit()) // res.MinGasPrice = - res.GasUsed = block.GasUsed() - res.UnixTimestamp = block.Time() - res.Transactions = make([]*TransactionRes, len(block.Transactions())) - for i, tx := range block.Transactions() { - v := NewTransactionRes(tx) - v.BlockHash = block.Hash() - v.BlockNumber = block.Number().Int64() - v.TxIndex = int64(i) - res.Transactions[i] = v - } - res.Uncles = make([]common.Hash, len(block.Uncles())) + res.GasUsed = newHexNum(block.GasUsed()) + res.UnixTimestamp = newHexNum(block.Time()) + res.Transactions = NewTransactionsRes(block.Transactions()) + res.Uncles = make([]*hexdata, len(block.Uncles())) for i, uncle := range block.Uncles() { - res.Uncles[i] = uncle.Hash() + res.Uncles[i] = newHexData(uncle.Hash()) } return res } type TransactionRes struct { - Hash common.Hash `json:"hash"` - Nonce uint64 `json:"nonce"` - BlockHash common.Hash `json:"blockHash,omitempty"` - BlockNumber int64 `json:"blockNumber,omitempty"` - TxIndex int64 `json:"transactionIndex,omitempty"` - From common.Address `json:"from"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Gas *big.Int `json:"gas"` - GasPrice *big.Int `json:"gasPrice"` - Input []byte `json:"input"` -} - -func (t *TransactionRes) MarshalJSON() ([]byte, error) { - var ext struct { - Hash string `json:"hash"` - Nonce string `json:"nonce"` - BlockHash string `json:"blockHash,omitempty"` - BlockNumber string `json:"blockNumber,omitempty"` - TxIndex string `json:"transactionIndex,omitempty"` - From string `json:"from"` - To interface{} `json:"to"` - Value string `json:"value"` - Gas string `json:"gas"` - GasPrice string `json:"gasPrice"` - Input string `json:"input"` - } - - ext.Hash = t.Hash.Hex() - ext.Nonce = common.ToHex(big.NewInt(int64(t.Nonce)).Bytes()) - ext.BlockHash = t.BlockHash.Hex() - ext.BlockNumber = common.ToHex(big.NewInt(t.BlockNumber).Bytes()) - ext.TxIndex = common.ToHex(big.NewInt(t.TxIndex).Bytes()) - ext.From = t.From.Hex() - if t.To == nil { - ext.To = nil - } else { - ext.To = t.To.Hex() - } - ext.Value = common.ToHex(t.Value.Bytes()) - ext.Gas = common.ToHex(t.Gas.Bytes()) - ext.GasPrice = common.ToHex(t.GasPrice.Bytes()) - ext.Input = common.ToHex(t.Input) - - return json.Marshal(ext) + Hash *hexdata `json:"hash"` + Nonce *hexnum `json:"nonce"` + BlockHash *hexdata `json:"blockHash"` + BlockNumber *hexnum `json:"blockNumber"` + TxIndex *hexnum `json:"transactionIndex"` + From *hexdata `json:"from"` + To *hexdata `json:"to"` + Value *hexnum `json:"value"` + Gas *hexnum `json:"gas"` + GasPrice *hexnum `json:"gasPrice"` + Input *hexdata `json:"input"` } func NewTransactionRes(tx *types.Transaction) *TransactionRes { var v = new(TransactionRes) - v.Hash = tx.Hash() - v.Nonce = tx.Nonce() - v.From, _ = tx.From() - v.To = tx.To() - v.Value = tx.Value() - v.Gas = tx.Gas() - v.GasPrice = tx.GasPrice() - v.Input = tx.Data() + v.Hash = newHexData(tx.Hash()) + v.Nonce = newHexNum(tx.Nonce()) + // v.BlockHash = + // v.BlockNumber = + // v.TxIndex = + from, _ := tx.From() + v.From = newHexData(from) + v.To = newHexData(tx.To()) + v.Value = newHexNum(tx.Value()) + v.Gas = newHexNum(tx.Gas()) + v.GasPrice = newHexNum(tx.GasPrice()) + v.Input = newHexData(tx.Data()) return v } -type FilterLogRes struct { - Hash string `json:"hash"` - Address string `json:"address"` - Data string `json:"data"` - BlockNumber string `json:"blockNumber"` - TransactionHash string `json:"transactionHash"` - BlockHash string `json:"blockHash"` - TransactionIndex string `json:"transactionIndex"` - LogIndex string `json:"logIndex"` +func NewTransactionsRes(txs []*types.Transaction) []*TransactionRes { + v := make([]*TransactionRes, len(txs)) + for i, tx := range txs { + v[i] = NewTransactionRes(tx) + } + return v } -type FilterWhisperRes struct { - Hash string `json:"hash"` - From string `json:"from"` - To string `json:"to"` - Expiry string `json:"expiry"` - Sent string `json:"sent"` - Ttl string `json:"ttl"` - Topics string `json:"topics"` - Payload string `json:"payload"` - WorkProved string `json:"workProved"` -} +// type FilterLogRes struct { +// Hash string `json:"hash"` +// Address string `json:"address"` +// Data string `json:"data"` +// BlockNumber string `json:"blockNumber"` +// TransactionHash string `json:"transactionHash"` +// BlockHash string `json:"blockHash"` +// TransactionIndex string `json:"transactionIndex"` +// LogIndex string `json:"logIndex"` +// } + +// type FilterWhisperRes struct { +// Hash string `json:"hash"` +// From string `json:"from"` +// To string `json:"to"` +// Expiry string `json:"expiry"` +// Sent string `json:"sent"` +// Ttl string `json:"ttl"` +// Topics string `json:"topics"` +// Payload string `json:"payload"` +// WorkProved string `json:"workProved"` +// } type LogRes struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data []byte `json:"data"` - Number uint64 `json:"number"` + Address *hexdata `json:"address"` + Topics []*hexdata `json:"topics"` + Data *hexdata `json:"data"` + BlockNumber *hexnum `json:"blockNumber"` + Hash *hexdata `json:"hash"` + LogIndex *hexnum `json:"logIndex"` + BlockHash *hexdata `json:"blockHash"` + TransactionHash *hexdata `json:"transactionHash"` + TransactionIndex *hexnum `json:"transactionIndex"` } func NewLogRes(log state.Log) LogRes { var l LogRes - l.Topics = make([]common.Hash, len(log.Topics())) - l.Address = log.Address() - l.Data = log.Data() - l.Number = log.Number() + l.Topics = make([]*hexdata, len(log.Topics())) for j, topic := range log.Topics() { - l.Topics[j] = topic - } - return l -} - -func (l *LogRes) MarshalJSON() ([]byte, error) { - var ext struct { - Address string `json:"address"` - Topics []string `json:"topics"` - Data string `json:"data"` - Number string `json:"number"` + l.Topics[j] = newHexData(topic) } + l.Address = newHexData(log.Address()) + l.Data = newHexData(log.Data()) + l.BlockNumber = newHexNum(log.Number()) - ext.Address = l.Address.Hex() - ext.Data = common.ToHex(l.Data) - ext.Number = common.ToHex(big.NewInt(int64(l.Number)).Bytes()) - ext.Topics = make([]string, len(l.Topics)) - for i, v := range l.Topics { - ext.Topics[i] = v.Hex() - } - - return json.Marshal(ext) + return l } func NewLogsRes(logs state.Logs) (ls []LogRes) { diff --git a/rpc/responses_test.go b/rpc/responses_test.go index 278939830..5c6c6a895 100644 --- a/rpc/responses_test.go +++ b/rpc/responses_test.go @@ -2,7 +2,9 @@ package rpc import ( "encoding/json" + "fmt" "math/big" + "regexp" "testing" "github.com/ethereum/go-ethereum/common" @@ -10,6 +12,15 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) +const ( + reHash = `"0x[0-9a-f]{64}"` // 32 bytes + reHashOpt = `"(0x[0-9a-f]{64})"|null` // 32 bytes or null + reAddress = `"0x[0-9a-f]{40}"` // 20 bytes + reAddressOpt = `"0x[0-9a-f]{40}"|null` // 20 bytes or null + reNum = `"0x([1-9a-f][0-9a-f]{1,15})|0"` // must not have left-padded zeros + reData = `"0x[0-9a-f]*"` // can be "empty" +) + func TestNewBlockRes(t *testing.T) { parentHash := common.HexToHash("0x01") coinbase := common.HexToAddress("0x01") @@ -18,56 +29,35 @@ func TestNewBlockRes(t *testing.T) { nonce := uint64(1) extra := "" block := types.NewBlock(parentHash, coinbase, root, difficulty, nonce, extra) - - _ = NewBlockRes(block) -} - -func TestBlockRes(t *testing.T) { - v := &BlockRes{ - BlockNumber: big.NewInt(0), - BlockHash: common.HexToHash("0x0"), - ParentHash: common.HexToHash("0x0"), - Nonce: [8]byte{0, 0, 0, 0, 0, 0, 0, 0}, - Sha3Uncles: common.HexToHash("0x0"), - LogsBloom: types.BytesToBloom([]byte{0}), - TransactionRoot: common.HexToHash("0x0"), - StateRoot: common.HexToHash("0x0"), - Miner: common.HexToAddress("0x0"), - Difficulty: big.NewInt(0), - TotalDifficulty: big.NewInt(0), - Size: big.NewInt(0), - ExtraData: []byte{}, - GasLimit: big.NewInt(0), - MinGasPrice: int64(0), - GasUsed: big.NewInt(0), - UnixTimestamp: int64(0), - // Transactions []*TransactionRes `json:"transactions"` - // Uncles []common.Hash `json:"uncles"` + tests := map[string]string{ + "number": reNum, + "hash": reHash, + "parentHash": reHash, + "nonce": reNum, + "sha3Uncles": reHash, + "logsBloom": reData, + "transactionRoot": reHash, + "stateRoot": reHash, + "miner": reAddress, + "difficulty": `"0x1"`, + "totalDifficulty": reNum, + "size": reNum, + "extraData": reData, + "gasLimit": reNum, + // "minGasPrice": "0x", + "gasUsed": reNum, + "timestamp": reNum, } - _, _ = json.Marshal(v) + v := NewBlockRes(block) + j, _ := json.Marshal(v) - // fmt.Println(string(j)) - -} - -func TestTransactionRes(t *testing.T) { - a := common.HexToAddress("0x0") - v := &TransactionRes{ - Hash: common.HexToHash("0x0"), - Nonce: uint64(0), - BlockHash: common.HexToHash("0x0"), - BlockNumber: int64(0), - TxIndex: int64(0), - From: common.HexToAddress("0x0"), - To: &a, - Value: big.NewInt(0), - Gas: big.NewInt(0), - GasPrice: big.NewInt(0), - Input: []byte{0}, + for k, re := range tests { + match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) + if !match { + t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j)) + } } - - _, _ = json.Marshal(v) } func TestNewTransactionRes(t *testing.T) { @@ -78,26 +68,80 @@ func TestNewTransactionRes(t *testing.T) { data := []byte{1, 2, 3} tx := types.NewTransactionMessage(to, amount, gasAmount, gasPrice, data) - _ = NewTransactionRes(tx) + tests := map[string]string{ + "hash": reHash, + "nonce": reNum, + "blockHash": reHash, + "blockNum": reNum, + "transactionIndex": reNum, + "from": reAddress, + "to": reAddressOpt, + "value": reNum, + "gas": reNum, + "gasPrice": reNum, + "input": reData, + } + + v := NewTransactionRes(tx) + v.BlockHash = newHexData(common.HexToHash("0x030201")) + v.BlockNumber = newHexNum(5) + v.TxIndex = newHexNum(0) + j, _ := json.Marshal(v) + for k, re := range tests { + match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) + if !match { + t.Error(fmt.Sprintf("`%s` output json does not match format %s. Source %s", k, re, j)) + } + } + } -func TestLogRes(t *testing.T) { - topics := make([]common.Hash, 3) - topics = append(topics, common.HexToHash("0x00")) - topics = append(topics, common.HexToHash("0x10")) - topics = append(topics, common.HexToHash("0x20")) +func TestNewLogRes(t *testing.T) { + log := makeStateLog(0) + tests := map[string]string{ + "address": reAddress, + // "topics": "[.*]" + "data": reData, + "blockNumber": reNum, + // "hash": reHash, + // "logIndex": reNum, + // "blockHash": reHash, + // "transactionHash": reHash, + "transactionIndex": reNum, + } + + v := NewLogRes(log) + j, _ := json.Marshal(v) + + for k, re := range tests { + match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) + if !match { + t.Error(fmt.Sprintf("`%s` output json does not match format %s. Got %s", k, re, j)) + } + } + +} - v := &LogRes{ - Topics: topics, - Address: common.HexToAddress("0x0"), - Data: []byte{1, 2, 3}, - Number: uint64(5), +func TestNewLogsRes(t *testing.T) { + logs := make([]state.Log, 3) + logs[0] = makeStateLog(1) + logs[1] = makeStateLog(2) + logs[2] = makeStateLog(3) + tests := map[string]string{} + + v := NewLogsRes(logs) + j, _ := json.Marshal(v) + + for k, re := range tests { + match, _ := regexp.MatchString(fmt.Sprintf(`[{.*"%s":%s.*}]`, k, re), string(j)) + if !match { + t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j)) + } } - _, _ = json.Marshal(v) } -func MakeStateLog(num int) state.Log { +func makeStateLog(num int) state.Log { address := common.HexToAddress("0x0") data := []byte{1, 2, 3} number := uint64(num) @@ -108,16 +152,3 @@ func MakeStateLog(num int) state.Log { log := state.NewLog(address, topics, data, number) return log } - -func TestNewLogRes(t *testing.T) { - log := MakeStateLog(0) - _ = NewLogRes(log) -} - -func TestNewLogsRes(t *testing.T) { - logs := make([]state.Log, 3) - logs[0] = MakeStateLog(1) - logs[1] = MakeStateLog(2) - logs[2] = MakeStateLog(3) - _ = NewLogsRes(logs) -} |