Merge github.com:holiman/go-ethereum

This commit is contained in:
Martin Holst Swende 2016-11-29 09:49:02 +01:00
commit ad6f304d80
36 changed files with 1548 additions and 725 deletions

View file

@ -1 +1 @@
1.5.4 1.5.5

View file

@ -225,7 +225,7 @@ func (ac *addrCache) scan() ([]Account, error) {
buf = new(bufio.Reader) buf = new(bufio.Reader)
addrs []Account addrs []Account
keyJSON struct { keyJSON struct {
Address common.Address `json:"address"` Address string `json:"address"`
} }
) )
for _, fi := range files { for _, fi := range files {
@ -241,15 +241,16 @@ func (ac *addrCache) scan() ([]Account, error) {
} }
buf.Reset(fd) buf.Reset(fd)
// Parse the address. // Parse the address.
keyJSON.Address = common.Address{} keyJSON.Address = ""
err = json.NewDecoder(buf).Decode(&keyJSON) err = json.NewDecoder(buf).Decode(&keyJSON)
addr := common.HexToAddress(keyJSON.Address)
switch { switch {
case err != nil: case err != nil:
glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err) 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) glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
default: default:
addrs = append(addrs, Account{Address: keyJSON.Address, File: path}) addrs = append(addrs, Account{Address: addr, File: path})
} }
fd.Close() fd.Close()
} }

View file

@ -72,6 +72,9 @@ var (
executablePath("abigen"), executablePath("abigen"),
executablePath("evm"), executablePath("evm"),
executablePath("geth"), executablePath("geth"),
executablePath("bzzd"),
executablePath("bzzhash"),
executablePath("bzzup"),
executablePath("rlpdump"), executablePath("rlpdump"),
} }
@ -89,6 +92,18 @@ var (
Name: "evm", 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.", 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", Name: "abigen",
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.", 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 // Prepare and upload a PodSpec to CocoaPods
if *deploy != "" { if *deploy != "" {
meta := newPodMetadata(env, archive) meta := newPodMetadata(env, archive)
build.Render("build/pod.podspec", meta.Name+".podspec", 0755, meta) build.Render("build/pod.podspec", "Geth.podspec", 0755, meta)
build.MustRunCommand("pod", *deploy, "push", meta.Name+".podspec", "--allow-warnings", "--verbose") build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose")
} }
} }
type podMetadata struct { type podMetadata struct {
Name string
Version string Version string
Commit string Commit string
Archive string Archive string
@ -850,14 +864,13 @@ func newPodMetadata(env build.Environment, archive string) podMetadata {
} }
} }
} }
name := "Geth" version := build.VERSION()
if isUnstableBuild(env) { if isUnstableBuild(env) {
name += "Develop" version += "-unstable." + env.Buildnum
} }
return podMetadata{ return podMetadata{
Name: name,
Archive: archive, Archive: archive,
Version: build.VERSION(), Version: version,
Commit: env.Commit, Commit: env.Commit,
Contributors: contribs, Contributors: contribs,
} }

View file

@ -1,5 +1,5 @@
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = '{{.Name}}' spec.name = 'Geth'
spec.version = '{{.Version}}' spec.version = '{{.Version}}'
spec.license = { :type => 'GNU Lesser General Public License, Version 3.0' } spec.license = { :type => 'GNU Lesser General Public License, Version 3.0' }
spec.homepage = 'https://github.com/ethereum/go-ethereum' spec.homepage = 'https://github.com/ethereum/go-ethereum'

View file

@ -23,6 +23,7 @@ import (
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
@ -38,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/swarm" "github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api" bzzapi "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/network"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
@ -61,17 +63,22 @@ var (
Name: "bzzport", Name: "bzzport",
Usage: "Swarm local http api port", 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{ SwarmConfigPathFlag = cli.StringFlag{
Name: "bzzconfig", Name: "bzzconfig",
Usage: "Swarm config file path (datadir/bzz)", Usage: "Swarm config file path (datadir/bzz)",
} }
SwarmSwapDisabled = cli.BoolFlag{ SwarmSwapEnabled = cli.BoolFlag{
Name: "bzznoswap", Name: "swap",
Usage: "Swarm SWAP disabled (default false)", Usage: "Swarm SWAP enabled (default false)",
} }
SwarmSyncDisabled = cli.BoolFlag{ SwarmSyncEnabled = cli.BoolTFlag{
Name: "bzznosync", Name: "sync",
Usage: "Swarm Syncing disabled (default false)", Usage: "Swarm Syncing enabled (default true)",
} }
EthAPI = cli.StringFlag{ EthAPI = cli.StringFlag{
Name: "ethapi", Name: "ethapi",
@ -86,6 +93,7 @@ func init() {
// Override flag defaults so bzzd can run alongside geth. // Override flag defaults so bzzd can run alongside geth.
utils.ListenPortFlag.Value = 30399 utils.ListenPortFlag.Value = 30399
utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"} utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"}
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3"
// Set up the cli app. // Set up the cli app.
app.Commands = nil app.Commands = nil
@ -96,21 +104,24 @@ func init() {
utils.BootnodesFlag, utils.BootnodesFlag,
utils.KeyStoreDirFlag, utils.KeyStoreDirFlag,
utils.ListenPortFlag, utils.ListenPortFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
utils.NetrestrictFlag, utils.NetrestrictFlag,
utils.MaxPeersFlag,
utils.NATFlag,
utils.NodeKeyFileFlag, utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag, utils.NodeKeyHexFlag,
utils.MaxPeersFlag,
utils.NATFlag,
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCApiFlag, utils.IPCApiFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
// bzzd-specific flags // bzzd-specific flags
EthAPI, EthAPI,
SwarmConfigPathFlag, SwarmConfigPathFlag,
SwarmSwapDisabled, SwarmSwapEnabled,
SwarmSyncDisabled, SwarmSyncEnabled,
SwarmPortFlag, SwarmPortFlag,
SwarmAccountFlag, SwarmAccountFlag,
SwarmNetworkIdFlag,
ChequebookAddrFlag, ChequebookAddrFlag,
} }
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
@ -138,7 +149,8 @@ func bzzd(ctx *cli.Context) error {
// Add bootnodes as initial peers. // Add bootnodes as initial peers.
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) { 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 { } else {
injectBootnodes(stack.Server(), defaultBootnodes) injectBootnodes(stack.Server(), defaultBootnodes)
} }
@ -155,7 +167,7 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
if bzzdir == "" { if bzzdir == "" {
bzzdir = stack.InstanceDir() bzzdir = stack.InstanceDir()
} }
bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey) bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name))
if err != nil { if err != nil {
utils.Fatalf("unable to configure swarm: %v", err) utils.Fatalf("unable to configure swarm: %v", err)
} }
@ -163,16 +175,18 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
if len(bzzport) > 0 { if len(bzzport) > 0 {
bzzconfig.Port = bzzport bzzconfig.Port = bzzport
} }
swapEnabled := !ctx.GlobalBool(SwarmSwapDisabled.Name) swapEnabled := ctx.GlobalBool(SwarmSwapEnabled.Name)
syncEnabled := !ctx.GlobalBool(SwarmSyncDisabled.Name) syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabled.Name)
ethapi := ctx.GlobalString(EthAPI.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) { 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 { if err != nil {
utils.Fatalf("Can't connect: %v", err) utils.Fatalf("Can't connect: %v", err)
} }
@ -241,6 +255,7 @@ func injectBootnodes(srv *p2p.Server, nodes []string) {
n, err := discover.ParseNode(url) n, err := discover.ParseNode(url)
if err != nil { if err != nil {
glog.Errorf("invalid bootnode %q", err) glog.Errorf("invalid bootnode %q", err)
continue
} }
srv.AddPeer(n) srv.AddPeer(n)
} }

