Add ERC20 transfer information to ethereum transactions

This commit is contained in:
Martin Boehm 2018-11-28 14:27:02 +01:00
parent 1f32a39d16
commit 8ac57a3d56
14 changed files with 385 additions and 84 deletions

View File

@ -2,6 +2,7 @@ package api
import ( import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/bchain/coins/eth"
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"encoding/json" "encoding/json"
@ -69,28 +70,49 @@ type Vout struct {
SpentHeight int `json:"spentHeight,omitempty"` 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 // Tx holds information about a transaction
type Tx struct { type Tx struct {
Txid string `json:"txid"` Txid string `json:"txid"`
Version int32 `json:"version,omitempty"` Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"` Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"` Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"` Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"` Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"` Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"` Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"` Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"` Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"` ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"` ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"` Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"` ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"` ValueInSat big.Int `json:"-"`
Fees string `json:"fees"` Fees string `json:"fees"`
FeesSat big.Int `json:"-"` FeesSat big.Int `json:"-"`
Hex string `json:"hex"` Hex string `json:"hex"`
CoinSpecificData interface{} `json:"-"` CoinSpecificData interface{} `json:"-"`
CoinSpecificJSON json.RawMessage `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 // Paging contains information about paging for address, blocks and block
@ -100,14 +122,6 @@ type Paging struct {
ItemsOnPage int `json:"itemsOnPage"` 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 // Address holds information about address and its transactions
type Address struct { type Address struct {
Paging Paging

View File

@ -2,6 +2,7 @@ package api
import ( import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/bchain/coins/eth"
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"bytes" "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) return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true)
} }
var ta *db.TxAddresses var ta *db.TxAddresses
var erc20t []Erc20Transfer
var ethSpecific *eth.EthereumTxData
var blockhash string var blockhash string
if bchainTx.Confirmations > 0 { if bchainTx.Confirmations > 0 {
if w.chainType == bchain.ChainBitcoinType { 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 if w.chainType == bchain.ChainBitcoinType {
feesSat.Sub(&valInSat, &valOutSat) // for coinbase transactions valIn is 0
if feesSat.Sign() == -1 { feesSat.Sub(&valInSat, &valOutSat)
feesSat.SetUint64(0) 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 // for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2 // size:=len(bchainTx.Hex) / 2
@ -238,6 +276,8 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
Vout: vouts, Vout: vouts,
CoinSpecificData: bchainTx.CoinSpecificData, CoinSpecificData: bchainTx.CoinSpecificData,
CoinSpecificJSON: sj, CoinSpecificJSON: sj,
Erc20Transfers: erc20t,
EthereumSpecific: ethSpecific,
} }
if spendingTxs { if spendingTxs {
glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) glog.Info("GetTransaction ", txid, " finished in ", time.Since(start))

45
bchain/basechain.go Normal file
View File

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

View File

@ -34,10 +34,14 @@ func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
var r big.Int var r big.Int
s := string(n) s := string(n)
i := strings.IndexByte(s, '.') i := strings.IndexByte(s, '.')
d := p.AmountDecimalPoint
if d > len(zeros) {
d = len(zeros)
}
if i == -1 { if i == -1 {
s = s + zeros[:p.AmountDecimalPoint] s = s + zeros[:d]
} else { } else {
z := p.AmountDecimalPoint - len(s) + i + 1 z := d - len(s) + i + 1
if z > 0 { if z > 0 {
s = s[:i] + s[i+1:] + zeros[:z] s = s[:i] + s[i+1:] + zeros[:z]
} else { } else {
@ -58,6 +62,9 @@ func AmountToDecimalString(a *big.Int, d int) string {
n = n[1:] n = n[1:]
s = "-" s = "-"
} }
if d > len(zeros) {
d = len(zeros)
}
if len(n) <= d { if len(n) <= d {
n = zeros[:d-len(n)+1] + n n = zeros[:d-len(n)+1] + n
} }

View File

@ -27,7 +27,8 @@ var amounts = []struct {
{big.NewInt(-8), "-0.00000008", 8, "!"}, {big.NewInt(-8), "-0.00000008", 8, "!"},
{big.NewInt(-89012345678), "-890.12345678", 8, "!"}, {big.NewInt(-89012345678), "-890.12345678", 8, "!"},
{big.NewInt(-12345), "-0.00012345", 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) { func TestBaseParser_AmountToDecimalString(t *testing.T) {

View File

@ -239,3 +239,18 @@ func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolE
func (c *blockChainWithMetrics) GetChainParser() bchain.BlockChainParser { func (c *blockChainWithMetrics) GetChainParser() bchain.BlockChainParser {
return c.b.GetChainParser() 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)
}

View File

@ -11,6 +11,7 @@ import (
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
"runtime/debug"
"time" "time"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -20,13 +21,11 @@ import (
// BitcoinRPC is an interface to JSON-RPC bitcoind service. // BitcoinRPC is an interface to JSON-RPC bitcoind service.
type BitcoinRPC struct { type BitcoinRPC struct {
*bchain.BaseChain
client http.Client client http.Client
rpcURL string rpcURL string
user string user string
password string password string
Parser bchain.BlockChainParser
Testnet bool
Network string
Mempool *bchain.MempoolBitcoinType Mempool *bchain.MempoolBitcoinType
ParseBlocks bool ParseBlocks bool
pushHandler func(bchain.NotificationType) pushHandler func(bchain.NotificationType)
@ -84,6 +83,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
} }
s := &BitcoinRPC{ s := &BitcoinRPC{
BaseChain: &bchain.BaseChain{},
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport}, client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
rpcURL: c.RPCURL, rpcURL: c.RPCURL,
user: c.RPCUser, user: c.RPCUser,
@ -158,14 +158,6 @@ func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
return nil return nil
} }
func (b *BitcoinRPC) IsTestnet() bool {
return b.Testnet
}
func (b *BitcoinRPC) GetNetworkName() string {
return b.Network
}
func (b *BitcoinRPC) GetCoinName() string { func (b *BitcoinRPC) GetCoinName() string {
return b.ChainConfig.CoinName return b.ChainConfig.CoinName
} }
@ -837,6 +829,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data)) glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
debug.PrintStack()
if len(data) > 0 && len(data) < 2048 { if len(data) > 0 && len(data) < 2048 {
err = errors.Errorf("Error: %v", string(data)) err = errors.Errorf("Error: %v", string(data))
} else { } else {
@ -881,8 +874,3 @@ func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
} }
return safeDecodeResponse(httpRes.Body, &res) return safeDecodeResponse(httpRes.Body, &res)
} }
// GetChainParser returns BlockChainParser
func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser {
return b.Parser
}

View File

@ -1,9 +1,15 @@
package eth package eth
import ( import (
"blockbook/bchain"
"context"
"encoding/hex"
"math/big" "math/big"
"sync"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/glog"
"github.com/juju/errors" "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 // doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event
const erc20EventTransferSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" 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 { type Erc20Transfer struct {
Contract string Contract string
From string From string
@ -33,6 +43,9 @@ type Erc20Transfer struct {
Tokens big.Int Tokens big.Int
} }
var cachedContracts = make(map[string]bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) { func addressFromPaddedHex(s string) (string, error) {
var t big.Int var t big.Int
_, ok := t.SetString(s, 0) _, ok := t.SetString(s, 0)
@ -70,3 +83,107 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) {
} }
return r, nil 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
}

View File

@ -1,4 +1,4 @@
// +build unittest // build unittest
package eth 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)
}
})
}
} }

View File

@ -16,6 +16,9 @@ import (
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length // EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
const EthereumTypeAddressDescriptorLen = 20 const EthereumTypeAddressDescriptorLen = 20
// EtherAmountDecimalPoint defines number of decimal points in Ether amounts
const EtherAmountDecimalPoint = 18
// EthereumParser handle // EthereumParser handle
type EthereumParser struct { type EthereumParser struct {
*bchain.BaseParser *bchain.BaseParser
@ -25,7 +28,7 @@ type EthereumParser struct {
func NewEthereumParser(b int) *EthereumParser { func NewEthereumParser(b int) *EthereumParser {
return &EthereumParser{&bchain.BaseParser{ return &EthereumParser{&bchain.BaseParser{
BlockAddressesToKeep: b, BlockAddressesToKeep: b,
AmountDecimalPoint: 18, AmountDecimalPoint: EtherAmountDecimalPoint,
}} }}
} }
@ -435,3 +438,36 @@ func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) {
} }
return r, nil 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
}

View File

@ -40,12 +40,11 @@ type Configuration struct {
// EthereumRPC is an interface to JSON-RPC eth service. // EthereumRPC is an interface to JSON-RPC eth service.
type EthereumRPC struct { type EthereumRPC struct {
*bchain.BaseChain
client *ethclient.Client client *ethclient.Client
rpc *rpc.Client rpc *rpc.Client
timeout time.Duration timeout time.Duration
Parser *EthereumParser Parser *EthereumParser
Testnet bool
Network string
Mempool *bchain.MempoolEthereumType Mempool *bchain.MempoolEthereumType
bestHeaderMu sync.Mutex bestHeaderMu sync.Mutex
bestHeader *ethtypes.Header bestHeader *ethtypes.Header
@ -77,6 +76,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
ec := ethclient.NewClient(rc) ec := ethclient.NewClient(rc)
s := &EthereumRPC{ s := &EthereumRPC{
BaseChain: &bchain.BaseChain{},
client: ec, client: ec,
rpc: rc, rpc: rc,
ChainConfig: &c, ChainConfig: &c,
@ -250,16 +250,6 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error {
return nil 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 // GetCoinName returns coin name
func (b *EthereumRPC) GetCoinName() string { func (b *EthereumRPC) GetCoinName() string {
return b.ChainConfig.CoinName return b.ChainConfig.CoinName
@ -652,6 +642,13 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
return result, nil 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 gets mempool transactions and maps output scripts to transactions.
// ResyncMempool is not reentrant, it should be called from a single thread. // ResyncMempool is not reentrant, it should be called from a single thread.
// Return value is number of transactions in mempool // Return value is number of transactions in mempool

View File

@ -68,7 +68,7 @@ func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
if !exists { if !exists {
tx, err := m.chain.GetTransactionForMempool(txid) tx, err := m.chain.GetTransactionForMempool(txid)
if err != nil { if err != nil {
glog.Error("cannot get transaction ", txid, ": ", err) glog.Warning("cannot get transaction ", txid, ": ", err)
continue continue
} }
io = make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin)) io = make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin))

View File

@ -159,6 +159,16 @@ func (ad AddressDescriptor) String() string {
return "ad:" + hex.EncodeToString(ad) 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 // OnNewBlockFunc is used to send notification about a new block
type OnNewBlockFunc func(hash string, height uint32) type OnNewBlockFunc func(hash string, height uint32)
@ -197,6 +207,10 @@ type BlockChain interface {
GetMempoolEntry(txid string) (*MempoolEntry, error) GetMempoolEntry(txid string) (*MempoolEntry, error)
// parser // parser
GetChainParser() BlockChainParser 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 // BlockChainParser defines common interface to parsing and conversions of block chain data

View File

@ -9,11 +9,11 @@ import (
) )
type fakeBlockChain struct { type fakeBlockChain struct {
parser bchain.BlockChainParser *bchain.BaseChain
} }
func NewFakeBlockChain(parser bchain.BlockChainParser) (*fakeBlockChain, error) { func NewFakeBlockChain(parser bchain.BlockChainParser) (*fakeBlockChain, error) {
return &fakeBlockChain{parser: parser}, nil return &fakeBlockChain{&bchain.BaseChain{Parser: parser}}, nil
} }
func (c *fakeBlockChain) Initialize() error { func (c *fakeBlockChain) Initialize() error {
@ -45,26 +45,26 @@ func (c *fakeBlockChain) GetChainInfo() (v *bchain.ChainInfo, err error) {
Chain: c.GetNetworkName(), Chain: c.GetNetworkName(),
Blocks: 2, Blocks: 2,
Headers: 2, Headers: 2,
Bestblockhash: GetTestBitcoinTypeBlock2(c.parser).BlockHeader.Hash, Bestblockhash: GetTestBitcoinTypeBlock2(c.Parser).BlockHeader.Hash,
Version: "001001", Version: "001001",
Subversion: c.GetSubversion(), Subversion: c.GetSubversion(),
}, nil }, nil
} }
func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) { 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) { 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) { func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) {
b1 := GetTestBitcoinTypeBlock1(c.parser) b1 := GetTestBitcoinTypeBlock1(c.Parser)
if height == b1.BlockHeader.Height { if height == b1.BlockHeader.Height {
return b1.BlockHeader.Hash, nil return b1.BlockHeader.Hash, nil
} }
b2 := GetTestBitcoinTypeBlock2(c.parser) b2 := GetTestBitcoinTypeBlock2(c.Parser)
if height == b2.BlockHeader.Height { if height == b2.BlockHeader.Height {
return b2.BlockHeader.Hash, nil 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) { func (c *fakeBlockChain) GetBlockHeader(hash string) (v *bchain.BlockHeader, err error) {
b1 := GetTestBitcoinTypeBlock1(c.parser) b1 := GetTestBitcoinTypeBlock1(c.Parser)
if hash == b1.BlockHeader.Hash { if hash == b1.BlockHeader.Hash {
return &b1.BlockHeader, nil return &b1.BlockHeader, nil
} }
b2 := GetTestBitcoinTypeBlock2(c.parser) b2 := GetTestBitcoinTypeBlock2(c.Parser)
if hash == b2.BlockHeader.Hash { if hash == b2.BlockHeader.Hash {
return &b2.BlockHeader, nil 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) { 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 { if hash == b1.BlockHeader.Hash || height == b1.BlockHeader.Height {
return b1, nil return b1, nil
} }
b2 := GetTestBitcoinTypeBlock2(c.parser) b2 := GetTestBitcoinTypeBlock2(c.Parser)
if hash == b2.BlockHeader.Hash || height == b2.BlockHeader.Height { if hash == b2.BlockHeader.Hash || height == b2.BlockHeader.Height {
return b2, nil 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) { func (c *fakeBlockChain) GetBlockInfo(hash string) (v *bchain.BlockInfo, err error) {
b1 := GetTestBitcoinTypeBlock1(c.parser) b1 := GetTestBitcoinTypeBlock1(c.Parser)
if hash == b1.BlockHeader.Hash { if hash == b1.BlockHeader.Hash {
return getBlockInfo(b1), nil return getBlockInfo(b1), nil
} }
b2 := GetTestBitcoinTypeBlock2(c.parser) b2 := GetTestBitcoinTypeBlock2(c.Parser)
if hash == b2.BlockHeader.Hash { if hash == b2.BlockHeader.Hash {
return getBlockInfo(b2), nil 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) { 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 { if v == nil {
v = getTxInBlock(GetTestBitcoinTypeBlock2(c.parser), txid) v = getTxInBlock(GetTestBitcoinTypeBlock2(c.Parser), txid)
} }
if v != nil { if v != nil {
return 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 { func (c *fakeBlockChain) GetChainParser() bchain.BlockChainParser {
return c.parser return c.Parser
} }