core/types, core/vm: improve docs, add JSON marshaling methods

In this commit, core/types's types learn how to encode and decode
themselves as JSON. The encoding is very similar to what the RPC API
uses. The RPC API is missing some output fields (e.g. transaction
signature values) which will be added to the API in a later commit. Some
fields that the API generates are ignored by the decoder methods here.
This commit is contained in:
Felix Lange 2016-08-04 03:55:33 +02:00
parent 0c9a858f2d
commit 704fde01e8
8 changed files with 590 additions and 100 deletions

View File

@ -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)

View File

@ -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 {

87
core/types/json.go Normal file
View File

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

136
core/types/json_test.go Normal file
View File

@ -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
}

View File

@ -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.

View File

@ -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 {

View File

@ -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

59
core/vm/log_test.go Normal file
View File

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