2018-03-15 01:19:02 -06:00
package eth
import (
"blockbook/bchain"
2018-03-15 04:38:31 -06:00
"context"
2018-03-24 17:30:09 -06:00
"encoding/hex"
2018-03-19 05:05:16 -06:00
"encoding/json"
2018-03-22 08:56:21 -06:00
"fmt"
2018-03-19 10:34:51 -06:00
"math/big"
2018-03-22 08:56:21 -06:00
"strconv"
2018-03-20 07:43:15 -06:00
"sync"
2018-03-15 01:19:02 -06:00
"time"
2018-03-15 04:38:31 -06:00
"github.com/golang/glog"
2018-03-19 05:05:16 -06:00
"github.com/juju/errors"
2018-03-15 04:38:31 -06:00
2018-03-22 08:56:21 -06:00
ethereum "github.com/ethereum/go-ethereum"
2018-03-19 10:34:51 -06:00
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
2018-03-15 01:19:02 -06:00
"github.com/ethereum/go-ethereum/ethclient"
2018-03-22 08:56:21 -06:00
"github.com/ethereum/go-ethereum/rpc"
2018-03-15 01:19:02 -06:00
)
2018-03-19 10:34:51 -06:00
type EthereumNet uint32
const (
MainNet EthereumNet = 1
TestNet EthereumNet = 3
)
2018-03-15 01:19:02 -06:00
// EthRPC is an interface to JSON-RPC eth service.
type EthRPC struct {
2018-03-28 02:25:08 -06:00
client * ethclient . Client
rpc * rpc . Client
timeout time . Duration
rpcURL string
Parser * EthParser
Testnet bool
Network string
2018-03-29 09:30:12 -06:00
Mempool * bchain . NonUTXOMempool
2018-03-28 02:25:08 -06:00
bestHeaderMu sync . Mutex
bestHeader * ethtypes . Header
chanNewBlock chan * ethtypes . Header
newBlockSubscription * rpc . ClientSubscription
2018-03-15 01:19:02 -06:00
}
2018-03-19 05:05:16 -06:00
type configuration struct {
RPCURL string ` json:"rpcURL" `
RPCTimeout int ` json:"rpcTimeout" `
}
2018-03-15 01:19:02 -06:00
// NewEthRPC returns new EthRPC instance.
2018-03-27 15:39:06 -06:00
func NewEthRPC ( config json . RawMessage , pushHandler func ( bchain . NotificationType ) ) ( bchain . BlockChain , error ) {
2018-03-19 05:05:16 -06:00
var err error
var c configuration
err = json . Unmarshal ( config , & c )
if err != nil {
return nil , errors . Annotatef ( err , "Invalid configuration file" )
}
2018-03-22 08:56:21 -06:00
rc , err := rpc . Dial ( c . RPCURL )
2018-03-15 01:19:02 -06:00
if err != nil {
return nil , err
}
2018-03-22 08:56:21 -06:00
ec := ethclient . NewClient ( rc )
2018-03-15 01:19:02 -06:00
s := & EthRPC {
2018-03-21 08:47:31 -06:00
client : ec ,
2018-03-22 08:56:21 -06:00
rpc : rc ,
2018-03-21 08:47:31 -06:00
rpcURL : c . RPCURL ,
2018-03-15 01:19:02 -06:00
}
// always create parser
s . Parser = & EthParser { }
2018-03-19 10:34:51 -06:00
s . timeout = time . Duration ( c . RPCTimeout ) * time . Second
2018-03-15 01:19:02 -06:00
2018-03-28 02:25:08 -06:00
// new blocks notifications handling
// the subscription is done in Initialize
s . chanNewBlock = make ( chan * ethtypes . Header )
go func ( ) {
for {
h , ok := <- s . chanNewBlock
if ! ok {
break
}
glog . V ( 2 ) . Info ( "rpc: new block header " , h . Number )
// update best header to the new header
s . bestHeaderMu . Lock ( )
s . bestHeader = h
s . bestHeaderMu . Unlock ( )
// notify blockbook
pushHandler ( bchain . NotificationNewBlock )
}
} ( )
2018-03-21 08:47:31 -06:00
return s , nil
}
2018-03-28 02:25:08 -06:00
// Initialize initializes ethereum rpc interface
2018-03-21 08:47:31 -06:00
func ( b * EthRPC ) Initialize ( ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
2018-03-19 10:34:51 -06:00
defer cancel ( )
2018-03-21 08:47:31 -06:00
id , err := b . client . NetworkID ( ctx )
2018-03-15 04:38:31 -06:00
if err != nil {
2018-03-21 08:47:31 -06:00
return err
2018-03-15 04:38:31 -06:00
}
2018-03-19 10:34:51 -06:00
// parameters for getInfo request
switch EthereumNet ( id . Uint64 ( ) ) {
case MainNet :
2018-03-21 08:47:31 -06:00
b . Testnet = false
b . Network = "livenet"
2018-03-19 10:34:51 -06:00
break
case TestNet :
2018-03-21 08:47:31 -06:00
b . Testnet = true
b . Network = "testnet"
2018-03-19 10:34:51 -06:00
break
default :
2018-03-21 08:47:31 -06:00
return errors . Errorf ( "Unknown network id %v" , id )
2018-03-19 10:34:51 -06:00
}
2018-03-21 08:47:31 -06:00
glog . Info ( "rpc: block chain " , b . Network )
2018-03-15 01:19:02 -06:00
2018-03-28 02:25:08 -06:00
// Subscribe to new blocks
sub , err := b . rpc . EthSubscribe ( ctx , b . chanNewBlock , "newHeads" )
if err != nil {
return errors . Annotatef ( err , "EthSubscribe newHeads" )
}
b . newBlockSubscription = sub
2018-03-29 09:30:12 -06:00
b . Mempool = bchain . NewNonUTXOMempool ( b )
2018-03-21 08:47:31 -06:00
return nil
2018-03-15 01:19:02 -06:00
}
2018-03-28 02:25:08 -06:00
// Shutdown cleans up rpc interface to ethereum
2018-03-19 05:05:16 -06:00
func ( b * EthRPC ) Shutdown ( ) error {
2018-03-28 02:25:08 -06:00
if b . newBlockSubscription != nil {
b . newBlockSubscription . Unsubscribe ( )
}
if b . rpc != nil {
b . rpc . Close ( )
}
close ( b . chanNewBlock )
glog . Info ( "rpc: shutdown" )
2018-03-19 05:05:16 -06:00
return nil
}
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) IsTestnet ( ) bool {
2018-03-19 10:36:18 -06:00
return b . Testnet
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetNetworkName ( ) string {
2018-03-19 10:36:18 -06:00
return b . Network
}
func ( b * EthRPC ) getBestHeader ( ) ( * ethtypes . Header , error ) {
2018-03-20 07:43:15 -06:00
b . bestHeaderMu . Lock ( )
defer b . bestHeaderMu . Unlock ( )
2018-03-19 10:36:18 -06:00
if b . bestHeader == nil {
var err error
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
b . bestHeader , err = b . client . HeaderByNumber ( ctx , nil )
if err != nil {
return nil , err
}
}
return b . bestHeader , nil
2018-03-15 01:19:02 -06:00
}
2018-03-20 07:43:15 -06:00
func ethHashToHash ( h ethcommon . Hash ) string {
return h . Hex ( ) [ 2 : ]
}
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) GetBestBlockHash ( ) ( string , error ) {
2018-03-19 10:36:18 -06:00
h , err := b . getBestHeader ( )
if err != nil {
return "" , err
}
2018-03-20 07:43:15 -06:00
return ethHashToHash ( h . Hash ( ) ) , nil
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetBestBlockHeight ( ) ( uint32 , error ) {
2018-03-19 10:36:18 -06:00
h , err := b . getBestHeader ( )
if err != nil {
return 0 , err
}
// TODO - can it grow over 2^32 ?
return uint32 ( h . Number . Uint64 ( ) ) , nil
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetBlockHash ( height uint32 ) ( string , error ) {
2018-03-19 10:36:18 -06:00
var n big . Int
n . SetUint64 ( uint64 ( height ) )
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
h , err := b . client . HeaderByNumber ( ctx , & n )
if err != nil {
2018-03-26 09:47:46 -06:00
if err == ethereum . NotFound {
return "" , bchain . ErrBlockNotFound
}
2018-03-26 10:14:26 -06:00
return "" , errors . Annotatef ( err , "height %v" , height )
2018-03-19 10:36:18 -06:00
}
2018-03-20 07:43:15 -06:00
return ethHashToHash ( h . Hash ( ) ) , nil
2018-03-15 01:19:02 -06:00
}
2018-03-20 07:43:15 -06:00
func ( b * EthRPC ) ethHeaderToBlockHeader ( h * ethtypes . Header ) ( * bchain . BlockHeader , error ) {
2018-03-22 08:56:21 -06:00
hn := h . Number . Uint64 ( )
c , err := b . computeConfirmations ( hn )
2018-03-19 10:36:18 -06:00
if err != nil {
return nil , err
}
2018-03-20 07:43:15 -06:00
return & bchain . BlockHeader {
Hash : ethHashToHash ( h . Hash ( ) ) ,
2018-03-22 08:56:21 -06:00
Height : uint32 ( hn ) ,
Confirmations : int ( c ) ,
2018-03-20 07:43:15 -06:00
// Next
// Prev
} , nil
}
func ( b * EthRPC ) GetBlockHeader ( hash string ) ( * bchain . BlockHeader , error ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
h , err := b . client . HeaderByHash ( ctx , ethcommon . HexToHash ( hash ) )
if err != nil {
2018-03-26 09:47:46 -06:00
if err == ethereum . NotFound {
return nil , bchain . ErrBlockNotFound
}
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "hash %v" , hash )
2018-03-19 10:36:18 -06:00
}
2018-03-20 07:43:15 -06:00
return b . ethHeaderToBlockHeader ( h )
2018-03-15 01:19:02 -06:00
}
2018-03-22 08:56:21 -06:00
func ( b * EthRPC ) computeConfirmations ( n uint64 ) ( uint32 , error ) {
bh , err := b . getBestHeader ( )
if err != nil {
return 0 , err
}
bn := bh . Number . Uint64 ( )
return uint32 ( bn - n ) , nil
}
type rpcTransaction struct {
2018-03-24 17:30:09 -06:00
AccountNonce string ` json:"nonce" gencodec:"required" `
Price 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" `
Payload string ` json:"input" gencodec:"required" `
Hash ethcommon . Hash ` json:"hash" rlp:"-" `
BlockNumber string
2018-03-22 08:56:21 -06:00
BlockHash ethcommon . Hash
2018-03-24 17:30:09 -06:00
From string
2018-03-22 08:56:21 -06:00
TransactionIndex string ` json:"transactionIndex" `
2018-03-24 17:30:09 -06:00
// Signature values
V string ` json:"v" gencodec:"required" `
R string ` json:"r" gencodec:"required" `
S string ` json:"s" gencodec:"required" `
2018-03-22 08:56:21 -06:00
}
type rpcBlock struct {
Hash ethcommon . Hash ` json:"hash" `
Transactions [ ] rpcTransaction ` json:"transactions" `
UncleHashes [ ] ethcommon . Hash ` json:"uncles" `
}
2018-03-24 17:30:09 -06:00
func ethNumber ( n string ) ( int64 , error ) {
if len ( n ) > 2 {
return strconv . ParseInt ( n [ 2 : ] , 16 , 64 )
}
return 0 , errors . Errorf ( "Not a number: '%v'" , n )
}
func ethTxToTx ( tx * rpcTransaction , blocktime int64 , confirmations uint32 ) ( * bchain . Tx , error ) {
txid := ethHashToHash ( tx . Hash )
2018-03-29 07:47:41 -06:00
var fa , ta [ ] string
if len ( tx . From ) > 2 {
fa = [ ] string { tx . From [ 2 : ] }
2018-03-22 08:56:21 -06:00
}
2018-03-24 17:30:09 -06:00
if len ( tx . To ) > 2 {
2018-03-29 07:47:41 -06:00
ta = [ ] string { tx . To [ 2 : ] }
2018-03-24 17:30:09 -06:00
}
b , err := json . Marshal ( tx )
if err != nil {
return nil , err
2018-03-22 08:56:21 -06:00
}
2018-03-24 17:30:09 -06:00
h := hex . EncodeToString ( b )
2018-03-22 08:56:21 -06:00
return & bchain . Tx {
Blocktime : blocktime ,
Confirmations : confirmations ,
2018-03-24 17:30:09 -06:00
Hex : h ,
2018-03-22 08:56:21 -06:00
// LockTime
Time : blocktime ,
Txid : txid ,
Vin : [ ] bchain . Vin {
{
2018-03-29 07:47:41 -06:00
Addresses : fa ,
2018-03-22 08:56:21 -06:00
// Coinbase
// ScriptSig
// Sequence
// Txid
// Vout
} ,
} ,
Vout : [ ] bchain . Vout {
{
2018-03-29 07:47:41 -06:00
N : 0 , // there is always up to one To address
2018-03-26 15:42:35 -06:00
// Value - cannot set, it does not fit precisely to float64
2018-03-22 08:56:21 -06:00
ScriptPubKey : bchain . ScriptPubKey {
// Hex
2018-03-29 07:47:41 -06:00
Addresses : ta ,
2018-03-22 08:56:21 -06:00
} ,
} ,
} ,
} , nil
}
2018-03-27 08:34:58 -06:00
// GetBlock returns block with given hash or height, hash has precedence if both passed
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) GetBlock ( hash string , height uint32 ) ( * bchain . Block , error ) {
2018-03-20 07:43:15 -06:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
2018-03-22 08:56:21 -06:00
var raw json . RawMessage
2018-03-26 05:57:40 -06:00
var err error
if hash != "" {
err = b . rpc . CallContext ( ctx , & raw , "eth_getBlockByHash" , ethcommon . HexToHash ( hash ) , true )
} else {
err = b . rpc . CallContext ( ctx , & raw , "eth_getBlockByNumber" , fmt . Sprintf ( "%#x" , height ) , true )
}
2018-03-20 07:43:15 -06:00
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
} else if len ( raw ) == 0 {
2018-03-26 09:47:46 -06:00
return nil , bchain . ErrBlockNotFound
2018-03-22 08:56:21 -06:00
}
// Decode header and transactions.
var head * ethtypes . Header
var body rpcBlock
if err := json . Unmarshal ( raw , & head ) ; err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
}
2018-03-26 09:47:46 -06:00
if head == nil {
return nil , bchain . ErrBlockNotFound
}
2018-03-22 08:56:21 -06:00
if err := json . Unmarshal ( raw , & body ) ; err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
}
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head . UncleHash == ethtypes . EmptyUncleHash && len ( body . UncleHashes ) > 0 {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( fmt . Errorf ( "server returned non-empty uncle list but block header indicates no uncles" ) , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
}
if head . UncleHash != ethtypes . EmptyUncleHash && len ( body . UncleHashes ) == 0 {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( fmt . Errorf ( "server returned empty uncle list but block header indicates uncles" ) , "hash %v, height %v" , hash , height )
2018-03-20 07:43:15 -06:00
}
2018-03-22 08:56:21 -06:00
if head . TxHash == ethtypes . EmptyRootHash && len ( body . Transactions ) > 0 {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( fmt . Errorf ( "server returned non-empty transaction list but block header indicates no transactions" ) , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
}
if head . TxHash != ethtypes . EmptyRootHash && len ( body . Transactions ) == 0 {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( fmt . Errorf ( "server returned empty transaction list but block header indicates transactions" ) , "hash %v, height %v" , hash , height )
2018-03-22 08:56:21 -06:00
}
bbh , err := b . ethHeaderToBlockHeader ( head )
btxs := make ( [ ] bchain . Tx , len ( body . Transactions ) )
for i , tx := range body . Transactions {
btx , err := ethTxToTx ( & tx , int64 ( head . Time . Uint64 ( ) ) , uint32 ( bbh . Confirmations ) )
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "hash %v, height %v, txid %v" , hash , height , tx . Hash . String ( ) )
2018-03-20 07:43:15 -06:00
}
2018-03-22 08:56:21 -06:00
btxs [ i ] = * btx
2018-03-20 07:43:15 -06:00
}
bbk := bchain . Block {
BlockHeader : * bbh ,
Txs : btxs ,
}
return & bbk , nil
2018-03-15 01:19:02 -06:00
}
2018-03-27 08:34:58 -06:00
// GetTransaction returns a transaction by the transaction ID.
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) GetTransaction ( txid string ) ( * bchain . Tx , error ) {
2018-03-20 07:43:15 -06:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
2018-03-24 17:30:09 -06:00
var tx * rpcTransaction
err := b . rpc . CallContext ( ctx , & tx , "eth_getTransactionByHash" , ethcommon . HexToHash ( txid ) )
2018-03-20 07:43:15 -06:00
if err != nil {
return nil , err
2018-03-24 17:30:09 -06:00
} else if tx == nil {
2018-03-22 08:56:21 -06:00
return nil , ethereum . NotFound
2018-03-24 17:30:09 -06:00
} else if tx . R == "" {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( fmt . Errorf ( "server returned transaction without signature" ) , "txid %v" , txid )
2018-03-20 07:43:15 -06:00
}
2018-03-22 08:56:21 -06:00
var btx * bchain . Tx
2018-03-24 17:30:09 -06:00
if tx . BlockNumber == "" {
2018-03-22 08:56:21 -06:00
// mempool tx
2018-03-24 17:30:09 -06:00
btx , err = ethTxToTx ( tx , 0 , 0 )
2018-03-22 08:56:21 -06:00
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "txid %v" , txid )
2018-03-22 08:56:21 -06:00
}
} else {
// non mempool tx - we must read the block header to get the block time
2018-03-24 17:30:09 -06:00
n , err := ethNumber ( tx . BlockNumber )
2018-03-22 08:56:21 -06:00
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "txid %v" , txid )
2018-03-22 08:56:21 -06:00
}
2018-03-24 17:30:09 -06:00
h , err := b . client . HeaderByHash ( ctx , tx . BlockHash )
2018-03-22 08:56:21 -06:00
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "txid %v" , txid )
2018-03-22 08:56:21 -06:00
}
confirmations , err := b . computeConfirmations ( uint64 ( n ) )
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "txid %v" , txid )
2018-03-22 08:56:21 -06:00
}
2018-03-24 17:30:09 -06:00
btx , err = ethTxToTx ( tx , h . Time . Int64 ( ) , confirmations )
2018-03-22 08:56:21 -06:00
if err != nil {
2018-03-26 10:14:26 -06:00
return nil , errors . Annotatef ( err , "txid %v" , txid )
2018-03-22 08:56:21 -06:00
}
2018-03-20 07:43:15 -06:00
}
2018-03-22 08:56:21 -06:00
return btx , nil
}
2018-03-29 09:30:12 -06:00
type rpcMempoolBlock struct {
Transactions [ ] string ` json:"transactions" `
}
2018-03-22 08:56:21 -06:00
func ( b * EthRPC ) GetMempool ( ) ( [ ] string , error ) {
2018-03-29 09:30:12 -06:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
var raw json . RawMessage
var err error
err = b . rpc . CallContext ( ctx , & raw , "eth_getBlockByNumber" , "pending" , false )
if err != nil {
return nil , err
} else if len ( raw ) == 0 {
return nil , bchain . ErrBlockNotFound
}
var body rpcMempoolBlock
if err := json . Unmarshal ( raw , & body ) ; err != nil {
return nil , err
}
return body . Transactions , nil
2018-03-15 01:19:02 -06:00
}
2018-03-27 08:34:58 -06:00
// EstimateFee returns fee estimation.
func ( b * EthRPC ) EstimateFee ( blocks int ) ( float64 , error ) {
return b . EstimateSmartFee ( blocks , true )
}
// EstimateSmartFee returns fee estimation.
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) EstimateSmartFee ( blocks int , conservative bool ) ( float64 , error ) {
2018-03-27 08:34:58 -06:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , b . timeout )
defer cancel ( )
// TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed
a := ethcommon . HexToAddress ( "0x1234567890123456789012345678901234567890" )
msg := ethereum . CallMsg {
To : & a ,
}
g , err := b . client . EstimateGas ( ctx , msg )
if err != nil {
return 0 , err
}
return float64 ( g ) , nil
2018-03-15 01:19:02 -06:00
}
2018-03-27 08:34:58 -06:00
// SendRawTransaction sends raw transaction.
2018-03-15 01:19:02 -06:00
func ( b * EthRPC ) SendRawTransaction ( tx string ) ( string , error ) {
2018-03-28 02:25:08 -06:00
return "" , errors . New ( "SendRawTransaction: not implemented" )
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) ResyncMempool ( onNewTxAddr func ( txid string , addr string ) ) error {
2018-03-29 09:30:12 -06:00
return b . Mempool . Resync ( onNewTxAddr )
2018-03-15 01:19:02 -06:00
}
2018-03-20 17:32:29 -06:00
func ( b * EthRPC ) GetMempoolTransactions ( address string ) ( [ ] string , error ) {
2018-03-29 09:30:12 -06:00
return b . Mempool . GetTransactions ( address )
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetMempoolSpentOutput ( outputTxid string , vout uint32 ) string {
2018-03-28 02:25:08 -06:00
return ""
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetMempoolEntry ( txid string ) ( * bchain . MempoolEntry , error ) {
2018-03-28 02:25:08 -06:00
return nil , errors . New ( "ResyncMempool: not implemented" )
2018-03-15 01:19:02 -06:00
}
func ( b * EthRPC ) GetChainParser ( ) bchain . BlockChainParser {
2018-03-19 10:36:18 -06:00
return b . Parser
2018-03-15 01:19:02 -06:00
}