From ec75953f509a94ccb20bd346a2413bfee1281072 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 15 Sep 2016 02:57:48 +0200 Subject: [PATCH 1/8] common/hexutil: new package for 0x hex encoding The new package is purpose-built to handle the encoding consumed and produced by the RPC API. --- common/hexutil/hexutil.go | 232 ++++++++++++++++++++++++++++ common/hexutil/hexutil_test.go | 186 ++++++++++++++++++++++ common/hexutil/json.go | 271 +++++++++++++++++++++++++++++++++ common/hexutil/json_test.go | 258 +++++++++++++++++++++++++++++++ 4 files changed, 947 insertions(+) create mode 100644 common/hexutil/hexutil.go create mode 100644 common/hexutil/hexutil_test.go create mode 100644 common/hexutil/json.go create mode 100644 common/hexutil/json_test.go diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go new file mode 100644 index 000000000..29e6de333 --- /dev/null +++ b/common/hexutil/hexutil.go @@ -0,0 +1,232 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* +Package hexutil implements hex encoding with 0x prefix. +This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads. + +Encoding Rules + +All hex data must have prefix "0x". + +For byte slices, the hex data must be of even length. An empty byte slice +encodes as "0x". + +Integers are encoded using the least amount of digits (no leading zero digits). Their +encoding may be of uneven length. The number zero encodes as "0x0". +*/ +package hexutil + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "strconv" +) + +const uintBits = 32 << (uint64(^uint(0)) >> 63) + +var ( + ErrEmptyString = errors.New("empty hex string") + ErrMissingPrefix = errors.New("missing 0x prefix for hex data") + ErrSyntax = errors.New("invalid hex") + ErrEmptyNumber = errors.New("hex number has no digits after 0x") + ErrLeadingZero = errors.New("hex number has leading zero digits after 0x") + ErrOddLength = errors.New("hex string has odd length") + ErrUint64Range = errors.New("hex number does not fit into 64 bits") + ErrUintRange = fmt.Errorf("hex number does not fit into %d bits", uintBits) +) + +// Decode decodes a hex string with 0x prefix. +func Decode(input string) ([]byte, error) { + if len(input) == 0 { + return nil, ErrEmptyString + } + if !has0xPrefix(input) { + return nil, ErrMissingPrefix + } + return hex.DecodeString(input[2:]) +} + +// MustDecode decodes a hex string with 0x prefix. It panics for invalid input. +func MustDecode(input string) []byte { + dec, err := Decode(input) + if err != nil { + panic(err) + } + return dec +} + +// Encode encodes b as a hex string with 0x prefix. +func Encode(b []byte) string { + enc := make([]byte, len(b)*2+2) + copy(enc, "0x") + hex.Encode(enc[2:], b) + return string(enc) +} + +// DecodeUint64 decodes a hex string with 0x prefix as a quantity. +func DecodeUint64(input string) (uint64, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 64) + if err != nil { + err = mapError(err) + } + return dec, err +} + +// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeUint64(input string) uint64 { + dec, err := DecodeUint64(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeUint64 encodes i as a hex string with 0x prefix. +func EncodeUint64(i uint64) string { + enc := make([]byte, 2, 10) + copy(enc, "0x") + return string(strconv.AppendUint(enc, i, 16)) +} + +var bigWordNibbles int + +func init() { + // This is a weird way to compute the number of nibbles required for big.Word. + // The usual way would be to use constant arithmetic but go vet can't handle that. + b, _ := new(big.Int).SetString("FFFFFFFFFF", 16) + switch len(b.Bits()) { + case 1: + bigWordNibbles = 16 + case 2: + bigWordNibbles = 8 + default: + panic("weird big.Word size") + } +} + +// DecodeBig decodes a hex string with 0x prefix as a quantity. +func DecodeBig(input string) (*big.Int, error) { + raw, err := checkNumber(input) + if err != nil { + return nil, err + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return nil, ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + dec := new(big.Int).SetBits(words) + return dec, nil +} + +// MustDecodeBig decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeBig(input string) *big.Int { + dec, err := DecodeBig(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeBig encodes bigint as a hex string with 0x prefix. +// The sign of the integer is ignored. +func EncodeBig(bigint *big.Int) string { + nbits := bigint.BitLen() + if nbits == 0 { + return "0x0" + } + enc := make([]byte, 2, (nbits/8)*2+2) + copy(enc, "0x") + for i := len(bigint.Bits()) - 1; i >= 0; i-- { + enc = strconv.AppendUint(enc, uint64(bigint.Bits()[i]), 16) + } + return string(enc) +} + +func has0xPrefix(input string) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkNumber(input string) (raw string, err error) { + if len(input) == 0 { + return "", ErrEmptyString + } + if !has0xPrefix(input) { + return "", ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return "", ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return "", ErrLeadingZero + } + return input, nil +} + +const badNibble = ^uint64(0) + +func decodeNibble(in byte) uint64 { + switch { + case in >= '0' && in <= '9': + return uint64(in - '0') + case in >= 'A' && in <= 'F': + return uint64(in - 'A' + 10) + case in >= 'a' && in <= 'f': + return uint64(in - 'a' + 10) + default: + return badNibble + } +} + +func mapError(err error) error { + if err, ok := err.(*strconv.NumError); ok { + switch err.Err { + case strconv.ErrRange: + return ErrUint64Range + case strconv.ErrSyntax: + return ErrSyntax + } + } + if _, ok := err.(hex.InvalidByteError); ok { + return ErrSyntax + } + if err == hex.ErrLength { + return ErrOddLength + } + return err +} diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go new file mode 100644 index 000000000..3f261c9a7 --- /dev/null +++ b/common/hexutil/hexutil_test.go @@ -0,0 +1,186 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package hexutil + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" +) + +type marshalTest struct { + input interface{} + want string +} + +type unmarshalTest struct { + input string + want interface{} + wantErr error +} + +var ( + encodeBytesTests = []marshalTest{ + {[]byte{}, "0x"}, + {[]byte{0}, "0x00"}, + {[]byte{0, 0, 1, 2}, "0x00000102"}, + } + + encodeBigTests = []marshalTest{ + {referenceBig("0"), "0x0"}, + {referenceBig("1"), "0x1"}, + {referenceBig("ff"), "0xff"}, + {referenceBig("112233445566778899aabbccddeeff"), "0x112233445566778899aabbccddeeff"}, + } + + encodeUint64Tests = []marshalTest{ + {uint64(0), "0x0"}, + {uint64(1), "0x1"}, + {uint64(0xff), "0xff"}, + {uint64(0x1122334455667788), "0x1122334455667788"}, + } + + decodeBytesTests = []unmarshalTest{ + // invalid + {input: ``, wantErr: ErrEmptyString}, + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x0`, wantErr: hex.ErrLength}, + {input: `0x023`, wantErr: hex.ErrLength}, + {input: `0xxx`, wantErr: hex.InvalidByteError('x')}, + {input: `0x01zz01`, wantErr: hex.InvalidByteError('z')}, + // valid + {input: `0x`, want: []byte{}}, + {input: `0X`, want: []byte{}}, + {input: `0x02`, want: []byte{0x02}}, + {input: `0X02`, want: []byte{0x02}}, + {input: `0xffffffffff`, want: []byte{0xff, 0xff, 0xff, 0xff, 0xff}}, + { + input: `0xffffffffffffffffffffffffffffffffffff`, + want: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + } + + decodeBigTests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: big.NewInt(0)}, + {input: `0x2`, want: big.NewInt(0x2)}, + {input: `0x2F2`, want: big.NewInt(0x2f2)}, + {input: `0X2F2`, want: big.NewInt(0x2f2)}, + {input: `0x1122aaff`, want: big.NewInt(0x1122aaff)}, + {input: `0xbBb`, want: big.NewInt(0xbbb)}, + {input: `0xfffffffff`, want: big.NewInt(0xfffffffff)}, + { + input: `0x112233445566778899aabbccddeeff`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `0xffffffffffffffffffffffffffffffffffff`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + } + + decodeUint64Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xfffffffffffffffff`, wantErr: ErrUintRange}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: uint64(0)}, + {input: `0x2`, want: uint64(0x2)}, + {input: `0x2F2`, want: uint64(0x2f2)}, + {input: `0X2F2`, want: uint64(0x2f2)}, + {input: `0x1122aaff`, want: uint64(0x1122aaff)}, + {input: `0xbbb`, want: uint64(0xbbb)}, + {input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)}, + } +) + +func TestEncode(t *testing.T) { + for _, test := range encodeBytesTests { + enc := Encode(test.input.([]byte)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecode(t *testing.T) { + for _, test := range decodeBytesTests { + dec, err := Decode(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), dec) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeBig(t *testing.T) { + for _, test := range encodeBigTests { + enc := EncodeBig(test.input.(*big.Int)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeBig(t *testing.T) { + for _, test := range decodeBigTests { + dec, err := DecodeBig(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec.Cmp(test.want.(*big.Int)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + enc := EncodeUint64(test.input.(uint64)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeUint64(t *testing.T) { + for _, test := range decodeUint64Tests { + dec, err := DecodeUint64(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} diff --git a/common/hexutil/json.go b/common/hexutil/json.go new file mode 100644 index 000000000..cbbadbed6 --- /dev/null +++ b/common/hexutil/json.go @@ -0,0 +1,271 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package hexutil + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "strconv" +) + +var ( + jsonNull = []byte("null") + jsonZero = []byte(`"0x0"`) + errNonString = errors.New("cannot unmarshal non-string as hex data") + errNegativeBigInt = errors.New("hexutil.Big: can't marshal negative integer") +) + +// Bytes marshals/unmarshals as a JSON string with 0x prefix. +// The empty slice marshals as "0x". +type Bytes []byte + +// MarshalJSON implements json.Marshaler. +func (b Bytes) MarshalJSON() ([]byte, error) { + result := make([]byte, len(b)*2+4) + copy(result, `"0x`) + hex.Encode(result[3:], b) + result[len(result)-1] = '"' + return result, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Bytes) UnmarshalJSON(input []byte) error { + raw, err := checkJSON(input) + if err != nil { + return err + } + dec := make([]byte, len(raw)/2) + if _, err = hex.Decode(dec, raw); err != nil { + err = mapError(err) + } else { + *b = dec + } + return err +} + +// String returns the hex encoding of b. +func (b Bytes) String() string { + return Encode(b) +} + +// UnmarshalJSON decodes input as a JSON string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalJSON method for fixed-size types: +// +// type Foo [8]byte +// +// func (f *Foo) UnmarshalJSON(input []byte) error { +// return hexutil.UnmarshalJSON("Foo", input, f[:]) +// } +func UnmarshalJSON(typname string, input, out []byte) error { + raw, err := checkJSON(input) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// Big marshals/unmarshals as a JSON string with 0x prefix. The zero value marshals as +// "0x0". Negative integers are not supported at this time. Attempting to marshal them +// will return an error. +type Big big.Int + +// MarshalJSON implements json.Marshaler. +func (b *Big) MarshalJSON() ([]byte, error) { + if b == nil { + return jsonNull, nil + } + bigint := (*big.Int)(b) + if bigint.Sign() == -1 { + return nil, errNegativeBigInt + } + nbits := bigint.BitLen() + if nbits == 0 { + return jsonZero, nil + } + enc := make([]byte, 3, (nbits/8)*2+4) + copy(enc, `"0x`) + for i := len(bigint.Bits()) - 1; i >= 0; i-- { + enc = strconv.AppendUint(enc, uint64(bigint.Bits()[i]), 16) + } + enc = append(enc, '"') + return enc, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Big) UnmarshalJSON(input []byte) error { + raw, err := checkNumberJSON(input) + if err != nil { + return err + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + var dec big.Int + dec.SetBits(words) + *b = (Big)(dec) + return nil +} + +// ToInt converts b to a big.Int. +func (b *Big) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns the hex encoding of b. +func (b *Big) String() string { + return EncodeBig(b.ToInt()) +} + +// Uint64 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint64 uint64 + +// MarshalJSON implements json.Marshaler. +func (b Uint64) MarshalJSON() ([]byte, error) { + buf := make([]byte, 3, 12) + copy(buf, `"0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + buf = append(buf, '"') + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint64) UnmarshalJSON(input []byte) error { + raw, err := checkNumberJSON(input) + if err != nil { + return err + } + if len(raw) > 16 { + return ErrUint64Range + } + var dec uint64 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += uint64(nib) + } + *b = Uint64(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint64) String() string { + return EncodeUint64(uint64(b)) +} + +// Uint marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint uint + +// MarshalJSON implements json.Marshaler. +func (b Uint) MarshalJSON() ([]byte, error) { + return Uint64(b).MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint) UnmarshalJSON(input []byte) error { + var u64 Uint64 + err := u64.UnmarshalJSON(input) + if err != nil { + return err + } else if u64 > Uint64(^uint(0)) { + return ErrUintRange + } + *b = Uint(u64) + return nil +} + +// String returns the hex encoding of b. +func (b Uint) String() string { + return EncodeUint64(uint64(b)) +} + +func isString(input []byte) bool { + return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' +} + +func bytesHave0xPrefix(input []byte) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkJSON(input []byte) (raw []byte, err error) { + if !isString(input) { + return nil, errNonString + } + if len(input) == 2 { + return nil, ErrEmptyString + } + if !bytesHave0xPrefix(input[1:]) { + return nil, ErrMissingPrefix + } + input = input[3 : len(input)-1] + if len(input)%2 != 0 { + return nil, ErrOddLength + } + return input, nil +} + +func checkNumberJSON(input []byte) (raw []byte, err error) { + if !isString(input) { + return nil, errNonString + } + input = input[1 : len(input)-1] + if len(input) == 0 { + return nil, ErrEmptyString + } + if !bytesHave0xPrefix(input) { + return nil, ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return nil, ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return nil, ErrLeadingZero + } + return input, nil +} diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go new file mode 100644 index 000000000..16f1b9aa7 --- /dev/null +++ b/common/hexutil/json_test.go @@ -0,0 +1,258 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package hexutil + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" +) + +func checkError(t *testing.T, input string, got, want error) bool { + if got == nil { + if want != nil { + t.Errorf("input %s: got no error, want %q", input, want) + return false + } + return true + } + if want == nil { + t.Errorf("input %s: unexpected error %q", input, got) + } else if got.Error() != want.Error() { + t.Errorf("input %s: got error %q, want %q", input, got, want) + } + return false +} + +func referenceBig(s string) *big.Int { + b, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid") + } + return b +} + +func referenceBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var unmarshalBytesTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errNonString}, + {input: "null", wantErr: errNonString}, + {input: "10", wantErr: errNonString}, + {input: `""`, wantErr: ErrEmptyString}, + {input: `"0"`, wantErr: ErrMissingPrefix}, + {input: `"0x0"`, wantErr: ErrOddLength}, + {input: `"0xxx"`, wantErr: ErrSyntax}, + {input: `"0x01zz01"`, wantErr: ErrSyntax}, + + // valid encoding + {input: `"0x"`, want: referenceBytes("")}, + {input: `"0x02"`, want: referenceBytes("02")}, + {input: `"0X02"`, want: referenceBytes("02")}, + {input: `"0xffffffffff"`, want: referenceBytes("ffffffffff")}, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBytes("ffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBytes(t *testing.T) { + for _, test := range unmarshalBytesTests { + var v Bytes + err := v.UnmarshalJSON([]byte(test.input)) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), []byte(v)) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Bytes + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBytes(t *testing.T) { + for _, test := range encodeBytesTests { + in := test.input.([]byte) + out, err := Bytes(in).MarshalJSON() + if err != nil { + t.Errorf("%x: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := Bytes(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalBigTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errNonString}, + {input: "null", wantErr: errNonString}, + {input: "10", wantErr: errNonString}, + {input: `""`, wantErr: ErrEmptyString}, + {input: `"0"`, wantErr: ErrMissingPrefix}, + {input: `"0x"`, wantErr: ErrEmptyNumber}, + {input: `"0x01"`, wantErr: ErrLeadingZero}, + {input: `"0xx"`, wantErr: ErrSyntax}, + {input: `"0x1zz01"`, wantErr: ErrSyntax}, + + // valid encoding + {input: `"0x0"`, want: big.NewInt(0)}, + {input: `"0x2"`, want: big.NewInt(0x2)}, + {input: `"0x2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0X2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)}, + {input: `"0xbBb"`, want: big.NewInt(0xbbb)}, + {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)}, + { + input: `"0x112233445566778899aabbccddeeff"`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBig(t *testing.T) { + for _, test := range unmarshalBigTests { + var v Big + err := v.UnmarshalJSON([]byte(test.input)) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want) + continue + } + } +} + +func BenchmarkUnmarshalBig(b *testing.B) { + input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Big + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBig(t *testing.T) { + for _, test := range encodeBigTests { + in := test.input.(*big.Int) + out, err := (*Big)(in).MarshalJSON() + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (*Big)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalUint64Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errNonString}, + {input: "null", wantErr: errNonString}, + {input: "10", wantErr: errNonString}, + {input: `""`, wantErr: ErrEmptyString}, + {input: `"0"`, wantErr: ErrMissingPrefix}, + {input: `"0x"`, wantErr: ErrEmptyNumber}, + {input: `"0x01"`, wantErr: ErrLeadingZero}, + {input: `"0xfffffffffffffffff"`, wantErr: ErrUintRange}, + {input: `"0xx"`, wantErr: ErrSyntax}, + {input: `"0x1zz01"`, wantErr: ErrSyntax}, + + // valid encoding + {input: `"0x0"`, want: uint64(0)}, + {input: `"0x2"`, want: uint64(0x2)}, + {input: `"0x2F2"`, want: uint64(0x2f2)}, + {input: `"0X2F2"`, want: uint64(0x2f2)}, + {input: `"0x1122aaff"`, want: uint64(0x1122aaff)}, + {input: `"0xbbb"`, want: uint64(0xbbb)}, + {input: `"0xffffffffffffffff"`, want: uint64(0xffffffffffffffff)}, +} + +func TestUnmarshalUint64(t *testing.T) { + for _, test := range unmarshalUint64Tests { + var v Uint64 + err := v.UnmarshalJSON([]byte(test.input)) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint64(v) != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint64(b *testing.B) { + input := []byte(`"0x123456789abcdf"`) + for i := 0; i < b.N; i++ { + var v Uint64 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + in := test.input.(uint64) + out, err := Uint64(in).MarshalJSON() + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint64)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} From 65e6319b1295908dc049c3e796763ffe96cd6c25 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 00:25:48 +0100 Subject: [PATCH 2/8] core/vm: use package hexutil for JSON handling --- core/vm/log.go | 48 +++++++++++++++++++-------------------------- core/vm/log_test.go | 3 +++ 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/core/vm/log.go b/core/vm/log.go index b292f5f43..06f941703 100644 --- a/core/vm/log.go +++ b/core/vm/log.go @@ -23,6 +23,7 @@ import ( "io" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rlp" ) @@ -47,12 +48,12 @@ type Log struct { type jsonLog struct { Address *common.Address `json:"address"` Topics *[]common.Hash `json:"topics"` - Data string `json:"data"` - BlockNumber string `json:"blockNumber"` - TxIndex string `json:"transactionIndex"` + Data *hexutil.Bytes `json:"data"` + BlockNumber *hexutil.Uint64 `json:"blockNumber"` + TxIndex *hexutil.Uint `json:"transactionIndex"` TxHash *common.Hash `json:"transactionHash"` BlockHash *common.Hash `json:"blockHash"` - Index string `json:"logIndex"` + Index *hexutil.Uint `json:"logIndex"` } func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log { @@ -85,12 +86,12 @@ func (r *Log) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonLog{ Address: &r.Address, Topics: &r.Topics, - Data: fmt.Sprintf("0x%x", r.Data), - BlockNumber: fmt.Sprintf("0x%x", r.BlockNumber), - TxIndex: fmt.Sprintf("0x%x", r.TxIndex), + Data: (*hexutil.Bytes)(&r.Data), + BlockNumber: (*hexutil.Uint64)(&r.BlockNumber), + TxIndex: (*hexutil.Uint)(&r.TxIndex), TxHash: &r.TxHash, BlockHash: &r.BlockHash, - Index: fmt.Sprintf("0x%x", r.Index), + Index: (*hexutil.Uint)(&r.Index), }) } @@ -100,29 +101,20 @@ func (r *Log) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &dec); err != nil { return err } - if dec.Address == nil || dec.Topics == nil || dec.Data == "" || dec.BlockNumber == "" || - dec.TxIndex == "" || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == "" { + if dec.Address == nil || dec.Topics == nil || dec.Data == nil || dec.BlockNumber == nil || + dec.TxIndex == nil || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == nil { return errMissingLogFields } - declog := Log{ - Address: *dec.Address, - Topics: *dec.Topics, - TxHash: *dec.TxHash, - BlockHash: *dec.BlockHash, + *r = Log{ + Address: *dec.Address, + Topics: *dec.Topics, + Data: *dec.Data, + BlockNumber: uint64(*dec.BlockNumber), + TxHash: *dec.TxHash, + TxIndex: uint(*dec.TxIndex), + BlockHash: *dec.BlockHash, + Index: uint(*dec.Index), } - if _, err := fmt.Sscanf(dec.Data, "0x%x", &declog.Data); err != nil { - return fmt.Errorf("invalid hex log data") - } - if _, err := fmt.Sscanf(dec.BlockNumber, "0x%x", &declog.BlockNumber); err != nil { - return fmt.Errorf("invalid hex log block number") - } - if _, err := fmt.Sscanf(dec.TxIndex, "0x%x", &declog.TxIndex); err != nil { - return fmt.Errorf("invalid hex log tx index") - } - if _, err := fmt.Sscanf(dec.Index, "0x%x", &declog.Index); err != nil { - return fmt.Errorf("invalid hex log index") - } - *r = declog return nil } diff --git a/core/vm/log_test.go b/core/vm/log_test.go index 775016f9c..4d3189558 100644 --- a/core/vm/log_test.go +++ b/core/vm/log_test.go @@ -28,6 +28,9 @@ var unmarshalLogTests = map[string]struct { "ok": { input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, }, + "empty data": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, + }, "missing data": { input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, wantError: errMissingLogFields, From 24f288770e90ab3fd6b30f9f16bf31396b6afeb7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 00:33:54 +0100 Subject: [PATCH 3/8] core/types: use package hexutil for JSON handling --- core/types/block.go | 37 +++--- core/types/bloom9.go | 13 +-- core/types/json.go | 108 ------------------ core/types/json_test.go | 232 -------------------------------------- core/types/receipt.go | 9 +- core/types/transaction.go | 33 +++--- 6 files changed, 40 insertions(+), 392 deletions(-) delete mode 100644 core/types/json.go delete mode 100644 core/types/json_test.go diff --git a/core/types/block.go b/core/types/block.go index 68504ffcc..2034bb0ff 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -29,6 +29,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/rlp" ) @@ -63,20 +64,12 @@ func (n BlockNonce) Uint64() uint64 { // MarshalJSON implements json.Marshaler func (n BlockNonce) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"0x%x"`, n)), nil + return hexutil.Bytes(n[:]).MarshalJSON() } // UnmarshalJSON implements json.Unmarshaler func (n *BlockNonce) UnmarshalJSON(input []byte) error { - var b hexBytes - if err := b.UnmarshalJSON(input); err != nil { - return err - } - if len(b) != 8 { - return errBadNonceSize - } - copy((*n)[:], b) - return nil + return hexutil.UnmarshalJSON("BlockNonce", input, n[:]) } // Header represents a block header in the Ethereum blockchain. @@ -106,12 +99,12 @@ type jsonHeader struct { TxHash *common.Hash `json:"transactionsRoot"` ReceiptHash *common.Hash `json:"receiptsRoot"` Bloom *Bloom `json:"logsBloom"` - Difficulty *hexBig `json:"difficulty"` - Number *hexBig `json:"number"` - GasLimit *hexBig `json:"gasLimit"` - GasUsed *hexBig `json:"gasUsed"` - Time *hexBig `json:"timestamp"` - Extra *hexBytes `json:"extraData"` + Difficulty *hexutil.Big `json:"difficulty"` + Number *hexutil.Big `json:"number"` + GasLimit *hexutil.Big `json:"gasLimit"` + GasUsed *hexutil.Big `json:"gasUsed"` + Time *hexutil.Big `json:"timestamp"` + Extra *hexutil.Bytes `json:"extraData"` MixDigest *common.Hash `json:"mixHash"` Nonce *BlockNonce `json:"nonce"` } @@ -151,12 +144,12 @@ func (h *Header) MarshalJSON() ([]byte, error) { TxHash: &h.TxHash, ReceiptHash: &h.ReceiptHash, Bloom: &h.Bloom, - Difficulty: (*hexBig)(h.Difficulty), - Number: (*hexBig)(h.Number), - GasLimit: (*hexBig)(h.GasLimit), - GasUsed: (*hexBig)(h.GasUsed), - Time: (*hexBig)(h.Time), - Extra: (*hexBytes)(&h.Extra), + Difficulty: (*hexutil.Big)(h.Difficulty), + Number: (*hexutil.Big)(h.Number), + GasLimit: (*hexutil.Big)(h.GasLimit), + GasUsed: (*hexutil.Big)(h.GasUsed), + Time: (*hexutil.Big)(h.Time), + Extra: (*hexutil.Bytes)(&h.Extra), MixDigest: &h.MixDigest, Nonce: &h.Nonce, }) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index d3945a734..a1d13e218 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -21,6 +21,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" ) @@ -77,20 +78,12 @@ func (b Bloom) TestBytes(test []byte) bool { // MarshalJSON encodes b as a hex string with 0x prefix. func (b Bloom) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"%#x"`, b[:])), nil + return hexutil.Bytes(b[:]).MarshalJSON() } // UnmarshalJSON b as a hex string with 0x prefix. func (b *Bloom) UnmarshalJSON(input []byte) error { - var dec hexBytes - if err := dec.UnmarshalJSON(input); err != nil { - return err - } - if len(dec) != bloomLength { - return fmt.Errorf("invalid bloom size, want %d bytes", bloomLength) - } - copy((*b)[:], dec) - return nil + return hexutil.UnmarshalJSON("Bloom", input, b[:]) } func CreateBloom(receipts Receipts) Bloom { diff --git a/core/types/json.go b/core/types/json.go deleted file mode 100644 index d2718a96d..000000000 --- a/core/types/json.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package types - -import ( - "encoding/hex" - "fmt" - "math/big" -) - -// JSON unmarshaling utilities. - -type hexBytes []byte - -func (b *hexBytes) MarshalJSON() ([]byte, error) { - if b != nil { - return []byte(fmt.Sprintf(`"0x%x"`, []byte(*b))), nil - } - return nil, nil -} - -func (b *hexBytes) UnmarshalJSON(input []byte) error { - if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' { - return fmt.Errorf("cannot unmarshal non-string into hexBytes") - } - input = input[1 : len(input)-1] - if len(input) < 2 || input[0] != '0' || input[1] != 'x' { - return fmt.Errorf("missing 0x prefix in hexBytes input %q", input) - } - dec := make(hexBytes, (len(input)-2)/2) - if _, err := hex.Decode(dec, input[2:]); err != nil { - return err - } - *b = dec - return nil -} - -type hexBig big.Int - -func (b *hexBig) MarshalJSON() ([]byte, error) { - if b != nil { - return []byte(fmt.Sprintf(`"0x%x"`, (*big.Int)(b))), nil - } - return nil, nil -} - -func (b *hexBig) UnmarshalJSON(input []byte) error { - raw, err := checkHexNumber(input) - if err != nil { - return err - } - dec, ok := new(big.Int).SetString(string(raw), 16) - if !ok { - return fmt.Errorf("invalid hex number") - } - *b = (hexBig)(*dec) - return nil -} - -type hexUint64 uint64 - -func (b *hexUint64) MarshalJSON() ([]byte, error) { - if b != nil { - return []byte(fmt.Sprintf(`"0x%x"`, *(*uint64)(b))), nil - } - return nil, nil -} - -func (b *hexUint64) UnmarshalJSON(input []byte) error { - raw, err := checkHexNumber(input) - if err != nil { - return err - } - _, err = fmt.Sscanf(string(raw), "%x", b) - return err -} - -func checkHexNumber(input []byte) (raw []byte, err error) { - if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' { - return nil, fmt.Errorf("cannot unmarshal non-string into hex number") - } - input = input[1 : len(input)-1] - if len(input) < 2 || input[0] != '0' || input[1] != 'x' { - return nil, fmt.Errorf("missing 0x prefix in hex number input %q", input) - } - if len(input) == 2 { - return nil, fmt.Errorf("empty hex number") - } - raw = input[2:] - if len(raw)%2 != 0 { - raw = append([]byte{'0'}, raw...) - } - return raw, nil -} diff --git a/core/types/json_test.go b/core/types/json_test.go deleted file mode 100644 index d80cda68b..000000000 --- a/core/types/json_test.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package types - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/common" -) - -var unmarshalHeaderTests = map[string]struct { - input string - wantHash common.Hash - wantError error -}{ - "block 0x1e2200": { - input: `{"difficulty":"0x311ca98cebfe","extraData":"0x7777772e62772e636f6d","gasLimit":"0x47db3d","gasUsed":"0x43760c","hash":"0x3724bc6b9dcd4a2b3a26e0ed9b821e7380b5b3d7dec7166c7983cead62a37e48","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","mixHash":"0x1ccfddb506dac5afc09b6f92eb09a043ffc8e08f7592250af57b9c64c20f9b25","nonce":"0x670bd98c79585197","number":"0x1e2200","parentHash":"0xd3e13296d064e7344f20c57c57b67a022f6bf7741fa42428c2db77e91abdf1f8","receiptsRoot":"0xeeab1776c1fafbe853a8ee0c1bafe2e775a1b6fdb6ff3e9f9410ddd4514889ff","sha3Uncles":"0x5fbfa4ec8b089678c53b6798cc0d9260ea40a529e06d5300aae35596262e0eb3","size":"0x57f","stateRoot":"0x62ad2007e4a3f31ea98e5d2fd150d894887bafde36eeac7331a60ae12053ec76","timestamp":"0x579b82f2","totalDifficulty":"0x24fe813c101d00f97","transactions":["0xb293408e85735bfc78b35aa89de8b48e49641e3d82e3d52ea2d44ec42a4e88cf","0x124acc383ff2da6faa0357829084dae64945221af6f6f09da1d11688b779f939","0xee090208b6051c442ccdf9ec19f66389e604d342a6d71144c7227ce995bef46f"],"transactionsRoot":"0xce0042dd9af0c1923dd7f58ca6faa156d39d4ef39fdb65c5bcd1d4b4720096db","uncles":["0x6818a31d1f204cf640c952082940b68b8db6d1b39ee71f7efe0e3629ed5d7eb3"]}`, - wantHash: common.HexToHash("0x3724bc6b9dcd4a2b3a26e0ed9b821e7380b5b3d7dec7166c7983cead62a37e48"), - }, - "bad nonce": { - input: `{"difficulty":"0x311ca98cebfe","extraData":"0x7777772e62772e636f6d","gasLimit":"0x47db3d","gasUsed":"0x43760c","hash":"0x3724bc6b9dcd4a2b3a26e0ed9b821e7380b5b3d7dec7166c7983cead62a37e48","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","mixHash":"0x1ccfddb506dac5afc09b6f92eb09a043ffc8e08f7592250af57b9c64c20f9b25","nonce":"0x670bd98c7958","number":"0x1e2200","parentHash":"0xd3e13296d064e7344f20c57c57b67a022f6bf7741fa42428c2db77e91abdf1f8","receiptsRoot":"0xeeab1776c1fafbe853a8ee0c1bafe2e775a1b6fdb6ff3e9f9410ddd4514889ff","sha3Uncles":"0x5fbfa4ec8b089678c53b6798cc0d9260ea40a529e06d5300aae35596262e0eb3","size":"0x57f","stateRoot":"0x62ad2007e4a3f31ea98e5d2fd150d894887bafde36eeac7331a60ae12053ec76","timestamp":"0x579b82f2","totalDifficulty":"0x24fe813c101d00f97","transactions":["0xb293408e85735bfc78b35aa89de8b48e49641e3d82e3d52ea2d44ec42a4e88cf","0x124acc383ff2da6faa0357829084dae64945221af6f6f09da1d11688b779f939","0xee090208b6051c442ccdf9ec19f66389e604d342a6d71144c7227ce995bef46f"],"transactionsRoot":"0xce0042dd9af0c1923dd7f58ca6faa156d39d4ef39fdb65c5bcd1d4b4720096db","uncles":["0x6818a31d1f204cf640c952082940b68b8db6d1b39ee71f7efe0e3629ed5d7eb3"]}`, - wantError: errBadNonceSize, - }, - "missing mixHash": { - input: `{"difficulty":"0x311ca98cebfe","extraData":"0x7777772e62772e636f6d","gasLimit":"0x47db3d","gasUsed":"0x43760c","hash":"0x3724bc6b9dcd4a2b3a26e0ed9b821e7380b5b3d7dec7166c7983cead62a37e48","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","nonce":"0x670bd98c79585197","number":"0x1e2200","parentHash":"0xd3e13296d064e7344f20c57c57b67a022f6bf7741fa42428c2db77e91abdf1f8","receiptsRoot":"0xeeab1776c1fafbe853a8ee0c1bafe2e775a1b6fdb6ff3e9f9410ddd4514889ff","sha3Uncles":"0x5fbfa4ec8b089678c53b6798cc0d9260ea40a529e06d5300aae35596262e0eb3","size":"0x57f","stateRoot":"0x62ad2007e4a3f31ea98e5d2fd150d894887bafde36eeac7331a60ae12053ec76","timestamp":"0x579b82f2","totalDifficulty":"0x24fe813c101d00f97","transactions":["0xb293408e85735bfc78b35aa89de8b48e49641e3d82e3d52ea2d44ec42a4e88cf","0x124acc383ff2da6faa0357829084dae64945221af6f6f09da1d11688b779f939","0xee090208b6051c442ccdf9ec19f66389e604d342a6d71144c7227ce995bef46f"],"transactionsRoot":"0xce0042dd9af0c1923dd7f58ca6faa156d39d4ef39fdb65c5bcd1d4b4720096db","uncles":["0x6818a31d1f204cf640c952082940b68b8db6d1b39ee71f7efe0e3629ed5d7eb3"]}`, - wantError: errMissingHeaderMixDigest, - }, - "missing fields": { - input: `{"gasLimit":"0x47db3d","gasUsed":"0x43760c","hash":"0x3724bc6b9dcd4a2b3a26e0ed9b821e7380b5b3d7dec7166c7983cead62a37e48","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","mixHash":"0x1ccfddb506dac5afc09b6f92eb09a043ffc8e08f7592250af57b9c64c20f9b25","nonce":"0x670bd98c79585197","number":"0x1e2200","parentHash":"0xd3e13296d064e7344f20c57c57b67a022f6bf7741fa42428c2db77e91abdf1f8","receiptsRoot":"0xeeab1776c1fafbe853a8ee0c1bafe2e775a1b6fdb6ff3e9f9410ddd4514889ff","sha3Uncles":"0x5fbfa4ec8b089678c53b6798cc0d9260ea40a529e06d5300aae35596262e0eb3","size":"0x57f","stateRoot":"0x62ad2007e4a3f31ea98e5d2fd150d894887bafde36eeac7331a60ae12053ec76","timestamp":"0x579b82f2","totalDifficulty":"0x24fe813c101d00f97","transactions":["0xb293408e85735bfc78b35aa89de8b48e49641e3d82e3d52ea2d44ec42a4e88cf","0x124acc383ff2da6faa0357829084dae64945221af6f6f09da1d11688b779f939","0xee090208b6051c442ccdf9ec19f66389e604d342a6d71144c7227ce995bef46f"],"transactionsRoot":"0xce0042dd9af0c1923dd7f58ca6faa156d39d4ef39fdb65c5bcd1d4b4720096db","uncles":["0x6818a31d1f204cf640c952082940b68b8db6d1b39ee71f7efe0e3629ed5d7eb3"]}`, - wantError: errMissingHeaderFields, - }, -} - -func TestUnmarshalHeader(t *testing.T) { - for name, test := range unmarshalHeaderTests { - var head *Header - err := json.Unmarshal([]byte(test.input), &head) - if !checkError(t, name, err, test.wantError) { - continue - } - if head.Hash() != test.wantHash { - t.Errorf("test %q: got hash %x, want %x", name, head.Hash(), test.wantHash) - continue - } - } -} - -func TestMarshalHeader(t *testing.T) { - for name, test := range unmarshalHeaderTests { - if test.wantError != nil { - continue - } - var original *Header - json.Unmarshal([]byte(test.input), &original) - - blob, err := json.Marshal(original) - if err != nil { - t.Errorf("test %q: failed to marshal header: %v", name, err) - continue - } - var proced *Header - if err := json.Unmarshal(blob, &proced); err != nil { - t.Errorf("Test %q: failed to unmarshal marhsalled header: %v", name, err) - continue - } - if !reflect.DeepEqual(original, proced) { - t.Errorf("test %q: header mismatch: have %+v, want %+v", name, proced, original) - continue - } - } -} - -var unmarshalTransactionTests = map[string]struct { - input string - wantHash common.Hash - wantFrom common.Address - wantError error -}{ - "value transfer": { - input: `{"blockHash":"0x0188a05dcc825bd1a05dab91bea0c03622542683446e56302eabb46097d4ae11","blockNumber":"0x1e478d","from":"0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689","gas":"0x15f90","gasPrice":"0x4a817c800","hash":"0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9","input":"0x","nonce":"0x58d","to":"0x88f252f674ac755feff877abf957d4aa05adce86","transactionIndex":"0x1","value":"0x19f0ec3ed71ec00","v":"0x1c","r":"0x53829f206c99b866672f987909d556cd1c2eb60e990a3425f65083977c14187b","s":"0x5cc52383e41c923ec7d63749c1f13a7236b540527ee5b9a78b3fb869a66f60e"}`, - wantHash: common.HexToHash("0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9"), - wantFrom: common.HexToAddress("0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689"), - }, - /* TODO skipping this test as this type can not be tested with the current signing approach - "bad signature fields": { - input: `{"blockHash":"0x0188a05dcc825bd1a05dab91bea0c03622542683446e56302eabb46097d4ae11","blockNumber":"0x1e478d","from":"0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689","gas":"0x15f90","gasPrice":"0x4a817c800","hash":"0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9","input":"0x","nonce":"0x58d","to":"0x88f252f674ac755feff877abf957d4aa05adce86","transactionIndex":"0x1","value":"0x19f0ec3ed71ec00","v":"0x58","r":"0x53829f206c99b866672f987909d556cd1c2eb60e990a3425f65083977c14187b","s":"0x5cc52383e41c923ec7d63749c1f13a7236b540527ee5b9a78b3fb869a66f60e"}`, - wantError: ErrInvalidSig, - }, - */ - "missing signature v": { - input: `{"blockHash":"0x0188a05dcc825bd1a05dab91bea0c03622542683446e56302eabb46097d4ae11","blockNumber":"0x1e478d","from":"0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689","gas":"0x15f90","gasPrice":"0x4a817c800","hash":"0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9","input":"0x","nonce":"0x58d","to":"0x88f252f674ac755feff877abf957d4aa05adce86","transactionIndex":"0x1","value":"0x19f0ec3ed71ec00","r":"0x53829f206c99b866672f987909d556cd1c2eb60e990a3425f65083977c14187b","s":"0x5cc52383e41c923ec7d63749c1f13a7236b540527ee5b9a78b3fb869a66f60e"}`, - wantError: errMissingTxSignatureFields, - }, - "missing signature fields": { - input: `{"blockHash":"0x0188a05dcc825bd1a05dab91bea0c03622542683446e56302eabb46097d4ae11","blockNumber":"0x1e478d","from":"0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689","gas":"0x15f90","gasPrice":"0x4a817c800","hash":"0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9","input":"0x","nonce":"0x58d","to":"0x88f252f674ac755feff877abf957d4aa05adce86","transactionIndex":"0x1","value":"0x19f0ec3ed71ec00"}`, - wantError: errMissingTxSignatureFields, - }, - "missing fields": { - input: `{"blockHash":"0x0188a05dcc825bd1a05dab91bea0c03622542683446e56302eabb46097d4ae11","blockNumber":"0x1e478d","from":"0xf36c3f6c4a2ce8d353fb92d5cd10d19ce69ae689","hash":"0xd91c08f1e27c5ce7e1f57d78d7c56a9ee446be07b9635d84d0475660ea8905e9","input":"0x","nonce":"0x58d","to":"0x88f252f674ac755feff877abf957d4aa05adce86","transactionIndex":"0x1","value":"0x19f0ec3ed71ec00","v":"0x1c","r":"0x53829f206c99b866672f987909d556cd1c2eb60e990a3425f65083977c14187b","s":"0x5cc52383e41c923ec7d63749c1f13a7236b540527ee5b9a78b3fb869a66f60e"}`, - wantError: errMissingTxFields, - }, -} - -func TestUnmarshalTransaction(t *testing.T) { - for name, test := range unmarshalTransactionTests { - var tx *Transaction - err := json.Unmarshal([]byte(test.input), &tx) - if !checkError(t, name, err, test.wantError) { - continue - } - - if tx.Hash() != test.wantHash { - t.Errorf("test %q: got hash %x, want %x", name, tx.Hash(), test.wantHash) - continue - } - from, err := Sender(HomesteadSigner{}, tx) - if err != nil { - t.Errorf("test %q: From error %v", name, err) - } - if from != test.wantFrom { - t.Errorf("test %q: sender mismatch: got %x, want %x", name, from, test.wantFrom) - } - } -} - -func TestMarshalTransaction(t *testing.T) { - for name, test := range unmarshalTransactionTests { - if test.wantError != nil { - continue - } - var original *Transaction - json.Unmarshal([]byte(test.input), &original) - - blob, err := json.Marshal(original) - if err != nil { - t.Errorf("test %q: failed to marshal transaction: %v", name, err) - continue - } - var proced *Transaction - if err := json.Unmarshal(blob, &proced); err != nil { - t.Errorf("Test %q: failed to unmarshal marhsalled transaction: %v", name, err) - continue - } - proced.Hash() // hack private fields to pass deep equal - if !reflect.DeepEqual(original, proced) { - t.Errorf("test %q: transaction mismatch: have %+v, want %+v", name, proced, original) - continue - } - } -} - -var unmarshalReceiptTests = map[string]struct { - input string - wantError error -}{ - "ok": { - input: `{"blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","blockNumber":"0x1e773b","contractAddress":null,"cumulativeGasUsed":"0x10cea","from":"0xdf21fa922215b1a56f5a6d6294e6e36c85a0acfb","gasUsed":"0xbae2","logs":[{"address":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000df21fa922215b1a56f5a6d6294e6e36c85a0acfb","0x00000000000000000000000032be343b94f860124dc4fee278fdcbd38c102d88"],"data":"0x0000000000000000000000000000000000000000000000027cfefc4f3f392700","blockNumber":"0x1e773b","transactionIndex":"0x1","transactionHash":"0x0b4cc7844537023b709953390e3881ec5b233703a8e8824dc03e13729a1bd95a","blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","logIndex":"0x0"}],"logsBloom":"0x00000000000000020000000000020000000000000000000000000000000000000000000000000000000000000000000000040000000000000100000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000010000000000000000000000000000000000000000000000010000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000","root":"0x6e8a06b2dac39ac5c9d4db5fb2a2a94ef7a6e5ec1c554079112112caf162998a","to":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413","transactionHash":"0x0b4cc7844537023b709953390e3881ec5b233703a8e8824dc03e13729a1bd95a","transactionIndex":"0x1"}`, - }, - "missing post state": { - input: `{"blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","blockNumber":"0x1e773b","contractAddress":null,"cumulativeGasUsed":"0x10cea","from":"0xdf21fa922215b1a56f5a6d6294e6e36c85a0acfb","gasUsed":"0xbae2","logs":[{"address":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000df21fa922215b1a56f5a6d6294e6e36c85a0acfb","0x00000000000000000000000032be343b94f860124dc4fee278fdcbd38c102d88"],"data":"0x0000000000000000000000000000000000000000000000027cfefc4f3f392700","blockNumber":"0x1e773b","transactionIndex":"0x1","transactionHash":"0x0b4cc7844537023b709953390e3881ec5b233703a8e8824dc03e13729a1bd95a","blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","logIndex":"0x0"}],"logsBloom":"0x00000000000000020000000000020000000000000000000000000000000000000000000000000000000000000000000000040000000000000100000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000010000000000000000000000000000000000000000000000010000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000","to":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413","transactionHash":"0x0b4cc7844537023b709953390e3881ec5b233703a8e8824dc03e13729a1bd95a","transactionIndex":"0x1"}`, - wantError: errMissingReceiptPostState, - }, - "missing fields": { - input: `{"blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","blockNumber":"0x1e773b","contractAddress":null,"cumulativeGasUsed":"0x10cea","from":"0xdf21fa922215b1a56f5a6d6294e6e36c85a0acfb","gasUsed":"0xbae2","logs":[{"address":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000df21fa922215b1a56f5a6d6294e6e36c85a0acfb","0x00000000000000000000000032be343b94f860124dc4fee278fdcbd38c102d88"],"data":"0x0000000000000000000000000000000000000000000000027cfefc4f3f392700","blockNumber":"0x1e773b","transactionIndex":"0x1","transactionHash":"0x0b4cc7844537023b709953390e3881ec5b233703a8e8824dc03e13729a1bd95a","blockHash":"0xad20a0f78d19d7857067a9c06e6411efeab7673e183e4a545f53b724bb7fabf0","logIndex":"0x0"}],"logsBloom":"0x00000000000000020000000000020000000000000000000000000000000000000000000000000000000000000000000000040000000000000100000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000010000000000000000000000000000000000000000000000010000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000","root":"0x6e8a06b2dac39ac5c9d4db5fb2a2a94ef7a6e5ec1c554079112112caf162998a","to":"0xbb9bc244d798123fde783fcc1c72d3bb8c189413"}`, - wantError: errMissingReceiptFields, - }, -} - -func TestUnmarshalReceipt(t *testing.T) { - for name, test := range unmarshalReceiptTests { - var r *Receipt - err := json.Unmarshal([]byte(test.input), &r) - checkError(t, name, err, test.wantError) - } -} - -func TestMarshalReceipt(t *testing.T) { - for name, test := range unmarshalReceiptTests { - if test.wantError != nil { - continue - } - var original *Receipt - json.Unmarshal([]byte(test.input), &original) - - blob, err := json.Marshal(original) - if err != nil { - t.Errorf("test %q: failed to marshal receipt: %v", name, err) - continue - } - var proced *Receipt - if err := json.Unmarshal(blob, &proced); err != nil { - t.Errorf("Test %q: failed to unmarshal marhsalled receipt: %v", name, err) - continue - } - if !reflect.DeepEqual(original, proced) { - t.Errorf("test %q: receipt mismatch: have %+v, want %+v", name, proced, original) - continue - } - } -} - -func checkError(t *testing.T, testname string, got, want error) bool { - if got == nil { - if want != nil { - t.Errorf("test %q: got no error, want %q", testname, want) - return false - } - return true - } - if want == nil { - t.Errorf("test %q: unexpected error %q", testname, got) - } else if got.Error() != want.Error() { - t.Errorf("test %q: got error %q, want %q", testname, got, want) - } - return false -} diff --git a/core/types/receipt.go b/core/types/receipt.go index b00fdabff..70c10d422 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -24,6 +24,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rlp" ) @@ -49,12 +50,12 @@ type Receipt struct { type jsonReceipt struct { PostState *common.Hash `json:"root"` - CumulativeGasUsed *hexBig `json:"cumulativeGasUsed"` + CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed"` Bloom *Bloom `json:"logsBloom"` Logs *vm.Logs `json:"logs"` TxHash *common.Hash `json:"transactionHash"` ContractAddress *common.Address `json:"contractAddress"` - GasUsed *hexBig `json:"gasUsed"` + GasUsed *hexutil.Big `json:"gasUsed"` } // NewReceipt creates a barebone transaction receipt, copying the init fields. @@ -90,12 +91,12 @@ func (r *Receipt) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonReceipt{ PostState: &root, - CumulativeGasUsed: (*hexBig)(r.CumulativeGasUsed), + CumulativeGasUsed: (*hexutil.Big)(r.CumulativeGasUsed), Bloom: &r.Bloom, Logs: &r.Logs, TxHash: &r.TxHash, ContractAddress: &r.ContractAddress, - GasUsed: (*hexBig)(r.GasUsed), + GasUsed: (*hexutil.Big)(r.GasUsed), }) } diff --git a/core/types/transaction.go b/core/types/transaction.go index 323bfaee6..1d2b1b561 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -27,6 +27,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -69,15 +70,15 @@ type txdata struct { type jsonTransaction struct { Hash *common.Hash `json:"hash"` - AccountNonce *hexUint64 `json:"nonce"` - Price *hexBig `json:"gasPrice"` - GasLimit *hexBig `json:"gas"` + AccountNonce *hexutil.Uint64 `json:"nonce"` + Price *hexutil.Big `json:"gasPrice"` + GasLimit *hexutil.Big `json:"gas"` Recipient *common.Address `json:"to"` - Amount *hexBig `json:"value"` - Payload *hexBytes `json:"input"` - V *hexBig `json:"v"` - R *hexBig `json:"r"` - S *hexBig `json:"s"` + Amount *hexutil.Big `json:"value"` + Payload *hexutil.Bytes `json:"input"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` } func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction { @@ -170,15 +171,15 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonTransaction{ Hash: &hash, - AccountNonce: (*hexUint64)(&tx.data.AccountNonce), - Price: (*hexBig)(tx.data.Price), - GasLimit: (*hexBig)(tx.data.GasLimit), + AccountNonce: (*hexutil.Uint64)(&tx.data.AccountNonce), + Price: (*hexutil.Big)(tx.data.Price), + GasLimit: (*hexutil.Big)(tx.data.GasLimit), Recipient: tx.data.Recipient, - Amount: (*hexBig)(tx.data.Amount), - Payload: (*hexBytes)(&tx.data.Payload), - V: (*hexBig)(tx.data.V), - R: (*hexBig)(tx.data.R), - S: (*hexBig)(tx.data.S), + Amount: (*hexutil.Big)(tx.data.Amount), + Payload: (*hexutil.Bytes)(&tx.data.Payload), + V: (*hexutil.Big)(tx.data.V), + R: (*hexutil.Big)(tx.data.R), + S: (*hexutil.Big)(tx.data.S), }) } From 1609df327575f9e8bc849de3a649990be89f93e2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 00:41:25 +0100 Subject: [PATCH 4/8] ethclient: use package hexutil for JSON handling --- ethclient/ethclient.go | 69 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index a095aa076..4f2c0bcfb 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rlp" @@ -156,9 +157,9 @@ func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (*typ // TransactionCount returns the total number of transactions in the given block. func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { - var num rpc.HexNumber + var num hexutil.Uint err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash) - return num.Uint(), err + return uint(num), err } // TransactionInBlock returns a single transaction at index in the given block. @@ -196,11 +197,11 @@ func toBlockNumArg(number *big.Int) string { } type rpcProgress struct { - StartingBlock rpc.HexNumber - CurrentBlock rpc.HexNumber - HighestBlock rpc.HexNumber - PulledStates rpc.HexNumber - KnownStates rpc.HexNumber + StartingBlock hexutil.Uint64 + CurrentBlock hexutil.Uint64 + HighestBlock hexutil.Uint64 + PulledStates hexutil.Uint64 + KnownStates hexutil.Uint64 } // SyncProgress retrieves the current progress of the sync algorithm. If there's @@ -220,11 +221,11 @@ func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, err return nil, err } return ðereum.SyncProgress{ - StartingBlock: progress.StartingBlock.Uint64(), - CurrentBlock: progress.CurrentBlock.Uint64(), - HighestBlock: progress.HighestBlock.Uint64(), - PulledStates: progress.PulledStates.Uint64(), - KnownStates: progress.KnownStates.Uint64(), + StartingBlock: uint64(progress.StartingBlock), + CurrentBlock: uint64(progress.CurrentBlock), + HighestBlock: uint64(progress.HighestBlock), + PulledStates: uint64(progress.PulledStates), + KnownStates: uint64(progress.KnownStates), }, nil } @@ -239,7 +240,7 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) // BalanceAt returns the wei balance of the given account. // The block number can be nil, in which case the balance is taken from the latest known block. func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { - var result rpc.HexNumber + var result hexutil.Big err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber)) return (*big.Int)(&result), err } @@ -247,7 +248,7 @@ func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNu // StorageAt returns the value of key in the contract storage of the given account. // The block number can be nil, in which case the value is taken from the latest known block. func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - var result rpc.HexBytes + var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber)) return result, err } @@ -255,7 +256,7 @@ func (ec *Client) StorageAt(ctx context.Context, account common.Address, key com // CodeAt returns the contract code of the given account. // The block number can be nil, in which case the code is taken from the latest known block. func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { - var result rpc.HexBytes + var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber)) return result, err } @@ -263,9 +264,9 @@ func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumbe // NonceAt returns the account nonce of the given account. // The block number can be nil, in which case the nonce is taken from the latest known block. func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { - var result rpc.HexNumber + var result hexutil.Uint64 err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber)) - return result.Uint64(), err + return uint64(result), err } // Filters @@ -299,21 +300,21 @@ func toFilterArg(q ethereum.FilterQuery) interface{} { // PendingBalanceAt returns the wei balance of the given account in the pending state. func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { - var result rpc.HexNumber + var result hexutil.Big err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending") return (*big.Int)(&result), err } // PendingStorageAt returns the value of key in the contract storage of the given account in the pending state. func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { - var result rpc.HexBytes + var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending") return result, err } // PendingCodeAt returns the contract code of the given account in the pending state. func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { - var result rpc.HexBytes + var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending") return result, err } @@ -321,16 +322,16 @@ func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([] // PendingNonceAt returns the account nonce of the given account in the pending state. // This is the nonce that should be used for the next transaction. func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - var result rpc.HexNumber + var result hexutil.Uint64 err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") - return result.Uint64(), err + return uint64(result), err } // PendingTransactionCount returns the total number of transactions in the pending state. func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { - var num rpc.HexNumber + var num hexutil.Uint err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending") - return num.Uint(), err + return uint(num), err } // TODO: SubscribePendingTransactions (needs server side) @@ -344,29 +345,29 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { // case the code is taken from the latest known block. Note that state from very old // blocks might not be available. func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - var hex string + var hex hexutil.Bytes err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber)) if err != nil { return nil, err } - return common.FromHex(hex), nil + return hex, nil } // PendingCallContract executes a message call transaction using the EVM. // The state seen by the contract call is the pending state. func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { - var hex string + var hex hexutil.Bytes err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending") if err != nil { return nil, err } - return common.FromHex(hex), nil + return hex, nil } // SuggestGasPrice retrieves the currently suggested gas price to allow a timely // execution of a transaction. func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - var hex rpc.HexNumber + var hex hexutil.Big if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { return nil, err } @@ -378,7 +379,7 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { // the true gas limit requirement as other transactions may be added or removed by miners, // but it should provide a basis for setting a reasonable default. func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (*big.Int, error) { - var hex rpc.HexNumber + var hex hexutil.Big err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) if err != nil { return nil, err @@ -404,16 +405,16 @@ func toCallArg(msg ethereum.CallMsg) interface{} { "to": msg.To, } if len(msg.Data) > 0 { - arg["data"] = fmt.Sprintf("%#x", msg.Data) + arg["data"] = hexutil.Bytes(msg.Data) } if msg.Value != nil { - arg["value"] = fmt.Sprintf("%#x", msg.Value) + arg["value"] = (*hexutil.Big)(msg.Value) } if msg.Gas != nil { - arg["gas"] = fmt.Sprintf("%#x", msg.Gas) + arg["gas"] = (*hexutil.Big)(msg.Gas) } if msg.GasPrice != nil { - arg["gasPrice"] = fmt.Sprintf("%#x", msg.GasPrice) + arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) } return arg } From 37e5816bcdaaca2380ce5a56d9a0834340733b31 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 00:58:22 +0100 Subject: [PATCH 5/8] common: use package hexutil for fixed size type encoding --- common/types.go | 58 +++++++------------------------------------- common/types_test.go | 32 +++++++++++++++--------- 2 files changed, 30 insertions(+), 60 deletions(-) diff --git a/common/types.go b/common/types.go index 70b7e7aae..8a456e965 100644 --- a/common/types.go +++ b/common/types.go @@ -17,14 +17,12 @@ package common import ( - "encoding/hex" - "encoding/json" - "errors" "fmt" "math/big" "math/rand" "reflect" - "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" ) const ( @@ -32,8 +30,6 @@ const ( AddressLength = 20 ) -var hashJsonLengthErr = errors.New("common: unmarshalJSON failed: hash must be exactly 32 bytes") - type ( // Hash represents the 32 byte Keccak256 hash of arbitrary data. Hash [HashLength]byte @@ -57,30 +53,16 @@ func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } func (h Hash) Str() string { return string(h[:]) } func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Big() *big.Int { return Bytes2Big(h[:]) } -func (h Hash) Hex() string { return "0x" + Bytes2Hex(h[:]) } +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // UnmarshalJSON parses a hash in its hex from to a hash. func (h *Hash) UnmarshalJSON(input []byte) error { - length := len(input) - if length >= 2 && input[0] == '"' && input[length-1] == '"' { - input = input[1 : length-1] - } - // strip "0x" for length check - if len(input) > 1 && strings.ToLower(string(input[:2])) == "0x" { - input = input[2:] - } - - // validate the length of the input hash - if len(input) != HashLength*2 { - return hashJsonLengthErr - } - h.SetBytes(FromHex(string(input))) - return nil + return hexutil.UnmarshalJSON("Hash", input, h[:]) } // Serialize given hash to JSON func (h Hash) MarshalJSON() ([]byte, error) { - return json.Marshal(h.Hex()) + return hexutil.Bytes(h[:]).MarshalJSON() } // Sets the hash to the value of b. If b is larger than len(h) it will panic @@ -142,7 +124,7 @@ func (a Address) Str() string { return string(a[:]) } func (a Address) Bytes() []byte { return a[:] } func (a Address) Big() *big.Int { return Bytes2Big(a[:]) } func (a Address) Hash() Hash { return BytesToHash(a[:]) } -func (a Address) Hex() string { return "0x" + Bytes2Hex(a[:]) } +func (a Address) Hex() string { return hexutil.Encode(a[:]) } // Sets the address to the value of b. If b is larger than len(a) it will panic func (a *Address) SetBytes(b []byte) { @@ -164,34 +146,12 @@ func (a *Address) Set(other Address) { // Serialize given address to JSON func (a Address) MarshalJSON() ([]byte, error) { - return json.Marshal(a.Hex()) + return hexutil.Bytes(a[:]).MarshalJSON() } // Parse address from raw json data -func (a *Address) UnmarshalJSON(data []byte) error { - if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' { - data = data[1 : len(data)-1] - } - - if len(data) > 2 && data[0] == '0' && data[1] == 'x' { - data = data[2:] - } - - if len(data) != 2*AddressLength { - return fmt.Errorf("Invalid address length, expected %d got %d bytes", 2*AddressLength, len(data)) - } - - n, err := hex.Decode(a[:], data) - if err != nil { - return err - } - - if n != AddressLength { - return fmt.Errorf("Invalid address") - } - - a.Set(HexToAddress(string(data))) - return nil +func (a *Address) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalJSON("Address", input, a[:]) } // PP Pretty Prints a byte slice in the following format: diff --git a/common/types_test.go b/common/types_test.go index de67cfcb5..e84780f43 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -18,7 +18,10 @@ package common import ( "math/big" + "strings" "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" ) func TestBytesConversion(t *testing.T) { @@ -38,19 +41,26 @@ func TestHashJsonValidation(t *testing.T) { var tests = []struct { Prefix string Size int - Error error + Error string }{ - {"", 2, hashJsonLengthErr}, - {"", 62, hashJsonLengthErr}, - {"", 66, hashJsonLengthErr}, - {"", 65, hashJsonLengthErr}, - {"0X", 64, nil}, - {"0x", 64, nil}, - {"0x", 62, hashJsonLengthErr}, + {"", 62, hexutil.ErrMissingPrefix.Error()}, + {"0x", 66, "hex string has length 66, want 64 for Hash"}, + {"0x", 63, hexutil.ErrOddLength.Error()}, + {"0x", 0, "hex string has length 0, want 64 for Hash"}, + {"0x", 64, ""}, + {"0X", 64, ""}, } - for i, test := range tests { - if err := h.UnmarshalJSON(append([]byte(test.Prefix), make([]byte, test.Size)...)); err != test.Error { - t.Errorf("test #%d: error mismatch: have %v, want %v", i, err, test.Error) + for _, test := range tests { + input := `"` + test.Prefix + strings.Repeat("0", test.Size) + `"` + err := h.UnmarshalJSON([]byte(input)) + if err == nil { + if test.Error != "" { + t.Errorf("%s: error mismatch: have nil, want %q", input, test.Error) + } + } else { + if err.Error() != test.Error { + t.Errorf("%s: error mismatch: have %q, want %q", input, err, test.Error) + } } } } From ec5f531f4b62b61d9636a03050af3453532a7b05 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 01:30:54 +0100 Subject: [PATCH 6/8] accounts: don't use common.Address for address field common.Address JSON encoding now enforces the 0x prefix, but key files don't have the prefix. --- accounts/addrcache.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/accounts/addrcache.go b/accounts/addrcache.go index 0a904f788..a99f23606 100644 --- a/accounts/addrcache.go +++ b/accounts/addrcache.go @@ -225,7 +225,7 @@ func (ac *addrCache) scan() ([]Account, error) { buf = new(bufio.Reader) addrs []Account keyJSON struct { - Address common.Address `json:"address"` + Address string `json:"address"` } ) for _, fi := range files { @@ -241,15 +241,16 @@ func (ac *addrCache) scan() ([]Account, error) { } buf.Reset(fd) // Parse the address. - keyJSON.Address = common.Address{} + keyJSON.Address = "" err = json.NewDecoder(buf).Decode(&keyJSON) + addr := common.HexToAddress(keyJSON.Address) switch { case err != nil: glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err) - case (keyJSON.Address == common.Address{}): + case (addr == common.Address{}): glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path) default: - addrs = append(addrs, Account{Address: keyJSON.Address, File: path}) + addrs = append(addrs, Account{Address: addr, File: path}) } fd.Close() } From be746628c76a14f47fc9fc44d0bcf386dd7b6483 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 02:21:46 +0100 Subject: [PATCH 7/8] eth/filters: simplify query object decoding --- eth/filters/api.go | 91 ++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 834513262..584f55afd 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -17,7 +17,6 @@ package filters import ( - "encoding/hex" "encoding/json" "errors" "fmt" @@ -28,6 +27,7 @@ import ( "golang.org/x/net/context" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -459,52 +459,28 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { if raw.Addresses != nil { // raw.Address can contain a single address or an array of addresses - var addresses []common.Address - if strAddrs, ok := raw.Addresses.([]interface{}); ok { - for i, addr := range strAddrs { + switch rawAddr := raw.Addresses.(type) { + case []interface{}: + for i, addr := range rawAddr { if strAddr, ok := addr.(string); ok { - if len(strAddr) >= 2 && strAddr[0] == '0' && (strAddr[1] == 'x' || strAddr[1] == 'X') { - strAddr = strAddr[2:] - } - if decAddr, err := hex.DecodeString(strAddr); err == nil { - addresses = append(addresses, common.BytesToAddress(decAddr)) - } else { - return fmt.Errorf("invalid address given") + addr, err := decodeAddress(strAddr) + if err != nil { + return fmt.Errorf("invalid address at index %d: %v", i, err) } + args.Addresses = append(args.Addresses, addr) } else { - return fmt.Errorf("invalid address on index %d", i) + return fmt.Errorf("non-string address at index %d", i) } } - } else if singleAddr, ok := raw.Addresses.(string); ok { - if len(singleAddr) >= 2 && singleAddr[0] == '0' && (singleAddr[1] == 'x' || singleAddr[1] == 'X') { - singleAddr = singleAddr[2:] + case string: + addr, err := decodeAddress(rawAddr) + if err != nil { + return fmt.Errorf("invalid address: %v", err) } - if decAddr, err := hex.DecodeString(singleAddr); err == nil { - addresses = append(addresses, common.BytesToAddress(decAddr)) - } else { - return fmt.Errorf("invalid address given") - } - } else { - return errors.New("invalid address(es) given") + args.Addresses = []common.Address{addr} + default: + return errors.New("invalid addresses in query") } - args.Addresses = addresses - } - - // helper function which parses a string to a topic hash - topicConverter := func(raw string) (common.Hash, error) { - if len(raw) == 0 { - return common.Hash{}, nil - } - if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') { - raw = raw[2:] - } - if len(raw) != 2*common.HashLength { - return common.Hash{}, errors.New("invalid topic(s)") - } - if decAddr, err := hex.DecodeString(raw); err == nil { - return common.BytesToHash(decAddr), nil - } - return common.Hash{}, errors.New("invalid topic(s)") } // topics is an array consisting of strings and/or arrays of strings. @@ -512,20 +488,25 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { if len(raw.Topics) > 0 { args.Topics = make([][]common.Hash, len(raw.Topics)) for i, t := range raw.Topics { - if t == nil { // ignore topic when matching logs + switch topic := t.(type) { + case nil: + // ignore topic when matching logs args.Topics[i] = []common.Hash{common.Hash{}} - } else if topic, ok := t.(string); ok { // match specific topic - top, err := topicConverter(topic) + + case string: + // match specific topic + top, err := decodeTopic(topic) if err != nil { return err } args.Topics[i] = []common.Hash{top} - } else if topics, ok := t.([]interface{}); ok { // or case e.g. [null, "topic0", "topic1"] - for _, rawTopic := range topics { + case []interface{}: + // or case e.g. [null, "topic0", "topic1"] + for _, rawTopic := range topic { if rawTopic == nil { args.Topics[i] = append(args.Topics[i], common.Hash{}) } else if topic, ok := rawTopic.(string); ok { - parsed, err := topicConverter(topic) + parsed, err := decodeTopic(topic) if err != nil { return err } @@ -534,7 +515,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { return fmt.Errorf("invalid topic(s)") } } - } else { + default: return fmt.Errorf("invalid topic(s)") } } @@ -542,3 +523,19 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { return nil } + +func decodeAddress(s string) (common.Address, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.AddressLength { + err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) + } + return common.BytesToAddress(b), err +} + +func decodeTopic(s string) (common.Hash, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.HashLength { + err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) + } + return common.BytesToHash(b), err +} From 91bceb4acee23b1c92b288dd3a301a98eba9f4d3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 02:22:03 +0100 Subject: [PATCH 8/8] ethclient: "addresses" -> "address" in filter query encoding --- ethclient/ethclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 4f2c0bcfb..00edd90e1 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -287,7 +287,7 @@ func toFilterArg(q ethereum.FilterQuery) interface{} { arg := map[string]interface{}{ "fromBlock": toBlockNumArg(q.FromBlock), "toBlock": toBlockNumArg(q.ToBlock), - "addresses": q.Addresses, + "address": q.Addresses, "topics": q.Topics, } if q.FromBlock == nil {