From 9b149649008d2d321f2d6b2c3add7003bdf500a4 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 31 Oct 2018 13:39:27 +0100 Subject: [PATCH 1/9] Bump geth dependency to v1.8.17 --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index cf32c2f0..975e36b9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -46,8 +46,8 @@ [[projects]] name = "github.com/ethereum/go-ethereum" packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","p2p/netutil","params","rlp","rpc","trie"] - revision = "89451f7c382ad2185987ee369f16416f89c28a7d" - version = "v1.8.15" + revision = "8bbe72075e4e16442c4e28d999edee12e294329e" + version = "v1.8.17" [[projects]] name = "github.com/go-stack/stack" From 13acef41d49f097077c3d210e80c1ad3f2c66afd Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 31 Oct 2018 17:17:22 +0100 Subject: [PATCH 2/9] Implement ETH GetChainInfo --- bchain/coins/btc/bitcoinrpc.go | 14 ++++++------ bchain/coins/eth/ethrpc.go | 40 ++++++++++++++++++++++++++++------ static/templates/index.html | 2 ++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index dfecbf71..9e978743 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -14,7 +14,6 @@ import ( "time" "github.com/btcsuite/btcd/wire" - "github.com/golang/glog" "github.com/juju/errors" ) @@ -36,6 +35,7 @@ type BitcoinRPC struct { RPCMarshaler RPCMarshaler } +// Configuration represents json config file type Configuration struct { CoinName string `json:"coin_name"` CoinShortcut string `json:"coin_shortcut"` @@ -580,7 +580,7 @@ func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain. return block, nil } -// GetBlockRaw returns block with given hash as bytes. +// GetBlockRaw returns block with given hash as bytes func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { glog.V(1).Info("rpc: getblock (verbosity=0) ", hash) @@ -602,7 +602,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { return hex.DecodeString(res.Result) } -// GetBlockFull returns block with given hash. +// GetBlockFull returns block with given hash func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { glog.V(1).Info("rpc: getblock (verbosity=2) ", hash) @@ -624,7 +624,7 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { return &res.Result, nil } -// GetMempool returns transactions in mempool. +// GetMempool returns transactions in mempool func (b *BitcoinRPC) GetMempool() ([]string, error) { glog.V(1).Info("rpc: getrawmempool") @@ -641,7 +641,7 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) { return res.Result, nil } -// GetTransactionForMempool returns a transaction by the transaction ID. +// GetTransactionForMempool returns a transaction by the transaction ID // It could be optimized for mempool, i.e. without block time and confirmations func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid) @@ -668,7 +668,7 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { return tx, nil } -// GetTransaction returns a transaction by the transaction ID. +// GetTransaction returns a transaction by the transaction ID func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { r, err := b.GetTransactionSpecific(txid) if err != nil { @@ -702,7 +702,7 @@ func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error // ResyncMempool gets mempool transactions and maps output scripts to transactions. // ResyncMempool is not reentrant, it should be called from a single thread. -// It returns number of transactions in mempool +// Return value is number of transactions in mempool func (b *BitcoinRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) { return b.Mempool.Resync(onNewTxAddr) } diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 8a5c98c9..8be6e5ea 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -10,14 +10,13 @@ import ( "sync" "time" - "github.com/golang/glog" - "github.com/juju/errors" - ethereum "github.com/ethereum/go-ethereum" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" + "github.com/golang/glog" + "github.com/juju/errors" ) // EthereumNet type specifies the type of ethereum network @@ -30,6 +29,7 @@ const ( TestNet EthereumNet = 3 ) +// Configuration represents json config file type Configuration struct { CoinName string `json:"coin_name"` CoinShortcut string `json:"coin_shortcut"` @@ -269,14 +269,30 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { if err != nil { return nil, err } - rv := &bchain.ChainInfo{} + h, err := b.getBestHeader() + if err != nil { + return nil, err + } + var ver, protocol string + if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil { + return nil, err + } + if err := b.rpc.CallContext(ctx, &protocol, "eth_protocolVersion"); err != nil { + return nil, err + } + rv := &bchain.ChainInfo{ + Blocks: int(h.Number.Int64()), + Bestblockhash: ethHashToHash(h.Hash()), + Difficulty: h.Difficulty.String(), + Version: ver, + ProtocolVersion: protocol, + } idi := int(id.Uint64()) if idi == 1 { rv.Chain = "mainnet" } else { rv.Chain = "testnet " + strconv.Itoa(idi) } - // TODO - return more information about the chain return rv, nil } @@ -302,6 +318,7 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) { return b.bestHeader, nil } +// GetBestBlockHash returns hash of the tip of the best-block-chain func (b *EthereumRPC) GetBestBlockHash() (string, error) { h, err := b.getBestHeader() if err != nil { @@ -310,6 +327,7 @@ func (b *EthereumRPC) GetBestBlockHash() (string, error) { return ethHashToHash(h.Hash()), nil } +// GetBestBlockHeight returns height of the tip of the best-block-chain func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { h, err := b.getBestHeader() if err != nil { @@ -319,6 +337,7 @@ func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { return uint32(h.Number.Uint64()), nil } +// GetBlockHash returns hash of block in best-block-chain at given height func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { var n big.Int n.SetUint64(uint64(height)) @@ -350,6 +369,7 @@ func (b *EthereumRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockH }, nil } +// GetBlockHeader returns header of block with given hash func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() @@ -512,6 +532,7 @@ type rpcMempoolBlock struct { Transactions []string `json:"transactions"` } +// GetMempool returns transactions in mempool func (b *EthereumRPC) GetMempool() ([]string, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() @@ -553,7 +574,7 @@ func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, return r, nil } -// SendRawTransaction sends raw transaction. +// SendRawTransaction sends raw transaction func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() @@ -574,6 +595,9 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { return result, nil } +// ResyncMempool gets mempool transactions and maps output scripts to transactions. +// ResyncMempool is not reentrant, it should be called from a single thread. +// Return value is number of transactions in mempool func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) { return b.Mempool.Resync(onNewTxAddr) } @@ -588,10 +612,12 @@ func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressD return b.Mempool.GetAddrDescTransactions(addrDesc) } +// GetMempoolEntry is not supported by etherem func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) { - return nil, errors.New("GetMempoolEntry: not implemented") + return nil, errors.New("GetMempoolEntry: not supported") } +// GetChainParser returns ethereum BlockChainParser func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { return b.Parser } diff --git a/static/templates/index.html b/static/templates/index.html index bf37c544..f6638adc 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -78,10 +78,12 @@ Difficulty {{$be.Difficulty}} + {{- if $be.Timeoffset -}} Timeoffset {{$be.Timeoffset}} + {{- end -}} {{- if $be.SizeOnDisk -}} Size On Disk From 38ba03365442eefce3bb3ee72175bed01b46dfea Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Tue, 6 Nov 2018 18:41:13 +0100 Subject: [PATCH 3/9] Introduce BitcoinType and EthereumType distinction of blockchains --- bchain/baseparser.go | 6 +-- bchain/coins/btc/bitcoinrpc.go | 4 +- bchain/coins/eth/ethparser.go | 10 ++--- bchain/coins/eth/ethrpc.go | 4 +- ...empool_utxo.go => mempool_bitcoin_type.go} | 22 +++++----- ...ol_nonutxo.go => mempool_ethereum_type.go} | 18 ++++---- bchain/types.go | 29 +++++++++---- db/bulkconnect.go | 9 ++-- db/rocksdb.go | 20 ++++----- db/rocksdb_test.go | 42 +++++++++---------- db/sync.go | 6 +-- docs/config.md | 4 +- server/public_test.go | 7 ++-- tests/dbtestdata/dbtestdata.go | 4 +- tests/dbtestdata/fakechain.go | 26 ++++++------ 15 files changed, 112 insertions(+), 99 deletions(-) rename bchain/{mempool_utxo.go => mempool_bitcoin_type.go} (86%) rename bchain/{mempool_nonutxo.go => mempool_ethereum_type.go} (81%) diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 48bb752e..d8a878ae 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -125,9 +125,9 @@ func (p *BaseParser) UnpackBlockHash(buf []byte) (string, error) { return hex.EncodeToString(buf), nil } -// IsUTXOChain returns true if the block chain is UTXO type, otherwise false -func (p *BaseParser) IsUTXOChain() bool { - return true +// GetChainType is type of the blockchain, default is ChainBitcoinType +func (p *BaseParser) GetChainType() ChainType { + return ChainBitcoinType } // PackTx packs transaction to byte array using protobuf diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 9e978743..8b69e8a2 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -27,7 +27,7 @@ type BitcoinRPC struct { Parser bchain.BlockChainParser Testnet bool Network string - Mempool *bchain.UTXOMempool + Mempool *bchain.MempoolBitcoinType ParseBlocks bool pushHandler func(bchain.NotificationType) mq *bchain.MQ @@ -115,7 +115,7 @@ func (b *BitcoinRPC) GetChainInfoAndInitializeMempool(bc bchain.BlockChain) (str } b.mq = mq - b.Mempool = bchain.NewUTXOMempool(bc, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers) + b.Mempool = bchain.NewMempoolBitcoinType(bc, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers) return chainName, nil } diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index d76aa6bf..90341cd5 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -7,12 +7,10 @@ import ( "math/big" "strconv" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/golang/protobuf/proto" "github.com/juju/errors" - - ethcommon "github.com/ethereum/go-ethereum/common" ) // EthereumParser handle @@ -293,7 +291,7 @@ func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { return hexutil.Encode(buf), nil } -// IsUTXOChain returns true if the block chain is UTXO type, otherwise false -func (p *EthereumParser) IsUTXOChain() bool { - return false +// GetChainType returns TypeEthereum +func (p *EthereumParser) GetChainType() bchain.ChainType { + return bchain.ChainEthereumType } diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 8be6e5ea..b1faa435 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -45,7 +45,7 @@ type EthereumRPC struct { Parser *EthereumParser Testnet bool Network string - Mempool *bchain.NonUTXOMempool + Mempool *bchain.MempoolEthereumType bestHeaderMu sync.Mutex bestHeader *ethtypes.Header bestHeaderTime time.Time @@ -185,7 +185,7 @@ func (b *EthereumRPC) Initialize() error { } // create mempool - b.Mempool = bchain.NewNonUTXOMempool(b) + b.Mempool = bchain.NewMempoolEthereumType(b) return nil } diff --git a/bchain/mempool_utxo.go b/bchain/mempool_bitcoin_type.go similarity index 86% rename from bchain/mempool_utxo.go rename to bchain/mempool_bitcoin_type.go index 86fe6a5e..6cd30a24 100644 --- a/bchain/mempool_utxo.go +++ b/bchain/mempool_bitcoin_type.go @@ -23,8 +23,8 @@ type txidio struct { io []addrIndex } -// UTXOMempool is mempool handle. -type UTXOMempool struct { +// MempoolBitcoinType is mempool handle. +type MempoolBitcoinType struct { chain BlockChain mux sync.Mutex txToInputOutput map[string][]addrIndex @@ -34,10 +34,10 @@ type UTXOMempool struct { onNewTxAddr OnNewTxAddrFunc } -// NewUTXOMempool creates new mempool handler. +// NewMempoolBitcoinType creates new mempool handler. // For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process -func NewUTXOMempool(chain BlockChain, workers int, subworkers int) *UTXOMempool { - m := &UTXOMempool{ +func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int) *MempoolBitcoinType { + m := &MempoolBitcoinType{ chain: chain, chanTxid: make(chan string, 1), chanAddrIndex: make(chan txidio, 1), @@ -68,7 +68,7 @@ func NewUTXOMempool(chain BlockChain, workers int, subworkers int) *UTXOMempool } // GetTransactions returns slice of mempool transactions for given address -func (m *UTXOMempool) GetTransactions(address string) ([]string, error) { +func (m *MempoolBitcoinType) GetTransactions(address string) ([]string, error) { parser := m.chain.GetChainParser() addrDesc, err := parser.GetAddrDescFromAddress(address) if err != nil { @@ -78,7 +78,7 @@ func (m *UTXOMempool) GetTransactions(address string) ([]string, error) { } // GetAddrDescTransactions returns slice of mempool transactions for given address descriptor -func (m *UTXOMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) { +func (m *MempoolBitcoinType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) { m.mux.Lock() defer m.mux.Unlock() outpoints := m.addrDescToTx[string(addrDesc)] @@ -89,14 +89,14 @@ func (m *UTXOMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]str return txs, nil } -func (m *UTXOMempool) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) { +func (m *MempoolBitcoinType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) { m.mux.Lock() defer m.mux.Unlock() m.txToInputOutput = newTxToInputOutput m.addrDescToTx = newAddrDescToTx } -func (m *UTXOMempool) getInputAddress(input outpoint) *addrIndex { +func (m *MempoolBitcoinType) getInputAddress(input outpoint) *addrIndex { itx, err := m.chain.GetTransactionForMempool(input.txid) if err != nil { glog.Error("cannot get transaction ", input.txid, ": ", err) @@ -115,7 +115,7 @@ func (m *UTXOMempool) getInputAddress(input outpoint) *addrIndex { } -func (m *UTXOMempool) getTxAddrs(txid string, chanInput chan outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) { +func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) { tx, err := m.chain.GetTransactionForMempool(txid) if err != nil { glog.Error("cannot get transaction ", txid, ": ", err) @@ -170,7 +170,7 @@ func (m *UTXOMempool) getTxAddrs(txid string, chanInput chan outpoint, chanResul // Resync gets mempool transactions and maps outputs to transactions. // Resync is not reentrant, it should be called from a single thread. // Read operations (GetTransactions) are safe. -func (m *UTXOMempool) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { +func (m *MempoolBitcoinType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { start := time.Now() glog.V(1).Info("mempool: resync") m.onNewTxAddr = onNewTxAddr diff --git a/bchain/mempool_nonutxo.go b/bchain/mempool_ethereum_type.go similarity index 81% rename from bchain/mempool_nonutxo.go rename to bchain/mempool_ethereum_type.go index c0151c50..08484135 100644 --- a/bchain/mempool_nonutxo.go +++ b/bchain/mempool_ethereum_type.go @@ -7,21 +7,21 @@ import ( "github.com/golang/glog" ) -// NonUTXOMempool is mempool handle of non UTXO chains -type NonUTXOMempool struct { +// MempoolEthereumType is mempool handle of EthereumType chains +type MempoolEthereumType struct { chain BlockChain mux sync.Mutex txToInputOutput map[string][]addrIndex addrDescToTx map[string][]outpoint } -// NewNonUTXOMempool creates new mempool handler. -func NewNonUTXOMempool(chain BlockChain) *NonUTXOMempool { - return &NonUTXOMempool{chain: chain} +// NewMempoolEthereumType creates new mempool handler. +func NewMempoolEthereumType(chain BlockChain) *MempoolEthereumType { + return &MempoolEthereumType{chain: chain} } // GetTransactions returns slice of mempool transactions for given address -func (m *NonUTXOMempool) GetTransactions(address string) ([]string, error) { +func (m *MempoolEthereumType) GetTransactions(address string) ([]string, error) { parser := m.chain.GetChainParser() addrDesc, err := parser.GetAddrDescFromAddress(address) if err != nil { @@ -31,7 +31,7 @@ func (m *NonUTXOMempool) GetTransactions(address string) ([]string, error) { } // GetAddrDescTransactions returns slice of mempool transactions for given address descriptor -func (m *NonUTXOMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) { +func (m *MempoolEthereumType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) { m.mux.Lock() defer m.mux.Unlock() outpoints := m.addrDescToTx[string(addrDesc)] @@ -42,7 +42,7 @@ func (m *NonUTXOMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([] return txs, nil } -func (m *NonUTXOMempool) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) { +func (m *MempoolEthereumType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) { m.mux.Lock() defer m.mux.Unlock() m.txToInputOutput = newTxToInputOutput @@ -52,7 +52,7 @@ func (m *NonUTXOMempool) updateMappings(newTxToInputOutput map[string][]addrInde // Resync gets mempool transactions and maps outputs to transactions. // Resync is not reentrant, it should be called from a single thread. // Read operations (GetTransactions) are safe. -func (m *NonUTXOMempool) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { +func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { start := time.Now() glog.V(1).Info("Mempool: resync") txs, err := m.chain.GetMempool() diff --git a/bchain/types.go b/bchain/types.go index 749adde7..59d05d39 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -9,6 +9,16 @@ import ( "math/big" ) +// ChainType is type of the blockchain +type ChainType int + +const ( + // ChainBitcoinType is blockchain derived from bitcoin + ChainBitcoinType = ChainType(iota) + // TypeEthereum is blockchain derived from ethereum + ChainEthereumType +) + // errors with specific meaning returned by blockchain rpc var ( // ErrBlockNotFound is returned when block is not found @@ -23,11 +33,13 @@ var ( ErrTxidMissing = errors.New("Txid missing") ) +// ScriptSig contains data about input script type ScriptSig struct { // Asm string `json:"asm"` Hex string `json:"hex"` } +// Vin contains data about tx output type Vin struct { Coinbase string `json:"coinbase"` Txid string `json:"txid"` @@ -37,6 +49,7 @@ type Vin struct { Addresses []string `json:"addresses"` } +// ScriptPubKey contains data about output script type ScriptPubKey struct { // Asm string `json:"asm"` Hex string `json:"hex,omitempty"` @@ -44,6 +57,7 @@ type ScriptPubKey struct { Addresses []string `json:"addresses"` } +// Vout contains data about tx output type Vout struct { ValueSat big.Int JsonValue json.Number `json:"value"` @@ -66,6 +80,7 @@ type Tx struct { Blocktime int64 `json:"blocktime,omitempty"` } +// Block is block header and list of transactions type Block struct { BlockHeader Txs []Tx `json:"tx"` @@ -93,6 +108,7 @@ type BlockInfo struct { Txids []string `json:"tx,omitempty"` } +// MempoolEntry is used to get data about mempool entry type MempoolEntry struct { Size uint32 `json:"size"` FeeSat big.Int @@ -110,6 +126,7 @@ type MempoolEntry struct { Depends []string `json:"depends"` } +// ChainInfo is used to get information about blockchain type ChainInfo struct { Chain string `json:"chain"` Blocks int `json:"blocks"` @@ -124,6 +141,7 @@ type ChainInfo struct { Warnings string `json:"warnings"` } +// RPCError defines rpc error returned by backend type RPCError struct { Code int `json:"code"` Message string `json:"message"` @@ -182,13 +200,10 @@ type BlockChain interface { // BlockChainParser defines common interface to parsing and conversions of block chain data type BlockChainParser interface { - // chain configuration description - // UTXO chains need "inputs" column in db, that map transactions to transactions that spend them - // non UTXO chains have mapping of address to input and output transactions directly in "outputs" column in db - IsUTXOChain() bool - // KeepBlockAddresses returns number of blocks which are to be kept in blockaddresses column - // and used in case of fork - // if 0 the blockaddresses column is not used at all (usually non UTXO chains) + // type of the blockchain + GetChainType() ChainType + // KeepBlockAddresses returns number of blocks which are to be kept in blockTxs column + // to be used for rollbacks KeepBlockAddresses() int // AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place AmountToDecimalString(a *big.Int) string diff --git a/db/bulkconnect.go b/db/bulkconnect.go index 60ca7a64..7221acad 100644 --- a/db/bulkconnect.go +++ b/db/bulkconnect.go @@ -22,7 +22,7 @@ type bulkAddresses struct { // BulkConnect is used to connect blocks in bulk, faster but if interrupted inconsistent way type BulkConnect struct { d *RocksDB - isUTXO bool + chainType bchain.ChainType bulkAddresses []bulkAddresses bulkAddressesCount int txAddressesMap map[string]*TxAddresses @@ -42,7 +42,7 @@ const ( func (d *RocksDB) InitBulkConnect() (*BulkConnect, error) { bc := &BulkConnect{ d: d, - isUTXO: d.chainParser.IsUTXOChain(), + chainType: d.chainParser.GetChainType(), txAddressesMap: make(map[string]*TxAddresses), balances: make(map[string]*AddrBalance), } @@ -168,11 +168,12 @@ func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error { // ConnectBlock connects block in bulk mode func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) error { b.height = block.Height - if !b.isUTXO { + // for non bitcoin types connect blocks in non bulk mode + if b.chainType != bchain.ChainBitcoinType { return b.d.ConnectBlock(block) } addresses := make(map[string][]outpoint) - if err := b.d.processAddressesUTXO(block, addresses, b.txAddressesMap, b.balances); err != nil { + if err := b.d.processAddressesBitcoinType(block, addresses, b.txAddressesMap, b.balances); err != nil { return err } var storeAddressesChan, storeBalancesChan chan error diff --git a/db/rocksdb.go b/db/rocksdb.go index e4e163f5..94d2f456 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -271,21 +271,21 @@ func (d *RocksDB) writeBlock(block *bchain.Block, op int) error { } } - isUTXO := d.chainParser.IsUTXOChain() + chainType := d.chainParser.GetChainType() if err := d.writeHeightFromBlock(wb, block, op); err != nil { return err } - if isUTXO { + if chainType == bchain.ChainBitcoinType { if op == opDelete { // block does not contain mapping tx-> input address, which is necessary to recreate // unspentTxs; therefore it is not possible to DisconnectBlocks this way - return errors.New("DisconnectBlock is not supported for UTXO chains") + return errors.New("DisconnectBlock is not supported for BitcoinType chains") } addresses := make(map[string][]outpoint) txAddressesMap := make(map[string]*TxAddresses) balances := make(map[string]*AddrBalance) - if err := d.processAddressesUTXO(block, addresses, txAddressesMap, balances); err != nil { + if err := d.processAddressesBitcoinType(block, addresses, txAddressesMap, balances); err != nil { return err } if err := d.storeAddresses(wb, block.Height, addresses); err != nil { @@ -300,8 +300,8 @@ func (d *RocksDB) writeBlock(block *bchain.Block, op int) error { if err := d.storeAndCleanupBlockTxs(wb, block); err != nil { return err } - } else { - if err := d.writeAddressesNonUTXO(wb, block, op); err != nil { + } else if chainType == bchain.ChainEthereumType { + if err := d.writeAddressesTypeEthereum(wb, block, op); err != nil { return err } } @@ -375,7 +375,7 @@ func (d *RocksDB) GetAndResetConnectBlockStats() string { return s } -func (d *RocksDB) processAddressesUTXO(block *bchain.Block, addresses map[string][]outpoint, txAddressesMap map[string]*TxAddresses, balances map[string]*AddrBalance) error { +func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map[string][]outpoint, txAddressesMap map[string]*TxAddresses, balances map[string]*AddrBalance) error { blockTxIDs := make([][]byte, len(block.Txs)) blockTxAddresses := make([]*TxAddresses, len(block.Txs)) // first process all outputs so that inputs can point to txs in this block @@ -861,7 +861,7 @@ func (d *RocksDB) addAddrDescToRecords(op int, wb *gorocksdb.WriteBatch, records return nil } -func (d *RocksDB) writeAddressesNonUTXO(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error { +func (d *RocksDB) writeAddressesTypeEthereum(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error { addresses := make(map[string][]outpoint) for _, tx := range block.Txs { btxID, err := d.chainParser.PackTxid(tx.Txid) @@ -1166,9 +1166,9 @@ func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32, return nil } -// DisconnectBlockRangeUTXO removes all data belonging to blocks in range lower-higher +// DisconnectBlockRangeBitcoinType removes all data belonging to blocks in range lower-higher // if they are in the range kept in the cfBlockTxids column -func (d *RocksDB) DisconnectBlockRangeUTXO(lower uint32, higher uint32) error { +func (d *RocksDB) DisconnectBlockRangeBitcoinType(lower uint32, higher uint32) error { glog.Infof("db: disconnecting blocks %d-%d", lower, higher) blocks := make([][]blockTxs, higher-lower+1) for height := lower; height <= higher; height++ { diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 771bde20..4d89b6b9 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -1,4 +1,4 @@ -// +build unittest +// build unittest package db @@ -151,7 +151,7 @@ func checkColumn(d *RocksDB, col int, kp []keyPair) error { return nil } -func verifyAfterUTXOBlock1(t *testing.T, d *RocksDB, afterDisconnect bool) { +func verifyAfterBitcoinTypeOBlock1(t *testing.T, d *RocksDB, afterDisconnect bool) { if err := checkColumn(d, cfHeight, []keyPair{ keyPair{ "000370d5", @@ -232,7 +232,7 @@ func verifyAfterUTXOBlock1(t *testing.T, d *RocksDB, afterDisconnect bool) { } } -func verifyAfterUTXOBlock2(t *testing.T, d *RocksDB) { +func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) { if err := checkColumn(d, cfHeight, []keyPair{ keyPair{ "000370d5", @@ -423,7 +423,7 @@ func testTxCache(t *testing.T, d *RocksDB, b *bchain.Block, tx *bchain.Tx) { } } -// TestRocksDB_Index_UTXO is an integration test probing the whole indexing functionality for UTXO chains +// TestRocksDB_Index_BitcoinType is an integration test probing the whole indexing functionality for BitcoinType chains // It does the following: // 1) Connect two blocks (inputs from 2nd block are spending some outputs from the 1st block) // 2) GetTransactions for various addresses / low-high ranges @@ -433,25 +433,25 @@ func testTxCache(t *testing.T, d *RocksDB, b *bchain.Block, tx *bchain.Tx) { // 6) Disconnect the block 2 using BlockTxs column // 7) Reconnect block 2 and check // After each step, the content of DB is examined and any difference against expected state is regarded as failure -func TestRocksDB_Index_UTXO(t *testing.T) { +func TestRocksDB_Index_BitcoinType(t *testing.T) { d := setupRocksDB(t, &testBitcoinParser{ BitcoinParser: bitcoinTestnetParser(), }) defer closeAndDestroyRocksDB(t, d) // connect 1st block - will log warnings about missing UTXO transactions in txAddresses column - block1 := dbtestdata.GetTestUTXOBlock1(d.chainParser) + block1 := dbtestdata.GetTestBitcoinTypeBlock1(d.chainParser) if err := d.ConnectBlock(block1); err != nil { t.Fatal(err) } - verifyAfterUTXOBlock1(t, d, false) + verifyAfterBitcoinTypeOBlock1(t, d, false) // connect 2nd block - use some outputs from the 1st block as the inputs and 1 input uses tx from the same block - block2 := dbtestdata.GetTestUTXOBlock2(d.chainParser) + block2 := dbtestdata.GetTestBitcoinTypeBlock2(d.chainParser) if err := d.ConnectBlock(block2); err != nil { t.Fatal(err) } - verifyAfterUTXOBlock2(t, d) + verifyAfterBitcoinTypeBlock2(t, d) // get transactions for various addresses / low-high ranges verifyGetTransactions(t, d, dbtestdata.Addr2, 0, 1000000, []txidVoutOutput{ @@ -532,27 +532,27 @@ func TestRocksDB_Index_UTXO(t *testing.T) { } } - // DisconnectBlock for UTXO chains is not possible + // DisconnectBlock for BitcoinType chains is not possible err = d.DisconnectBlock(block2) - if err == nil || err.Error() != "DisconnectBlock is not supported for UTXO chains" { + if err == nil || err.Error() != "DisconnectBlock is not supported for BitcoinType chains" { t.Fatal(err) } - verifyAfterUTXOBlock2(t, d) + verifyAfterBitcoinTypeBlock2(t, d) // try to disconnect both blocks, however only the last one is kept, it is not possible - err = d.DisconnectBlockRangeUTXO(225493, 225494) + err = d.DisconnectBlockRangeBitcoinType(225493, 225494) if err == nil || err.Error() != "Cannot disconnect blocks with height 225493 and lower. It is necessary to rebuild index." { t.Fatal(err) } - verifyAfterUTXOBlock2(t, d) + verifyAfterBitcoinTypeBlock2(t, d) // disconnect the 2nd block, verify that the db contains only data from the 1st block with restored unspentTxs // and that the cached tx is removed - err = d.DisconnectBlockRangeUTXO(225494, 225494) + err = d.DisconnectBlockRangeBitcoinType(225494, 225494) if err != nil { t.Fatal(err) } - verifyAfterUTXOBlock1(t, d, true) + verifyAfterBitcoinTypeOBlock1(t, d, true) if err := checkColumn(d, cfTransactions, []keyPair{}); err != nil { { t.Fatal(err) @@ -563,7 +563,7 @@ func TestRocksDB_Index_UTXO(t *testing.T) { if err := d.ConnectBlock(block2); err != nil { t.Fatal(err) } - verifyAfterUTXOBlock2(t, d) + verifyAfterBitcoinTypeBlock2(t, d) // test public methods for address balance and tx addresses @@ -627,7 +627,7 @@ func TestRocksDB_Index_UTXO(t *testing.T) { } -func Test_BulkConnect_UTXO(t *testing.T) { +func Test_BulkConnect_BitcoinType(t *testing.T) { d := setupRocksDB(t, &testBitcoinParser{ BitcoinParser: bitcoinTestnetParser(), }) @@ -642,7 +642,7 @@ func Test_BulkConnect_UTXO(t *testing.T) { t.Fatal("DB not in DbStateInconsistent") } - if err := bc.ConnectBlock(dbtestdata.GetTestUTXOBlock1(d.chainParser), false); err != nil { + if err := bc.ConnectBlock(dbtestdata.GetTestBitcoinTypeBlock1(d.chainParser), false); err != nil { t.Fatal(err) } if err := checkColumn(d, cfBlockTxs, []keyPair{}); err != nil { @@ -651,7 +651,7 @@ func Test_BulkConnect_UTXO(t *testing.T) { } } - if err := bc.ConnectBlock(dbtestdata.GetTestUTXOBlock2(d.chainParser), true); err != nil { + if err := bc.ConnectBlock(dbtestdata.GetTestBitcoinTypeBlock2(d.chainParser), true); err != nil { t.Fatal(err) } @@ -663,7 +663,7 @@ func Test_BulkConnect_UTXO(t *testing.T) { t.Fatal("DB not in DbStateOpen") } - verifyAfterUTXOBlock2(t, d) + verifyAfterBitcoinTypeBlock2(t, d) } func Test_packBigint_unpackBigint(t *testing.T) { diff --git a/db/sync.go b/db/sync.go index 6bae828f..c408bb72 100644 --- a/db/sync.go +++ b/db/sync.go @@ -393,9 +393,9 @@ func (w *SyncWorker) getBlockChain(out chan blockResult, done chan struct{}) { // DisconnectBlocks removes all data belonging to blocks in range lower-higher, func (w *SyncWorker) DisconnectBlocks(lower uint32, higher uint32, hashes []string) error { glog.Infof("sync: disconnecting blocks %d-%d", lower, higher) - // if the chain is UTXO, always use DisconnectBlockRange - if w.chain.GetChainParser().IsUTXOChain() { - return w.db.DisconnectBlockRangeUTXO(lower, higher) + // if the chain is ChainBitcoinType, always use DisconnectBlockRange + if w.chain.GetChainParser().GetChainType() == bchain.ChainBitcoinType { + return w.db.DisconnectBlockRangeBitcoinType(lower, higher) } blocks := make([]*bchain.Block, len(hashes)) var err error diff --git a/docs/config.md b/docs/config.md index 509edd8c..9c796b16 100644 --- a/docs/config.md +++ b/docs/config.md @@ -86,8 +86,8 @@ Good examples of coin configuration are * `parse` – Use binary parser for block decoding if *true* else call verbose back-end RPC method that returns JSON. Note that verbose method is slow and not every coin support it. However there are coin implementations that don't support binary parsing (e.g. ZCash). - * `mempool_workers` – Number of workers for UTXO mempool. - * `mempool_sub_workers` – Number of subworkers for UTXO mempool. + * `mempool_workers` – Number of workers for BitcoinType mempool. + * `mempool_sub_workers` – Number of subworkers for BitcoinType mempool. * `block_addresses_to_keep` – Number of blocks that are to be kept in blockaddresses column. * `additional_params` – Object of coin-specific params. diff --git a/server/public_test.go b/server/public_test.go index 6e9b4bc8..46370dfe 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -48,10 +48,10 @@ func setupRocksDB(t *testing.T, parser bchain.BlockChainParser) (*db.RocksDB, *c } d.SetInternalState(is) // import data - if err := d.ConnectBlock(dbtestdata.GetTestUTXOBlock1(parser)); err != nil { + if err := d.ConnectBlock(dbtestdata.GetTestBitcoinTypeBlock1(parser)); err != nil { t.Fatal(err) } - if err := d.ConnectBlock(dbtestdata.GetTestUTXOBlock2(parser)); err != nil { + if err := d.ConnectBlock(dbtestdata.GetTestBitcoinTypeBlock2(parser)); err != nil { t.Fatal(err) } return d, is, tmp @@ -575,7 +575,7 @@ func socketioTests(t *testing.T, ts *httptest.Server) { } } -func Test_PublicServer_UTXO(t *testing.T) { +func Test_PublicServer_BitcoinType(t *testing.T) { s, dbpath := setupPublicHTTPServer(t) defer closeAndDestroyPublicServer(t, s, dbpath) s.ConnectFullPublicInterface() @@ -585,5 +585,4 @@ func Test_PublicServer_UTXO(t *testing.T) { httpTests(t, ts) socketioTests(t, ts) - } diff --git a/tests/dbtestdata/dbtestdata.go b/tests/dbtestdata/dbtestdata.go index 1c4ad328..3f0a92d2 100644 --- a/tests/dbtestdata/dbtestdata.go +++ b/tests/dbtestdata/dbtestdata.go @@ -54,7 +54,7 @@ func AddressToPubKeyHex(addr string, parser bchain.BlockChainParser) string { return hex.EncodeToString(b) } -func GetTestUTXOBlock1(parser bchain.BlockChainParser) *bchain.Block { +func GetTestBitcoinTypeBlock1(parser bchain.BlockChainParser) *bchain.Block { return &bchain.Block{ BlockHeader: bchain.BlockHeader{ Height: 225493, @@ -119,7 +119,7 @@ func GetTestUTXOBlock1(parser bchain.BlockChainParser) *bchain.Block { } } -func GetTestUTXOBlock2(parser bchain.BlockChainParser) *bchain.Block { +func GetTestBitcoinTypeBlock2(parser bchain.BlockChainParser) *bchain.Block { return &bchain.Block{ BlockHeader: bchain.BlockHeader{ Height: 225494, diff --git a/tests/dbtestdata/fakechain.go b/tests/dbtestdata/fakechain.go index d29740b9..b7ef7cba 100644 --- a/tests/dbtestdata/fakechain.go +++ b/tests/dbtestdata/fakechain.go @@ -45,26 +45,26 @@ func (c *fakeBlockChain) GetChainInfo() (v *bchain.ChainInfo, err error) { Chain: c.GetNetworkName(), Blocks: 2, Headers: 2, - Bestblockhash: GetTestUTXOBlock2(c.parser).BlockHeader.Hash, + Bestblockhash: GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Hash, Version: "001001", Subversion: c.GetSubversion(), }, nil } func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) { - return GetTestUTXOBlock2(c.parser).BlockHeader.Hash, nil + return GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Hash, nil } func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) { - return GetTestUTXOBlock2(c.parser).BlockHeader.Height, nil + return GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Height, nil } func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) { - b1 := GetTestUTXOBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.parser) if height == b1.BlockHeader.Height { return b1.BlockHeader.Hash, nil } - b2 := GetTestUTXOBlock2(c.parser) + b2 := GetTestBitcoinTypeBlock2(c.parser) if height == b2.BlockHeader.Height { return b2.BlockHeader.Hash, nil } @@ -72,11 +72,11 @@ func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) { } func (c *fakeBlockChain) GetBlockHeader(hash string) (v *bchain.BlockHeader, err error) { - b1 := GetTestUTXOBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.parser) if hash == b1.BlockHeader.Hash { return &b1.BlockHeader, nil } - b2 := GetTestUTXOBlock2(c.parser) + b2 := GetTestBitcoinTypeBlock2(c.parser) if hash == b2.BlockHeader.Hash { return &b2.BlockHeader, nil } @@ -84,11 +84,11 @@ func (c *fakeBlockChain) GetBlockHeader(hash string) (v *bchain.BlockHeader, err } func (c *fakeBlockChain) GetBlock(hash string, height uint32) (v *bchain.Block, err error) { - b1 := GetTestUTXOBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.parser) if hash == b1.BlockHeader.Hash || height == b1.BlockHeader.Height { return b1, nil } - b2 := GetTestUTXOBlock2(c.parser) + b2 := GetTestBitcoinTypeBlock2(c.parser) if hash == b2.BlockHeader.Hash || height == b2.BlockHeader.Height { return b2, nil } @@ -106,11 +106,11 @@ func getBlockInfo(b *bchain.Block) *bchain.BlockInfo { } func (c *fakeBlockChain) GetBlockInfo(hash string) (v *bchain.BlockInfo, err error) { - b1 := GetTestUTXOBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.parser) if hash == b1.BlockHeader.Hash { return getBlockInfo(b1), nil } - b2 := GetTestUTXOBlock2(c.parser) + b2 := GetTestBitcoinTypeBlock2(c.parser) if hash == b2.BlockHeader.Hash { return getBlockInfo(b2), nil } @@ -131,9 +131,9 @@ func getTxInBlock(b *bchain.Block, txid string) *bchain.Tx { } func (c *fakeBlockChain) GetTransaction(txid string) (v *bchain.Tx, err error) { - v = getTxInBlock(GetTestUTXOBlock1(c.parser), txid) + v = getTxInBlock(GetTestBitcoinTypeBlock1(c.parser), txid) if v == nil { - v = getTxInBlock(GetTestUTXOBlock2(c.parser), txid) + v = getTxInBlock(GetTestBitcoinTypeBlock2(c.parser), txid) } if v != nil { return v, nil From fac728bc5179829754212484fb35fb11e9fd5e8a Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 7 Nov 2018 00:24:53 +0100 Subject: [PATCH 4/9] Fix api.GetTransaction for EthereumType blockchain --- api/worker.go | 88 +++++++++++++++++++++-------------- bchain/coins/eth/ethparser.go | 21 ++++++++- bchain/types.go | 2 +- db/rocksdb.go | 6 ++- db/txcache.go | 49 ++++++++++++------- 5 files changed, 108 insertions(+), 58 deletions(-) diff --git a/api/worker.go b/api/worker.go index f7572058..e30f3bdd 100644 --- a/api/worker.go +++ b/api/worker.go @@ -20,6 +20,7 @@ type Worker struct { txCache *db.TxCache chain bchain.BlockChain chainParser bchain.BlockChainParser + chainType bchain.ChainType is *common.InternalState } @@ -30,6 +31,7 @@ func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is txCache: txCache, chain: chain, chainParser: chain.GetChainParser(), + chainType: chain.GetChainParser().GetChainType(), is: is, } return w, nil @@ -102,9 +104,12 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { if err != nil { return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true) } - ta, err := w.db.GetTxAddresses(txid) - if err != nil { - return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) + var ta *db.TxAddresses + if w.chainType == bchain.ChainBitcoinType { + ta, err = w.db.GetTxAddresses(txid) + if err != nil { + return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) + } } var blockhash string if bchainTx.Confirmations > 0 { @@ -123,45 +128,56 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { vin.Vout = bchainVin.Vout vin.Sequence = int64(bchainVin.Sequence) vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex - // bchainVin.Txid=="" is coinbase transaction - if bchainVin.Txid != "" { - // load spending addresses from TxAddresses - tas, err := w.db.GetTxAddresses(bchainVin.Txid) - if err != nil { - return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid) - } - if tas == nil { - // mempool transactions are not in TxAddresses but confirmed should be there, log a problem - if bchainTx.Confirmations > 0 { - glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses") - } - // try to load from backend - otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) + if w.chainType == bchain.ChainBitcoinType { + // bchainVin.Txid=="" is coinbase transaction + if bchainVin.Txid != "" { + // load spending addresses from TxAddresses + tas, err := w.db.GetTxAddresses(bchainVin.Txid) if err != nil { - return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) + return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid) } - if len(otx.Vout) > int(vin.Vout) { - vout := &otx.Vout[vin.Vout] - vin.ValueSat = vout.ValueSat - vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout) + if tas == nil { + // mempool transactions are not in TxAddresses but confirmed should be there, log a problem + if bchainTx.Confirmations > 0 { + glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses") + } + // try to load from backend + otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) if err != nil { - glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) - } - } - } else { - if len(tas.Outputs) > int(vin.Vout) { - output := &tas.Outputs[vin.Vout] - vin.ValueSat = output.ValueSat - vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) - vin.AddrDesc = output.AddrDesc - vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser) - if err != nil { - glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i) + return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) + } + if len(otx.Vout) > int(vin.Vout) { + vout := &otx.Vout[vin.Vout] + vin.ValueSat = vout.ValueSat + vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout) + if err != nil { + glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) + } + } + } else { + if len(tas.Outputs) > int(vin.Vout) { + output := &tas.Outputs[vin.Vout] + vin.ValueSat = output.ValueSat + vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) + vin.AddrDesc = output.AddrDesc + vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser) + if err != nil { + glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i) + } } } + vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) + valInSat.Add(&valInSat, &vin.ValueSat) + } + } else if w.chainType == bchain.ChainEthereumType { + if len(bchainVin.Addresses) > 0 { + vin.AddrDesc, err = w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) + if err != nil { + glog.Errorf("GetAddrDescFromAddress error %v, tx %v, bchainVin %v", err, bchainTx.Txid, bchainVin) + } + vin.Addresses = bchainVin.Addresses + vin.Searchable = true } - vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) - valInSat.Add(&valInSat, &vin.ValueSat) } } vouts := make([]Vout, len(bchainTx.Vout)) diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 90341cd5..72f6c1fb 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -291,7 +291,26 @@ func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { return hexutil.Encode(buf), nil } -// GetChainType returns TypeEthereum +// GetChainType returns EthereumType func (p *EthereumParser) GetChainType() bchain.ChainType { return bchain.ChainEthereumType } + +// GetHeightFromTx returns ethereum specific data from bchain.Tx +func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { + // TODO - temporary implementation - will use bchain.Tx.SpecificData field + b, err := hex.DecodeString(tx.Hex) + if err != nil { + return 0, err + } + var r rpcTransaction + var n uint64 + err = json.Unmarshal(b, &r) + if err != nil { + return 0, err + } + if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil { + return 0, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber) + } + return uint32(n), nil +} diff --git a/bchain/types.go b/bchain/types.go index 59d05d39..327dfdcd 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -15,7 +15,7 @@ type ChainType int const ( // ChainBitcoinType is blockchain derived from bitcoin ChainBitcoinType = ChainType(iota) - // TypeEthereum is blockchain derived from ethereum + // ChainEthereumType is blockchain derived from ethereum ChainEthereumType ) diff --git a/db/rocksdb.go b/db/rocksdb.go index 94d2f456..b795aaee 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -301,9 +301,11 @@ func (d *RocksDB) writeBlock(block *bchain.Block, op int) error { return err } } else if chainType == bchain.ChainEthereumType { - if err := d.writeAddressesTypeEthereum(wb, block, op); err != nil { + if err := d.writeAddressesEthereumType(wb, block, op); err != nil { return err } + } else { + return errors.New("Unknown chain type") } return d.db.Write(d.wo, wb) @@ -861,7 +863,7 @@ func (d *RocksDB) addAddrDescToRecords(op int, wb *gorocksdb.WriteBatch, records return nil } -func (d *RocksDB) writeAddressesTypeEthereum(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error { +func (d *RocksDB) writeAddressesEthereumType(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error { addresses := make(map[string][]outpoint) for _, tx := range block.Txs { btxID, err := d.chainParser.PackTxid(tx.Txid) diff --git a/db/txcache.go b/db/txcache.go index 25f17bff..b20d21fe 100644 --- a/db/txcache.go +++ b/db/txcache.go @@ -2,18 +2,21 @@ package db import ( "blockbook/bchain" + "blockbook/bchain/coins/eth" "blockbook/common" "github.com/golang/glog" + "github.com/juju/errors" ) // TxCache is handle to TxCacheServer type TxCache struct { - db *RocksDB - chain bchain.BlockChain - metrics *common.Metrics - is *common.InternalState - enabled bool + db *RocksDB + chain bchain.BlockChain + metrics *common.Metrics + is *common.InternalState + enabled bool + chainType bchain.ChainType } // NewTxCache creates new TxCache interface and returns its handle @@ -22,11 +25,12 @@ func NewTxCache(db *RocksDB, chain bchain.BlockChain, metrics *common.Metrics, i glog.Info("txcache: disabled") } return &TxCache{ - db: db, - chain: chain, - metrics: metrics, - is: is, - enabled: enabled, + db: db, + chain: chain, + metrics: metrics, + is: is, + enabled: enabled, + chainType: chain.GetChainParser().GetChainType(), }, nil } @@ -56,18 +60,27 @@ func (c *TxCache) GetTransaction(txid string) (*bchain.Tx, uint32, error) { c.metrics.TxCacheEfficiency.With(common.Labels{"status": "miss"}).Inc() // cache only confirmed transactions if tx.Confirmations > 0 { - ta, err := c.db.GetTxAddresses(txid) - if err != nil { - return nil, 0, err - } - // the transaction may me not yet indexed, in that case get the height from the backend - if ta == nil { - h, err = c.chain.GetBestBlockHeight() + if c.chainType == bchain.ChainBitcoinType { + ta, err := c.db.GetTxAddresses(txid) + if err != nil { + return nil, 0, err + } + // the transaction may me not yet indexed, in that case get the height from the backend + if ta == nil { + h, err = c.chain.GetBestBlockHeight() + if err != nil { + return nil, 0, err + } + } else { + h = ta.Height + } + } else if c.chainType == bchain.ChainEthereumType { + h, err = eth.GetHeightFromTx(tx) if err != nil { return nil, 0, err } } else { - h = ta.Height + return nil, 0, errors.New("Unknown chain type") } if c.enabled { err = c.db.PutTx(tx, h, tx.Blocktime) From 4a0575732129821edaf77dd8564afc56d3965e6f Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 9 Nov 2018 13:08:43 +0100 Subject: [PATCH 5/9] 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 From edf7193df421f8248587c9801355c26c320c8118 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Mon, 12 Nov 2018 14:55:15 +0100 Subject: [PATCH 6/9] Pack eth transactions including receipt --- bchain/coins/eth/erc20_test.go | 2 +- bchain/coins/eth/ethparser.go | 76 ++++++++++++++++++++++-------- bchain/coins/eth/ethparser_test.go | 30 ++++++------ bchain/coins/eth/ethrpc.go | 4 ++ bchain/coins/eth/tx.pb.go | 54 ++++++++++----------- bchain/coins/eth/tx.proto | 2 +- 6 files changed, 105 insertions(+), 63 deletions(-) diff --git a/bchain/coins/eth/erc20_test.go b/bchain/coins/eth/erc20_test.go index c4bb5b02..d806ab9e 100644 --- a/bchain/coins/eth/erc20_test.go +++ b/bchain/coins/eth/erc20_test.go @@ -1,4 +1,4 @@ -// build unittest +// +build unittest package eth diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index b2a15e9e..4b92c676 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -29,7 +29,7 @@ func NewEthereumParser() *EthereumParser { type rpcTransaction struct { AccountNonce string `json:"nonce" gencodec:"required"` - Price string `json:"gasPrice" gencodec:"required"` + GasPrice string `json:"gasPrice" gencodec:"required"` GasLimit string `json:"gas" gencodec:"required"` To string `json:"to" rlp:"nil"` // nil means contract creation Value string `json:"value" gencodec:"required"` @@ -105,7 +105,9 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc } tx.BlockHash = bh h := hex.EncodeToString(b) - glog.Info(h) + if receipt != nil { + glog.Info(tx.Hash.Hex(), ": ", h) + } vs, err := hexutil.DecodeBig(tx.Value) if err != nil { return nil, err @@ -212,6 +214,7 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ( return nil, err } pt := &ProtoCompleteTransaction{} + pt.Tx = &ProtoCompleteTransaction_TxType{} if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil { return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce) } @@ -230,8 +233,8 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ( if pt.Tx.Payload, err = hexDecode(r.Tx.Payload); err != nil { return nil, errors.Annotatef(err, "Payload %v", r.Tx.Payload) } - if pt.Tx.Price, err = hexDecodeBig(r.Tx.Price); err != nil { - return nil, errors.Annotatef(err, "Price %v", r.Tx.Price) + if pt.Tx.GasPrice, err = hexDecodeBig(r.Tx.GasPrice); err != nil { + return nil, errors.Annotatef(err, "Price %v", r.Tx.GasPrice) } // if pt.R, err = hexDecodeBig(r.R); err != nil { // return nil, errors.Annotatef(err, "R %v", r.R) @@ -252,6 +255,36 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ( if pt.Tx.Value, err = hexDecodeBig(r.Tx.Value); err != nil { return nil, errors.Annotatef(err, "Value %v", r.Tx.Value) } + if r.Receipt != nil { + pt.Receipt = &ProtoCompleteTransaction_ReceiptType{} + if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil { + return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed) + } + if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil { + return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status) + } + ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs)) + for i, l := range r.Receipt.Logs { + d, err := hexutil.Decode(l.Data) + if err != nil { + return nil, errors.Annotatef(err, "Data cannot be decoded %v", l) + } + t := make([][]byte, len(l.Topics)) + for j, s := range l.Topics { + t[j], err = hexutil.Decode(s) + if err != nil { + return nil, errors.Annotatef(err, "Topic cannot be decoded %v", l) + } + } + ptLogs[i] = &ProtoCompleteTransaction_ReceiptType_LogType{ + Address: l.Address.Bytes(), + Data: d, + Topics: t, + } + + } + pt.Receipt.Log = ptLogs + } return proto.Marshal(pt) } @@ -269,7 +302,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit), Hash: ethcommon.BytesToHash(pt.Tx.Hash), Payload: hexutil.Encode(pt.Tx.Payload), - Price: hexEncodeBig(pt.Tx.Price), + GasPrice: hexEncodeBig(pt.Tx.GasPrice), // R: hexEncodeBig(pt.R), // S: hexEncodeBig(pt.S), // V: hexEncodeBig(pt.V), @@ -277,24 +310,27 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)), Value: hexEncodeBig(pt.Tx.Value), } - 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) + var rr *rpcReceipt + if pt.Receipt != nil { + 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, + } } - 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, } } - rr := rpcReceipt{ - GasUsed: hexEncodeBig(pt.Receipt.GasUsed), - Status: hexEncodeBig(pt.Receipt.Status), - Logs: logs, - } - tx, err := p.ethTxToTx(&rt, &rr, int64(pt.BlockTime), 0) + tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0) if err != nil { return nil, 0, err } diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index 71650737..fb082683 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -1,10 +1,11 @@ -// build unittest +// +build unittest package eth import ( "blockbook/bchain" "encoding/hex" + "fmt" "math/big" "reflect" "testing" @@ -66,27 +67,27 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) { var ( testTx1, testTx2 bchain.Tx - testTxPacked1 = "08aebf0a1205012a05f20018a0f73622081234567890abcdef2a24f025caaf00000000000000000000000000000000000000000000000000000000000002253220e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d38f095af014092f4c1d5054a14682b7903a11098cf770c7aef4aa02a85b3f3601a5214dacc9c61754a0c4616fc5323dc946e89eb272302580162011b6a201bd40a31122c03918df6d166d740a6a3a22f08a25934ceb1688c62977661c80c7220607fbc15c1f7995a4258f5a9bccc63b040362d1991d5efe1361c56222e4ca89f" - testTxPacked2 = "08ece40212050430e234001888a4012201213220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b38889eaf0140fa83c3d5054a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f52143e3a3d69dc66ba10737f531ed088954a9ec89d97580a6201296a20f7161c170d43573ad9c8d701cdaf714ff2a548a562b0dc639230d17889fcd40572203c4977fc90385a27efa0032e17b49fd575b2826cb56e3d1ecf21524f2a94f915" + testTxPacked1 = "08d38388021092f4c1d5051aa20108d001120509502f900018d5e1042a44a9059cbb00000000000000000000000008e93c026b6454b7437d097aabd550f98cb89ed300000000000000000000000000000000000000000000021e19e0c9bab24000003220a9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b1013a144af4114f73d1c1c903ac9e0361b379d1291808a2421420cd153de35d469ba46127a0c8f18626b59a256a22a8010a02cb391201011a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000021e19e0c9bab24000001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a2000000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a1a2000000000000000000000000008e93c026b6454b7437d097aabd550f98cb89ed3" + testTxPacked2 = "08889eaf0110fa83c3d5051a6908ece40212050430e234001888a40122081bc0159d530e60003220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b3a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f42143e3a3d69dc66ba10737f531ed088954a9ec89d97480a22070a025208120101" ) func init() { testTx1 = bchain.Tx{ Blocktime: 1521515026, - Hex: "7b226e6f6e6365223a2230783239666165222c226761735072696365223a223078313261303566323030222c22676173223a2230786462626130222c22746f223a22307836383262373930336131313039386366373730633761656634616130326138356233663336303161222c2276616c7565223a22307831323334353637383930616263646566222c22696e707574223a223078663032356361616630303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030323235222c2268617368223a22307865366231363864366262336438656437386530336462663832386236626664316662363133663665313239636261363234393634393834353533373234633564222c22626c6f636b4e756d626572223a223078326263616630222c2266726f6d223a22307864616363396336313735346130633436313666633533323364633934366538396562323732333032222c227472616e73616374696f6e496e646578223a22307831222c2276223a2230783162222c2272223a22307831626434306133313132326330333931386466366431363664373430613661336132326630386132353933346365623136383863363239373736363163383063222c2273223a22307836303766626331356331663739393561343235386635613962636363363362303430333632643139393164356566653133363163353632323265346361383966227d", + Hex: "7b227478223a7b226e6f6e6365223a2230786430222c226761735072696365223a223078393530326639303030222c22676173223a2230783133306435222c22746f223a22307834616634313134663733643163316339303361633965303336316233373964313239313830386132222c2276616c7565223a22307830222c22696e707574223a22307861393035396362623030303030303030303030303030303030303030303030303038653933633032366236343534623734333764303937616162643535306639386362383965643330303030303030303030303030303030303030303030303030303030303030303030303030303030303030303032316531396530633962616232343030303030222c2268617368223a22307861396364303838616261323133313030306461366633386133336332303136396261656534373632313864656561366237383732303730306238393562313031222c22626c6f636b4e756d626572223a223078343230316433222c2266726f6d223a22307832306364313533646533356434363962613436313237613063386631383632366235396132353661222c227472616e73616374696f6e496e646578223a22307830227d2c2272656365697074223a7b2267617355736564223a22307863623339222c22737461747573223a22307831222c226c6f6773223a5b7b2261646472657373223a22307834616634313134663733643163316339303361633965303336316233373964313239313830386132222c22746f70696373223a5b22307864646632353261643162653263383962363963326230363866633337386461613935326261376631363363346131313632386635356134646635323362336566222c22307830303030303030303030303030303030303030303030303032306364313533646533356434363962613436313237613063386631383632366235396132353661222c22307830303030303030303030303030303030303030303030303030386539336330323662363435346237343337643039376161626435353066393863623839656433225d2c2264617461223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303032316531396530633962616232343030303030227d5d7d7d", Time: 1521515026, - Txid: "0xe6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d", + Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101", Vin: []bchain.Vin{ { - Addresses: []string{"0xdacc9c61754a0c4616fc5323dc946e89eb272302"}, + Addresses: []string{"0x20cd153de35d469ba46127a0c8f18626b59a256a"}, }, }, Vout: []bchain.Vout{ { - ValueSat: *big.NewInt(1311768467294899695), + ValueSat: *big.NewInt(0), ScriptPubKey: bchain.ScriptPubKey{ - Addresses: []string{"0x682b7903a11098cf770c7aef4aa02a85b3f3601a"}, + Addresses: []string{"0x4af4114f73d1c1c903ac9e0361b379d1291808a2"}, }, }, }, @@ -94,7 +95,7 @@ func init() { testTx2 = bchain.Tx{ Blocktime: 1521533434, - Hex: "7b226e6f6e6365223a22307862323663222c226761735072696365223a223078343330653233343030222c22676173223a22307835323038222c22746f223a22307835353565653131666264646330653439613962616233353861383934316164393566666462343866222c2276616c7565223a2230783231222c22696e707574223a223078222c2268617368223a22307863643634373135313535326235313332623261656637633962653030646336663733616663353930316464653135376161623133313333356261616138353362222c22626c6f636b4e756d626572223a223078326263663038222c2266726f6d223a22307833653361336436396463363662613130373337663533316564303838393534613965633839643937222c227472616e73616374696f6e496e646578223a22307861222c2276223a2230783239222c2272223a22307866373136316331373064343335373361643963386437303163646166373134666632613534386135363262306463363339323330643137383839666364343035222c2273223a22307833633439373766633930333835613237656661303033326531376234396664353735623238323663623536653364316563663231353234663261393466393135227d", + Hex: "7b227478223a7b226e6f6e6365223a22307862323663222c226761735072696365223a223078343330653233343030222c22676173223a22307835323038222c22746f223a22307835353565653131666264646330653439613962616233353861383934316164393566666462343866222c2276616c7565223a22307831626330313539643533306536303030222c22696e707574223a223078222c2268617368223a22307863643634373135313535326235313332623261656637633962653030646336663733616663353930316464653135376161623133313333356261616138353362222c22626c6f636b4e756d626572223a223078326263663038222c2266726f6d223a22307833653361336436396463363662613130373337663533316564303838393534613965633839643937222c227472616e73616374696f6e496e646578223a22307861227d2c2272656365697074223a7b2267617355736564223a22307835323038222c22737461747573223a22307831222c226c6f6773223a5b5d7d7d", Time: 1521533434, Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", Vin: []bchain.Vin{ @@ -104,7 +105,7 @@ func init() { }, Vout: []bchain.Vout{ { - ValueSat: *big.NewInt(33), + ValueSat: *big.NewInt(1999622000000000000), ScriptPubKey: bchain.ScriptPubKey{ Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"}, }, @@ -130,7 +131,7 @@ func TestEthereumParser_PackTx(t *testing.T) { name: "1", args: args{ tx: &testTx1, - height: 2870000, + height: 4325843, blockTime: 1521515026, }, want: testTxPacked1, @@ -177,7 +178,7 @@ func TestEthereumParser_UnpackTx(t *testing.T) { name: "1", args: args{hex: testTxPacked1}, want: &testTx1, - want1: 2870000, + want1: 4325843, }, { name: "2", @@ -198,8 +199,9 @@ func TestEthereumParser_UnpackTx(t *testing.T) { t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("EthereumParser.UnpackTx() got = %v, want %v", got, tt.want) + // DeepEqual compares empty nil slices as not equal + if fmt.Sprint(got) != fmt.Sprint(tt.want) { + t.Errorf("EthereumParser.UnpackTx() got = %+v, want %+v", got, tt.want) } if got1 != tt.want1 { t.Errorf("EthereumParser.UnpackTx() got1 = %v, want %v", got1, tt.want1) diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index c08acfe0..468aee97 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -246,18 +246,22 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error { return nil } +// IsTestnet returns true if the network is testnet func (b *EthereumRPC) IsTestnet() bool { return b.Testnet } +// GetNetworkName returns network name func (b *EthereumRPC) GetNetworkName() string { return b.Network } +// GetCoinName returns coin name func (b *EthereumRPC) GetCoinName() string { return b.ChainConfig.CoinName } +// GetSubversion returns empty string, ethereum does not have subversion func (b *EthereumRPC) GetSubversion() string { return "" } diff --git a/bchain/coins/eth/tx.pb.go b/bchain/coins/eth/tx.pb.go index 621d3fa6..ff10c6cb 100644 --- a/bchain/coins/eth/tx.pb.go +++ b/bchain/coins/eth/tx.pb.go @@ -69,7 +69,7 @@ func (m *ProtoCompleteTransaction) GetReceipt() *ProtoCompleteTransaction_Receip 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"` + GasPrice []byte `protobuf:"bytes,2,opt,name=GasPrice,proto3" json:"GasPrice,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"` @@ -93,9 +93,9 @@ func (m *ProtoCompleteTransaction_TxType) GetAccountNonce() uint64 { return 0 } -func (m *ProtoCompleteTransaction_TxType) GetPrice() []byte { +func (m *ProtoCompleteTransaction_TxType) GetGasPrice() []byte { if m != nil { - return m.Price + return m.GasPrice } return nil } @@ -233,28 +233,28 @@ func init() { proto.RegisterFile("tx.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 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, + 0x14, 0xc6, 0xe9, 0x9f, 0xf9, 0xb3, 0xa7, 0x55, 0x24, 0x88, 0x84, 0xe2, 0x45, 0x59, 0xbc, 0xa8, + 0x5e, 0x14, 0x5c, 0x7d, 0x81, 0x75, 0xc4, 0x55, 0x18, 0xd6, 0x21, 0x46, 0xef, 0xb3, 0x69, 0xd8, + 0x29, 0xb6, 0x4d, 0x69, 0x52, 0xe8, 0xbe, 0x91, 0x2f, 0xe4, 0xbb, 0x78, 0x29, 0x39, 0x4d, 0xd7, + 0x11, 0x51, 0xbc, 0x3b, 0xbf, 0x6f, 0xce, 0x37, 0xf9, 0xbe, 0xa4, 0xb0, 0xb5, 0x53, 0xd9, 0x0f, + 0xda, 0x6a, 0x12, 0x29, 0x7b, 0x3c, 0xff, 0xb6, 0x02, 0x7a, 0x70, 0xb8, 0xd3, 0x6d, 0xdf, 0x28, + 0xab, 0xf8, 0x20, 0x3a, 0x23, 0xa4, 0xad, 0x75, 0x47, 0x72, 0x48, 0xde, 0x34, 0x5a, 0x7e, 0xbd, + 0x1e, 0xdb, 0x1b, 0x35, 0xd0, 0x20, 0x0f, 0x8a, 0x07, 0xec, 0x54, 0x22, 0x4f, 0xe1, 0x0c, 0x91, + 0xd7, 0xad, 0xa2, 0x61, 0x1e, 0x14, 0x31, 0xfb, 0x25, 0x90, 0xd7, 0x10, 0xf2, 0x89, 0x46, 0x79, + 0x50, 0x24, 0x17, 0xcf, 0x4a, 0x65, 0x8f, 0xe5, 0xdf, 0x8e, 0x2a, 0xf9, 0xc4, 0xef, 0x7a, 0xc5, + 0x42, 0x3e, 0x91, 0x1d, 0x6c, 0x98, 0x92, 0xaa, 0xee, 0x2d, 0x8d, 0xd1, 0xfa, 0xfc, 0xdf, 0x56, + 0xbf, 0x8c, 0xfe, 0xc5, 0x99, 0xfd, 0x08, 0x60, 0x3d, 0xff, 0x27, 0x39, 0x87, 0xf4, 0x52, 0x4a, + 0x3d, 0x76, 0xf6, 0x5a, 0x77, 0x52, 0x61, 0x8d, 0x98, 0xfd, 0xa6, 0x91, 0x0c, 0xb6, 0x57, 0xc2, + 0x1c, 0x86, 0x5a, 0xce, 0x35, 0x52, 0x76, 0xcf, 0xfe, 0xb7, 0x7d, 0xdd, 0xd6, 0x16, 0xbb, 0xc4, + 0xec, 0x9e, 0xc9, 0x63, 0x58, 0x7d, 0x11, 0xcd, 0xa8, 0x30, 0x69, 0xca, 0x66, 0x20, 0x14, 0x36, + 0x07, 0x71, 0xd7, 0x68, 0x51, 0xd1, 0x15, 0xea, 0x0b, 0x12, 0x02, 0xf1, 0x7b, 0x61, 0x8e, 0x74, + 0x8d, 0x32, 0xce, 0xe4, 0x21, 0x84, 0x5c, 0xd3, 0x0d, 0x2a, 0x21, 0xd7, 0x6e, 0xe7, 0xdd, 0xa0, + 0x5b, 0xba, 0x9d, 0x77, 0xdc, 0x4c, 0x5e, 0xc0, 0xa3, 0x93, 0xca, 0x1f, 0xba, 0x4a, 0x4d, 0xf4, + 0x0c, 0x9f, 0xe3, 0x0f, 0x3d, 0xfb, 0x1e, 0x40, 0x72, 0x72, 0x27, 0x2e, 0xcd, 0x95, 0x30, 0x9f, + 0x8d, 0xaa, 0xb0, 0x7a, 0xca, 0x16, 0x24, 0x4f, 0x60, 0xfd, 0xc9, 0x0a, 0x3b, 0x1a, 0xdf, 0xd9, + 0x13, 0xd9, 0x41, 0xb4, 0xd7, 0xb7, 0x34, 0xca, 0xa3, 0x22, 0xb9, 0x78, 0xf9, 0xdf, 0xb7, 0x5f, + 0xee, 0xf5, 0x2d, 0xbe, 0x82, 0x73, 0x67, 0x1f, 0x61, 0xe3, 0xd9, 0x25, 0xb8, 0xac, 0xaa, 0x41, + 0x19, 0xb3, 0x24, 0xf0, 0xe8, 0xba, 0xbe, 0x15, 0x56, 0xf8, 0xf3, 0x71, 0x76, 0xa9, 0xb8, 0xee, + 0x6b, 0x69, 0x30, 0x40, 0xca, 0x3c, 0xdd, 0xac, 0xf1, 0xb3, 0x7d, 0xf5, 0x33, 0x00, 0x00, 0xff, + 0xff, 0xde, 0xd5, 0x28, 0xa3, 0xc2, 0x02, 0x00, 0x00, } diff --git a/bchain/coins/eth/tx.proto b/bchain/coins/eth/tx.proto index 7d7f23ba..ef7c4ce0 100644 --- a/bchain/coins/eth/tx.proto +++ b/bchain/coins/eth/tx.proto @@ -4,7 +4,7 @@ syntax = "proto3"; message ProtoCompleteTransaction { message TxType { uint64 AccountNonce = 1; - bytes Price = 2; + bytes GasPrice = 2; uint64 GasLimit = 3; bytes Value = 4; bytes Payload = 5; From 6d3e171b710bbdb2c367471ede7ff32467144f1f Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Tue, 13 Nov 2018 10:31:27 +0100 Subject: [PATCH 7/9] Handle coin specific tx data more efficiently --- api/types.go | 37 +++++++++++----------- api/worker.go | 49 ++++++++++++++++++------------ bchain/coins/blockchain.go | 4 +-- bchain/coins/btc/bitcoinrpc.go | 13 ++++++-- bchain/coins/eth/ethparser.go | 31 ++++++++++--------- bchain/coins/eth/ethparser_test.go | 2 ++ bchain/coins/eth/ethrpc.go | 29 ++++++++++-------- bchain/types.go | 9 +++--- server/public.go | 15 +++------ server/socketio.go | 4 +-- static/templates/tx.html | 4 +-- tests/dbtestdata/fakechain.go | 4 +-- 12 files changed, 111 insertions(+), 90 deletions(-) diff --git a/api/types.go b/api/types.go index 8a535f24..9925bc7d 100644 --- a/api/types.go +++ b/api/types.go @@ -70,24 +70,25 @@ type Vout struct { // Tx holds information about a transaction type Tx struct { - Txid string `json:"txid"` - Version int32 `json:"version,omitempty"` - Locktime uint32 `json:"locktime,omitempty"` - Vin []Vin `json:"vin"` - Vout []Vout `json:"vout"` - Blockhash string `json:"blockhash,omitempty"` - Blockheight int `json:"blockheight"` - Confirmations uint32 `json:"confirmations"` - Time int64 `json:"time,omitempty"` - Blocktime int64 `json:"blocktime"` - ValueOut string `json:"valueOut"` - ValueOutSat big.Int `json:"-"` - Size int `json:"size,omitempty"` - ValueIn string `json:"valueIn"` - ValueInSat big.Int `json:"-"` - Fees string `json:"fees"` - FeesSat big.Int `json:"-"` - Hex string `json:"hex"` + Txid string `json:"txid"` + Version int32 `json:"version,omitempty"` + Locktime uint32 `json:"locktime,omitempty"` + Vin []Vin `json:"vin"` + Vout []Vout `json:"vout"` + Blockhash string `json:"blockhash,omitempty"` + Blockheight int `json:"blockheight"` + Confirmations uint32 `json:"confirmations"` + Time int64 `json:"time,omitempty"` + Blocktime int64 `json:"blocktime"` + ValueOut string `json:"valueOut"` + ValueOutSat big.Int `json:"-"` + Size int `json:"size,omitempty"` + ValueIn string `json:"valueIn"` + ValueInSat big.Int `json:"-"` + Fees string `json:"fees"` + FeesSat big.Int `json:"-"` + Hex string `json:"hex"` + CoinSpecificData interface{} `json:"-"` } // Paging contains information about paging for address, blocks and block diff --git a/api/worker.go b/api/worker.go index e30f3bdd..f1aa6538 100644 --- a/api/worker.go +++ b/api/worker.go @@ -5,6 +5,7 @@ import ( "blockbook/common" "blockbook/db" "bytes" + "encoding/json" "fmt" "math/big" "strconv" @@ -82,7 +83,7 @@ func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) err // GetSpendingTxid returns transaction id of transaction that spent given output func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { start := time.Now() - tx, err := w.GetTransaction(txid, false) + tx, err := w.GetTransaction(txid, false, false) if err != nil { return "", err } @@ -98,7 +99,7 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { } // GetTransaction reads transaction data from txid -func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { +func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificData bool) (*Tx, error) { start := time.Now() bchainTx, height, err := w.txCache.GetTransaction(txid) if err != nil { @@ -210,24 +211,32 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { } // for now do not return size, we would have to compute vsize of segwit transactions // size:=len(bchainTx.Hex) / 2 + var sd json.RawMessage + if specificData { + sd, err = w.chain.GetTransactionSpecific(bchainTx) + if err != nil { + return nil, err + } + } r := &Tx{ - Blockhash: blockhash, - Blockheight: int(height), - Blocktime: bchainTx.Blocktime, - Confirmations: bchainTx.Confirmations, - Fees: w.chainParser.AmountToDecimalString(&feesSat), - FeesSat: feesSat, - Locktime: bchainTx.LockTime, - Time: bchainTx.Time, - Txid: bchainTx.Txid, - ValueIn: w.chainParser.AmountToDecimalString(&valInSat), - ValueInSat: valInSat, - ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), - ValueOutSat: valOutSat, - Version: bchainTx.Version, - Hex: bchainTx.Hex, - Vin: vins, - Vout: vouts, + Blockhash: blockhash, + Blockheight: int(height), + Blocktime: bchainTx.Blocktime, + Confirmations: bchainTx.Confirmations, + Fees: w.chainParser.AmountToDecimalString(&feesSat), + FeesSat: feesSat, + Locktime: bchainTx.LockTime, + Time: bchainTx.Time, + Txid: bchainTx.Txid, + ValueIn: w.chainParser.AmountToDecimalString(&valInSat), + ValueInSat: valInSat, + ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), + ValueOutSat: valOutSat, + Version: bchainTx.Version, + Hex: bchainTx.Hex, + Vin: vins, + Vout: vouts, + CoinSpecificData: sd, } if spendingTxs { glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) @@ -426,7 +435,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b // load mempool transactions var uBalSat big.Int for _, tx := range txm { - tx, err := w.GetTransaction(tx, false) + tx, err := w.GetTransaction(tx, false, false) // mempool transaction may fail if err != nil { glog.Error("GetTransaction in mempool ", tx, ": ", err) diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 2e02db45..cd6f58eb 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -187,9 +187,9 @@ func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err e return c.b.GetTransaction(txid) } -func (c *blockChainWithMetrics) GetTransactionSpecific(txid string) (v json.RawMessage, err error) { +func (c *blockChainWithMetrics) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) { defer func(s time.Time) { c.observeRPCLatency("GetTransactionSpecific", s, err) }(time.Now()) - return c.b.GetTransactionSpecific(txid) + return c.b.GetTransactionSpecific(tx) } func (c *blockChainWithMetrics) GetTransactionForMempool(txid string) (v *bchain.Tx, err error) { diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 8b69e8a2..f17d4db1 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -670,11 +670,12 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { // GetTransaction returns a transaction by the transaction ID func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { - r, err := b.GetTransactionSpecific(txid) + r, err := b.getRawTransaction(txid) if err != nil { return nil, err } tx, err := b.Parser.ParseTxFromJson(r) + tx.CoinSpecificData = r if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } @@ -682,7 +683,15 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { } // GetTransactionSpecific returns json as returned by backend, with all coin specific data -func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) { +func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { + if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok { + return csd, nil + } + return b.getRawTransaction(tx.Txid) +} + +// getRawTransaction returns json as returned by backend, with all coin specific data +func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) { glog.V(1).Info("rpc: getrawtransaction ", txid) res := ResGetRawTransaction{} diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 4b92c676..48cce14b 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -79,7 +79,7 @@ func ethNumber(n string) (int64, error) { return 0, errors.Errorf("Not a number: '%v'", n) } -func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32) (*bchain.Tx, error) { +func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, marshallHex bool) (*bchain.Tx, error) { txid := ethHashToHash(tx.Hash) var ( fa, ta []string @@ -91,22 +91,24 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc if len(tx.To) > 2 { ta = []string{tx.To} } - - // completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex - bh := tx.BlockHash - tx.BlockHash = nil ct := completeTransaction{ Tx: tx, Receipt: receipt, } - b, err := json.Marshal(ct) - if err != nil { - return nil, err - } - tx.BlockHash = bh - h := hex.EncodeToString(b) - if receipt != nil { - glog.Info(tx.Hash.Hex(), ": ", h) + var h string + if marshallHex { + // completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex + bh := tx.BlockHash + tx.BlockHash = nil + b, err := json.Marshal(ct) + if err != nil { + return nil, err + } + tx.BlockHash = bh + h = hex.EncodeToString(b) + if receipt != nil { + glog.Info(tx.Hash.Hex(), ": ", h) + } } vs, err := hexutil.DecodeBig(tx.Value) if err != nil { @@ -139,6 +141,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc }, }, }, + CoinSpecificData: ct, }, nil } @@ -330,7 +333,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { Logs: logs, } } - tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0) + tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, true) if err != nil { return nil, 0, err } diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index fb082683..6294cb71 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -199,6 +199,8 @@ func TestEthereumParser_UnpackTx(t *testing.T) { t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr) return } + // CoinSpecificData are not set in want struct + got.CoinSpecificData = nil // DeepEqual compares empty nil slices as not equal if fmt.Sprint(got) != fmt.Sprint(tt.want) { t.Errorf("EthereumParser.UnpackTx() got = %+v, want %+v", got, tt.want) diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 468aee97..5bb486ad 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -447,7 +447,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error // TODO - get ERC20 events btxs := make([]bchain.Tx, len(body.Transactions)) for i, tx := range body.Transactions { - btx, err := b.Parser.ethTxToTx(&tx, nil, int64(head.Time.Uint64()), uint32(bbh.Confirmations)) + btx, err := b.Parser.ethTxToTx(&tx, nil, int64(head.Time.Uint64()), uint32(bbh.Confirmations), false) if err != nil { return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String()) } @@ -493,7 +493,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { var btx *bchain.Tx if tx.BlockNumber == "" { // mempool tx - btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0) + btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } @@ -516,7 +516,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, &receipt, h.Time.Int64(), confirmations) + btx, err = b.Parser.ethTxToTx(tx, &receipt, h.Time.Int64(), confirmations, true) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) } @@ -525,17 +525,20 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { } // GetTransactionSpecific returns json as returned by backend, with all coin specific data -func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) { - ctx, cancel := context.WithTimeout(context.Background(), b.timeout) - defer cancel() - var tx json.RawMessage - err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) - if err != nil { - return nil, err - } else if tx == nil { - return nil, ethereum.NotFound +func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { + csd, ok := tx.CoinSpecificData.(completeTransaction) + if !ok { + ntx, err := b.GetTransaction(tx.Txid) + if err != nil { + return nil, err + } + csd, ok = ntx.CoinSpecificData.(completeTransaction) + if !ok { + return nil, errors.New("Cannot get CoinSpecificData") + } } - return tx, nil + m, err := json.Marshal(&csd) + return json.RawMessage(m), err } type rpcMempoolBlock struct { diff --git a/bchain/types.go b/bchain/types.go index 327dfdcd..5053ff4d 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -75,9 +75,10 @@ type Tx struct { Vin []Vin `json:"vin"` Vout []Vout `json:"vout"` // BlockHash string `json:"blockhash,omitempty"` - Confirmations uint32 `json:"confirmations,omitempty"` - Time int64 `json:"time,omitempty"` - Blocktime int64 `json:"blocktime,omitempty"` + Confirmations uint32 `json:"confirmations,omitempty"` + Time int64 `json:"time,omitempty"` + Blocktime int64 `json:"blocktime,omitempty"` + CoinSpecificData interface{} `json:"-"` } // Block is block header and list of transactions @@ -185,7 +186,7 @@ type BlockChain interface { GetMempool() ([]string, error) GetTransaction(txid string) (*Tx, error) GetTransactionForMempool(txid string) (*Tx, error) - GetTransactionSpecific(txid string) (json.RawMessage, error) + GetTransactionSpecific(tx *Tx) (json.RawMessage, error) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) EstimateFee(blocks int) (big.Int, error) SendRawTransaction(tx string) (string, error) diff --git a/server/public.go b/server/public.go index e581d81f..de61b9fc 100644 --- a/server/public.go +++ b/server/public.go @@ -335,7 +335,6 @@ type TemplateData struct { Address *api.Address AddrStr string Tx *api.Tx - TxSpecific json.RawMessage Error *api.APIError Blocks *api.Blocks Block *api.Block @@ -391,23 +390,17 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData { func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { var tx *api.Tx - var txSpecific json.RawMessage var err error s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc() if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { txid := r.URL.Path[i+1:] - tx, err = s.api.GetTransaction(txid, false) - if err != nil { - return errorTpl, nil, err - } - txSpecific, err = s.chain.GetTransactionSpecific(txid) + tx, err = s.api.GetTransaction(txid, false, true) if err != nil { return errorTpl, nil, err } } data := s.newTemplateData() data.Tx = tx - data.TxSpecific = txSpecific return txTpl, data, nil } @@ -520,7 +513,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t http.Redirect(w, r, joinURL("/block/", block.Hash), 302) return noTpl, nil, nil } - tx, err = s.api.GetTransaction(q, false) + tx, err = s.api.GetTransaction(q, false, false) if err == nil { http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) return noTpl, nil, nil @@ -655,7 +648,7 @@ func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) { return nil, api.NewAPIError("Parameter 'spending' cannot be converted to boolean", true) } } - tx, err = s.api.GetTransaction(txid, spendingTxs) + tx, err = s.api.GetTransaction(txid, spendingTxs, false) } return tx, err } @@ -666,7 +659,7 @@ func (s *PublicServer) apiTxSpecific(r *http.Request) (interface{}, error) { s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx-specific"}).Inc() if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { txid := r.URL.Path[i+1:] - tx, err = s.chain.GetTransactionSpecific(txid) + tx, err = s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) } return tx, err } diff --git a/server/socketio.go b/server/socketio.go index 2cdd1361..387dd256 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -387,7 +387,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r to = opts.To } for txi := opts.From; txi < to; txi++ { - tx, err := s.api.GetTransaction(txids[txi], false) + tx, err := s.api.GetTransaction(txids[txi], false, false) if err != nil { return res, err } @@ -627,7 +627,7 @@ type resultGetDetailedTransaction struct { } func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetailedTransaction, err error) { - tx, err := s.api.GetTransaction(txid, false) + tx, err := s.api.GetTransaction(txid, false, false) if err != nil { return res, err } diff --git a/static/templates/tx.html b/static/templates/tx.html index b04983ba..22aca6a3 100644 --- a/static/templates/tx.html +++ b/static/templates/tx.html @@ -1,4 +1,4 @@ -{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}{{$txSpecific := .TxSpecific}} +{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}

Transaction

{{$tx.Txid}} @@ -47,7 +47,7 @@