From ef03abcd1c98e169eaf16cee1f7ce93dce0b9474 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 9 Nov 2018 13:08:43 +0100 Subject: [PATCH] Process tx receipts and ERC20 tokens WIP --- bchain/coins/eth/erc20.go | 71 ++++++++ bchain/coins/eth/erc20_test.go | 112 ++++++++++++ bchain/coins/eth/ethparser.go | 159 ++++++++++------- bchain/coins/eth/ethparser_test.go | 2 +- bchain/coins/eth/ethrpc.go | 46 ++--- bchain/coins/eth/tx.pb.go | 269 +++++++++++++++++++---------- bchain/coins/eth/tx.proto | 41 +++-- 7 files changed, 513 insertions(+), 187 deletions(-) create mode 100644 bchain/coins/eth/erc20.go create mode 100644 bchain/coins/eth/erc20_test.go diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go new file mode 100644 index 00000000..0d2a6a5f --- /dev/null +++ b/bchain/coins/eth/erc20.go @@ -0,0 +1,71 @@ +package eth + +import ( + "math/big" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/juju/errors" +) + +var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"}, +{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"}, +{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"}, +{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"}, +{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"}, +{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"}, +{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"}, +{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"}, +{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"}, +{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}, +{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}, +{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"}, +{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"}, +{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` + +// doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event +const erc20EventTransferSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + +type erc20Transfer struct { + Contract ethcommon.Address + From ethcommon.Address + To ethcommon.Address + Tokens big.Int +} + +func addressFromPaddedHex(s string) (*ethcommon.Address, error) { + var t big.Int + _, ok := t.SetString(s, 0) + if !ok { + return nil, errors.New("Data is not a number") + } + a := ethcommon.BigToAddress(&t) + return &a, nil +} + +func erc20GetTransfersFromLog(logs []*rpcLog) ([]erc20Transfer, error) { + var r []erc20Transfer + for _, l := range logs { + if len(l.Topics) == 3 && l.Topics[0] == erc20EventTransferSignature { + var t big.Int + _, ok := t.SetString(l.Data, 0) + if !ok { + return nil, errors.New("Data is not a number") + } + from, err := addressFromPaddedHex(l.Topics[1]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(l.Topics[2]) + if err != nil { + return nil, err + } + r = append(r, erc20Transfer{ + Contract: l.Address, + From: *from, + To: *to, + Tokens: t, + }) + } + } + return r, nil +} diff --git a/bchain/coins/eth/erc20_test.go b/bchain/coins/eth/erc20_test.go new file mode 100644 index 00000000..c4bb5b02 --- /dev/null +++ b/bchain/coins/eth/erc20_test.go @@ -0,0 +1,112 @@ +// build unittest + +package eth + +import ( + "math/big" + "reflect" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" +) + +func TestErc20_erc20GetTransfersFromLog(t *testing.T) { + tests := []struct { + name string + args []*rpcLog + want []erc20Transfer + wantErr bool + }{ + { + name: "1", + args: []*rpcLog{ + &rpcLog{ + Address: ethcommon.HexToAddress("0x76a45e8976499ab9ae223cc584019341d5a84e96"), + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8", + "0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2", + }, + Data: "0x0000000000000000000000000000000000000000000000000000000000000123", + }, + }, + want: []erc20Transfer{ + { + Contract: ethcommon.HexToAddress("0x76a45e8976499ab9ae223cc584019341d5a84e96"), + From: ethcommon.HexToAddress("0x2aacf811ac1a60081ea39f7783c0d26c500871a8"), + To: ethcommon.HexToAddress("0xe9a5216ff992cfa01594d43501a56e12769eb9d2"), + Tokens: *big.NewInt(0x123), + }, + }, + }, + { + name: "2", + args: []*rpcLog{ + &rpcLog{ // Transfer + Address: ethcommon.HexToAddress("0x0d0f936ee4c93e25944694d6c121de94d9760f11"), + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + }, + Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b", + }, + &rpcLog{ // Transfer + Address: ethcommon.HexToAddress("0xc778417e063141139fce010982780140aa0cd5ab"), + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + }, + Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0", + }, + &rpcLog{ // not Transfer + Address: ethcommon.HexToAddress("0x479cc461fecd078f766ecc58533d6f69580cf3ac"), + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000", + }, + &rpcLog{ // not Transfer + Address: ethcommon.HexToAddress("0x0d0f936ee4c93e25944694d6c121de94d9760f11"), + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b", + "0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + }, + want: []erc20Transfer{ + { + Contract: ethcommon.HexToAddress("0x0d0f936ee4c93e25944694d6c121de94d9760f11"), + From: ethcommon.HexToAddress("0x6f44cceb49b4a5812d54b6f494fc2febf25511ed"), + To: ethcommon.HexToAddress("0x4bda106325c335df99eab7fe363cac8a0ba2a24d"), + Tokens: *big.NewInt(0x6a8313d60b1f606b), + }, + { + Contract: ethcommon.HexToAddress("0xc778417e063141139fce010982780140aa0cd5ab"), + From: ethcommon.HexToAddress("0x4bda106325c335df99eab7fe363cac8a0ba2a24d"), + To: ethcommon.HexToAddress("0x6f44cceb49b4a5812d54b6f494fc2febf25511ed"), + Tokens: *big.NewInt(0x308fd0e798ac0), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := erc20GetTransfersFromLog(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want) + } + }) + } + +} diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 72f6c1fb..b2a15e9e 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -9,6 +9,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/glog" "github.com/golang/protobuf/proto" "github.com/juju/errors" ) @@ -38,16 +39,33 @@ type rpcTransaction struct { BlockHash *ethcommon.Hash `json:"blockHash,omitempty"` From string `json:"from"` TransactionIndex string `json:"transactionIndex"` - // Signature values - V string `json:"v" gencodec:"required"` - R string `json:"r" gencodec:"required"` - S string `json:"s" gencodec:"required"` + // Signature values - ignored + // V string `json:"v" gencodec:"required"` + // R string `json:"r" gencodec:"required"` + // S string `json:"s" gencodec:"required"` +} + +type rpcLog struct { + Address ethcommon.Address `json:"address" gencodec:"required"` + Topics []string `json:"topics" gencodec:"required"` + Data string `json:"data" gencodec:"required"` +} + +type rpcReceipt struct { + GasUsed string `json:"gasUsed" gencodec:"required"` + Status string `json:"status"` + Logs []*rpcLog `json:"logs" gencodec:"required"` +} + +type completeTransaction struct { + Tx *rpcTransaction `json:"tx"` + Receipt *rpcReceipt `json:"receipt,omitempty"` } type rpcBlock struct { Hash ethcommon.Hash `json:"hash"` + Size string `json:"size"` Transactions []rpcTransaction `json:"transactions"` - UncleHashes []ethcommon.Hash `json:"uncles"` } func ethHashToHash(h ethcommon.Hash) string { @@ -61,7 +79,7 @@ func ethNumber(n string) (int64, error) { return 0, errors.Errorf("Not a number: '%v'", n) } -func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) { +func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32) (*bchain.Tx, error) { txid := ethHashToHash(tx.Hash) var ( fa, ta []string @@ -73,15 +91,21 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma if len(tx.To) > 2 { ta = []string{tx.To} } - // temporarily, the complete rpcTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex + + // completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex bh := tx.BlockHash tx.BlockHash = nil - b, err := json.Marshal(tx) + ct := completeTransaction{ + Tx: tx, + Receipt: receipt, + } + b, err := json.Marshal(ct) if err != nil { return nil, err } tx.BlockHash = bh h := hex.EncodeToString(b) + glog.Info(h) vs, err := hexutil.DecodeBig(tx.Value) if err != nil { return nil, err @@ -181,79 +205,96 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ( if err != nil { return nil, err } - var r rpcTransaction + var r completeTransaction var n uint64 err = json.Unmarshal(b, &r) if err != nil { return nil, err } - pt := &ProtoTransaction{} - if pt.AccountNonce, err = hexutil.DecodeUint64(r.AccountNonce); err != nil { - return nil, errors.Annotatef(err, "AccountNonce %v", r.AccountNonce) + pt := &ProtoCompleteTransaction{} + if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil { + return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce) } - if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil { - return nil, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber) + if n, err = hexutil.DecodeUint64(r.Tx.BlockNumber); err != nil { + return nil, errors.Annotatef(err, "BlockNumber %v", r.Tx.BlockNumber) } pt.BlockNumber = uint32(n) pt.BlockTime = uint64(blockTime) - if pt.From, err = hexDecode(r.From); err != nil { - return nil, errors.Annotatef(err, "From %v", r.From) + if pt.Tx.From, err = hexDecode(r.Tx.From); err != nil { + return nil, errors.Annotatef(err, "From %v", r.Tx.From) } - if pt.GasLimit, err = hexutil.DecodeUint64(r.GasLimit); err != nil { - return nil, errors.Annotatef(err, "GasLimit %v", r.GasLimit) + if pt.Tx.GasLimit, err = hexutil.DecodeUint64(r.Tx.GasLimit); err != nil { + return nil, errors.Annotatef(err, "GasLimit %v", r.Tx.GasLimit) } - pt.Hash = r.Hash.Bytes() - if pt.Payload, err = hexDecode(r.Payload); err != nil { - return nil, errors.Annotatef(err, "Payload %v", r.Payload) + pt.Tx.Hash = r.Tx.Hash.Bytes() + if pt.Tx.Payload, err = hexDecode(r.Tx.Payload); err != nil { + return nil, errors.Annotatef(err, "Payload %v", r.Tx.Payload) } - if pt.Price, err = hexDecodeBig(r.Price); err != nil { - return nil, errors.Annotatef(err, "Price %v", r.Price) + if pt.Tx.Price, err = hexDecodeBig(r.Tx.Price); err != nil { + return nil, errors.Annotatef(err, "Price %v", r.Tx.Price) } - if pt.R, err = hexDecodeBig(r.R); err != nil { - return nil, errors.Annotatef(err, "R %v", r.R) + // if pt.R, err = hexDecodeBig(r.R); err != nil { + // return nil, errors.Annotatef(err, "R %v", r.R) + // } + // if pt.S, err = hexDecodeBig(r.S); err != nil { + // return nil, errors.Annotatef(err, "S %v", r.S) + // } + // if pt.V, err = hexDecodeBig(r.V); err != nil { + // return nil, errors.Annotatef(err, "V %v", r.V) + // } + if pt.Tx.To, err = hexDecode(r.Tx.To); err != nil { + return nil, errors.Annotatef(err, "To %v", r.Tx.To) } - if pt.S, err = hexDecodeBig(r.S); err != nil { - return nil, errors.Annotatef(err, "S %v", r.S) + if n, err = hexutil.DecodeUint64(r.Tx.TransactionIndex); err != nil { + return nil, errors.Annotatef(err, "TransactionIndex %v", r.Tx.TransactionIndex) } - if pt.V, err = hexDecodeBig(r.V); err != nil { - return nil, errors.Annotatef(err, "V %v", r.V) - } - if pt.To, err = hexDecode(r.To); err != nil { - return nil, errors.Annotatef(err, "To %v", r.To) - } - if n, err = hexutil.DecodeUint64(r.TransactionIndex); err != nil { - return nil, errors.Annotatef(err, "TransactionIndex %v", r.TransactionIndex) - } - pt.TransactionIndex = uint32(n) - if pt.Value, err = hexDecodeBig(r.Value); err != nil { - return nil, errors.Annotatef(err, "Value %v", r.Value) + pt.Tx.TransactionIndex = uint32(n) + if pt.Tx.Value, err = hexDecodeBig(r.Tx.Value); err != nil { + return nil, errors.Annotatef(err, "Value %v", r.Tx.Value) } return proto.Marshal(pt) } // UnpackTx unpacks transaction from byte array func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { - var pt ProtoTransaction + var pt ProtoCompleteTransaction err := proto.Unmarshal(buf, &pt) if err != nil { return nil, 0, err } - r := rpcTransaction{ - AccountNonce: hexutil.EncodeUint64(pt.AccountNonce), - BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)), - From: hexutil.Encode(pt.From), - GasLimit: hexutil.EncodeUint64(pt.GasLimit), - Hash: ethcommon.BytesToHash(pt.Hash), - Payload: hexutil.Encode(pt.Payload), - Price: hexEncodeBig(pt.Price), - R: hexEncodeBig(pt.R), - S: hexEncodeBig(pt.S), - V: hexEncodeBig(pt.V), - To: hexutil.Encode(pt.To), - TransactionIndex: hexutil.EncodeUint64(uint64(pt.TransactionIndex)), - Value: hexEncodeBig(pt.Value), + rt := rpcTransaction{ + AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce), + BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)), + From: hexutil.Encode(pt.Tx.From), + GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit), + Hash: ethcommon.BytesToHash(pt.Tx.Hash), + Payload: hexutil.Encode(pt.Tx.Payload), + Price: hexEncodeBig(pt.Tx.Price), + // R: hexEncodeBig(pt.R), + // S: hexEncodeBig(pt.S), + // V: hexEncodeBig(pt.V), + To: hexutil.Encode(pt.Tx.To), + TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)), + Value: hexEncodeBig(pt.Tx.Value), } - tx, err := p.ethTxToTx(&r, int64(pt.BlockTime), 0) + logs := make([]*rpcLog, len(pt.Receipt.Log)) + for i, l := range pt.Receipt.Log { + topics := make([]string, len(l.Topics)) + for j, t := range l.Topics { + topics[j] = hexutil.Encode(t) + } + logs[i] = &rpcLog{ + Address: ethcommon.BytesToAddress(l.Address), + Data: hexutil.Encode(l.Data), + Topics: topics, + } + } + rr := rpcReceipt{ + GasUsed: hexEncodeBig(pt.Receipt.GasUsed), + Status: hexEncodeBig(pt.Receipt.Status), + Logs: logs, + } + tx, err := p.ethTxToTx(&rt, &rr, int64(pt.BlockTime), 0) if err != nil { return nil, 0, err } @@ -303,14 +344,14 @@ func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { if err != nil { return 0, err } - var r rpcTransaction + var ct completeTransaction var n uint64 - err = json.Unmarshal(b, &r) + err = json.Unmarshal(b, &ct) if err != nil { return 0, err } - if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil { - return 0, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber) + if n, err = hexutil.DecodeUint64(ct.Tx.BlockNumber); err != nil { + return 0, errors.Annotatef(err, "BlockNumber %v", ct.Tx.BlockNumber) } return uint32(n), nil } diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index 2a7ee8bf..71650737 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -1,4 +1,4 @@ -// +build unittest +// build unittest package eth diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index b1faa435..c08acfe0 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -12,6 +12,7 @@ import ( ethereum "github.com/ethereum/go-ethereum" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" @@ -423,12 +424,6 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) } // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. - if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 { - return nil, errors.Annotatef(fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles"), "hash %v, height %v", hash, height) - } - if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 { - return nil, errors.Annotatef(fmt.Errorf("server returned empty uncle list but block header indicates uncles"), "hash %v, height %v", hash, height) - } if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 { return nil, errors.Annotatef(fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions"), "hash %v, height %v", hash, height) } @@ -439,11 +434,16 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error if err != nil { return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) } - // TODO - this is probably not the correct size - bbh.Size = len(raw) + bigSize, err := hexutil.DecodeBig(body.Size) + if err != nil { + glog.Error("invalid size of block ", body.Hash, ": ", body.Size) + } else { + bbh.Size = int(bigSize.Int64()) + } + // TODO - get ERC20 events btxs := make([]bchain.Tx, len(body.Transactions)) for i, tx := range body.Transactions { - btx, err := b.Parser.ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations)) + btx, err := b.Parser.ethTxToTx(&tx, nil, int64(head.Time.Uint64()), uint32(bbh.Confirmations)) if err != nil { return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String()) } @@ -473,32 +473,38 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var tx *rpcTransaction - err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) + hash := ethcommon.HexToHash(txid) + err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash) if err != nil { return nil, err } else if tx == nil { return nil, ethereum.NotFound - } else if tx.R == "" { - if !b.isETC { - return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid) - } else { - glog.Warning("server returned transaction without signature, txid ", txid) - } } + // else if tx.R == "" { + // if !b.isETC { + // return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid) + // } + // glog.Warning("server returned transaction without signature, txid ", txid) + // } var btx *bchain.Tx if tx.BlockNumber == "" { // mempool tx - btx, err = b.Parser.ethTxToTx(tx, 0, 0) + btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } } else { // non mempool tx - we must read the block header to get the block time - n, err := ethNumber(tx.BlockNumber) + h, err := b.client.HeaderByHash(ctx, *tx.BlockHash) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } - h, err := b.client.HeaderByHash(ctx, *tx.BlockHash) + var receipt rpcReceipt + err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + n, err := ethNumber(tx.BlockNumber) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } @@ -506,7 +512,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } - btx, err = b.Parser.ethTxToTx(tx, h.Time.Int64(), confirmations) + btx, err = b.Parser.ethTxToTx(tx, &receipt, h.Time.Int64(), confirmations) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } diff --git a/bchain/coins/eth/tx.pb.go b/bchain/coins/eth/tx.pb.go index 17b0860c..621d3fa6 100644 --- a/bchain/coins/eth/tx.pb.go +++ b/bchain/coins/eth/tx.pb.go @@ -8,7 +8,7 @@ It is generated from these files: tx.proto It has these top-level messages: - ProtoTransaction + ProtoCompleteTransaction */ package eth @@ -27,149 +27,234 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package -type ProtoTransaction struct { - AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"` - Price []byte `protobuf:"bytes,2,opt,name=Price,proto3" json:"Price,omitempty"` - GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"` - Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"` - Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"` - Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"` - BlockNumber uint32 `protobuf:"varint,7,opt,name=BlockNumber" json:"BlockNumber,omitempty"` - BlockTime uint64 `protobuf:"varint,8,opt,name=BlockTime" json:"BlockTime,omitempty"` - To []byte `protobuf:"bytes,9,opt,name=To,proto3" json:"To,omitempty"` - From []byte `protobuf:"bytes,10,opt,name=From,proto3" json:"From,omitempty"` - TransactionIndex uint32 `protobuf:"varint,11,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"` - V []byte `protobuf:"bytes,12,opt,name=V,proto3" json:"V,omitempty"` - R []byte `protobuf:"bytes,13,opt,name=R,proto3" json:"R,omitempty"` - S []byte `protobuf:"bytes,14,opt,name=S,proto3" json:"S,omitempty"` +type ProtoCompleteTransaction struct { + BlockNumber uint32 `protobuf:"varint,1,opt,name=BlockNumber" json:"BlockNumber,omitempty"` + BlockTime uint64 `protobuf:"varint,2,opt,name=BlockTime" json:"BlockTime,omitempty"` + Tx *ProtoCompleteTransaction_TxType `protobuf:"bytes,3,opt,name=Tx" json:"Tx,omitempty"` + Receipt *ProtoCompleteTransaction_ReceiptType `protobuf:"bytes,4,opt,name=Receipt" json:"Receipt,omitempty"` } -func (m *ProtoTransaction) Reset() { *m = ProtoTransaction{} } -func (m *ProtoTransaction) String() string { return proto.CompactTextString(m) } -func (*ProtoTransaction) ProtoMessage() {} -func (*ProtoTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *ProtoCompleteTransaction) Reset() { *m = ProtoCompleteTransaction{} } +func (m *ProtoCompleteTransaction) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction) ProtoMessage() {} +func (*ProtoCompleteTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } -func (m *ProtoTransaction) GetAccountNonce() uint64 { - if m != nil { - return m.AccountNonce - } - return 0 -} - -func (m *ProtoTransaction) GetPrice() []byte { - if m != nil { - return m.Price - } - return nil -} - -func (m *ProtoTransaction) GetGasLimit() uint64 { - if m != nil { - return m.GasLimit - } - return 0 -} - -func (m *ProtoTransaction) GetValue() []byte { - if m != nil { - return m.Value - } - return nil -} - -func (m *ProtoTransaction) GetPayload() []byte { - if m != nil { - return m.Payload - } - return nil -} - -func (m *ProtoTransaction) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - -func (m *ProtoTransaction) GetBlockNumber() uint32 { +func (m *ProtoCompleteTransaction) GetBlockNumber() uint32 { if m != nil { return m.BlockNumber } return 0 } -func (m *ProtoTransaction) GetBlockTime() uint64 { +func (m *ProtoCompleteTransaction) GetBlockTime() uint64 { if m != nil { return m.BlockTime } return 0 } -func (m *ProtoTransaction) GetTo() []byte { +func (m *ProtoCompleteTransaction) GetTx() *ProtoCompleteTransaction_TxType { + if m != nil { + return m.Tx + } + return nil +} + +func (m *ProtoCompleteTransaction) GetReceipt() *ProtoCompleteTransaction_ReceiptType { + if m != nil { + return m.Receipt + } + return nil +} + +type ProtoCompleteTransaction_TxType struct { + AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"` + Price []byte `protobuf:"bytes,2,opt,name=Price,proto3" json:"Price,omitempty"` + GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"` + Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"` + Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"` + Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"` + To []byte `protobuf:"bytes,7,opt,name=To,proto3" json:"To,omitempty"` + From []byte `protobuf:"bytes,8,opt,name=From,proto3" json:"From,omitempty"` + TransactionIndex uint32 `protobuf:"varint,9,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"` +} + +func (m *ProtoCompleteTransaction_TxType) Reset() { *m = ProtoCompleteTransaction_TxType{} } +func (m *ProtoCompleteTransaction_TxType) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction_TxType) ProtoMessage() {} +func (*ProtoCompleteTransaction_TxType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 0} +} + +func (m *ProtoCompleteTransaction_TxType) GetAccountNonce() uint64 { + if m != nil { + return m.AccountNonce + } + return 0 +} + +func (m *ProtoCompleteTransaction_TxType) GetPrice() []byte { + if m != nil { + return m.Price + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetGasLimit() uint64 { + if m != nil { + return m.GasLimit + } + return 0 +} + +func (m *ProtoCompleteTransaction_TxType) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetPayload() []byte { + if m != nil { + return m.Payload + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetTo() []byte { if m != nil { return m.To } return nil } -func (m *ProtoTransaction) GetFrom() []byte { +func (m *ProtoCompleteTransaction_TxType) GetFrom() []byte { if m != nil { return m.From } return nil } -func (m *ProtoTransaction) GetTransactionIndex() uint32 { +func (m *ProtoCompleteTransaction_TxType) GetTransactionIndex() uint32 { if m != nil { return m.TransactionIndex } return 0 } -func (m *ProtoTransaction) GetV() []byte { +type ProtoCompleteTransaction_ReceiptType struct { + GasUsed []byte `protobuf:"bytes,1,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"` + Status []byte `protobuf:"bytes,2,opt,name=Status,proto3" json:"Status,omitempty"` + Log []*ProtoCompleteTransaction_ReceiptType_LogType `protobuf:"bytes,3,rep,name=Log" json:"Log,omitempty"` +} + +func (m *ProtoCompleteTransaction_ReceiptType) Reset() { *m = ProtoCompleteTransaction_ReceiptType{} } +func (m *ProtoCompleteTransaction_ReceiptType) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction_ReceiptType) ProtoMessage() {} +func (*ProtoCompleteTransaction_ReceiptType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 1} +} + +func (m *ProtoCompleteTransaction_ReceiptType) GetGasUsed() []byte { if m != nil { - return m.V + return m.GasUsed } return nil } -func (m *ProtoTransaction) GetR() []byte { +func (m *ProtoCompleteTransaction_ReceiptType) GetStatus() []byte { if m != nil { - return m.R + return m.Status } return nil } -func (m *ProtoTransaction) GetS() []byte { +func (m *ProtoCompleteTransaction_ReceiptType) GetLog() []*ProtoCompleteTransaction_ReceiptType_LogType { if m != nil { - return m.S + return m.Log + } + return nil +} + +type ProtoCompleteTransaction_ReceiptType_LogType struct { + Address []byte `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"` + Topics [][]byte `protobuf:"bytes,3,rep,name=Topics,proto3" json:"Topics,omitempty"` +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) Reset() { + *m = ProtoCompleteTransaction_ReceiptType_LogType{} +} +func (m *ProtoCompleteTransaction_ReceiptType_LogType) String() string { + return proto.CompactTextString(m) +} +func (*ProtoCompleteTransaction_ReceiptType_LogType) ProtoMessage() {} +func (*ProtoCompleteTransaction_ReceiptType_LogType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 1, 0} +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetTopics() [][]byte { + if m != nil { + return m.Topics } return nil } func init() { - proto.RegisterType((*ProtoTransaction)(nil), "eth.ProtoTransaction") + proto.RegisterType((*ProtoCompleteTransaction)(nil), "eth.ProtoCompleteTransaction") + proto.RegisterType((*ProtoCompleteTransaction_TxType)(nil), "eth.ProtoCompleteTransaction.TxType") + proto.RegisterType((*ProtoCompleteTransaction_ReceiptType)(nil), "eth.ProtoCompleteTransaction.ReceiptType") + proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType") } func init() { proto.RegisterFile("tx.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 262 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbd, 0x6a, 0xeb, 0x40, - 0x10, 0x85, 0x59, 0x59, 0xb6, 0xe5, 0xb1, 0x6c, 0xcc, 0x70, 0x8b, 0xe1, 0x92, 0x42, 0xb8, 0x12, - 0x29, 0xd2, 0xe4, 0x09, 0x92, 0x22, 0x3f, 0x10, 0x8c, 0x90, 0x85, 0xfa, 0xf5, 0x7a, 0xc1, 0x22, - 0x92, 0x26, 0x48, 0x2b, 0x70, 0x5e, 0x38, 0xcf, 0x11, 0x76, 0x44, 0x12, 0x87, 0x74, 0xf3, 0x7d, - 0x70, 0xf6, 0x2c, 0x07, 0x22, 0x77, 0xbe, 0x79, 0xeb, 0xd8, 0x31, 0x4e, 0xac, 0x3b, 0x6d, 0x3f, - 0x02, 0xd8, 0x64, 0x1e, 0x8b, 0x4e, 0xb7, 0xbd, 0x36, 0xae, 0xe2, 0x16, 0xb7, 0x10, 0xdf, 0x19, - 0xc3, 0x43, 0xeb, 0x76, 0xdc, 0x1a, 0x4b, 0x2a, 0x51, 0x69, 0x98, 0xff, 0x72, 0xf8, 0x0f, 0xa6, - 0x59, 0x57, 0x19, 0x4b, 0x41, 0xa2, 0xd2, 0x38, 0x1f, 0x01, 0xff, 0x43, 0xf4, 0xa8, 0xfb, 0x97, - 0xaa, 0xa9, 0x1c, 0x4d, 0x24, 0xf5, 0xcd, 0x3e, 0x51, 0xea, 0x7a, 0xb0, 0x14, 0x8e, 0x09, 0x01, - 0x24, 0x98, 0x67, 0xfa, 0xbd, 0x66, 0x7d, 0xa4, 0xa9, 0xf8, 0x2f, 0x44, 0x84, 0xf0, 0x49, 0xf7, - 0x27, 0x9a, 0x89, 0x96, 0x1b, 0x13, 0x58, 0xde, 0xd7, 0x6c, 0x5e, 0x77, 0x43, 0x73, 0xb0, 0x1d, - 0xcd, 0x13, 0x95, 0xae, 0xf2, 0x4b, 0x85, 0x57, 0xb0, 0x10, 0x2c, 0xaa, 0xc6, 0x52, 0x24, 0x5f, - 0xf8, 0x11, 0xb8, 0x86, 0xa0, 0x60, 0x5a, 0xc8, 0x8b, 0x41, 0xc1, 0xbe, 0xe3, 0xa1, 0xe3, 0x86, - 0x60, 0xec, 0xf0, 0x37, 0x5e, 0xc3, 0xe6, 0x62, 0x8c, 0xe7, 0xf6, 0x68, 0xcf, 0xb4, 0x94, 0xa2, - 0x3f, 0x1e, 0x63, 0x50, 0x25, 0xc5, 0x12, 0x56, 0xa5, 0xa7, 0x9c, 0x56, 0x23, 0xe5, 0x9e, 0xf6, - 0xb4, 0x1e, 0x69, 0x7f, 0x98, 0xc9, 0xe8, 0xb7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x15, 0xc8, - 0xe4, 0x30, 0x80, 0x01, 0x00, 0x00, + // 393 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30, + 0x14, 0xc6, 0xe9, 0x9f, 0x69, 0x67, 0x4f, 0xab, 0x48, 0x10, 0x09, 0xc5, 0x8b, 0xb2, 0x78, 0x51, + 0xbd, 0x28, 0xb8, 0xfa, 0x02, 0xeb, 0x88, 0xab, 0x30, 0xac, 0x43, 0x8c, 0xde, 0x67, 0xd3, 0xb0, + 0x53, 0x6c, 0x9b, 0xd2, 0xa4, 0xd0, 0x7d, 0x1d, 0xdf, 0xc9, 0x17, 0xf1, 0x09, 0x24, 0xa7, 0xad, + 0x8e, 0x88, 0xb2, 0x77, 0xe7, 0xf7, 0x71, 0xbe, 0xc9, 0xf7, 0x9d, 0x29, 0x6c, 0xed, 0x54, 0xf6, + 0x83, 0xb6, 0x9a, 0x04, 0xca, 0x1e, 0xcf, 0xbf, 0x6d, 0x80, 0x1e, 0x1c, 0xee, 0x74, 0xdb, 0x37, + 0xca, 0x2a, 0x3e, 0x88, 0xce, 0x08, 0x69, 0x6b, 0xdd, 0x91, 0x1c, 0x92, 0x37, 0x8d, 0x96, 0x5f, + 0xaf, 0xc7, 0xf6, 0x46, 0x0d, 0xd4, 0xcb, 0xbd, 0xe2, 0x01, 0x3b, 0x95, 0xc8, 0x53, 0x38, 0x43, + 0xe4, 0x75, 0xab, 0xa8, 0x9f, 0x7b, 0x45, 0xc8, 0x7e, 0x0b, 0xe4, 0x35, 0xf8, 0x7c, 0xa2, 0x41, + 0xee, 0x15, 0xc9, 0xc5, 0xb3, 0x52, 0xd9, 0x63, 0xf9, 0xaf, 0xa7, 0x4a, 0x3e, 0xf1, 0xbb, 0x5e, + 0x31, 0x9f, 0x4f, 0x64, 0x07, 0x31, 0x53, 0x52, 0xd5, 0xbd, 0xa5, 0x21, 0x5a, 0x9f, 0xff, 0xdf, + 0xba, 0x2c, 0xa3, 0x7f, 0x75, 0x66, 0x3f, 0x3c, 0x88, 0xe6, 0xdf, 0x24, 0xe7, 0x90, 0x5e, 0x4a, + 0xa9, 0xc7, 0xce, 0x5e, 0xeb, 0x4e, 0x2a, 0xac, 0x11, 0xb2, 0x3f, 0x34, 0xf2, 0x18, 0x36, 0x87, + 0xa1, 0x96, 0x73, 0x87, 0x94, 0xcd, 0x40, 0x32, 0xd8, 0x5e, 0x09, 0xb3, 0xaf, 0xdb, 0xda, 0x62, + 0x8b, 0x90, 0xfd, 0x62, 0xe7, 0xf8, 0x22, 0x9a, 0x51, 0x61, 0xc6, 0x94, 0xcd, 0x40, 0x28, 0xc4, + 0x07, 0x71, 0xd7, 0x68, 0x51, 0xd1, 0x0d, 0xea, 0x2b, 0x12, 0x02, 0xe1, 0x7b, 0x61, 0x8e, 0x34, + 0x42, 0x19, 0x67, 0xf2, 0x10, 0x7c, 0xae, 0x69, 0x8c, 0x8a, 0xcf, 0xb5, 0xdb, 0x79, 0x37, 0xe8, + 0x96, 0x6e, 0xe7, 0x1d, 0x37, 0x93, 0x17, 0xf0, 0xe8, 0xa4, 0xec, 0x87, 0xae, 0x52, 0x13, 0x3d, + 0xc3, 0x3f, 0xe2, 0x2f, 0x3d, 0xfb, 0xee, 0x41, 0x72, 0x72, 0x0d, 0x97, 0xe6, 0x4a, 0x98, 0xcf, + 0x46, 0x55, 0x58, 0x3a, 0x65, 0x2b, 0x92, 0x27, 0x10, 0x7d, 0xb2, 0xc2, 0x8e, 0x66, 0x29, 0xbc, + 0x10, 0xd9, 0x41, 0xb0, 0xd7, 0xb7, 0x34, 0xc8, 0x83, 0x22, 0xb9, 0x78, 0x79, 0xef, 0xbb, 0x97, + 0x7b, 0x7d, 0x8b, 0xf7, 0x77, 0xee, 0xec, 0x23, 0xc4, 0x0b, 0xbb, 0x04, 0x97, 0x55, 0x35, 0x28, + 0x63, 0xd6, 0x04, 0x0b, 0xba, 0xae, 0x6f, 0x85, 0x15, 0xcb, 0xfb, 0x38, 0xbb, 0x54, 0x5c, 0xf7, + 0xb5, 0x34, 0x18, 0x20, 0x65, 0x0b, 0xdd, 0x44, 0xf8, 0xc1, 0xbe, 0xfa, 0x19, 0x00, 0x00, 0xff, + 0xff, 0x84, 0x73, 0x4b, 0xa3, 0xbc, 0x02, 0x00, 0x00, } diff --git a/bchain/coins/eth/tx.proto b/bchain/coins/eth/tx.proto index c7ed0fa9..7d7f23ba 100644 --- a/bchain/coins/eth/tx.proto +++ b/bchain/coins/eth/tx.proto @@ -1,19 +1,30 @@ syntax = "proto3"; package eth; - message ProtoTransaction { - uint64 AccountNonce = 1; - bytes Price = 2; - uint64 GasLimit = 3; - bytes Value = 4; - bytes Payload = 5; - bytes Hash = 6; - uint32 BlockNumber = 7; - uint64 BlockTime = 8; - bytes To = 9; - bytes From = 10; - uint32 TransactionIndex = 11; - bytes V = 12; - bytes R = 13; - bytes S = 14; + message ProtoCompleteTransaction { + message TxType { + uint64 AccountNonce = 1; + bytes Price = 2; + uint64 GasLimit = 3; + bytes Value = 4; + bytes Payload = 5; + bytes Hash = 6; + bytes To = 7; + bytes From = 8; + uint32 TransactionIndex = 9; + } + message ReceiptType { + message LogType { + bytes Address = 1; + bytes Data = 2; + repeated bytes Topics = 3; + } + bytes GasUsed = 1; + bytes Status = 2; + repeated LogType Log = 3; + } + uint32 BlockNumber = 1; + uint64 BlockTime = 2; + TxType Tx = 3; + ReceiptType Receipt = 4; } \ No newline at end of file