diff --git a/core/types/block.go b/core/types/block.go
index 599359247..559fbdd20 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -18,9 +18,9 @@
package types
import (
- "bytes"
"encoding/binary"
"encoding/json"
+ "errors"
"fmt"
"io"
"math/big"
@@ -33,25 +33,53 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
+var (
+ EmptyRootHash = DeriveSha(Transactions{})
+ EmptyUncleHash = CalcUncleHash(nil)
+)
+
+var (
+ errMissingHeaderMixDigest = errors.New("missing mixHash in JSON block header")
+ errMissingHeaderFields = errors.New("missing required JSON block header fields")
+ errBadNonceSize = errors.New("invalid block nonce size, want 8 bytes")
+)
+
// A BlockNonce is a 64-bit hash which proves (combined with the
// mix-hash) that a sufficient amount of computation has been carried
// out on a block.
type BlockNonce [8]byte
+// EncodeNonce converts the given integer to a block nonce.
func EncodeNonce(i uint64) BlockNonce {
var n BlockNonce
binary.BigEndian.PutUint64(n[:], i)
return n
}
+// Uint64 returns the integer value of a block nonce.
func (n BlockNonce) Uint64() uint64 {
return binary.BigEndian.Uint64(n[:])
}
+// MarshalJSON implements json.Marshaler
func (n BlockNonce) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"0x%x"`, n)), nil
}
+// UnmarshalJSON implements json.Unmarshaler
+func (n *BlockNonce) UnmarshalJSON(input []byte) error {
+ var b hexBytes
+ if err := b.UnmarshalJSON(input); err != nil {
+ return err
+ }
+ if len(b) != 8 {
+ return errBadNonceSize
+ }
+ copy((*n)[:], b)
+ return nil
+}
+
+// Header represents Ethereum block headers.
type Header struct {
ParentHash common.Hash // Hash to the previous block
UncleHash common.Hash // Uncles of this block
@@ -70,10 +98,31 @@ type Header struct {
Nonce BlockNonce
}
+type jsonHeader struct {
+ ParentHash *common.Hash `json:"parentHash"`
+ UncleHash *common.Hash `json:"sha3Uncles"`
+ Coinbase *common.Address `json:"miner"`
+ Root *common.Hash `json:"stateRoot"`
+ TxHash *common.Hash `json:"transactionsRoot"`
+ ReceiptHash *common.Hash `json:"receiptRoot"`
+ Bloom *Bloom `json:"logsBloom"`
+ Difficulty *hexBig `json:"difficulty"`
+ Number *hexBig `json:"number"`
+ GasLimit *hexBig `json:"gasLimit"`
+ GasUsed *hexBig `json:"gasUsed"`
+ Time *hexBig `json:"timestamp"`
+ Extra *hexBytes `json:"extraData"`
+ MixDigest *common.Hash `json:"mixHash"`
+ Nonce *BlockNonce `json:"nonce"`
+}
+
+// Hash returns the block hash of the header, which is simply the keccak256 hash of its
+// RLP encoding.
func (h *Header) Hash() common.Hash {
return rlpHash(h)
}
+// HashNoNonce returns the hash which is used as input for the proof-of-work search.
func (h *Header) HashNoNonce() common.Hash {
return rlpHash([]interface{}{
h.ParentHash,
@@ -92,48 +141,63 @@ func (h *Header) HashNoNonce() common.Hash {
})
}
-func (h *Header) UnmarshalJSON(data []byte) error {
- var ext struct {
- ParentHash string
- Coinbase string
- Difficulty string
- GasLimit string
- Time *big.Int
- Extra string
- }
- dec := json.NewDecoder(bytes.NewReader(data))
- if err := dec.Decode(&ext); err != nil {
- return err
- }
-
- h.ParentHash = common.HexToHash(ext.ParentHash)
- h.Coinbase = common.HexToAddress(ext.Coinbase)
- h.Difficulty = common.String2Big(ext.Difficulty)
- h.Time = ext.Time
- h.Extra = []byte(ext.Extra)
- return nil
+// MarshalJSON encodes headers into the web3 RPC response block format.
+func (h *Header) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&jsonHeader{
+ ParentHash: &h.ParentHash,
+ UncleHash: &h.UncleHash,
+ Coinbase: &h.Coinbase,
+ Root: &h.Root,
+ TxHash: &h.TxHash,
+ ReceiptHash: &h.ReceiptHash,
+ Bloom: &h.Bloom,
+ Difficulty: (*hexBig)(h.Difficulty),
+ Number: (*hexBig)(h.Number),
+ GasLimit: (*hexBig)(h.GasLimit),
+ GasUsed: (*hexBig)(h.GasUsed),
+ Time: (*hexBig)(h.Time),
+ Extra: (*hexBytes)(&h.Extra),
+ MixDigest: &h.MixDigest,
+ Nonce: &h.Nonce,
+ })
}
-func (h *Header) MarshalJSON() ([]byte, error) {
- fields := map[string]interface{}{
- "hash": h.Hash(),
- "parentHash": h.ParentHash,
- "number": fmt.Sprintf("%#x", h.Number),
- "nonce": h.Nonce,
- "receiptRoot": h.ReceiptHash,
- "logsBloom": h.Bloom,
- "sha3Uncles": h.UncleHash,
- "stateRoot": h.Root,
- "miner": h.Coinbase,
- "difficulty": fmt.Sprintf("%#x", h.Difficulty),
- "extraData": fmt.Sprintf("0x%x", h.Extra),
- "gasLimit": fmt.Sprintf("%#x", h.GasLimit),
- "gasUsed": fmt.Sprintf("%#x", h.GasUsed),
- "timestamp": fmt.Sprintf("%#x", h.Time),
- "transactionsRoot": h.TxHash,
+// UnmarshalJSON decodes headers from the web3 RPC response block format.
+func (h *Header) UnmarshalJSON(input []byte) error {
+ var dec jsonHeader
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
}
-
- return json.Marshal(fields)
+ // Ensure that all fields are set. MixDigest is checked separately because
+ // it is a recent addition to the spec (as of August 2016) and older RPC server
+ // implementations might not provide it.
+ if dec.MixDigest == nil {
+ return errMissingHeaderMixDigest
+ }
+ if dec.ParentHash == nil || dec.UncleHash == nil || dec.Coinbase == nil ||
+ dec.Root == nil || dec.TxHash == nil || dec.ReceiptHash == nil ||
+ dec.Bloom == nil || dec.Difficulty == nil || dec.Number == nil ||
+ dec.GasLimit == nil || dec.GasUsed == nil || dec.Time == nil ||
+ dec.Extra == nil || dec.Nonce == nil {
+ return errMissingHeaderFields
+ }
+ // Assign all values.
+ h.ParentHash = *dec.ParentHash
+ h.UncleHash = *dec.UncleHash
+ h.Coinbase = *dec.Coinbase
+ h.Root = *dec.Root
+ h.TxHash = *dec.TxHash
+ h.ReceiptHash = *dec.ReceiptHash
+ h.Bloom = *dec.Bloom
+ h.Difficulty = (*big.Int)(dec.Difficulty)
+ h.Number = (*big.Int)(dec.Number)
+ h.GasLimit = (*big.Int)(dec.GasLimit)
+ h.GasUsed = (*big.Int)(dec.GasUsed)
+ h.Time = (*big.Int)(dec.Time)
+ h.Extra = *dec.Extra
+ h.MixDigest = *dec.MixDigest
+ h.Nonce = *dec.Nonce
+ return nil
}
func rlpHash(x interface{}) (h common.Hash) {
@@ -150,6 +214,7 @@ type Body struct {
Uncles []*Header
}
+// Block represents a block in the Ethereum blockchain.
type Block struct {
header *Header
uncles []*Header
@@ -198,11 +263,6 @@ type storageblock struct {
TD *big.Int
}
-var (
- EmptyRootHash = DeriveSha(Transactions{})
- EmptyUncleHash = CalcUncleHash(nil)
-)
-
// NewBlock creates a new block. The input data is copied,
// changes to header and to the field values will not affect the
// block.
@@ -275,23 +335,7 @@ func CopyHeader(h *Header) *Header {
return &cpy
}
-func (b *Block) ValidateFields() error {
- if b.header == nil {
- return fmt.Errorf("header is nil")
- }
- for i, transaction := range b.transactions {
- if transaction == nil {
- return fmt.Errorf("transaction %d is nil", i)
- }
- }
- for i, uncle := range b.uncles {
- if uncle == nil {
- return fmt.Errorf("uncle %d is nil", i)
- }
- }
- return nil
-}
-
+// DecodeRLP decodes the Ethereum
func (b *Block) DecodeRLP(s *rlp.Stream) error {
var eb extblock
_, size, _ := s.Kind()
@@ -303,6 +347,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
return nil
}
+// EncodeRLP serializes b into the Ethereum RLP block format.
func (b *Block) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, extblock{
Header: b.header,
@@ -322,6 +367,7 @@ func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error {
}
// TODO: copies
+
func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions }
@@ -409,8 +455,8 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
return block
}
-// Implement pow.Block
-
+// Hash returns the keccak256 hash of b's header.
+// The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash {
if hash := b.hash.Load(); hash != nil {
return hash.(common.Hash)
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index ecf2bffc2..d3945a734 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -31,28 +31,34 @@ type bytesBacked interface {
const bloomLength = 256
+// Bloom represents a 256 bit bloom filter.
type Bloom [bloomLength]byte
+// BytesToBloom converts a byte slice to a bloom filter.
+// It panics if b is not of suitable size.
func BytesToBloom(b []byte) Bloom {
var bloom Bloom
bloom.SetBytes(b)
return bloom
}
+// SetBytes sets the content of b to the given bytes.
+// It panics if d is not of suitable size.
func (b *Bloom) SetBytes(d []byte) {
if len(b) < len(d) {
panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
}
-
copy(b[bloomLength-len(d):], d)
}
+// Add adds d to the filter. Future calls of Test(d) will return true.
func (b *Bloom) Add(d *big.Int) {
bin := new(big.Int).SetBytes(b[:])
bin.Or(bin, bloom9(d.Bytes()))
b.SetBytes(bin.Bytes())
}
+// Big converts b to a big integer.
func (b Bloom) Big() *big.Int {
return common.Bytes2Big(b[:])
}
@@ -69,8 +75,22 @@ func (b Bloom) TestBytes(test []byte) bool {
return b.Test(common.BytesToBig(test))
}
+// MarshalJSON encodes b as a hex string with 0x prefix.
func (b Bloom) MarshalJSON() ([]byte, error) {
- return []byte(fmt.Sprintf(`"%#x"`, b.Bytes())), nil
+ return []byte(fmt.Sprintf(`"%#x"`, b[:])), nil
+}
+
+// UnmarshalJSON b as a hex string with 0x prefix.
+func (b *Bloom) UnmarshalJSON(input []byte) error {
+ var dec hexBytes
+ if err := dec.UnmarshalJSON(input); err != nil {
+ return err
+ }
+ if len(dec) != bloomLength {
+ return fmt.Errorf("invalid bloom size, want %d bytes", bloomLength)
+ }
+ copy((*b)[:], dec)
+ return nil
}
func CreateBloom(receipts Receipts) Bloom {
diff --git a/core/types/json.go b/core/types/json.go
new file mode 100644
index 000000000..403e79899
--- /dev/null
+++ b/core/types/json.go
@@ -0,0 +1,87 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package types
+
+import (
+ "encoding/hex"
+ "fmt"
+ "math/big"
+)
+
+// JSON unmarshaling utilities.
+
+type hexBytes []byte
+
+func (b *hexBytes) 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) 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) UnmarshalJSON(input []byte) error {
+ raw, err := checkHexNumber(input)
+ if err != nil {
+ return err
+ }
+ _, err = fmt.Sscanf(string(raw), "%x", b)
+ return err
+}
+
+func checkHexNumber(input []byte) (raw []byte, err error) {
+ if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' {
+ return nil, fmt.Errorf("cannot unmarshal non-string into hex number")
+ }
+ input = input[1 : len(input)-1]
+ if len(input) < 2 || input[0] != '0' || input[1] != 'x' {
+ return nil, fmt.Errorf("missing 0x prefix in hex number input %q", input)
+ }
+ if len(input) == 2 {
+ return nil, fmt.Errorf("empty hex number")
+ }
+ raw = input[2:]
+ if len(raw)%2 != 0 {
+ raw = append([]byte{'0'}, raw...)
+ }
+ return raw, nil
+}
diff --git a/core/types/json_test.go b/core/types/json_test.go
new file mode 100644
index 000000000..5f422b873
--- /dev/null
+++ b/core/types/json_test.go
@@ -0,0 +1,136 @@
+package types
+
+import (
+ "encoding/json"
+ "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","receiptRoot":"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","receiptRoot":"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","receiptRoot":"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","receiptRoot":"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
+ }
+ }
+}
+
+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"),
+ },
+ "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 := tx.From()
+ 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)
+ }
+ }
+}
+
+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 checkError(t *testing.T, testname string, got, want error) bool {
+ if got == nil {
+ if want != nil {
+ t.Errorf("test %q: got no error, want %q", testname, want)
+ return false
+ }
+ return true
+ }
+ if want == nil {
+ t.Errorf("test %q: unexpected error %q", testname, got)
+ } else if got.Error() != want.Error() {
+ t.Errorf("test %q: got error %q, want %q", testname, got, want)
+ }
+ return false
+}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 5f847fc5c..9f820eb18 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -17,6 +17,8 @@
package types
import (
+ "encoding/json"
+ "errors"
"fmt"
"io"
"math/big"
@@ -26,6 +28,11 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
+var (
+ errMissingReceiptPostState = errors.New("missing post state root in JSON receipt")
+ errMissingReceiptFields = errors.New("missing required JSON receipt fields")
+)
+
// Receipt represents the results of a transaction.
type Receipt struct {
// Consensus fields
@@ -34,12 +41,22 @@ type Receipt struct {
Bloom Bloom
Logs vm.Logs
- // Implementation fields
+ // Implementation fields (don't reorder!)
TxHash common.Hash
ContractAddress common.Address
GasUsed *big.Int
}
+type jsonReceipt struct {
+ PostState *common.Hash `json:"root"`
+ CumulativeGasUsed *hexBig `json:"cumulativeGasUsed"`
+ Bloom *Bloom `json:"logsBloom"`
+ Logs *vm.Logs `json:"logs"`
+ TxHash *common.Hash `json:"transactionHash"`
+ ContractAddress *common.Address `json:"contractAddress"`
+ GasUsed *hexBig `json:"gasUsed"`
+}
+
// NewReceipt creates a barebone transaction receipt, copying the init fields.
func NewReceipt(root []byte, cumulativeGasUsed *big.Int) *Receipt {
return &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: new(big.Int).Set(cumulativeGasUsed)}
@@ -67,13 +84,34 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
return nil
}
-// RlpEncode implements common.RlpEncode required for SHA3 derivation.
-func (r *Receipt) RlpEncode() []byte {
- bytes, err := rlp.EncodeToBytes(r)
- if err != nil {
- panic(err)
+// UnmarshalJSON decodes the web3 RPC receipt format.
+func (r *Receipt) UnmarshalJSON(input []byte) error {
+ var dec jsonReceipt
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
}
- return bytes
+ // Ensure that all fields are set. PostState is checked separately because it is a
+ // recent addition to the RPC spec (as of August 2016) and older implementations might
+ // not provide it. Note that ContractAddress is not checked because it can be null.
+ if dec.PostState == nil {
+ return errMissingReceiptPostState
+ }
+ if dec.CumulativeGasUsed == nil || dec.Bloom == nil ||
+ dec.Logs == nil || dec.TxHash == nil || dec.GasUsed == nil {
+ return errMissingReceiptFields
+ }
+ *r = Receipt{
+ PostState: (*dec.PostState)[:],
+ CumulativeGasUsed: (*big.Int)(dec.CumulativeGasUsed),
+ Bloom: *dec.Bloom,
+ Logs: *dec.Logs,
+ TxHash: *dec.TxHash,
+ GasUsed: (*big.Int)(dec.GasUsed),
+ }
+ if dec.ContractAddress != nil {
+ r.ContractAddress = *dec.ContractAddress
+ }
+ return nil
}
// String implements the Stringer interface.
@@ -122,7 +160,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
return nil
}
-// Receipts is a wrapper around a Receipt array to implement types.DerivableList.
+// Receipts is a wrapper around a Receipt array to implement DerivableList.
type Receipts []*Receipt
// Len returns the number of receipts in this list.
diff --git a/core/types/transaction.go b/core/types/transaction.go
index c71c98aa7..d369d7772 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -19,6 +19,7 @@ package types
import (
"container/heap"
"crypto/ecdsa"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -28,12 +29,15 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/logger"
- "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp"
)
-var ErrInvalidSig = errors.New("invalid v, r, s values")
+var ErrInvalidSig = errors.New("invalid transaction v, r, s values")
+
+var (
+ errMissingTxSignatureFields = errors.New("missing required JSON transaction signature fields")
+ errMissingTxFields = errors.New("missing required JSON transaction fields")
+)
type Transaction struct {
data txdata
@@ -53,6 +57,20 @@ type txdata struct {
R, S *big.Int // signature
}
+type jsonTransaction struct {
+ Hash *common.Hash `json:"hash"`
+ AccountNonce *hexUint64 `json:"nonce"`
+ Price *hexBig `json:"gasPrice"`
+ GasLimit *hexBig `json:"gas"`
+ Recipient *common.Address `json:"to"`
+ Amount *hexBig `json:"value"`
+ Payload *hexBytes `json:"input"`
+ V *hexUint64 `json:"v"`
+ R *hexBig `json:"r"`
+ S *hexBig `json:"s"`
+}
+
+// NewContractCreation creates a new transaction with no recipient.
func NewContractCreation(nonce uint64, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction {
if len(data) > 0 {
data = common.CopyBytes(data)
@@ -69,6 +87,7 @@ func NewContractCreation(nonce uint64, amount, gasLimit, gasPrice *big.Int, data
}}
}
+// NewTransaction creates a new transaction with the given fields.
func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction {
if len(data) > 0 {
data = common.CopyBytes(data)
@@ -95,10 +114,12 @@ func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice
return &Transaction{data: d}
}
+// DecodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &tx.data)
}
+// DecodeRLP implements rlp.Decoder
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind()
err := s.Decode(&tx.data)
@@ -108,6 +129,42 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
return err
}
+// UnmarshalJSON decodes the web3 RPC transaction format.
+func (tx *Transaction) UnmarshalJSON(input []byte) error {
+ var dec jsonTransaction
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ // Ensure that all fields are set. V, R, S are checked separately because they're a
+ // recent addition to the RPC spec (as of August 2016) and older implementations might
+ // not provide them. Note that Recipient is not checked because it can be missing for
+ // contract creations.
+ if dec.V == nil || dec.R == nil || dec.S == nil {
+ return errMissingTxSignatureFields
+ }
+ if !crypto.ValidateSignatureValues(byte(*dec.V), (*big.Int)(dec.R), (*big.Int)(dec.S), false) {
+ return ErrInvalidSig
+ }
+ if dec.AccountNonce == nil || dec.Price == nil || dec.GasLimit == nil || dec.Amount == nil || dec.Payload == nil {
+ return errMissingTxFields
+ }
+ // Assign the fields. This is not atomic but reusing transactions
+ // for decoding isn't thread safe anyway.
+ *tx = Transaction{}
+ tx.data = txdata{
+ AccountNonce: uint64(*dec.AccountNonce),
+ Recipient: dec.Recipient,
+ Amount: (*big.Int)(dec.Amount),
+ GasLimit: (*big.Int)(dec.GasLimit),
+ Price: (*big.Int)(dec.Price),
+ Payload: *dec.Payload,
+ V: byte(*dec.V),
+ R: (*big.Int)(dec.R),
+ S: (*big.Int)(dec.S),
+ }
+ return nil
+}
+
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() *big.Int { return new(big.Int).Set(tx.data.GasLimit) }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
@@ -215,6 +272,7 @@ func (tx *Transaction) Cost() *big.Int {
return total
}
+// SignatureValues returns the ECDSA signature values contained in the transaction.
func (tx *Transaction) SignatureValues() (v byte, r *big.Int, s *big.Int) {
return tx.data.V, new(big.Int).Set(tx.data.R), new(big.Int).Set(tx.data.S)
}
@@ -235,7 +293,6 @@ func (tx *Transaction) publicKey(homestead bool) ([]byte, error) {
hash := tx.SigHash()
pub, err := crypto.Ecrecover(hash[:], sig)
if err != nil {
- glog.V(logger.Error).Infof("Could not get pubkey from signature: ", err)
return nil, err
}
if len(pub) == 0 || pub[0] != 4 {
diff --git a/core/vm/log.go b/core/vm/log.go
index e4cc6021b..b292f5f43 100644
--- a/core/vm/log.go
+++ b/core/vm/log.go
@@ -18,6 +18,7 @@ package vm
import (
"encoding/json"
+ "errors"
"fmt"
"io"
@@ -25,18 +26,33 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
-type Log struct {
- // Consensus fields
- Address common.Address
- Topics []common.Hash
- Data []byte
+var errMissingLogFields = errors.New("missing required JSON log fields")
- // Derived fields (don't reorder!)
- BlockNumber uint64
- TxHash common.Hash
- TxIndex uint
- BlockHash common.Hash
- Index uint
+// Log represents a contract log event. These events are generated by the LOG
+// opcode and stored/indexed by the node.
+type Log struct {
+ // Consensus fields.
+ Address common.Address // address of the contract that generated the event
+ Topics []common.Hash // list of topics provided by the contract.
+ Data []byte // supplied by the contract, usually ABI-encoded
+
+ // Derived fields (don't reorder!).
+ BlockNumber uint64 // block in which the transaction was included
+ TxHash common.Hash // hash of the transaction
+ TxIndex uint // index of the transaction in the block
+ BlockHash common.Hash // hash of the block in which the transaction was included
+ Index uint // index of the log in the receipt
+}
+
+type jsonLog struct {
+ Address *common.Address `json:"address"`
+ Topics *[]common.Hash `json:"topics"`
+ Data string `json:"data"`
+ BlockNumber string `json:"blockNumber"`
+ TxIndex string `json:"transactionIndex"`
+ TxHash *common.Hash `json:"transactionHash"`
+ BlockHash *common.Hash `json:"blockHash"`
+ Index string `json:"logIndex"`
}
func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log {
@@ -64,19 +80,50 @@ func (l *Log) String() string {
return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index)
}
+// MarshalJSON implements json.Marshaler.
func (r *Log) MarshalJSON() ([]byte, error) {
- fields := map[string]interface{}{
- "address": r.Address,
- "data": fmt.Sprintf("%#x", r.Data),
- "blockNumber": fmt.Sprintf("%#x", r.BlockNumber),
- "logIndex": fmt.Sprintf("%#x", r.Index),
- "blockHash": r.BlockHash,
- "transactionHash": r.TxHash,
- "transactionIndex": fmt.Sprintf("%#x", r.TxIndex),
- "topics": r.Topics,
- }
+ return json.Marshal(&jsonLog{
+ Address: &r.Address,
+ Topics: &r.Topics,
+ Data: fmt.Sprintf("0x%x", r.Data),
+ BlockNumber: fmt.Sprintf("0x%x", r.BlockNumber),
+ TxIndex: fmt.Sprintf("0x%x", r.TxIndex),
+ TxHash: &r.TxHash,
+ BlockHash: &r.BlockHash,
+ Index: fmt.Sprintf("0x%x", r.Index),
+ })
+}
- return json.Marshal(fields)
+// UnmarshalJSON implements json.Umarshaler.
+func (r *Log) UnmarshalJSON(input []byte) error {
+ var dec jsonLog
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Address == nil || dec.Topics == nil || dec.Data == "" || dec.BlockNumber == "" ||
+ dec.TxIndex == "" || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == "" {
+ return errMissingLogFields
+ }
+ declog := Log{
+ Address: *dec.Address,
+ Topics: *dec.Topics,
+ TxHash: *dec.TxHash,
+ BlockHash: *dec.BlockHash,
+ }
+ 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
}
type Logs []*Log
diff --git a/core/vm/log_test.go b/core/vm/log_test.go
new file mode 100644
index 000000000..775016f9c
--- /dev/null
+++ b/core/vm/log_test.go
@@ -0,0 +1,59 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+var unmarshalLogTests = map[string]struct {
+ input string
+ wantError error
+}{
+ "ok": {
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ },
+ "missing data": {
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ wantError: errMissingLogFields,
+ },
+}
+
+func TestUnmarshalLog(t *testing.T) {
+ for name, test := range unmarshalLogTests {
+ var log *Log
+ err := json.Unmarshal([]byte(test.input), &log)
+ checkError(t, name, err, test.wantError)
+ }
+}
+
+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
+}