From ec75953f509a94ccb20bd346a2413bfee1281072 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 15 Sep 2016 02:57:48 +0200 Subject: [PATCH 01/17] 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 0000000000..29e6de3335 --- /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 0000000000..3f261c9a7e --- /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 0000000000..cbbadbed6a --- /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 0000000000..16f1b9aa71 --- /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 02/17] 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 b292f5f436..06f9417039 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 775016f9c3..4d31895580 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 03/17] 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 68504ffcc8..2034bb0ff7 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 d3945a734b..a1d13e2186 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 d2718a96d1..0000000000 --- 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 d80cda68b2..0000000000 --- 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 b00fdabff6..70c10d4225 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 323bfaee60..1d2b1b5615 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 04/17] 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 a095aa0767..4f2c0bcfbf 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 e4bf004560acf18919d5a1b952eeda521ae07aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tr=C3=B3n?= Date: Mon, 28 Nov 2016 11:25:19 +0100 Subject: [PATCH 05/17] build/ci: add swarm related executables (bzzd, bzzup, bzzhash) to binary packages (#3354) --- build/ci.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build/ci.go b/build/ci.go index 61e8614ed2..29c4b8b331 100644 --- a/build/ci.go +++ b/build/ci.go @@ -72,6 +72,9 @@ var ( executablePath("abigen"), executablePath("evm"), executablePath("geth"), + executablePath("bzzd"), + executablePath("bzzhash"), + executablePath("bzzup"), executablePath("rlpdump"), } @@ -89,6 +92,18 @@ var ( Name: "evm", Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.", }, + { + Name: "bzzd", + Description: "Ethereum Swarm daemon", + }, + { + Name: "bzzup", + Description: "Ethereum Swarm command line file/directory uploader", + }, + { + Name: "bzzhash", + Description: "Ethereum Swarm file/directory hash calculator", + }, { Name: "abigen", Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.", From 665bb43a4c9e97d6f23de02575b92156881a2db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 28 Nov 2016 11:31:15 +0100 Subject: [PATCH 06/17] light: implemented VMState.Empty() (#3357) --- light/state_object.go | 9 +++++++-- light/vm_env.go | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/light/state_object.go b/light/state_object.go index 61c3888fe3..6161d2dfbe 100644 --- a/light/state_object.go +++ b/light/state_object.go @@ -201,14 +201,19 @@ func (self *StateObject) Copy() *StateObject { // Attribute accessors // +// empty returns whether the account is considered empty. +func (self *StateObject) empty() bool { + return self.nonce == 0 && self.balance.BitLen() == 0 && bytes.Equal(self.codeHash, emptyCodeHash) +} + // Balance returns the account balance func (self *StateObject) Balance() *big.Int { return self.balance } // Address returns the address of the contract/account -func (c *StateObject) Address() common.Address { - return c.address +func (self *StateObject) Address() common.Address { + return self.address } // Code returns the contract code diff --git a/light/vm_env.go b/light/vm_env.go index 5d330b072a..d4d7bcce72 100644 --- a/light/vm_env.go +++ b/light/vm_env.go @@ -263,6 +263,13 @@ func (s *VMState) Exist(addr common.Address) bool { return res } +// Empty returns true if the account at the given address is considered empty +func (s *VMState) Empty(addr common.Address) bool { + so, err := s.state.GetStateObject(s.ctx, addr) + s.errHandler(err) + return so == nil || so.empty() +} + // HasSuicided returns true if the given account has been marked for deletion // or false if the account does not exist func (s *VMState) HasSuicided(addr common.Address) bool { From 37e5816bcdaaca2380ce5a56d9a0834340733b31 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 00:58:22 +0100 Subject: [PATCH 07/17] 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 70b7e7aae8..8a456e965e 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 de67cfcb5f..e84780f43d 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 08/17] 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 0a904f7883..a99f23606d 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 09/17] 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 8345132629..584f55afd8 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 10/17] 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 4f2c0bcfbf..00edd90e1c 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 { From f81660b6db9c4dfef54ab353bf8b5b1e627d34c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 28 Nov 2016 12:56:13 +0200 Subject: [PATCH 11/17] build: use single CocoaPods package, pre-release dev builds --- build/ci.go | 12 +++++------- build/pod.podspec | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/build/ci.go b/build/ci.go index 29c4b8b331..c202480b2b 100644 --- a/build/ci.go +++ b/build/ci.go @@ -826,13 +826,12 @@ func doXCodeFramework(cmdline []string) { // Prepare and upload a PodSpec to CocoaPods if *deploy != "" { meta := newPodMetadata(env, archive) - build.Render("build/pod.podspec", meta.Name+".podspec", 0755, meta) - build.MustRunCommand("pod", *deploy, "push", meta.Name+".podspec", "--allow-warnings", "--verbose") + build.Render("build/pod.podspec", "Geth.podspec", 0755, meta) + build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose") } } type podMetadata struct { - Name string Version string Commit string Archive string @@ -865,14 +864,13 @@ func newPodMetadata(env build.Environment, archive string) podMetadata { } } } - name := "Geth" + version := build.VERSION() if isUnstableBuild(env) { - name += "Develop" + version += "-unstable." + env.Buildnum } return podMetadata{ - Name: name, Archive: archive, - Version: build.VERSION(), + Version: version, Commit: env.Commit, Contributors: contribs, } diff --git a/build/pod.podspec b/build/pod.podspec index a1799ecc3e..2c14c280c7 100644 --- a/build/pod.podspec +++ b/build/pod.podspec @@ -1,5 +1,5 @@ Pod::Spec.new do |spec| - spec.name = '{{.Name}}' + spec.name = 'Geth' spec.version = '{{.Version}}' spec.license = { :type => 'GNU Lesser General Public License, Version 3.0' } spec.homepage = 'https://github.com/ethereum/go-ethereum' From e949a2ed2f1c59ed5ff1f187ad987c882656e6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tr=C3=B3n?= Date: Mon, 28 Nov 2016 13:29:33 +0100 Subject: [PATCH 12/17] cmd/bzzd: swarm daemon fixes (#3359) * cmd/bzzd: add missing p2p/discovery flags * cmd/bzzd: fix two bugs crashing bzzd if bootnodes flag given * cmd/bzzd: make no swap default, renamed flag bzznoswap->bzzswap * internal/web3ext: correct methods for bzz IPC module * cmd/bzzd: ethapi param not mandatory. Warning if no blockchain * cmd/bzzd: correct default IPC modules in help string * cmd/utils: fix help description for networkId - add Ropsten * cmd/bzz, swarm/api, swarm/network: add swarm networkId flag * cmd/bzzd: change nosync flag to sync and BootTFlag --- cmd/bzzd/main.go | 51 ++++++++++++++++++++++++------------- cmd/utils/flags.go | 2 +- internal/web3ext/web3ext.go | 16 ++---------- swarm/api/config.go | 4 ++- swarm/api/config_test.go | 7 ++--- swarm/network/protocol.go | 18 ++++++++----- swarm/swarm.go | 4 +-- 7 files changed, 56 insertions(+), 46 deletions(-) diff --git a/cmd/bzzd/main.go b/cmd/bzzd/main.go index a3e87dc8a1..4bb2ca04a6 100644 --- a/cmd/bzzd/main.go +++ b/cmd/bzzd/main.go @@ -23,6 +23,7 @@ import ( "os" "runtime" "strconv" + "strings" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" @@ -38,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/swarm" bzzapi "github.com/ethereum/go-ethereum/swarm/api" + "github.com/ethereum/go-ethereum/swarm/network" "gopkg.in/urfave/cli.v1" ) @@ -61,17 +63,22 @@ var ( Name: "bzzport", Usage: "Swarm local http api port", } + SwarmNetworkIdFlag = cli.IntFlag{ + Name: "bzznetworkid", + Usage: "Network identifier (integer, default 322=swarm testnet)", + Value: network.NetworkId, + } SwarmConfigPathFlag = cli.StringFlag{ Name: "bzzconfig", Usage: "Swarm config file path (datadir/bzz)", } - SwarmSwapDisabled = cli.BoolFlag{ - Name: "bzznoswap", - Usage: "Swarm SWAP disabled (default false)", + SwarmSwapEnabled = cli.BoolFlag{ + Name: "swap", + Usage: "Swarm SWAP enabled (default false)", } - SwarmSyncDisabled = cli.BoolFlag{ - Name: "bzznosync", - Usage: "Swarm Syncing disabled (default false)", + SwarmSyncEnabled = cli.BoolTFlag{ + Name: "sync", + Usage: "Swarm Syncing enabled (default true)", } EthAPI = cli.StringFlag{ Name: "ethapi", @@ -86,6 +93,7 @@ func init() { // Override flag defaults so bzzd can run alongside geth. utils.ListenPortFlag.Value = 30399 utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"} + utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3" // Set up the cli app. app.Commands = nil @@ -96,21 +104,24 @@ func init() { utils.BootnodesFlag, utils.KeyStoreDirFlag, utils.ListenPortFlag, + utils.NoDiscoverFlag, + utils.DiscoveryV5Flag, utils.NetrestrictFlag, - utils.MaxPeersFlag, - utils.NATFlag, utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, + utils.MaxPeersFlag, + utils.NATFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, // bzzd-specific flags EthAPI, SwarmConfigPathFlag, - SwarmSwapDisabled, - SwarmSyncDisabled, + SwarmSwapEnabled, + SwarmSyncEnabled, SwarmPortFlag, SwarmAccountFlag, + SwarmNetworkIdFlag, ChequebookAddrFlag, } app.Flags = append(app.Flags, debug.Flags...) @@ -138,7 +149,8 @@ func bzzd(ctx *cli.Context) error { // Add bootnodes as initial peers. if ctx.GlobalIsSet(utils.BootnodesFlag.Name) { - injectBootnodes(stack.Server(), ctx.GlobalStringSlice(utils.BootnodesFlag.Name)) + bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",") + injectBootnodes(stack.Server(), bootnodes) } else { injectBootnodes(stack.Server(), defaultBootnodes) } @@ -155,7 +167,7 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) { if bzzdir == "" { bzzdir = stack.InstanceDir() } - bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey) + bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name)) if err != nil { utils.Fatalf("unable to configure swarm: %v", err) } @@ -163,16 +175,18 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) { if len(bzzport) > 0 { bzzconfig.Port = bzzport } - swapEnabled := !ctx.GlobalBool(SwarmSwapDisabled.Name) - syncEnabled := !ctx.GlobalBool(SwarmSyncDisabled.Name) + swapEnabled := ctx.GlobalBool(SwarmSwapEnabled.Name) + syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabled.Name) ethapi := ctx.GlobalString(EthAPI.Name) - if ethapi == "" { - utils.Fatalf("Option %q must not be empty", EthAPI.Name) - } boot := func(ctx *node.ServiceContext) (node.Service, error) { - client, err := ethclient.Dial(ethapi) + var client *ethclient.Client + if ethapi == "" { + err = fmt.Errorf("use ethapi flag to connect to a an eth client and talk to the blockchain") + } else { + client, err = ethclient.Dial(ethapi) + } if err != nil { utils.Fatalf("Can't connect: %v", err) } @@ -241,6 +255,7 @@ func injectBootnodes(srv *p2p.Server, nodes []string) { n, err := discover.ParseNode(url) if err != nil { glog.Errorf("invalid bootnode %q", err) + continue } srv.AddPeer(n) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cb91f45390..abd344df01 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -116,7 +116,7 @@ var ( } NetworkIdFlag = cli.IntFlag{ Name: "networkid", - Usage: "Network identifier (integer, 0=Olympic, 1=Frontier, 2=Morden)", + Usage: "Network identifier (integer, 0=Olympic (disused), 1=Frontier, 2=Morden (disused), 3=Ropsten)", Value: eth.NetworkId, } OlympicFlag = cli.BoolFlag{ diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index adf2366793..ce50d3634c 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -37,12 +37,6 @@ web3._extend({ property: 'bzz', methods: [ - new web3._extend.Method({ - name: 'blockNetworkRead', - call: 'bzz_blockNetworkRead', - params: 1, - inputFormatter: [null] - }), new web3._extend.Method({ name: 'syncEnabled', call: 'bzz_syncEnabled', @@ -68,17 +62,11 @@ web3._extend({ inputFormatter: [null, null] }), new web3._extend.Method({ - name: 'retrieve', - call: 'bzz_retrieve', + name: 'resolve', + call: 'bzz_resolve', params: 1, inputFormatter: [null] }), - new web3._extend.Method({ - name: 'store', - call: 'bzz_store', - params: 2, - inputFormatter: [null] - }), new web3._extend.Method({ name: 'get', call: 'bzz_get', diff --git a/swarm/api/config.go b/swarm/api/config.go index c04a015efa..f9d3575f7f 100644 --- a/swarm/api/config.go +++ b/swarm/api/config.go @@ -54,11 +54,12 @@ type Config struct { PublicKey string BzzKey string EnsRoot common.Address + NetworkId uint64 } // config is agnostic to where private key is coming from // so managing accounts is outside swarm and left to wrappers -func NewConfig(path string, contract common.Address, prvKey *ecdsa.PrivateKey) (self *Config, err error) { +func NewConfig(path string, contract common.Address, prvKey *ecdsa.PrivateKey, networkId uint64) (self *Config, err error) { address := crypto.PubkeyToAddress(prvKey.PublicKey) // default beneficiary address dirpath := filepath.Join(path, "bzz-"+common.Bytes2Hex(address.Bytes())) err = os.MkdirAll(dirpath, os.ModePerm) @@ -82,6 +83,7 @@ func NewConfig(path string, contract common.Address, prvKey *ecdsa.PrivateKey) ( PublicKey: pubkeyhex, BzzKey: keyhex, EnsRoot: toyNetEnsRoot, + NetworkId: networkId, } data, err = ioutil.ReadFile(confpath) if err != nil { diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go index 8fe3ddacc2..39ac2d0677 100644 --- a/swarm/api/config_test.go +++ b/swarm/api/config_test.go @@ -83,7 +83,8 @@ var ( "Port": "8500", "PublicKey": "0x045f5cfd26692e48d0017d380349bcf50982488bc11b5145f3ddf88b24924299048450542d43527fbe29a5cb32f38d62755393ac002e6bfdd71b8d7ba725ecd7a3", "BzzKey": "0xe861964402c0b78e2d44098329b8545726f215afa737d803714a4338552fcb81", - "EnsRoot": "0xd344889e0be3e9ef6c26b0f60ef66a32e83c1b69" + "EnsRoot": "0xd344889e0be3e9ef6c26b0f60ef66a32e83c1b69", + "NetworkId": 323 }` ) @@ -95,7 +96,7 @@ func TestConfigWriteRead(t *testing.T) { defer os.RemoveAll(tmp) prvkey := crypto.ToECDSA(common.Hex2Bytes(hexprvkey)) - orig, err := NewConfig(tmp, common.Address{}, prvkey) + orig, err := NewConfig(tmp, common.Address{}, prvkey, 323) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -109,7 +110,7 @@ func TestConfigWriteRead(t *testing.T) { t.Fatalf("default config mismatch:\nexpected: %v\ngot: %v", exp, string(data)) } - conf, err := NewConfig(tmp, common.Address{}, prvkey) + conf, err := NewConfig(tmp, common.Address{}, prvkey, 323) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/swarm/network/protocol.go b/swarm/network/protocol.go index 5e65108d6b..a3ffd338f0 100644 --- a/swarm/network/protocol.go +++ b/swarm/network/protocol.go @@ -95,6 +95,7 @@ type bzz struct { errors *errs.Errors // errors table backend chequebook.Backend lastActive time.Time + NetworkId uint64 swap *swap.Swap // swap instance for the peer connection swapParams *bzzswap.SwapParams // swap settings both local and remote @@ -126,7 +127,7 @@ on each peer connection The Run function of the Bzz protocol class creates a bzz instance which will represent the peer for the swarm hive and all peer-aware components */ -func Bzz(cloud StorageHandler, backend chequebook.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams) (p2p.Protocol, error) { +func Bzz(cloud StorageHandler, backend chequebook.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams, networkId uint64) (p2p.Protocol, error) { // a single global request db is created for all peer connections // this is to persist delivery backlog and aid syncronisation @@ -134,13 +135,15 @@ func Bzz(cloud StorageHandler, backend chequebook.Backend, hive *Hive, dbaccess if err != nil { return p2p.Protocol{}, fmt.Errorf("error setting up request db: %v", err) } - + if networkId == 0 { + networkId = NetworkId + } return p2p.Protocol{ Name: "bzz", Version: Version, Length: ProtocolLength, Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return run(requestDb, cloud, backend, hive, dbaccess, sp, sy, p, rw) + return run(requestDb, cloud, backend, hive, dbaccess, sp, sy, networkId, p, rw) }, }, nil } @@ -157,7 +160,7 @@ the main protocol loop that * whenever the loop terminates, the peer will disconnect with Subprotocol error * whenever handlers return an error the loop terminates */ -func run(requestDb *storage.LDBDatabase, depo StorageHandler, backend chequebook.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams, p *p2p.Peer, rw p2p.MsgReadWriter) (err error) { +func run(requestDb *storage.LDBDatabase, depo StorageHandler, backend chequebook.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams, networkId uint64, p *p2p.Peer, rw p2p.MsgReadWriter) (err error) { self := &bzz{ storage: depo, @@ -175,6 +178,7 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, backend chequebook syncParams: sy, swapEnabled: hive.swapEnabled, syncEnabled: true, + NetworkId: networkId, } // handle handshake @@ -340,7 +344,7 @@ func (self *bzz) handleStatus() (err error) { Version: uint64(Version), ID: "honey", Addr: self.selfAddr(), - NetworkId: uint64(NetworkId), + NetworkId: uint64(self.NetworkId), Swap: &bzzswap.SwapProfile{ Profile: self.swapParams.Profile, PayProfile: self.swapParams.PayProfile, @@ -372,8 +376,8 @@ func (self *bzz) handleStatus() (err error) { return self.protoError(ErrDecode, " %v: %v", msg, err) } - if status.NetworkId != NetworkId { - return self.protoError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) + if status.NetworkId != self.NetworkId { + return self.protoError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, self.NetworkId) } if Version != status.Version { diff --git a/swarm/swarm.go b/swarm/swarm.go index 1dce8cafd1..7e38944de0 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -209,7 +209,7 @@ func (self *Swarm) Stop() error { // implements the node.Service interface func (self *Swarm) Protocols() []p2p.Protocol { - proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams) + proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams, self.config.NetworkId) if err != nil { return nil } @@ -279,7 +279,7 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) { return } - config, err := api.NewConfig(datadir, common.Address{}, prvKey) + config, err := api.NewConfig(datadir, common.Address{}, prvKey, network.NetworkId) if err != nil { return } From 318ad3c1e424912ff69c9febc4e08a0137f8803f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 28 Nov 2016 13:55:56 +0100 Subject: [PATCH 13/17] common/hexutil: fix Test{Decode,Unmarshal}Uint64 on 32bit arch (#3363) --- common/hexutil/hexutil_test.go | 2 +- common/hexutil/json_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go index 3f261c9a7e..b58b4745c5 100644 --- a/common/hexutil/hexutil_test.go +++ b/common/hexutil/hexutil_test.go @@ -105,7 +105,7 @@ var ( {input: `0`, wantErr: ErrMissingPrefix}, {input: `0x`, wantErr: ErrEmptyNumber}, {input: `0x01`, wantErr: ErrLeadingZero}, - {input: `0xfffffffffffffffff`, wantErr: ErrUintRange}, + {input: `0xfffffffffffffffff`, wantErr: ErrUint64Range}, {input: `0xx`, wantErr: ErrSyntax}, {input: `0x1zz01`, wantErr: ErrSyntax}, // valid diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 16f1b9aa71..2d2e2fc0fd 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -202,7 +202,7 @@ var unmarshalUint64Tests = []unmarshalTest{ {input: `"0"`, wantErr: ErrMissingPrefix}, {input: `"0x"`, wantErr: ErrEmptyNumber}, {input: `"0x01"`, wantErr: ErrLeadingZero}, - {input: `"0xfffffffffffffffff"`, wantErr: ErrUintRange}, + {input: `"0xfffffffffffffffff"`, wantErr: ErrUint64Range}, {input: `"0xx"`, wantErr: ErrSyntax}, {input: `"0x1zz01"`, wantErr: ErrSyntax}, From b5be6b72cb06ded22075a41db6abc670d33c5ea7 Mon Sep 17 00:00:00 2001 From: bas-vk Date: Mon, 28 Nov 2016 14:59:06 +0100 Subject: [PATCH 14/17] eth/filter: add support for pending logs (#3219) --- eth/filters/api.go | 80 ++++++++------- eth/filters/api_test.go | 8 +- eth/filters/filter.go | 15 ++- eth/filters/filter_system.go | 149 +++++++++++++++++++++------- eth/filters/filter_system_test.go | 155 +++++++++++++++++++++++++----- eth/filters/filter_test.go | 3 + miner/worker.go | 19 +++- 7 files changed, 328 insertions(+), 101 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 584f55afd8..d5dd57743d 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -239,11 +239,17 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - rpcSub := notifier.CreateSubscription() + var ( + rpcSub = notifier.CreateSubscription() + matchedLogs = make(chan []Log) + ) + + logsSub, err := api.events.SubscribeLogs(crit, matchedLogs) + if err != nil { + return nil, err + } go func() { - matchedLogs := make(chan []Log) - logsSub := api.events.SubscribeLogs(crit, matchedLogs) for { select { @@ -276,18 +282,20 @@ type FilterCriteria struct { // used to retrieve logs when the state changes. This method cannot be // used to fetch logs that are already stored in the state. // +// Default criteria for the from and to block are "latest". +// Using "latest" as block number will return logs for mined blocks. +// Using "pending" as block number returns logs for not yet mined (pending) blocks. +// In case logs are removed (chain reorg) previously returned logs are returned +// again but with the removed property set to true. +// +// In case "fromBlock" > "toBlock" an error is returned. +// // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter -func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) rpc.ID { - var ( - logs = make(chan []Log) - logsSub = api.events.SubscribeLogs(crit, logs) - ) - - if crit.FromBlock == nil { - crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) - } - if crit.ToBlock == nil { - crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) +func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { + logs := make(chan []Log) + logsSub, err := api.events.SubscribeLogs(crit, logs) + if err != nil { + return rpc.ID(""), err } api.filtersMu.Lock() @@ -312,7 +320,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) rpc.ID { } }() - return logsSub.ID + return logsSub.ID, nil } // GetLogs returns logs matching the given argument that are stored within the state. @@ -363,28 +371,38 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]Log api.filtersMu.Unlock() if !found || f.typ != LogsSubscription { - return []Log{}, nil + return nil, fmt.Errorf("filter not found") } filter := New(api.backend, api.useMipMap) - filter.SetBeginBlock(f.crit.FromBlock.Int64()) - filter.SetEndBlock(f.crit.ToBlock.Int64()) + if f.crit.FromBlock != nil { + filter.SetBeginBlock(f.crit.FromBlock.Int64()) + } else { + filter.SetBeginBlock(rpc.LatestBlockNumber.Int64()) + } + if f.crit.ToBlock != nil { + filter.SetEndBlock(f.crit.ToBlock.Int64()) + } else { + filter.SetEndBlock(rpc.LatestBlockNumber.Int64()) + } filter.SetAddresses(f.crit.Addresses) filter.SetTopics(f.crit.Topics) - logs, err := filter.Find(ctx) - return returnLogs(logs), err + logs, err:= filter.Find(ctx) + if err != nil { + return nil, err + } + return returnLogs(logs), nil } // GetFilterChanges returns the logs for the filter with the given id since // last time is was called. This can be used for polling. // // For pending transaction and block filters the result is []common.Hash. -// (pending)Log filters return []Log. If the filter could not be found -// []interface{}{} is returned. +// (pending)Log filters return []Log. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges -func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) interface{} { +func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() @@ -400,15 +418,15 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) interface{} { case PendingTransactionsSubscription, BlocksSubscription: hashes := f.hashes f.hashes = nil - return returnHashes(hashes) - case PendingLogsSubscription, LogsSubscription: + return returnHashes(hashes), nil + case LogsSubscription: logs := f.logs f.logs = nil - return returnLogs(logs) + return returnLogs(logs), nil } } - return []interface{}{} + return []interface{}{}, fmt.Errorf("filter not found") } // returnHashes is a helper that will return an empty hash array case the given hash array is nil, @@ -443,15 +461,11 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { return err } - if raw.From == nil || raw.From.Int64() < 0 { - args.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) - } else { + if raw.From != nil { args.FromBlock = big.NewInt(raw.From.Int64()) } - if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 { - args.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) - } else { + if raw.ToBlock != nil { args.ToBlock = big.NewInt(raw.ToBlock.Int64()) } diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 98eb6cbaa2..068a5ea243 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -42,11 +42,11 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { if err := json.Unmarshal([]byte("{}"), &test0); err != nil { t.Fatal(err) } - if test0.FromBlock.Int64() != rpc.LatestBlockNumber.Int64() { - t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.FromBlock) + if test0.FromBlock != nil { + t.Fatalf("expected nil, got %d", test0.FromBlock) } - if test0.ToBlock.Int64() != rpc.LatestBlockNumber.Int64() { - t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.ToBlock) + if test0.ToBlock != nil { + t.Fatalf("expected nil, got %d", test0.ToBlock) } if len(test0.Addresses) != 0 { t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses)) diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 4004af3002..ce7383fb39 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -20,6 +20,8 @@ import ( "math" "time" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -162,7 +164,7 @@ func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []Log, er } unfiltered = append(unfiltered, rl...) } - logs = append(logs, filterLogs(unfiltered, f.addresses, f.topics)...) + logs = append(logs, filterLogs(unfiltered, nil, nil, f.addresses, f.topics)...) } } @@ -179,12 +181,18 @@ func includes(addresses []common.Address, a common.Address) bool { return false } -func filterLogs(logs []Log, addresses []common.Address, topics [][]common.Hash) []Log { +func filterLogs(logs []Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []Log { var ret []Log - // Filter the logs for interesting stuff Logs: for _, log := range logs { + if fromBlock != nil && fromBlock.Int64() >= 0 && uint64(fromBlock.Int64()) > log.BlockNumber { + continue + } + if toBlock != nil && toBlock.Int64() >= 0 && uint64(toBlock.Int64()) < log.BlockNumber { + continue + } + if len(addresses) > 0 && !includes(addresses, log.Address) { continue } @@ -211,7 +219,6 @@ Logs: continue Logs } } - ret = append(ret, log) } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index c2c072a9f9..b59718aea5 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -43,13 +43,17 @@ const ( UnknownSubscription Type = iota // LogsSubscription queries for new or removed (chain reorg) logs LogsSubscription - // PendingLogsSubscription queries for logs for the pending block + // PendingLogsSubscription queries for logs in pending blocks PendingLogsSubscription + // MinedAndPendingLogsSubscription queries for logs in mined and pending blocks. + MinedAndPendingLogsSubscription // PendingTransactionsSubscription queries tx hashes for pending // transactions entering the pending state PendingTransactionsSubscription // BlocksSubscription queries hashes for blocks that are imported BlocksSubscription + // LastSubscription keeps track of the last index + LastIndexSubscription ) var ( @@ -63,19 +67,26 @@ type Log struct { Removed bool `json:"removed"` } +// MarshalJSON returns *l as the JSON encoding of l. func (l *Log) MarshalJSON() ([]byte, error) { fields := map[string]interface{}{ "address": l.Address, "data": fmt.Sprintf("0x%x", l.Data), - "blockNumber": fmt.Sprintf("%#x", l.BlockNumber), + "blockNumber": nil, "logIndex": fmt.Sprintf("%#x", l.Index), - "blockHash": l.BlockHash, + "blockHash": nil, "transactionHash": l.TxHash, "transactionIndex": fmt.Sprintf("%#x", l.TxIndex), "topics": l.Topics, "removed": l.Removed, } + // mined logs + if l.BlockHash != (common.Hash{}) { + fields["blockNumber"] = fmt.Sprintf("%#x", l.BlockNumber) + fields["blockHash"] = l.BlockHash + } + return json.Marshal(fields) } @@ -169,8 +180,65 @@ func (es *EventSystem) subscribe(sub *subscription) *Subscription { } // SubscribeLogs creates a subscription that will write all logs matching the +// given criteria to the given logs channel. Default value for the from and to +// block is "latest". If the fromBlock > toBlock an error is returned. +func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) (*Subscription, error) { + var from, to rpc.BlockNumber + if crit.FromBlock == nil { + from = rpc.LatestBlockNumber + } else { + from = rpc.BlockNumber(crit.FromBlock.Int64()) + } + if crit.ToBlock == nil { + to = rpc.LatestBlockNumber + } else { + to = rpc.BlockNumber(crit.ToBlock.Int64()) + } + + // only interested in pending logs + if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber { + return es.subscribePendingLogs(crit, logs), nil + } + // only interested in new mined logs + if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { + return es.subscribeLogs(crit, logs), nil + } + // only interested in mined logs within a specific block range + if from >= 0 && to >= 0 && to >= from { + return es.subscribeLogs(crit, logs), nil + } + // interested in mined logs from a specific block number, new logs and pending logs + if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber { + return es.subscribeMinedPendingLogs(crit, logs), nil + } + // interested in logs from a specific block number to new mined blocks + if from >= 0 && to == rpc.LatestBlockNumber { + return es.subscribeLogs(crit, logs), nil + } + return nil, fmt.Errorf("invalid from and to block combination: from > to") +} + +// subscribeMinedPendingLogs creates a subscription that returned mined and +// pending logs that match the given criteria. +func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan []Log) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: MinedAndPendingLogsSubscription, + logsCrit: crit, + created: time.Now(), + logs: logs, + hashes: make(chan common.Hash), + headers: make(chan *types.Header), + installed: make(chan struct{}), + err: make(chan error), + } + + return es.subscribe(sub) +} + +// subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. -func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) *Subscription { +func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []Log) *Subscription { sub := &subscription{ id: rpc.NewID(), typ: LogsSubscription, @@ -186,9 +254,9 @@ func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) *Subs return es.subscribe(sub) } -// SubscribePendingLogs creates a subscription that will write pending logs matching the -// given criteria to the given channel. -func (es *EventSystem) SubscribePendingLogs(crit FilterCriteria, logs chan []Log) *Subscription { +// subscribePendingLogs creates a subscription that writes transaction hashes for +// transactions that enter the transaction pool. +func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []Log) *Subscription { sub := &subscription{ id: rpc.NewID(), typ: PendingLogsSubscription, @@ -204,23 +272,6 @@ func (es *EventSystem) SubscribePendingLogs(crit FilterCriteria, logs chan []Log return es.subscribe(sub) } -// SubscribePendingTxEvents creates a sbuscription that writes transaction hashes for -// transactions that enter the transaction pool. -func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscription { - sub := &subscription{ - id: rpc.NewID(), - typ: PendingTransactionsSubscription, - created: time.Now(), - logs: make(chan []Log), - hashes: hashes, - headers: make(chan *types.Header), - installed: make(chan struct{}), - err: make(chan error), - } - - return es.subscribe(sub) -} - // SubscribeNewHeads creates a subscription that writes the header of a block that is // imported in the chain. func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { @@ -238,6 +289,23 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti return es.subscribe(sub) } +// SubscribePendingTxEvents creates a subscription that writes transaction hashes for +// transactions that enter the transaction pool. +func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: PendingTransactionsSubscription, + created: time.Now(), + logs: make(chan []Log), + hashes: hashes, + headers: make(chan *types.Header), + installed: make(chan struct{}), + err: make(chan error), + } + + return es.subscribe(sub) +} + type filterIndex map[Type]map[rpc.ID]*subscription // broadcast event to filters that match criteria. @@ -251,7 +319,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) { if len(e) > 0 { for _, f := range filters[LogsSubscription] { if ev.Time.After(f.created) { - if matchedLogs := filterLogs(convertLogs(e, false), f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { + if matchedLogs := filterLogs(convertLogs(e, false), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { f.logs <- matchedLogs } } @@ -260,7 +328,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) { case core.RemovedLogsEvent: for _, f := range filters[LogsSubscription] { if ev.Time.After(f.created) { - if matchedLogs := filterLogs(convertLogs(e.Logs, true), f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { + if matchedLogs := filterLogs(convertLogs(e.Logs, true), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { f.logs <- matchedLogs } } @@ -268,7 +336,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) { case core.PendingLogsEvent: for _, f := range filters[PendingLogsSubscription] { if ev.Time.After(f.created) { - if matchedLogs := filterLogs(convertLogs(e.Logs, false), f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { + if matchedLogs := filterLogs(convertLogs(e.Logs, false), nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 { f.logs <- matchedLogs } } @@ -351,8 +419,8 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. } unfiltered = append(unfiltered, rl...) } - logs := filterLogs(unfiltered, addresses, topics) - //fmt.Println("found", len(logs)) + + logs := filterLogs(unfiltered, nil, nil, addresses, topics) return logs } return nil @@ -364,6 +432,11 @@ func (es *EventSystem) eventLoop() { index = make(filterIndex) sub = es.mux.Subscribe(core.PendingLogsEvent{}, core.RemovedLogsEvent{}, vm.Logs{}, core.TxPreEvent{}, core.ChainEvent{}) ) + + for i := UnknownSubscription; i < LastIndexSubscription; i++ { + index[i] = make(map[rpc.ID]*subscription) + } + for { select { case ev, active := <-sub.Chan(): @@ -372,13 +445,22 @@ func (es *EventSystem) eventLoop() { } es.broadcast(index, ev) case f := <-es.install: - if _, found := index[f.typ]; !found { - index[f.typ] = make(map[rpc.ID]*subscription) + if f.typ == MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + index[LogsSubscription][f.id] = f + index[PendingLogsSubscription][f.id] = f + } else { + index[f.typ][f.id] = f } - index[f.typ][f.id] = f close(f.installed) case f := <-es.uninstall: - delete(index[f.typ], f.id) + if f.typ == MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + delete(index[LogsSubscription], f.id) + delete(index[PendingLogsSubscription], f.id) + } else { + delete(index[f.typ], f.id) + } close(f.err) } } @@ -386,6 +468,7 @@ func (es *EventSystem) eventLoop() { // convertLogs is a helper utility that converts vm.Logs to []filter.Log. func convertLogs(in vm.Logs, removed bool) []Log { + logs := make([]Log, len(in)) for i, l := range in { logs[i] = Log{l, removed} diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index d6d4199cc2..e8591a2e46 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -34,13 +34,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var ( - mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() - backend = &testBackend{mux, db} - api = NewPublicFilterAPI(backend, false) -) - type testBackend struct { mux *event.TypeMux db ethdb.Database @@ -81,6 +74,11 @@ func TestBlockSubscription(t *testing.T) { t.Parallel() var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + genesis = core.WriteGenesisBlockForTesting(db) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {}) chainEvents = []core.ChainEvent{} @@ -130,6 +128,11 @@ func TestPendingTxFilter(t *testing.T) { t.Parallel() var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + transactions = []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), new(big.Int), new(big.Int), nil), types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), new(big.Int), new(big.Int), nil), @@ -150,9 +153,13 @@ func TestPendingTxFilter(t *testing.T) { } for { - h := api.GetFilterChanges(fid0).([]common.Hash) - hashes = append(hashes, h...) + results, err := api.GetFilterChanges(fid0) + if err != nil { + t.Fatalf("Unable to retrieve logs: %v", err) + } + h := results.([]common.Hash) + hashes = append(hashes, h...) if len(hashes) >= len(transactions) { break } @@ -167,11 +174,86 @@ func TestPendingTxFilter(t *testing.T) { } } +// TestLogFilterCreation test whether a given filter criteria makes sense. +// If not it must return an error. +func TestLogFilterCreation(t *testing.T) { + var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + + testCases = []struct { + crit FilterCriteria + success bool + }{ + // defaults + {FilterCriteria{}, true}, + // valid block number range + {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, true}, + // "mined" block range to pending + {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, true}, + // new mined and pending blocks + {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, true}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}, false}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, false}, + } + ) + + for i, test := range testCases { + _, err := api.NewFilter(test.crit) + if test.success && err != nil { + t.Errorf("expected filter creation for case %d to success, got %v", i, err) + } + if !test.success && err == nil { + t.Errorf("expected testcase %d to fail with an error", i) + } + } +} + +// TestInvalidLogFilterCreation tests whether invalid filter log criteria results in an error +// when the filter is created. +func TestInvalidLogFilterCreation(t *testing.T) { + t.Parallel() + + var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + ) + + // different situations where log filter creation should fail. + // Reason: fromBlock > toBlock + testCases := []FilterCriteria{ + 0: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, + 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, + 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, + } + + for i, test := range testCases { + if _, err := api.NewFilter(test); err == nil { + t.Errorf("Expected NewFilter for case #%d to fail", i) + } + } +} + // TestLogFilter tests whether log filters match the correct logs that are posted to the event mux. func TestLogFilter(t *testing.T) { t.Parallel() var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333") @@ -180,8 +262,8 @@ func TestLogFilter(t *testing.T) { secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222") notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999") + // posted twice, once as vm.Logs and once as core.PendingLogsEvent allLogs = vm.Logs{ - // Note, these are used for comparison of the test cases. vm.NewLog(firstAddr, []common.Hash{}, []byte(""), 0), vm.NewLog(firstAddr, []common.Hash{firstTopic}, []byte(""), 1), vm.NewLog(secondAddr, []common.Hash{firstTopic}, []byte(""), 1), @@ -189,45 +271,64 @@ func TestLogFilter(t *testing.T) { vm.NewLog(thirdAddress, []common.Hash{secondTopic}, []byte(""), 3), } + expectedCase7 = vm.Logs{allLogs[3], allLogs[4], allLogs[0], allLogs[1], allLogs[2], allLogs[3], allLogs[4]} + expectedCase11 = vm.Logs{allLogs[1], allLogs[2], allLogs[1], allLogs[2]} + testCases = []struct { crit FilterCriteria expected vm.Logs id rpc.ID }{ // match all - {FilterCriteria{}, allLogs, ""}, + 0: {FilterCriteria{}, allLogs, ""}, // match none due to no matching addresses - {FilterCriteria{Addresses: []common.Address{common.Address{}, notUsedAddress}, Topics: [][]common.Hash{allLogs[0].Topics}}, vm.Logs{}, ""}, + 1: {FilterCriteria{Addresses: []common.Address{common.Address{}, notUsedAddress}, Topics: [][]common.Hash{allLogs[0].Topics}}, vm.Logs{}, ""}, // match logs based on addresses, ignore topics - {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""}, + 2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""}, // match none due to no matching topics (match with address) - {FilterCriteria{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{[]common.Hash{notUsedTopic}}}, vm.Logs{}, ""}, + 3: {FilterCriteria{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{[]common.Hash{notUsedTopic}}}, vm.Logs{}, ""}, // match logs based on addresses and topics - {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{[]common.Hash{firstTopic, secondTopic}}}, allLogs[3:5], ""}, + 4: {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{[]common.Hash{firstTopic, secondTopic}}}, allLogs[3:5], ""}, // match logs based on multiple addresses and "or" topics - {FilterCriteria{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{[]common.Hash{firstTopic, secondTopic}}}, allLogs[2:5], ""}, - // block numbers are ignored for filters created with New***Filter, these return all logs that match the given criterias when the state changes - {FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, allLogs[:2], ""}, + 5: {FilterCriteria{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{[]common.Hash{firstTopic, secondTopic}}}, allLogs[2:5], ""}, + // logs in the pending block + 6: {FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, allLogs[:2], ""}, + // mined logs with block num >= 2 or pending logs + 7: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, expectedCase7, ""}, + // all "mined" logs with block num >= 2 + 8: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs[3:], ""}, + // all "mined" logs + 9: {FilterCriteria{ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs, ""}, + // all "mined" logs with 1>= block num <=2 and topic secondTopic + 10: {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{[]common.Hash{secondTopic}}}, allLogs[3:4], ""}, + // all "mined" and pending logs with topic firstTopic + 11: {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), Topics: [][]common.Hash{[]common.Hash{firstTopic}}}, expectedCase11, ""}, } - - err error ) // create all filters for i := range testCases { - testCases[i].id = api.NewFilter(testCases[i].crit) + testCases[i].id, _ = api.NewFilter(testCases[i].crit) } // raise events time.Sleep(1 * time.Second) - if err = mux.Post(allLogs); err != nil { + if err := mux.Post(allLogs); err != nil { + t.Fatal(err) + } + if err := mux.Post(core.PendingLogsEvent{Logs: allLogs}); err != nil { t.Fatal(err) } for i, tt := range testCases { var fetched []Log for { // fetch all expected logs - fetched = append(fetched, api.GetFilterChanges(tt.id).([]Log)...) + results, err := api.GetFilterChanges(tt.id) + if err != nil { + t.Fatalf("Unable to fetch logs: %v", err) + } + + fetched = append(fetched, results.([]Log)...) if len(fetched) >= len(tt.expected) { break } @@ -247,7 +348,6 @@ func TestLogFilter(t *testing.T) { if !reflect.DeepEqual(fetched[l].Log, tt.expected[l]) { t.Errorf("invalid log on index %d for case %d", l, i) } - } } } @@ -257,6 +357,11 @@ func TestPendingLogsSubscription(t *testing.T) { t.Parallel() var ( + mux = new(event.TypeMux) + db, _ = ethdb.NewMemDatabase() + backend = &testBackend{mux, db} + api = NewPublicFilterAPI(backend, false) + firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333") @@ -319,7 +424,7 @@ func TestPendingLogsSubscription(t *testing.T) { // (some) events are posted. for i := range testCases { testCases[i].c = make(chan []Log) - testCases[i].sub = api.events.SubscribePendingLogs(testCases[i].crit, testCases[i].c) + testCases[i].sub, _ = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c) } for n, test := range testCases { diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index a8c767ead6..ab6a87851f 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/event" ) func makeReceipt(addr common.Address) *types.Receipt { @@ -51,6 +52,7 @@ func BenchmarkMipmaps(b *testing.B) { var ( db, _ = ethdb.NewLDBDatabase(dir, 0, 0) + mux = new(event.TypeMux) backend = &testBackend{mux, db} key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -126,6 +128,7 @@ func TestFilters(t *testing.T) { var ( db, _ = ethdb.NewLDBDatabase(dir, 0, 0) + mux = new(event.TypeMux) backend = &testBackend{mux, db} key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key1.PublicKey) diff --git a/miner/worker.go b/miner/worker.go index 2933b6bd3b..ca00c7229c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -262,6 +262,7 @@ func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) ( func (self *worker) wait() { for { + mustCommitNewWork := true for result := range self.recv { atomic.AddInt32(&self.atWork, -1) @@ -315,6 +316,8 @@ func (self *worker) wait() { core.WriteReceipts(self.chainDb, work.receipts) // Write map map bloom filters core.WriteMipmapBloom(self.chainDb, block.NumberU64(), work.receipts) + // implicit by posting ChainHeadEvent + mustCommitNewWork = false } // broadcast before waiting for validation @@ -343,7 +346,9 @@ func (self *worker) wait() { } glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm) - self.commitNewWork() + if mustCommitNewWork { + self.commitNewWork() + } } } } @@ -451,6 +456,7 @@ func (self *worker) commitNewWork() { tstart := time.Now() parent := self.chain.CurrentBlock() + tstamp := tstart.Unix() if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 { tstamp = parent.Time().Int64() + 1 @@ -618,7 +624,16 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB txs.Shift() } } + if len(coalescedLogs) > 0 || env.tcount > 0 { + // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined + // logs by filling in the block hash when the block was mined by the local miner. This can + // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. + cpy := make(vm.Logs, len(coalescedLogs)) + for i, l := range coalescedLogs { + cpy[i] = new(vm.Log) + *cpy[i] = *l + } go func(logs vm.Logs, tcount int) { if len(logs) > 0 { mux.Post(core.PendingLogsEvent{Logs: logs}) @@ -626,7 +641,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB if tcount > 0 { mux.Post(core.PendingStateEvent{}) } - }(coalescedLogs, env.tcount) + }(cpy, env.tcount) } } From b70acf3c5bd335d8dedc2a5aa6eff05c4c32105e Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 28 Nov 2016 14:59:26 +0100 Subject: [PATCH 15/17] params: stable version 1.5.4 (#3360) --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 78ab56e3fb..c75c96d7bf 100644 --- a/params/version.go +++ b/params/version.go @@ -19,10 +19,10 @@ package params import "fmt" const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 5 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 5 // Minor version component of the current release + VersionPatch = 4 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From bf24b120d7ab952282c5c12c36d249626b1293ea Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 28 Nov 2016 15:09:12 +0100 Subject: [PATCH 16/17] VERSION, params: bump unstable version 1.5.5 (#3361) --- VERSION | 2 +- params/version.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 94fe62c274..9075be4951 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.4 +1.5.5 diff --git a/params/version.go b/params/version.go index c75c96d7bf..f8c0d3c9ac 100644 --- a/params/version.go +++ b/params/version.go @@ -19,10 +19,10 @@ package params import "fmt" const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 5 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 5 // Minor version component of the current release + VersionPatch = 5 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 61ccb43487a1d751e20b39d8328cb00994cdf796 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Mon, 28 Nov 2016 21:56:38 +0100 Subject: [PATCH 17/17] core/types: Document Transaction.To (#3366) --- core/types/transaction.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/types/transaction.go b/core/types/transaction.go index 1d2b1b5615..f566dc365f 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -234,6 +234,8 @@ func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amo func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) CheckNonce() bool { return true } +// To returns the recipient address of the transaction. +// It returns nil if the transaction is a contract creation. func (tx *Transaction) To() *common.Address { if tx.data.Recipient == nil { return nil