View file

@ -116,7 +116,7 @@ var (
} }
NetworkIdFlag = cli.IntFlag{ NetworkIdFlag = cli.IntFlag{
Name: "networkid", 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, Value: eth.NetworkId,
} }
OlympicFlag = cli.BoolFlag{ OlympicFlag = cli.BoolFlag{

232
common/hexutil/hexutil.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
/*
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}
}
}

271
common/hexutil/json.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

258
common/hexutil/json_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}
}
}

View file

@ -17,14 +17,12 @@
package common package common
import ( import (
"encoding/hex"
"encoding/json"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"math/rand" "math/rand"
"reflect" "reflect"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
const ( const (
@ -32,8 +30,6 @@ const (
AddressLength = 20 AddressLength = 20
) )
var hashJsonLengthErr = errors.New("common: unmarshalJSON failed: hash must be exactly 32 bytes")
type ( type (
// Hash represents the 32 byte Keccak256 hash of arbitrary data. // Hash represents the 32 byte Keccak256 hash of arbitrary data.
Hash [HashLength]byte 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) Str() string { return string(h[:]) }
func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Bytes() []byte { return h[:] }
func (h Hash) Big() *big.Int { return Bytes2Big(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. // UnmarshalJSON parses a hash in its hex from to a hash.
func (h *Hash) UnmarshalJSON(input []byte) error { func (h *Hash) UnmarshalJSON(input []byte) error {
length := len(input) return hexutil.UnmarshalJSON("Hash", input, h[:])
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
} }
// Serialize given hash to JSON // Serialize given hash to JSON
func (h Hash) MarshalJSON() ([]byte, error) { 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 // 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) Bytes() []byte { return a[:] }
func (a Address) Big() *big.Int { return Bytes2Big(a[:]) } func (a Address) Big() *big.Int { return Bytes2Big(a[:]) }
func (a Address) Hash() Hash { return BytesToHash(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 // Sets the address to the value of b. If b is larger than len(a) it will panic
func (a *Address) SetBytes(b []byte) { func (a *Address) SetBytes(b []byte) {
@ -164,34 +146,12 @@ func (a *Address) Set(other Address) {
// Serialize given address to JSON // Serialize given address to JSON
func (a Address) MarshalJSON() ([]byte, error) { func (a Address) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Hex()) return hexutil.Bytes(a[:]).MarshalJSON()
} }
// Parse address from raw json data // Parse address from raw json data
func (a *Address) UnmarshalJSON(data []byte) error { func (a *Address) UnmarshalJSON(input []byte) error {
if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' { return hexutil.UnmarshalJSON("Address", input, a[:])
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
} }
// PP Pretty Prints a byte slice in the following format: // PP Pretty Prints a byte slice in the following format:

View file

@ -18,7 +18,10 @@ package common
import ( import (
"math/big" "math/big"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
func TestBytesConversion(t *testing.T) { func TestBytesConversion(t *testing.T) {
@ -38,19 +41,26 @@ func TestHashJsonValidation(t *testing.T) {
var tests = []struct { var tests = []struct {
Prefix string Prefix string
Size int Size int
Error error Error string
}{ }{
{"", 2, hashJsonLengthErr}, {"", 62, hexutil.ErrMissingPrefix.Error()},
{"", 62, hashJsonLengthErr}, {"0x", 66, "hex string has length 66, want 64 for Hash"},
{"", 66, hashJsonLengthErr}, {"0x", 63, hexutil.ErrOddLength.Error()},
{"", 65, hashJsonLengthErr}, {"0x", 0, "hex string has length 0, want 64 for Hash"},
{"0X", 64, nil}, {"0x", 64, ""},
{"0x", 64, nil}, {"0X", 64, ""},
{"0x", 62, hashJsonLengthErr}, }
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)
} }
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)
} }
} }
} }

View file

@ -29,6 +29,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "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/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -63,20 +64,12 @@ func (n BlockNonce) Uint64() uint64 {
// MarshalJSON implements json.Marshaler // MarshalJSON implements json.Marshaler
func (n BlockNonce) MarshalJSON() ([]byte, error) { func (n BlockNonce) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"0x%x"`, n)), nil return hexutil.Bytes(n[:]).MarshalJSON()
} }
// UnmarshalJSON implements json.Unmarshaler // UnmarshalJSON implements json.Unmarshaler
func (n *BlockNonce) UnmarshalJSON(input []byte) error { func (n *BlockNonce) UnmarshalJSON(input []byte) error {
var b hexBytes return hexutil.UnmarshalJSON("BlockNonce", input, n[:])
if err := b.UnmarshalJSON(input); err != nil {
return err
}
if len(b) != 8 {
return errBadNonceSize
}
copy((*n)[:], b)
return nil
} }
// Header represents a block header in the Ethereum blockchain. // Header represents a block header in the Ethereum blockchain.
@ -106,12 +99,12 @@ type jsonHeader struct {
TxHash *common.Hash `json:"transactionsRoot"` TxHash *common.Hash `json:"transactionsRoot"`
ReceiptHash *common.Hash `json:"receiptsRoot"` ReceiptHash *common.Hash `json:"receiptsRoot"`
Bloom *Bloom `json:"logsBloom"` Bloom *Bloom `json:"logsBloom"`
Difficulty *hexBig `json:"difficulty"` Difficulty *hexutil.Big `json:"difficulty"`
Number *hexBig `json:"number"` Number *hexutil.Big `json:"number"`
GasLimit *hexBig `json:"gasLimit"` GasLimit *hexutil.Big `json:"gasLimit"`
GasUsed *hexBig `json:"gasUsed"` GasUsed *hexutil.Big `json:"gasUsed"`
Time *hexBig `json:"timestamp"` Time *hexutil.Big `json:"timestamp"`
Extra *hexBytes `json:"extraData"` Extra *hexutil.Bytes `json:"extraData"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
} }
@ -151,12 +144,12 @@ func (h *Header) MarshalJSON() ([]byte, error) {
TxHash: &h.TxHash, TxHash: &h.TxHash,
ReceiptHash: &h.ReceiptHash, ReceiptHash: &h.ReceiptHash,
Bloom: &h.Bloom, Bloom: &h.Bloom,
Difficulty: (*hexBig)(h.Difficulty), Difficulty: (*hexutil.Big)(h.Difficulty),
Number: (*hexBig)(h.Number), Number: (*hexutil.Big)(h.Number),
GasLimit: (*hexBig)(h.GasLimit), GasLimit: (*hexutil.Big)(h.GasLimit),
GasUsed: (*hexBig)(h.GasUsed), GasUsed: (*hexutil.Big)(h.GasUsed),
Time: (*hexBig)(h.Time), Time: (*hexutil.Big)(h.Time),
Extra: (*hexBytes)(&h.Extra), Extra: (*hexutil.Bytes)(&h.Extra),
MixDigest: &h.MixDigest, MixDigest: &h.MixDigest,
Nonce: &h.Nonce, Nonce: &h.Nonce,
}) })

View file

@ -21,6 +21,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "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. // MarshalJSON encodes b as a hex string with 0x prefix.
func (b Bloom) MarshalJSON() ([]byte, error) { 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. // UnmarshalJSON b as a hex string with 0x prefix.
func (b *Bloom) UnmarshalJSON(input []byte) error { func (b *Bloom) UnmarshalJSON(input []byte) error {
var dec hexBytes return hexutil.UnmarshalJSON("Bloom", input, b[:])
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
} }
func CreateBloom(receipts Receipts) Bloom { func CreateBloom(receipts Receipts) Bloom {

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -24,6 +24,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/core/vm"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -49,12 +50,12 @@ type Receipt struct {
type jsonReceipt struct { type jsonReceipt struct {
PostState *common.Hash `json:"root"` PostState *common.Hash `json:"root"`
CumulativeGasUsed *hexBig `json:"cumulativeGasUsed"` CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed"`
Bloom *Bloom `json:"logsBloom"` Bloom *Bloom `json:"logsBloom"`
Logs *vm.Logs `json:"logs"` Logs *vm.Logs `json:"logs"`
TxHash *common.Hash `json:"transactionHash"` TxHash *common.Hash `json:"transactionHash"`
ContractAddress *common.Address `json:"contractAddress"` ContractAddress *common.Address `json:"contractAddress"`
GasUsed *hexBig `json:"gasUsed"` GasUsed *hexutil.Big `json:"gasUsed"`
} }
// NewReceipt creates a barebone transaction receipt, copying the init fields. // NewReceipt creates a barebone transaction receipt, copying the init fields.
@ -90,12 +91,12 @@ func (r *Receipt) MarshalJSON() ([]byte, error) {
return json.Marshal(&jsonReceipt{ return json.Marshal(&jsonReceipt{
PostState: &root, PostState: &root,
CumulativeGasUsed: (*hexBig)(r.CumulativeGasUsed), CumulativeGasUsed: (*hexutil.Big)(r.CumulativeGasUsed),
Bloom: &r.Bloom, Bloom: &r.Bloom,
Logs: &r.Logs, Logs: &r.Logs,
TxHash: &r.TxHash, TxHash: &r.TxHash,
ContractAddress: &r.ContractAddress, ContractAddress: &r.ContractAddress,
GasUsed: (*hexBig)(r.GasUsed), GasUsed: (*hexutil.Big)(r.GasUsed),
}) })
} }

View file

@ -27,6 +27,7 @@ import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -69,15 +70,15 @@ type txdata struct {
type jsonTransaction struct { type jsonTransaction struct {
Hash *common.Hash `json:"hash"` Hash *common.Hash `json:"hash"`
AccountNonce *hexUint64 `json:"nonce"` AccountNonce *hexutil.Uint64 `json:"nonce"`
Price *hexBig `json:"gasPrice"` Price *hexutil.Big `json:"gasPrice"`
GasLimit *hexBig `json:"gas"` GasLimit *hexutil.Big `json:"gas"`
Recipient *common.Address `json:"to"` Recipient *common.Address `json:"to"`
Amount *hexBig `json:"value"` Amount *hexutil.Big `json:"value"`
Payload *hexBytes `json:"input"` Payload *hexutil.Bytes `json:"input"`
V *hexBig `json:"v"` V *hexutil.Big `json:"v"`
R *hexBig `json:"r"` R *hexutil.Big `json:"r"`
S *hexBig `json:"s"` S *hexutil.Big `json:"s"`
} }
func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction { 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{ return json.Marshal(&jsonTransaction{
Hash: &hash, Hash: &hash,
AccountNonce: (*hexUint64)(&tx.data.AccountNonce), AccountNonce: (*hexutil.Uint64)(&tx.data.AccountNonce),
Price: (*hexBig)(tx.data.Price), Price: (*hexutil.Big)(tx.data.Price),
GasLimit: (*hexBig)(tx.data.GasLimit), GasLimit: (*hexutil.Big)(tx.data.GasLimit),
Recipient: tx.data.Recipient, Recipient: tx.data.Recipient,
Amount: (*hexBig)(tx.data.Amount), Amount: (*hexutil.Big)(tx.data.Amount),
Payload: (*hexBytes)(&tx.data.Payload), Payload: (*hexutil.Bytes)(&tx.data.Payload),
V: (*hexBig)(tx.data.V), V: (*hexutil.Big)(tx.data.V),
R: (*hexBig)(tx.data.R), R: (*hexutil.Big)(tx.data.R),
S: (*hexBig)(tx.data.S), 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) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) CheckNonce() bool { return true } 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 { func (tx *Transaction) To() *common.Address {
if tx.data.Recipient == nil { if tx.data.Recipient == nil {
return nil return nil

View file

@ -23,6 +23,7 @@ import (
"io" "io"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -47,12 +48,12 @@ type Log struct {
type jsonLog struct { type jsonLog struct {
Address *common.Address `json:"address"` Address *common.Address `json:"address"`
Topics *[]common.Hash `json:"topics"` Topics *[]common.Hash `json:"topics"`
Data string `json:"data"` Data *hexutil.Bytes `json:"data"`
BlockNumber string `json:"blockNumber"` BlockNumber *hexutil.Uint64 `json:"blockNumber"`
TxIndex string `json:"transactionIndex"` TxIndex *hexutil.Uint `json:"transactionIndex"`
TxHash *common.Hash `json:"transactionHash"` TxHash *common.Hash `json:"transactionHash"`
BlockHash *common.Hash `json:"blockHash"` 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 { 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{ return json.Marshal(&jsonLog{
Address: &r.Address, Address: &r.Address,
Topics: &r.Topics, Topics: &r.Topics,
Data: fmt.Sprintf("0x%x", r.Data), Data: (*hexutil.Bytes)(&r.Data),
BlockNumber: fmt.Sprintf("0x%x", r.BlockNumber), BlockNumber: (*hexutil.Uint64)(&r.BlockNumber),
TxIndex: fmt.Sprintf("0x%x", r.TxIndex), TxIndex: (*hexutil.Uint)(&r.TxIndex),
TxHash: &r.TxHash, TxHash: &r.TxHash,
BlockHash: &r.BlockHash, 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 { if err := json.Unmarshal(input, &dec); err != nil {
return err return err
} }
if dec.Address == nil || dec.Topics == nil || dec.Data == "" || dec.BlockNumber == "" || if dec.Address == nil || dec.Topics == nil || dec.Data == nil || dec.BlockNumber == nil ||
dec.TxIndex == "" || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == "" { dec.TxIndex == nil || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == nil {
return errMissingLogFields return errMissingLogFields
} }
declog := Log{ *r = Log{
Address: *dec.Address, Address: *dec.Address,
Topics: *dec.Topics, Topics: *dec.Topics,
Data: *dec.Data,
BlockNumber: uint64(*dec.BlockNumber),
TxHash: *dec.TxHash, TxHash: *dec.TxHash,
TxIndex: uint(*dec.TxIndex),
BlockHash: *dec.BlockHash, 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 return nil
} }

View file

@ -28,6 +28,9 @@ var unmarshalLogTests = map[string]struct {
"ok": { "ok": {
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, 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": { "missing data": {
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
wantError: errMissingLogFields, wantError: errMissingLogFields,

View file

@ -17,7 +17,6 @@
package filters package filters
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -28,6 +27,7 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/ethereum/go-ethereum/common" "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/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "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 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() { go func() {
matchedLogs := make(chan []Log)
logsSub := api.events.SubscribeLogs(crit, matchedLogs)
for { for {
select { select {
@ -276,18 +282,20 @@ type FilterCriteria struct {
// used to retrieve logs when the state changes. This method cannot be // used to retrieve logs when the state changes. This method cannot be
// used to fetch logs that are already stored in the state. // 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 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) rpc.ID { func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
var ( logs := make(chan []Log)
logs = make(chan []Log) logsSub, err := api.events.SubscribeLogs(crit, logs)
logsSub = api.events.SubscribeLogs(crit, logs) if err != nil {
) return rpc.ID(""), err
if crit.FromBlock == nil {
crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
}
if crit.ToBlock == nil {
crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
} }
api.filtersMu.Lock() 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. // 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() api.filtersMu.Unlock()
if !found || f.typ != LogsSubscription { if !found || f.typ != LogsSubscription {
return []Log{}, nil return nil, fmt.Errorf("filter not found")
} }
filter := New(api.backend, api.useMipMap) filter := New(api.backend, api.useMipMap)
if f.crit.FromBlock != nil {
filter.SetBeginBlock(f.crit.FromBlock.Int64()) filter.SetBeginBlock(f.crit.FromBlock.Int64())
} else {
filter.SetBeginBlock(rpc.LatestBlockNumber.Int64())
}
if f.crit.ToBlock != nil {
filter.SetEndBlock(f.crit.ToBlock.Int64()) filter.SetEndBlock(f.crit.ToBlock.Int64())
} else {
filter.SetEndBlock(rpc.LatestBlockNumber.Int64())
}
filter.SetAddresses(f.crit.Addresses) filter.SetAddresses(f.crit.Addresses)
filter.SetTopics(f.crit.Topics) filter.SetTopics(f.crit.Topics)
logs, err := filter.Find(ctx) logs, err:= filter.Find(ctx)
return returnLogs(logs), err if err != nil {
return nil, err
}
return returnLogs(logs), nil
} }
// GetFilterChanges returns the logs for the filter with the given id since // GetFilterChanges returns the logs for the filter with the given id since
// last time is was called. This can be used for polling. // last time is was called. This can be used for polling.
// //
// For pending transaction and block filters the result is []common.Hash. // For pending transaction and block filters the result is []common.Hash.
// (pending)Log filters return []Log. If the filter could not be found // (pending)Log filters return []Log.
// []interface{}{} is returned.
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges // 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() api.filtersMu.Lock()
defer api.filtersMu.Unlock() defer api.filtersMu.Unlock()
@ -400,15 +418,15 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) interface{} {
case PendingTransactionsSubscription, BlocksSubscription: case PendingTransactionsSubscription, BlocksSubscription:
hashes := f.hashes hashes := f.hashes
f.hashes = nil f.hashes = nil
return returnHashes(hashes) return returnHashes(hashes), nil
case PendingLogsSubscription, LogsSubscription: case LogsSubscription:
logs := f.logs logs := f.logs
f.logs = nil 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, // 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 return err
} }
if raw.From == nil || raw.From.Int64() < 0 { if raw.From != nil {
args.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
} else {
args.FromBlock = big.NewInt(raw.From.Int64()) args.FromBlock = big.NewInt(raw.From.Int64())
} }
if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 { if raw.ToBlock != nil {
args.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
} else {
args.ToBlock = big.NewInt(raw.ToBlock.Int64()) args.ToBlock = big.NewInt(raw.ToBlock.Int64())
} }
@ -459,52 +473,28 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
if raw.Addresses != nil { if raw.Addresses != nil {
// raw.Address can contain a single address or an array of addresses // raw.Address can contain a single address or an array of addresses
var addresses []common.Address switch rawAddr := raw.Addresses.(type) {
if strAddrs, ok := raw.Addresses.([]interface{}); ok { case []interface{}:
for i, addr := range strAddrs { for i, addr := range rawAddr {
if strAddr, ok := addr.(string); ok { if strAddr, ok := addr.(string); ok {
if len(strAddr) >= 2 && strAddr[0] == '0' && (strAddr[1] == 'x' || strAddr[1] == 'X') { addr, err := decodeAddress(strAddr)
strAddr = strAddr[2:] if err != nil {
return fmt.Errorf("invalid address at index %d: %v", i, err)
} }
if decAddr, err := hex.DecodeString(strAddr); err == nil { args.Addresses = append(args.Addresses, addr)
addresses = append(addresses, common.BytesToAddress(decAddr))
} else { } else {
return fmt.Errorf("invalid address given") return fmt.Errorf("non-string address at index %d", i)
}
} else {
return fmt.Errorf("invalid address on index %d", i)
} }
} }
} else if singleAddr, ok := raw.Addresses.(string); ok { case string:
if len(singleAddr) >= 2 && singleAddr[0] == '0' && (singleAddr[1] == 'x' || singleAddr[1] == 'X') { addr, err := decodeAddress(rawAddr)
singleAddr = singleAddr[2:] if err != nil {
return fmt.Errorf("invalid address: %v", err)
} }
if decAddr, err := hex.DecodeString(singleAddr); err == nil { args.Addresses = []common.Address{addr}
addresses = append(addresses, common.BytesToAddress(decAddr)) default:
} else { return errors.New("invalid addresses in query")
return fmt.Errorf("invalid address given")
} }
} else {
return errors.New("invalid address(es) given")
}
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. // 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 { if len(raw.Topics) > 0 {
args.Topics = make([][]common.Hash, len(raw.Topics)) args.Topics = make([][]common.Hash, len(raw.Topics))
for i, t := range 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{}} 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 { if err != nil {
return err return err
} }
args.Topics[i] = []common.Hash{top} args.Topics[i] = []common.Hash{top}
} else if topics, ok := t.([]interface{}); ok { // or case e.g. [null, "topic0", "topic1"] case []interface{}:
for _, rawTopic := range topics { // or case e.g. [null, "topic0", "topic1"]
for _, rawTopic := range topic {
if rawTopic == nil { if rawTopic == nil {
args.Topics[i] = append(args.Topics[i], common.Hash{}) args.Topics[i] = append(args.Topics[i], common.Hash{})
} else if topic, ok := rawTopic.(string); ok { } else if topic, ok := rawTopic.(string); ok {
parsed, err := topicConverter(topic) parsed, err := decodeTopic(topic)
if err != nil { if err != nil {
return err return err
} }
@ -534,7 +529,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
return fmt.Errorf("invalid topic(s)") return fmt.Errorf("invalid topic(s)")
} }
} }
} else { default:
return fmt.Errorf("invalid topic(s)") return fmt.Errorf("invalid topic(s)")
} }
} }
@ -542,3 +537,19 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
return nil 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
}

View file

@ -42,11 +42,11 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
if err := json.Unmarshal([]byte("{}"), &test0); err != nil { if err := json.Unmarshal([]byte("{}"), &test0); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if test0.FromBlock.Int64() != rpc.LatestBlockNumber.Int64() { if test0.FromBlock != nil {
t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.FromBlock) t.Fatalf("expected nil, got %d", test0.FromBlock)
} }
if test0.ToBlock.Int64() != rpc.LatestBlockNumber.Int64() { if test0.ToBlock != nil {
t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.ToBlock) t.Fatalf("expected nil, got %d", test0.ToBlock)
} }
if len(test0.Addresses) != 0 { if len(test0.Addresses) != 0 {
t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses)) t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses))

View file

@ -20,6 +20,8 @@ import (
"math" "math"
"time" "time"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "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...) 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 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 var ret []Log
// Filter the logs for interesting stuff // Filter the logs for interesting stuff
Logs: Logs:
for _, log := range 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) { if len(addresses) > 0 && !includes(addresses, log.Address) {
continue continue
} }
@ -211,7 +219,6 @@ Logs:
continue Logs continue Logs
} }
} }
ret = append(ret, log) ret = append(ret, log)
} }

View file

@ -43,13 +43,17 @@ const (
UnknownSubscription Type = iota UnknownSubscription Type = iota
// LogsSubscription queries for new or removed (chain reorg) logs // LogsSubscription queries for new or removed (chain reorg) logs
LogsSubscription LogsSubscription
// PendingLogsSubscription queries for logs for the pending block // PendingLogsSubscription queries for logs in pending blocks
PendingLogsSubscription PendingLogsSubscription
// MinedAndPendingLogsSubscription queries for logs in mined and pending blocks.
MinedAndPendingLogsSubscription
// PendingTransactionsSubscription queries tx hashes for pending // PendingTransactionsSubscription queries tx hashes for pending
// transactions entering the pending state // transactions entering the pending state
PendingTransactionsSubscription PendingTransactionsSubscription
// BlocksSubscription queries hashes for blocks that are imported // BlocksSubscription queries hashes for blocks that are imported
BlocksSubscription BlocksSubscription
// LastSubscription keeps track of the last index
LastIndexSubscription
) )
var ( var (
@ -63,19 +67,26 @@ type Log struct {
Removed bool `json:"removed"` Removed bool `json:"removed"`
} }
// MarshalJSON returns *l as the JSON encoding of l.
func (l *Log) MarshalJSON() ([]byte, error) { func (l *Log) MarshalJSON() ([]byte, error) {
fields := map[string]interface{}{ fields := map[string]interface{}{
"address": l.Address, "address": l.Address,
"data": fmt.Sprintf("0x%x", l.Data), "data": fmt.Sprintf("0x%x", l.Data),
"blockNumber": fmt.Sprintf("%#x", l.BlockNumber), "blockNumber": nil,
"logIndex": fmt.Sprintf("%#x", l.Index), "logIndex": fmt.Sprintf("%#x", l.Index),
"blockHash": l.BlockHash, "blockHash": nil,
"transactionHash": l.TxHash, "transactionHash": l.TxHash,
"transactionIndex": fmt.Sprintf("%#x", l.TxIndex), "transactionIndex": fmt.Sprintf("%#x", l.TxIndex),
"topics": l.Topics, "topics": l.Topics,
"removed": l.Removed, "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) 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 // 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. // 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{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: LogsSubscription, typ: LogsSubscription,
@ -186,9 +254,9 @@ func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) *Subs
return es.subscribe(sub) return es.subscribe(sub)
} }
// SubscribePendingLogs creates a subscription that will write pending logs matching the // subscribePendingLogs creates a subscription that writes transaction hashes for
// given criteria to the given channel. // transactions that enter the transaction pool.
func (es *EventSystem) SubscribePendingLogs(crit FilterCriteria, logs chan []Log) *Subscription { func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []Log) *Subscription {
sub := &subscription{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: PendingLogsSubscription, typ: PendingLogsSubscription,
@ -204,23 +272,6 @@ func (es *EventSystem) SubscribePendingLogs(crit FilterCriteria, logs chan []Log
return es.subscribe(sub) 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 // SubscribeNewHeads creates a subscription that writes the header of a block that is
// imported in the chain. // imported in the chain.
func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { 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) 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 type filterIndex map[Type]map[rpc.ID]*subscription
// broadcast event to filters that match criteria. // broadcast event to filters that match criteria.
@ -251,7 +319,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
if len(e) > 0 { if len(e) > 0 {
for _, f := range filters[LogsSubscription] { for _, f := range filters[LogsSubscription] {
if ev.Time.After(f.created) { 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 f.logs <- matchedLogs
} }
} }
@ -260,7 +328,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
case core.RemovedLogsEvent: case core.RemovedLogsEvent:
for _, f := range filters[LogsSubscription] { for _, f := range filters[LogsSubscription] {
if ev.Time.After(f.created) { 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 f.logs <- matchedLogs
} }
} }
@ -268,7 +336,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
case core.PendingLogsEvent: case core.PendingLogsEvent:
for _, f := range filters[PendingLogsSubscription] { for _, f := range filters[PendingLogsSubscription] {
if ev.Time.After(f.created) { 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 f.logs <- matchedLogs
} }
} }
@ -351,8 +419,8 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.
} }
unfiltered = append(unfiltered, rl...) unfiltered = append(unfiltered, rl...)
} }
logs := filterLogs(unfiltered, addresses, topics)
//fmt.Println("found", len(logs)) logs := filterLogs(unfiltered, nil, nil, addresses, topics)
return logs return logs
} }
return nil return nil
@ -364,6 +432,11 @@ func (es *EventSystem) eventLoop() {
index = make(filterIndex) index = make(filterIndex)
sub = es.mux.Subscribe(core.PendingLogsEvent{}, core.RemovedLogsEvent{}, vm.Logs{}, core.TxPreEvent{}, core.ChainEvent{}) 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 { for {
select { select {
case ev, active := <-sub.Chan(): case ev, active := <-sub.Chan():
@ -372,13 +445,22 @@ func (es *EventSystem) eventLoop() {
} }
es.broadcast(index, ev) es.broadcast(index, ev)
case f := <-es.install: case f := <-es.install:
if _, found := index[f.typ]; !found { if f.typ == MinedAndPendingLogsSubscription {
index[f.typ] = make(map[rpc.ID]*subscription) // 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) close(f.installed)
case f := <-es.uninstall: case f := <-es.uninstall:
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) delete(index[f.typ], f.id)
}
close(f.err) close(f.err)
} }
} }
@ -386,6 +468,7 @@ func (es *EventSystem) eventLoop() {
// convertLogs is a helper utility that converts vm.Logs to []filter.Log. // convertLogs is a helper utility that converts vm.Logs to []filter.Log.
func convertLogs(in vm.Logs, removed bool) []Log { func convertLogs(in vm.Logs, removed bool) []Log {
logs := make([]Log, len(in)) logs := make([]Log, len(in))
for i, l := range in { for i, l := range in {
logs[i] = Log{l, removed} logs[i] = Log{l, removed}

View file

@ -34,13 +34,6 @@ import (
"github.com/ethereum/go-ethereum/rpc" "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 { type testBackend struct {
mux *event.TypeMux mux *event.TypeMux
db ethdb.Database db ethdb.Database
@ -81,6 +74,11 @@ func TestBlockSubscription(t *testing.T) {
t.Parallel() t.Parallel()
var ( var (
mux = new(event.TypeMux)
db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
api = NewPublicFilterAPI(backend, false)
genesis = core.WriteGenesisBlockForTesting(db) genesis = core.WriteGenesisBlockForTesting(db)
chain, _ = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {}) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {})
chainEvents = []core.ChainEvent{} chainEvents = []core.ChainEvent{}
@ -130,6 +128,11 @@ func TestPendingTxFilter(t *testing.T) {
t.Parallel() t.Parallel()
var ( var (
mux = new(event.TypeMux)
db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
api = NewPublicFilterAPI(backend, false)
transactions = []*types.Transaction{ transactions = []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), new(big.Int), new(big.Int), nil), 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), 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 { for {
h := api.GetFilterChanges(fid0).([]common.Hash) results, err := api.GetFilterChanges(fid0)
hashes = append(hashes, h...) if err != nil {
t.Fatalf("Unable to retrieve logs: %v", err)
}
h := results.([]common.Hash)
hashes = append(hashes, h...)
if len(hashes) >= len(transactions) { if len(hashes) >= len(transactions) {
break 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. // TestLogFilter tests whether log filters match the correct logs that are posted to the event mux.
func TestLogFilter(t *testing.T) { func TestLogFilter(t *testing.T) {
t.Parallel() t.Parallel()
var ( var (
mux = new(event.TypeMux)
db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
api = NewPublicFilterAPI(backend, false)
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333") thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
@ -180,8 +262,8 @@ func TestLogFilter(t *testing.T) {
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222") secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999") notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999")
// posted twice, once as vm.Logs and once as core.PendingLogsEvent
allLogs = vm.Logs{ 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{}, []byte(""), 0),
vm.NewLog(firstAddr, []common.Hash{firstTopic}, []byte(""), 1), vm.NewLog(firstAddr, []common.Hash{firstTopic}, []byte(""), 1),
vm.NewLog(secondAddr, []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), 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 { testCases = []struct {
crit FilterCriteria crit FilterCriteria
expected vm.Logs expected vm.Logs
id rpc.ID id rpc.ID
}{ }{
// match all // match all
{FilterCriteria{}, allLogs, ""}, 0: {FilterCriteria{}, allLogs, ""},
// match none due to no matching addresses // 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 // 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) // 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 // 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 // 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], ""}, 5: {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 // logs in the pending block
{FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, allLogs[:2], ""}, 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 // create all filters
for i := range testCases { for i := range testCases {
testCases[i].id = api.NewFilter(testCases[i].crit) testCases[i].id, _ = api.NewFilter(testCases[i].crit)
} }
// raise events // raise events
time.Sleep(1 * time.Second) 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) t.Fatal(err)
} }
for i, tt := range testCases { for i, tt := range testCases {
var fetched []Log var fetched []Log
for { // fetch all expected logs 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) { if len(fetched) >= len(tt.expected) {
break break
} }
@ -247,7 +348,6 @@ func TestLogFilter(t *testing.T) {
if !reflect.DeepEqual(fetched[l].Log, tt.expected[l]) { if !reflect.DeepEqual(fetched[l].Log, tt.expected[l]) {
t.Errorf("invalid log on index %d for case %d", l, i) t.Errorf("invalid log on index %d for case %d", l, i)
} }
} }
} }
} }
@ -257,6 +357,11 @@ func TestPendingLogsSubscription(t *testing.T) {
t.Parallel() t.Parallel()
var ( var (
mux = new(event.TypeMux)
db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
api = NewPublicFilterAPI(backend, false)
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333") thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
@ -319,7 +424,7 @@ func TestPendingLogsSubscription(t *testing.T) {
// (some) events are posted. // (some) events are posted.
for i := range testCases { for i := range testCases {
testCases[i].c = make(chan []Log) 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 { for n, test := range testCases {

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/event"
) )
func makeReceipt(addr common.Address) *types.Receipt { func makeReceipt(addr common.Address) *types.Receipt {
@ -51,6 +52,7 @@ func BenchmarkMipmaps(b *testing.B) {
var ( var (
db, _ = ethdb.NewLDBDatabase(dir, 0, 0) db, _ = ethdb.NewLDBDatabase(dir, 0, 0)
mux = new(event.TypeMux)
backend = &testBackend{mux, db} backend = &testBackend{mux, db}
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
@ -126,6 +128,7 @@ func TestFilters(t *testing.T) {
var ( var (
db, _ = ethdb.NewLDBDatabase(dir, 0, 0) db, _ = ethdb.NewLDBDatabase(dir, 0, 0)
mux = new(event.TypeMux)
backend = &testBackend{mux, db} backend = &testBackend{mux, db}
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key1.PublicKey) addr = crypto.PubkeyToAddress(key1.PublicKey)

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/rlp" "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. // TransactionCount returns the total number of transactions in the given block.
func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { 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) 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. // TransactionInBlock returns a single transaction at index in the given block.
@ -196,11 +197,11 @@ func toBlockNumArg(number *big.Int) string {
} }
type rpcProgress struct { type rpcProgress struct {
StartingBlock rpc.HexNumber StartingBlock hexutil.Uint64
CurrentBlock rpc.HexNumber CurrentBlock hexutil.Uint64
HighestBlock rpc.HexNumber HighestBlock hexutil.Uint64
PulledStates rpc.HexNumber PulledStates hexutil.Uint64
KnownStates rpc.HexNumber KnownStates hexutil.Uint64
} }
// SyncProgress retrieves the current progress of the sync algorithm. If there's // 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 nil, err
} }
return &ethereum.SyncProgress{ return &ethereum.SyncProgress{
StartingBlock: progress.StartingBlock.Uint64(), StartingBlock: uint64(progress.StartingBlock),
CurrentBlock: progress.CurrentBlock.Uint64(), CurrentBlock: uint64(progress.CurrentBlock),
HighestBlock: progress.HighestBlock.Uint64(), HighestBlock: uint64(progress.HighestBlock),
PulledStates: progress.PulledStates.Uint64(), PulledStates: uint64(progress.PulledStates),
KnownStates: progress.KnownStates.Uint64(), KnownStates: uint64(progress.KnownStates),
}, nil }, 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. // 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. // 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) { 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)) err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber))
return (*big.Int)(&result), err 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. // 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. // 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) { 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)) err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber))
return result, err 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. // 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. // 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) { 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)) err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber))
return result, err 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. // 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. // 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) { 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)) err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber))
return result.Uint64(), err return uint64(result), err
} }
// Filters // Filters
@ -286,7 +287,7 @@ func toFilterArg(q ethereum.FilterQuery) interface{} {
arg := map[string]interface{}{ arg := map[string]interface{}{
"fromBlock": toBlockNumArg(q.FromBlock), "fromBlock": toBlockNumArg(q.FromBlock),
"toBlock": toBlockNumArg(q.ToBlock), "toBlock": toBlockNumArg(q.ToBlock),
"addresses": q.Addresses, "address": q.Addresses,
"topics": q.Topics, "topics": q.Topics,
} }
if q.FromBlock == nil { 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. // 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) { 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") err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending")
return (*big.Int)(&result), err return (*big.Int)(&result), err
} }
// PendingStorageAt returns the value of key in the contract storage of the given account in the pending state. // 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) { 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") err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending")
return result, err return result, err
} }
// PendingCodeAt returns the contract code of the given account in the pending state. // 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) { 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") err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending")
return result, err 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. // 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. // This is the nonce that should be used for the next transaction.
func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 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") 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. // PendingTransactionCount returns the total number of transactions in the pending state.
func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { 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") err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending")
return num.Uint(), err return uint(num), err
} }
// TODO: SubscribePendingTransactions (needs server side) // 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 // case the code is taken from the latest known block. Note that state from very old
// blocks might not be available. // blocks might not be available.
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 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)) err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return common.FromHex(hex), nil return hex, nil
} }
// PendingCallContract executes a message call transaction using the EVM. // PendingCallContract executes a message call transaction using the EVM.
// The state seen by the contract call is the pending state. // The state seen by the contract call is the pending state.
func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { 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") err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return common.FromHex(hex), nil return hex, nil
} }
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely // SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction. // execution of a transaction.
func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 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 { if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil {
return nil, err 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, // 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. // but it should provide a basis for setting a reasonable default.
func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (*big.Int, error) { 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)) err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg))
if err != nil { if err != nil {
return nil, err return nil, err
@ -404,16 +405,16 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
"to": msg.To, "to": msg.To,
} }
if len(msg.Data) > 0 { if len(msg.Data) > 0 {
arg["data"] = fmt.Sprintf("%#x", msg.Data) arg["data"] = hexutil.Bytes(msg.Data)
} }
if msg.Value != nil { if msg.Value != nil {
arg["value"] = fmt.Sprintf("%#x", msg.Value) arg["value"] = (*hexutil.Big)(msg.Value)
} }
if msg.Gas != nil { if msg.Gas != nil {
arg["gas"] = fmt.Sprintf("%#x", msg.Gas) arg["gas"] = (*hexutil.Big)(msg.Gas)
} }
if msg.GasPrice != nil { if msg.GasPrice != nil {
arg["gasPrice"] = fmt.Sprintf("%#x", msg.GasPrice) arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
} }
return arg return arg
} }

View file

@ -37,12 +37,6 @@ web3._extend({
property: 'bzz', property: 'bzz',
methods: methods:
[ [
new web3._extend.Method({
name: 'blockNetworkRead',
call: 'bzz_blockNetworkRead',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'syncEnabled', name: 'syncEnabled',
call: 'bzz_syncEnabled', call: 'bzz_syncEnabled',
@ -68,17 +62,11 @@ web3._extend({
inputFormatter: [null, null] inputFormatter: [null, null]
}), }),
new web3._extend.Method({ new web3._extend.Method({
name: 'retrieve', name: 'resolve',
call: 'bzz_retrieve', call: 'bzz_resolve',
params: 1, params: 1,
inputFormatter: [null] inputFormatter: [null]
}), }),
new web3._extend.Method({
name: 'store',
call: 'bzz_store',
params: 2,
inputFormatter: [null]
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'get', name: 'get',
call: 'bzz_get', call: 'bzz_get',

View file

@ -201,14 +201,19 @@ func (self *StateObject) Copy() *StateObject {
// Attribute accessors // 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 // Balance returns the account balance
func (self *StateObject) Balance() *big.Int { func (self *StateObject) Balance() *big.Int {
return self.balance return self.balance
} }
// Address returns the address of the contract/account // Address returns the address of the contract/account
func (c *StateObject) Address() common.Address { func (self *StateObject) Address() common.Address {
return c.address return self.address
} }
// Code returns the contract code // Code returns the contract code

View file

@ -263,6 +263,13 @@ func (s *VMState) Exist(addr common.Address) bool {
return res 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 // HasSuicided returns true if the given account has been marked for deletion
// or false if the account does not exist // or false if the account does not exist
func (s *VMState) HasSuicided(addr common.Address) bool { func (s *VMState) HasSuicided(addr common.Address) bool {

View file

@ -277,6 +277,7 @@ func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) (
func (self *worker) wait() { func (self *worker) wait() {
for { for {
mustCommitNewWork := true
for result := range self.recv { for result := range self.recv {
atomic.AddInt32(&self.atWork, -1) atomic.AddInt32(&self.atWork, -1)
@ -330,6 +331,8 @@ func (self *worker) wait() {
core.WriteReceipts(self.chainDb, work.receipts) core.WriteReceipts(self.chainDb, work.receipts)
// Write map map bloom filters // Write map map bloom filters
core.WriteMipmapBloom(self.chainDb, block.NumberU64(), work.receipts) core.WriteMipmapBloom(self.chainDb, block.NumberU64(), work.receipts)
// implicit by posting ChainHeadEvent
mustCommitNewWork = false
} }
// broadcast before waiting for validation // broadcast before waiting for validation
@ -358,9 +361,11 @@ func (self *worker) wait() {
} }
glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm) glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm)
if mustCommitNewWork {
self.commitNewWork() self.commitNewWork()
} }
} }
}
} }
// push sends a new work task to currently live miner agents. // push sends a new work task to currently live miner agents.
@ -466,6 +471,7 @@ func (self *worker) commitNewWork() {
tstart := time.Now() tstart := time.Now()
parent := self.chain.CurrentBlock() parent := self.chain.CurrentBlock()
tstamp := tstart.Unix() tstamp := tstart.Unix()
if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 { if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 {
tstamp = parent.Time().Int64() + 1 tstamp = parent.Time().Int64() + 1
@ -633,7 +639,16 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
txs.Shift() txs.Shift()
} }
} }
if len(coalescedLogs) > 0 || env.tcount > 0 { 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) { go func(logs vm.Logs, tcount int) {
if len(logs) > 0 { if len(logs) > 0 {
mux.Post(core.PendingLogsEvent{Logs: logs}) mux.Post(core.PendingLogsEvent{Logs: logs})
@ -641,7 +656,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
if tcount > 0 { if tcount > 0 {
mux.Post(core.PendingStateEvent{}) mux.Post(core.PendingStateEvent{})
} }
}(coalescedLogs, env.tcount) }(cpy, env.tcount)
} }
} }

View file

@ -21,7 +21,7 @@ import "fmt"
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 5 // Minor 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 VersionMeta = "unstable" // Version metadata to append to the version string
) )

View file

@ -54,11 +54,12 @@ type Config struct {
PublicKey string PublicKey string
BzzKey string BzzKey string
EnsRoot common.Address EnsRoot common.Address
NetworkId uint64
} }
// config is agnostic to where private key is coming from // config is agnostic to where private key is coming from
// so managing accounts is outside swarm and left to wrappers // 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 address := crypto.PubkeyToAddress(prvKey.PublicKey) // default beneficiary address
dirpath := filepath.Join(path, "bzz-"+common.Bytes2Hex(address.Bytes())) dirpath := filepath.Join(path, "bzz-"+common.Bytes2Hex(address.Bytes()))
err = os.MkdirAll(dirpath, os.ModePerm) err = os.MkdirAll(dirpath, os.ModePerm)
@ -82,6 +83,7 @@ func NewConfig(path string, contract common.Address, prvKey *ecdsa.PrivateKey) (
PublicKey: pubkeyhex, PublicKey: pubkeyhex,
BzzKey: keyhex, BzzKey: keyhex,
EnsRoot: toyNetEnsRoot, EnsRoot: toyNetEnsRoot,
NetworkId: networkId,
} }
data, err = ioutil.ReadFile(confpath) data, err = ioutil.ReadFile(confpath)
if err != nil { if err != nil {

View file

@ -83,7 +83,8 @@ var (
"Port": "8500", "Port": "8500",
"PublicKey": "0x045f5cfd26692e48d0017d380349bcf50982488bc11b5145f3ddf88b24924299048450542d43527fbe29a5cb32f38d62755393ac002e6bfdd71b8d7ba725ecd7a3", "PublicKey": "0x045f5cfd26692e48d0017d380349bcf50982488bc11b5145f3ddf88b24924299048450542d43527fbe29a5cb32f38d62755393ac002e6bfdd71b8d7ba725ecd7a3",
"BzzKey": "0xe861964402c0b78e2d44098329b8545726f215afa737d803714a4338552fcb81", "BzzKey": "0xe861964402c0b78e2d44098329b8545726f215afa737d803714a4338552fcb81",
"EnsRoot": "0xd344889e0be3e9ef6c26b0f60ef66a32e83c1b69" "EnsRoot": "0xd344889e0be3e9ef6c26b0f60ef66a32e83c1b69",
"NetworkId": 323
}` }`
) )
@ -95,7 +96,7 @@ func TestConfigWriteRead(t *testing.T) {
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
prvkey := crypto.ToECDSA(common.Hex2Bytes(hexprvkey)) prvkey := crypto.ToECDSA(common.Hex2Bytes(hexprvkey))
orig, err := NewConfig(tmp, common.Address{}, prvkey) orig, err := NewConfig(tmp, common.Address{}, prvkey, 323)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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)) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }

View file

@ -95,6 +95,7 @@ type bzz struct {
errors *errs.Errors // errors table errors *errs.Errors // errors table
backend chequebook.Backend backend chequebook.Backend
lastActive time.Time lastActive time.Time
NetworkId uint64
swap *swap.Swap // swap instance for the peer connection swap *swap.Swap // swap instance for the peer connection
swapParams *bzzswap.SwapParams // swap settings both local and remote 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 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 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 // a single global request db is created for all peer connections
// this is to persist delivery backlog and aid syncronisation // 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 { if err != nil {
return p2p.Protocol{}, fmt.Errorf("error setting up request db: %v", err) return p2p.Protocol{}, fmt.Errorf("error setting up request db: %v", err)
} }
if networkId == 0 {
networkId = NetworkId
}
return p2p.Protocol{ return p2p.Protocol{
Name: "bzz", Name: "bzz",
Version: Version, Version: Version,
Length: ProtocolLength, Length: ProtocolLength,
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { 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 }, nil
} }
@ -157,7 +160,7 @@ the main protocol loop that
* whenever the loop terminates, the peer will disconnect with Subprotocol error * whenever the loop terminates, the peer will disconnect with Subprotocol error
* whenever handlers return an error the loop terminates * 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{ self := &bzz{
storage: depo, storage: depo,
@ -175,6 +178,7 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, backend chequebook
syncParams: sy, syncParams: sy,
swapEnabled: hive.swapEnabled, swapEnabled: hive.swapEnabled,
syncEnabled: true, syncEnabled: true,
NetworkId: networkId,
} }
// handle handshake // handle handshake
@ -340,7 +344,7 @@ func (self *bzz) handleStatus() (err error) {
Version: uint64(Version), Version: uint64(Version),
ID: "honey", ID: "honey",
Addr: self.selfAddr(), Addr: self.selfAddr(),
NetworkId: uint64(NetworkId), NetworkId: uint64(self.NetworkId),
Swap: &bzzswap.SwapProfile{ Swap: &bzzswap.SwapProfile{
Profile: self.swapParams.Profile, Profile: self.swapParams.Profile,
PayProfile: self.swapParams.PayProfile, PayProfile: self.swapParams.PayProfile,
@ -372,8 +376,8 @@ func (self *bzz) handleStatus() (err error) {
return self.protoError(ErrDecode, " %v: %v", msg, err) return self.protoError(ErrDecode, " %v: %v", msg, err)
} }
if status.NetworkId != NetworkId { if status.NetworkId != self.NetworkId {
return self.protoError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) return self.protoError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, self.NetworkId)
} }
if Version != status.Version { if Version != status.Version {

View file

@ -209,7 +209,7 @@ func (self *Swarm) Stop() error {
// implements the node.Service interface // implements the node.Service interface
func (self *Swarm) Protocols() []p2p.Protocol { 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 { if err != nil {
return nil return nil
} }
@ -279,7 +279,7 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
return return
} }
config, err := api.NewConfig(datadir, common.Address{}, prvKey) config, err := api.NewConfig(datadir, common.Address{}, prvKey, network.NetworkId)
if err != nil { if err != nil {
return return
} }