From 8ac57a3d56baac17330d62dbb8e49b99cf96be65 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 28 Nov 2018 14:27:02 +0100 Subject: [PATCH] Add ERC20 transfer information to ethereum transactions --- api/types.go | 70 +++++++++++-------- api/worker.go | 48 +++++++++++-- bchain/basechain.go | 45 ++++++++++++ bchain/baseparser.go | 11 ++- bchain/baseparser_test.go | 3 +- bchain/coins/blockchain.go | 15 ++++ bchain/coins/btc/bitcoinrpc.go | 20 ++---- bchain/coins/eth/erc20.go | 119 +++++++++++++++++++++++++++++++- bchain/coins/eth/erc20_test.go | 31 ++++++++- bchain/coins/eth/ethparser.go | 38 +++++++++- bchain/coins/eth/ethrpc.go | 21 +++--- bchain/mempool_ethereum_type.go | 2 +- bchain/types.go | 14 ++++ tests/dbtestdata/fakechain.go | 32 ++++----- 14 files changed, 385 insertions(+), 84 deletions(-) create mode 100644 bchain/basechain.go diff --git a/api/types.go b/api/types.go index 111aca89..41c446b9 100644 --- a/api/types.go +++ b/api/types.go @@ -2,6 +2,7 @@ package api import ( "blockbook/bchain" + "blockbook/bchain/coins/eth" "blockbook/common" "blockbook/db" "encoding/json" @@ -69,28 +70,49 @@ type Vout struct { SpentHeight int `json:"spentHeight,omitempty"` } +// Erc20Token contains info about ERC20 token held by an address +type Erc20Token struct { + Contract string `json:"contract"` + Txs int `json:"txs"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Balance string `json:"balance"` +} + +// Erc20Transfer contains info about ERC20 transfer done in a transaction +type Erc20Transfer struct { + From string `json:"from"` + To string `json:"to"` + Contract string `json:"contract"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Tokens string `json:"tokens"` +} + // 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"` - CoinSpecificData interface{} `json:"-"` - CoinSpecificJSON json.RawMessage `json:"-"` + 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:"-"` + CoinSpecificJSON json.RawMessage `json:"-"` + Erc20Transfers []Erc20Transfer `json:"erc20transfers,omitempty"` + EthereumSpecific *eth.EthereumTxData `json:"ethereumspecific,omitempty"` } // Paging contains information about paging for address, blocks and block @@ -100,14 +122,6 @@ type Paging struct { ItemsOnPage int `json:"itemsOnPage"` } -type Erc20Token struct { - Contract string `json:"contract"` - Txs int `json:"txs"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Value string `json:"value"` -} - // Address holds information about address and its transactions type Address struct { Paging diff --git a/api/worker.go b/api/worker.go index 3700b250..168b6d00 100644 --- a/api/worker.go +++ b/api/worker.go @@ -2,6 +2,7 @@ package api import ( "blockbook/bchain" + "blockbook/bchain/coins/eth" "blockbook/common" "blockbook/db" "bytes" @@ -106,6 +107,8 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true) } var ta *db.TxAddresses + var erc20t []Erc20Transfer + var ethSpecific *eth.EthereumTxData var blockhash string if bchainTx.Confirmations > 0 { if w.chainType == bchain.ChainBitcoinType { @@ -204,10 +207,45 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool } } } - // for coinbase transactions valIn is 0 - feesSat.Sub(&valInSat, &valOutSat) - if feesSat.Sign() == -1 { - feesSat.SetUint64(0) + if w.chainType == bchain.ChainBitcoinType { + // for coinbase transactions valIn is 0 + feesSat.Sub(&valInSat, &valOutSat) + if feesSat.Sign() == -1 { + feesSat.SetUint64(0) + } + } else if w.chainType == bchain.ChainEthereumType { + ets, err := eth.GetErc20FromTx(bchainTx) + if err != nil { + glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx) + } + erc20t = make([]Erc20Transfer, len(ets)) + for i := range ets { + e := &ets[i] + cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract) + if err != nil { + glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract) + continue + } + erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd) + if err != nil { + glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract) + erc20c = &bchain.Erc20Contract{} + } + erc20t[i] = Erc20Transfer{ + Contract: e.Contract, + From: e.From, + To: e.To, + Tokens: bchain.AmountToDecimalString(&e.Tokens, erc20c.Decimals), + Name: erc20c.Name, + Symbol: erc20c.Symbol, + } + } + ethSpecific = eth.GetEthereumTxData(bchainTx) + feesSat.Mul(ethSpecific.GasPriceNum, ethSpecific.GasUsed) + if len(bchainTx.Vout) > 0 { + valInSat = bchainTx.Vout[0].ValueSat + } + valOutSat = valInSat } // for now do not return size, we would have to compute vsize of segwit transactions // size:=len(bchainTx.Hex) / 2 @@ -238,6 +276,8 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool Vout: vouts, CoinSpecificData: bchainTx.CoinSpecificData, CoinSpecificJSON: sj, + Erc20Transfers: erc20t, + EthereumSpecific: ethSpecific, } if spendingTxs { glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) diff --git a/bchain/basechain.go b/bchain/basechain.go new file mode 100644 index 00000000..f7c1d1fa --- /dev/null +++ b/bchain/basechain.go @@ -0,0 +1,45 @@ +package bchain + +import ( + "errors" + "math/big" +) + +// BaseChain is base type for bchain.BlockChain +type BaseChain struct { + Parser BlockChainParser + Testnet bool + Network string +} + +// TODO more bchain.BlockChain methods + +// GetChainParser returns BlockChainParser +func (b *BaseChain) GetChainParser() BlockChainParser { + return b.Parser +} + +// IsTestnet returns true if the blockchain is testnet +func (b *BaseChain) IsTestnet() bool { + return b.Testnet +} + +// GetNetworkName returns network name +func (b *BaseChain) GetNetworkName() string { + return b.Network +} + +// EthereumTypeGetBalance is not supported +func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) { + return nil, errors.New("Not supported") +} + +// EthereumTypeGetErc20ContractInfo is not supported +func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) { + return nil, errors.New("Not supported") +} + +// EthereumTypeGetErc20ContractBalance is not supported +func (b *BaseChain) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) { + return nil, errors.New("Not supported") +} diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 7d4f3875..fbd4b58f 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -34,10 +34,14 @@ func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) { var r big.Int s := string(n) i := strings.IndexByte(s, '.') + d := p.AmountDecimalPoint + if d > len(zeros) { + d = len(zeros) + } if i == -1 { - s = s + zeros[:p.AmountDecimalPoint] + s = s + zeros[:d] } else { - z := p.AmountDecimalPoint - len(s) + i + 1 + z := d - len(s) + i + 1 if z > 0 { s = s[:i] + s[i+1:] + zeros[:z] } else { @@ -58,6 +62,9 @@ func AmountToDecimalString(a *big.Int, d int) string { n = n[1:] s = "-" } + if d > len(zeros) { + d = len(zeros) + } if len(n) <= d { n = zeros[:d-len(n)+1] + n } diff --git a/bchain/baseparser_test.go b/bchain/baseparser_test.go index cfb20a68..668ed267 100644 --- a/bchain/baseparser_test.go +++ b/bchain/baseparser_test.go @@ -27,7 +27,8 @@ var amounts = []struct { {big.NewInt(-8), "-0.00000008", 8, "!"}, {big.NewInt(-89012345678), "-890.12345678", 8, "!"}, {big.NewInt(-12345), "-0.00012345", 8, "!"}, - {big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places + {big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places + {big.NewInt(12345678), "0.0000000000000000000000000000000012345678", 1234, "!"}, // test of too big number decimal places } func TestBaseParser_AmountToDecimalString(t *testing.T) { diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index cd6f58eb..c3699d4b 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -239,3 +239,18 @@ func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolE func (c *blockChainWithMetrics) GetChainParser() bchain.BlockChainParser { return c.b.GetChainParser() } + +func (c *blockChainWithMetrics) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (v *big.Int, err error) { + defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetBalance", s, err) }(time.Now()) + return c.b.EthereumTypeGetBalance(addrDesc) +} + +func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) { + defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now()) + return c.b.EthereumTypeGetErc20ContractInfo(contractDesc) +} + +func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) { + defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now()) + return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc) +} diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index f17d4db1..fbef4dd5 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -11,6 +11,7 @@ import ( "math/big" "net" "net/http" + "runtime/debug" "time" "github.com/btcsuite/btcd/wire" @@ -20,13 +21,11 @@ import ( // BitcoinRPC is an interface to JSON-RPC bitcoind service. type BitcoinRPC struct { + *bchain.BaseChain client http.Client rpcURL string user string password string - Parser bchain.BlockChainParser - Testnet bool - Network string Mempool *bchain.MempoolBitcoinType ParseBlocks bool pushHandler func(bchain.NotificationType) @@ -84,6 +83,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT } s := &BitcoinRPC{ + BaseChain: &bchain.BaseChain{}, client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport}, rpcURL: c.RPCURL, user: c.RPCUser, @@ -158,14 +158,6 @@ func (b *BitcoinRPC) Shutdown(ctx context.Context) error { return nil } -func (b *BitcoinRPC) IsTestnet() bool { - return b.Testnet -} - -func (b *BitcoinRPC) GetNetworkName() string { - return b.Network -} - func (b *BitcoinRPC) GetCoinName() string { return b.ChainConfig.CoinName } @@ -837,6 +829,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) { defer func() { if r := recover(); r != nil { glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data)) + debug.PrintStack() if len(data) > 0 && len(data) < 2048 { err = errors.Errorf("Error: %v", string(data)) } else { @@ -881,8 +874,3 @@ func (b *BitcoinRPC) Call(req interface{}, res interface{}) error { } return safeDecodeResponse(httpRes.Body, &res) } - -// GetChainParser returns BlockChainParser -func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser { - return b.Parser -} diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go index 1935ba68..6d9e2bc4 100644 --- a/bchain/coins/eth/erc20.go +++ b/bchain/coins/eth/erc20.go @@ -1,9 +1,15 @@ package eth import ( + "blockbook/bchain" + "context" + "encoding/hex" "math/big" + "sync" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/glog" "github.com/juju/errors" ) @@ -24,8 +30,12 @@ var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":" // doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event const erc20EventTransferSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +const erc20NameSignature = "0x06fdde03" +const erc20SymbolSignature = "0x95d89b41" +const erc20DecimalsSignature = "0x313ce567" +const erc20BalanceOf = "0x70a08231" -// Erc20Transfer contains a single Erc20 token transfer +// Erc20Transfer contains a single ERC20 token transfer type Erc20Transfer struct { Contract string From string @@ -33,6 +43,9 @@ type Erc20Transfer struct { Tokens big.Int } +var cachedContracts = make(map[string]bchain.Erc20Contract) +var cachedContractsMux sync.Mutex + func addressFromPaddedHex(s string) (string, error) { var t big.Int _, ok := t.SetString(s, 0) @@ -70,3 +83,107 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) { } return r, nil } + +func (b *EthereumRPC) ethCall(data, to string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var r string + err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{ + "data": data, + "to": to, + }, "latest") + if err != nil { + return "", err + } + return r, nil +} + +func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) == 64 { + var n big.Int + _, ok := n.SetString(data, 16) + if ok { + return &n + } + } + glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) + return nil +} + +func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) == 192 { + n := parseErc20NumericProperty(contractDesc, data[64:128]) + if n != nil { + l := n.Uint64() + if l <= 32 { + b, err := hex.DecodeString(data[128 : 128+2*l]) + if err == nil { + return string(b) + } + } + } + } + glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) + return "" +} + +// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract +func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { + cds := string(contractDesc) + cachedContractsMux.Lock() + contract, found := cachedContracts[cds] + cachedContractsMux.Unlock() + if !found { + address := hexutil.Encode(contractDesc) + data, err := b.ethCall(erc20NameSignature, address) + if err != nil { + return nil, err + } + name := parseErc20StringProperty(contractDesc, data) + data, err = b.ethCall(erc20SymbolSignature, address) + if err != nil { + return nil, err + } + symbol := parseErc20StringProperty(contractDesc, data) + data, err = b.ethCall(erc20DecimalsSignature, address) + if err != nil { + return nil, err + } + contract = bchain.Erc20Contract{ + Name: name, + Symbol: symbol, + } + d := parseErc20NumericProperty(contractDesc, data) + if d != nil { + contract.Decimals = int(uint8(d.Uint64())) + } else { + contract.Decimals = EtherAmountDecimalPoint + } + cachedContractsMux.Lock() + cachedContracts[cds] = contract + cachedContractsMux.Unlock() + } + return &contract, nil +} + +// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address +func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { + addr := hexutil.Encode(addrDesc) + contract := hexutil.Encode(contractDesc) + req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] + data, err := b.ethCall(req, contract) + if err != nil { + return nil, err + } + r := parseErc20NumericProperty(contractDesc, data) + if r == nil { + return nil, errors.New("Invalid balance") + } + return r, nil +} diff --git a/bchain/coins/eth/erc20_test.go b/bchain/coins/eth/erc20_test.go index 1b5b6fbd..98accd00 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 @@ -108,5 +108,32 @@ func TestErc20_erc20GetTransfersFromLog(t *testing.T) { } }) } - +} + +func TestErc20_parseErc20StringProperty(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "1", + args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000", + want: "XPLODDE", + }, + { + name: "1", + args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000", + want: "XPLODDE", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseErc20StringProperty(nil, tt.args) + // the addresses could have different case + if got != tt.want { + t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want) + } + }) + } } diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 6232a2a1..fbaeff15 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -16,6 +16,9 @@ import ( // EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length const EthereumTypeAddressDescriptorLen = 20 +// EtherAmountDecimalPoint defines number of decimal points in Ether amounts +const EtherAmountDecimalPoint = 18 + // EthereumParser handle type EthereumParser struct { *bchain.BaseParser @@ -25,7 +28,7 @@ type EthereumParser struct { func NewEthereumParser(b int) *EthereumParser { return &EthereumParser{&bchain.BaseParser{ BlockAddressesToKeep: b, - AmountDecimalPoint: 18, + AmountDecimalPoint: EtherAmountDecimalPoint, }} } @@ -435,3 +438,36 @@ func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) { } return r, nil } + +// EthereumTxData contains ethereum specific transaction data +type EthereumTxData struct { + Status int `json:"status"` // 1 OK, 0 Fail, -1 pending + Nonce uint64 `json:"nonce"` + GasLimit *big.Int `json:"gaslimit"` + GasUsed *big.Int `json:"gasused"` + GasPrice string `json:"gasprice"` + GasPriceNum *big.Int `json:"-"` +} + +// GetEthereumTxData returns EthereumTxData from bchain.Tx +func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData { + etd := EthereumTxData{Status: -1} + csd, ok := tx.CoinSpecificData.(completeTransaction) + if ok { + if csd.Tx != nil { + etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce) + etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit) + etd.GasPriceNum, _ = hexutil.DecodeBig(csd.Tx.GasPrice) + etd.GasPrice = bchain.AmountToDecimalString(etd.GasPriceNum, EtherAmountDecimalPoint) + } + if csd.Receipt != nil { + if csd.Receipt.Status == "0x1" { + etd.Status = 1 + } else { + etd.Status = 0 + } + etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed) + } + } + return &etd +} diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 1a0dd049..cbe24e1d 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -40,12 +40,11 @@ type Configuration struct { // EthereumRPC is an interface to JSON-RPC eth service. type EthereumRPC struct { + *bchain.BaseChain client *ethclient.Client rpc *rpc.Client timeout time.Duration Parser *EthereumParser - Testnet bool - Network string Mempool *bchain.MempoolEthereumType bestHeaderMu sync.Mutex bestHeader *ethtypes.Header @@ -77,6 +76,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification ec := ethclient.NewClient(rc) s := &EthereumRPC{ + BaseChain: &bchain.BaseChain{}, client: ec, rpc: rc, ChainConfig: &c, @@ -250,16 +250,6 @@ 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 @@ -652,6 +642,13 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { return result, nil } +// EthereumTypeGetBalance returns current balance of an address +func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), 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 diff --git a/bchain/mempool_ethereum_type.go b/bchain/mempool_ethereum_type.go index 08484135..660c020d 100644 --- a/bchain/mempool_ethereum_type.go +++ b/bchain/mempool_ethereum_type.go @@ -68,7 +68,7 @@ func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { if !exists { tx, err := m.chain.GetTransactionForMempool(txid) if err != nil { - glog.Error("cannot get transaction ", txid, ": ", err) + glog.Warning("cannot get transaction ", txid, ": ", err) continue } io = make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin)) diff --git a/bchain/types.go b/bchain/types.go index 5053ff4d..db6b06e4 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -159,6 +159,16 @@ func (ad AddressDescriptor) String() string { return "ad:" + hex.EncodeToString(ad) } +// EthereumType specific + +// Erc20Contract contains info about ERC20 contract +type Erc20Contract struct { + Contract string + Name string + Symbol string + Decimals int +} + // OnNewBlockFunc is used to send notification about a new block type OnNewBlockFunc func(hash string, height uint32) @@ -197,6 +207,10 @@ type BlockChain interface { GetMempoolEntry(txid string) (*MempoolEntry, error) // parser GetChainParser() BlockChainParser + // EthereumType specific + EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) + EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) + EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) } // BlockChainParser defines common interface to parsing and conversions of block chain data diff --git a/tests/dbtestdata/fakechain.go b/tests/dbtestdata/fakechain.go index e379badf..e0643195 100644 --- a/tests/dbtestdata/fakechain.go +++ b/tests/dbtestdata/fakechain.go @@ -9,11 +9,11 @@ import ( ) type fakeBlockChain struct { - parser bchain.BlockChainParser + *bchain.BaseChain } func NewFakeBlockChain(parser bchain.BlockChainParser) (*fakeBlockChain, error) { - return &fakeBlockChain{parser: parser}, nil + return &fakeBlockChain{&bchain.BaseChain{Parser: parser}}, nil } func (c *fakeBlockChain) Initialize() error { @@ -45,26 +45,26 @@ func (c *fakeBlockChain) GetChainInfo() (v *bchain.ChainInfo, err error) { Chain: c.GetNetworkName(), Blocks: 2, Headers: 2, - Bestblockhash: GetTestBitcoinTypeBlock2(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 GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Hash, nil + return GetTestBitcoinTypeBlock2(c.Parser).BlockHeader.Hash, nil } func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) { - return GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Height, nil + return GetTestBitcoinTypeBlock2(c.Parser).BlockHeader.Height, nil } func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) { - b1 := GetTestBitcoinTypeBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.Parser) if height == b1.BlockHeader.Height { return b1.BlockHeader.Hash, nil } - b2 := GetTestBitcoinTypeBlock2(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 := GetTestBitcoinTypeBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.Parser) if hash == b1.BlockHeader.Hash { return &b1.BlockHeader, nil } - b2 := GetTestBitcoinTypeBlock2(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 := GetTestBitcoinTypeBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.Parser) if hash == b1.BlockHeader.Hash || height == b1.BlockHeader.Height { return b1, nil } - b2 := GetTestBitcoinTypeBlock2(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 := GetTestBitcoinTypeBlock1(c.parser) + b1 := GetTestBitcoinTypeBlock1(c.Parser) if hash == b1.BlockHeader.Hash { return getBlockInfo(b1), nil } - b2 := GetTestBitcoinTypeBlock2(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(GetTestBitcoinTypeBlock1(c.parser), txid) + v = getTxInBlock(GetTestBitcoinTypeBlock1(c.Parser), txid) if v == nil { - v = getTxInBlock(GetTestBitcoinTypeBlock2(c.parser), txid) + v = getTxInBlock(GetTestBitcoinTypeBlock2(c.Parser), txid) } if v != nil { return v, nil @@ -195,5 +195,5 @@ func (c *fakeBlockChain) GetMempoolEntry(txid string) (v *bchain.MempoolEntry, e } func (c *fakeBlockChain) GetChainParser() bchain.BlockChainParser { - return c.parser + return c.Parser }