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/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()
}
diff --git a/build/ci.go b/build/ci.go
index 61e8614ed2..c202480b2b 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.",
@@ -811,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
@@ -850,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'
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/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..b58b4745c5
--- /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: ErrUint64Range},
+ {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..2d2e2fc0fd
--- /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: ErrUint64Range},
+ {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
+ }
+ }
+}
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)
+ }
}
}
}
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..f566dc365f 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),
})
}
@@ -233,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
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,
diff --git a/eth/filters/api.go b/eth/filters/api.go
index 8345132629..d5dd57743d 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"
@@ -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())
}
@@ -459,52 +473,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 +502,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 +529,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
return fmt.Errorf("invalid topic(s)")
}
}
- } else {
+ default:
return fmt.Errorf("invalid topic(s)")
}
}
@@ -542,3 +537,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
+}
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/ethclient/ethclient.go b/ethclient/ethclient.go
index a095aa0767..00edd90e1c 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
@@ -286,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 {
@@ -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
}
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/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 {
diff --git a/miner/worker.go b/miner/worker.go
index c9f66fb9fc..edbd502c14 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -277,6 +277,7 @@ func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) (
func (self *worker) wait() {
for {
+ mustCommitNewWork := true
for result := range self.recv {
atomic.AddInt32(&self.atWork, -1)
@@ -330,6 +331,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
@@ -358,7 +361,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()
+ }
}
}
}
@@ -466,6 +471,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
@@ -633,7 +639,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})
@@ -641,7 +656,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
if tcount > 0 {
mux.Post(core.PendingStateEvent{})
}
- }(coalescedLogs, env.tcount)
+ }(cpy, env.tcount)
}
}
diff --git a/params/version.go b/params/version.go
index 78ab56e3fb..f8c0d3c9ac 100644
--- a/params/version.go
+++ b/params/version.go
@@ -21,7 +21,7 @@ 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
+ VersionPatch = 5 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
)
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
}