github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/bchain/coins/eth/ethrpc.go (about)

     1  package eth
     2  
     3  import (
     4  	"blockbook/bchain"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math/big"
     9  	"strconv"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/golang/glog"
    14  	"github.com/juju/errors"
    15  
    16  	ethereum "github.com/ethereum/go-ethereum"
    17  	ethcommon "github.com/ethereum/go-ethereum/common"
    18  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    19  	"github.com/ethereum/go-ethereum/ethclient"
    20  	"github.com/ethereum/go-ethereum/rpc"
    21  )
    22  
    23  // EthereumNet type specifies the type of ethereum network
    24  type EthereumNet uint32
    25  
    26  const (
    27  	// MainNet is production network
    28  	MainNet EthereumNet = 1
    29  	// TestNet is Ropsten test network
    30  	TestNet EthereumNet = 3
    31  )
    32  
    33  type Configuration struct {
    34  	CoinName     string `json:"coin_name"`
    35  	CoinShortcut string `json:"coin_shortcut"`
    36  	RPCURL       string `json:"rpc_url"`
    37  	RPCTimeout   int    `json:"rpc_timeout"`
    38  }
    39  
    40  // EthereumRPC is an interface to JSON-RPC eth service.
    41  type EthereumRPC struct {
    42  	client               *ethclient.Client
    43  	rpc                  *rpc.Client
    44  	timeout              time.Duration
    45  	Parser               *EthereumParser
    46  	Testnet              bool
    47  	Network              string
    48  	Mempool              *bchain.NonUTXOMempool
    49  	bestHeaderMu         sync.Mutex
    50  	bestHeader           *ethtypes.Header
    51  	bestHeaderTime       time.Time
    52  	chanNewBlock         chan *ethtypes.Header
    53  	newBlockSubscription *rpc.ClientSubscription
    54  	chanNewTx            chan ethcommon.Hash
    55  	newTxSubscription    *rpc.ClientSubscription
    56  	ChainConfig          *Configuration
    57  	isETC                bool
    58  }
    59  
    60  // NewEthereumRPC returns new EthRPC instance.
    61  func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
    62  	var err error
    63  	var c Configuration
    64  	err = json.Unmarshal(config, &c)
    65  	if err != nil {
    66  		return nil, errors.Annotatef(err, "Invalid configuration file")
    67  	}
    68  	rc, err := rpc.Dial(c.RPCURL)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	ec := ethclient.NewClient(rc)
    73  
    74  	s := &EthereumRPC{
    75  		client:      ec,
    76  		rpc:         rc,
    77  		ChainConfig: &c,
    78  	}
    79  
    80  	// always create parser
    81  	s.Parser = NewEthereumParser()
    82  	s.timeout = time.Duration(c.RPCTimeout) * time.Second
    83  
    84  	// detect ethereum classic
    85  	s.isETC = s.ChainConfig.CoinName == "Ethereum Classic"
    86  
    87  	// new blocks notifications handling
    88  	// the subscription is done in Initialize
    89  	s.chanNewBlock = make(chan *ethtypes.Header)
    90  	go func() {
    91  		for {
    92  			h, ok := <-s.chanNewBlock
    93  			if !ok {
    94  				break
    95  			}
    96  			glog.V(2).Info("rpc: new block header ", h.Number)
    97  			// update best header to the new header
    98  			s.bestHeaderMu.Lock()
    99  			s.bestHeader = h
   100  			s.bestHeaderTime = time.Now()
   101  			s.bestHeaderMu.Unlock()
   102  			// notify blockbook
   103  			pushHandler(bchain.NotificationNewBlock)
   104  		}
   105  	}()
   106  
   107  	// new mempool transaction notifications handling
   108  	// the subscription is done in Initialize
   109  	s.chanNewTx = make(chan ethcommon.Hash)
   110  	go func() {
   111  		for {
   112  			t, ok := <-s.chanNewTx
   113  			if !ok {
   114  				break
   115  			}
   116  			if glog.V(2) {
   117  				glog.Info("rpc: new tx ", t.Hex())
   118  			}
   119  			pushHandler(bchain.NotificationNewTx)
   120  		}
   121  	}()
   122  
   123  	return s, nil
   124  }
   125  
   126  // Initialize initializes ethereum rpc interface
   127  func (b *EthereumRPC) Initialize() error {
   128  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   129  	defer cancel()
   130  
   131  	id, err := b.client.NetworkID(ctx)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	// parameters for getInfo request
   137  	switch EthereumNet(id.Uint64()) {
   138  	case MainNet:
   139  		b.Testnet = false
   140  		b.Network = "livenet"
   141  		break
   142  	case TestNet:
   143  		b.Testnet = true
   144  		b.Network = "testnet"
   145  		break
   146  	default:
   147  		return errors.Errorf("Unknown network id %v", id)
   148  	}
   149  	glog.Info("rpc: block chain ", b.Network)
   150  
   151  	if b.isETC {
   152  		glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
   153  	} else {
   154  		// subscriptions
   155  		if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
   156  			// invalidate the previous subscription - it is either the first one or there was an error
   157  			b.newBlockSubscription = nil
   158  			ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   159  			defer cancel()
   160  			sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
   161  			if err != nil {
   162  				return nil, errors.Annotatef(err, "EthSubscribe newHeads")
   163  			}
   164  			b.newBlockSubscription = sub
   165  			glog.Info("Subscribed to newHeads")
   166  			return sub, nil
   167  		}); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
   172  		// invalidate the previous subscription - it is either the first one or there was an error
   173  		b.newTxSubscription = nil
   174  		ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   175  		defer cancel()
   176  		sub, err := b.rpc.EthSubscribe(ctx, b.chanNewTx, "newPendingTransactions")
   177  		if err != nil {
   178  			return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions")
   179  		}
   180  		b.newTxSubscription = sub
   181  		glog.Info("Subscribed to newPendingTransactions")
   182  		return sub, nil
   183  	}); err != nil {
   184  		return err
   185  	}
   186  
   187  	// create mempool
   188  	b.Mempool = bchain.NewNonUTXOMempool(b)
   189  
   190  	return nil
   191  }
   192  
   193  // subscribe subscribes notification and tries to resubscribe in case of error
   194  func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error {
   195  	s, err := f()
   196  	if err != nil {
   197  		return err
   198  	}
   199  	go func() {
   200  	Loop:
   201  		for {
   202  			// wait for error in subscription
   203  			e := <-s.Err()
   204  			// nil error means sub.Unsubscribe called, exit goroutine
   205  			if e == nil {
   206  				return
   207  			}
   208  			glog.Error("Subscription error ", e)
   209  			timer := time.NewTimer(time.Second)
   210  			// try in 1 second interval to resubscribe
   211  			for {
   212  				select {
   213  				case e = <-s.Err():
   214  					if e == nil {
   215  						return
   216  					}
   217  				case <-timer.C:
   218  					ns, err := f()
   219  					if err == nil {
   220  						// subscription successful, restart wait for next error
   221  						s = ns
   222  						continue Loop
   223  					}
   224  					timer.Reset(time.Second)
   225  				}
   226  			}
   227  		}
   228  	}()
   229  	return nil
   230  }
   231  
   232  // Shutdown cleans up rpc interface to ethereum
   233  func (b *EthereumRPC) Shutdown(ctx context.Context) error {
   234  	if b.newBlockSubscription != nil {
   235  		b.newBlockSubscription.Unsubscribe()
   236  	}
   237  	if b.newTxSubscription != nil {
   238  		b.newTxSubscription.Unsubscribe()
   239  	}
   240  	if b.rpc != nil {
   241  		b.rpc.Close()
   242  	}
   243  	close(b.chanNewBlock)
   244  	glog.Info("rpc: shutdown")
   245  	return nil
   246  }
   247  
   248  func (b *EthereumRPC) IsTestnet() bool {
   249  	return b.Testnet
   250  }
   251  
   252  func (b *EthereumRPC) GetNetworkName() string {
   253  	return b.Network
   254  }
   255  
   256  func (b *EthereumRPC) GetCoinName() string {
   257  	return b.ChainConfig.CoinName
   258  }
   259  
   260  func (b *EthereumRPC) GetSubversion() string {
   261  	return ""
   262  }
   263  
   264  // GetChainInfo returns information about the connected backend
   265  func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
   266  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   267  	defer cancel()
   268  	id, err := b.client.NetworkID(ctx)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	rv := &bchain.ChainInfo{}
   273  	idi := int(id.Uint64())
   274  	if idi == 1 {
   275  		rv.Chain = "mainnet"
   276  	} else {
   277  		rv.Chain = "testnet " + strconv.Itoa(idi)
   278  	}
   279  	// TODO  - return more information about the chain
   280  	return rv, nil
   281  }
   282  
   283  func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
   284  	b.bestHeaderMu.Lock()
   285  	defer b.bestHeaderMu.Unlock()
   286  	// ETC does not have newBlocks subscription, bestHeader must be updated very often (each 1 second)
   287  	if b.isETC {
   288  		if b.bestHeaderTime.Add(1 * time.Second).Before(time.Now()) {
   289  			b.bestHeader = nil
   290  		}
   291  	}
   292  	if b.bestHeader == nil {
   293  		var err error
   294  		ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   295  		defer cancel()
   296  		b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
   297  		if err != nil {
   298  			return nil, err
   299  		}
   300  		b.bestHeaderTime = time.Now()
   301  	}
   302  	return b.bestHeader, nil
   303  }
   304  
   305  func (b *EthereumRPC) GetBestBlockHash() (string, error) {
   306  	h, err := b.getBestHeader()
   307  	if err != nil {
   308  		return "", err
   309  	}
   310  	return ethHashToHash(h.Hash()), nil
   311  }
   312  
   313  func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) {
   314  	h, err := b.getBestHeader()
   315  	if err != nil {
   316  		return 0, err
   317  	}
   318  	// TODO - can it grow over 2^32 ?
   319  	return uint32(h.Number.Uint64()), nil
   320  }
   321  
   322  func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
   323  	var n big.Int
   324  	n.SetUint64(uint64(height))
   325  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   326  	defer cancel()
   327  	h, err := b.client.HeaderByNumber(ctx, &n)
   328  	if err != nil {
   329  		if err == ethereum.NotFound {
   330  			return "", bchain.ErrBlockNotFound
   331  		}
   332  		return "", errors.Annotatef(err, "height %v", height)
   333  	}
   334  	return ethHashToHash(h.Hash()), nil
   335  }
   336  
   337  func (b *EthereumRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) {
   338  	hn := h.Number.Uint64()
   339  	c, err := b.computeConfirmations(hn)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	return &bchain.BlockHeader{
   344  		Hash:          ethHashToHash(h.Hash()),
   345  		Height:        uint32(hn),
   346  		Confirmations: int(c),
   347  		Time:          int64(h.Time.Uint64()),
   348  		// Next
   349  		// Prev
   350  	}, nil
   351  }
   352  
   353  func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
   354  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   355  	defer cancel()
   356  	h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash))
   357  	if err != nil {
   358  		if err == ethereum.NotFound {
   359  			return nil, bchain.ErrBlockNotFound
   360  		}
   361  		return nil, errors.Annotatef(err, "hash %v", hash)
   362  	}
   363  	return b.ethHeaderToBlockHeader(h)
   364  }
   365  
   366  func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
   367  	bh, err := b.getBestHeader()
   368  	if err != nil {
   369  		return 0, err
   370  	}
   371  	bn := bh.Number.Uint64()
   372  	// transaction in the best block has 1 confirmation
   373  	return uint32(bn - n + 1), nil
   374  }
   375  
   376  // GetBlock returns block with given hash or height, hash has precedence if both passed
   377  func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
   378  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   379  	defer cancel()
   380  	var raw json.RawMessage
   381  	var err error
   382  	if hash != "" {
   383  		err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), true)
   384  	} else {
   385  
   386  		err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), true)
   387  	}
   388  	if err != nil {
   389  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   390  	} else if len(raw) == 0 {
   391  		return nil, bchain.ErrBlockNotFound
   392  	}
   393  	// Decode header and transactions.
   394  	var head *ethtypes.Header
   395  	var body rpcBlock
   396  	if err := json.Unmarshal(raw, &head); err != nil {
   397  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   398  	}
   399  	if head == nil {
   400  		return nil, bchain.ErrBlockNotFound
   401  	}
   402  	if err := json.Unmarshal(raw, &body); err != nil {
   403  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   404  	}
   405  	// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
   406  	if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 {
   407  		return nil, errors.Annotatef(fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles"), "hash %v, height %v", hash, height)
   408  	}
   409  	if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 {
   410  		return nil, errors.Annotatef(fmt.Errorf("server returned empty uncle list but block header indicates uncles"), "hash %v, height %v", hash, height)
   411  	}
   412  	if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 {
   413  		return nil, errors.Annotatef(fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions"), "hash %v, height %v", hash, height)
   414  	}
   415  	if head.TxHash != ethtypes.EmptyRootHash && len(body.Transactions) == 0 {
   416  		return nil, errors.Annotatef(fmt.Errorf("server returned empty transaction list but block header indicates transactions"), "hash %v, height %v", hash, height)
   417  	}
   418  	bbh, err := b.ethHeaderToBlockHeader(head)
   419  	if err != nil {
   420  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   421  	}
   422  	// TODO - this is probably not the correct size
   423  	bbh.Size = len(raw)
   424  	btxs := make([]bchain.Tx, len(body.Transactions))
   425  	for i, tx := range body.Transactions {
   426  		btx, err := b.Parser.ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations))
   427  		if err != nil {
   428  			return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String())
   429  		}
   430  		btxs[i] = *btx
   431  	}
   432  	bbk := bchain.Block{
   433  		BlockHeader: *bbh,
   434  		Txs:         btxs,
   435  	}
   436  	return &bbk, nil
   437  }
   438  
   439  // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
   440  func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
   441  	// TODO - implement
   442  	return nil, errors.New("Not implemented yet")
   443  }
   444  
   445  // GetTransactionForMempool returns a transaction by the transaction ID.
   446  // It could be optimized for mempool, i.e. without block time and confirmations
   447  func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
   448  	return b.GetTransaction(txid)
   449  }
   450  
   451  // GetTransaction returns a transaction by the transaction ID.
   452  func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
   453  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   454  	defer cancel()
   455  	var tx *rpcTransaction
   456  	err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
   457  	if err != nil {
   458  		return nil, err
   459  	} else if tx == nil {
   460  		return nil, ethereum.NotFound
   461  	} else if tx.R == "" {
   462  		if !b.isETC {
   463  			return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid)
   464  		} else {
   465  			glog.Warning("server returned transaction without signature, txid ", txid)
   466  		}
   467  	}
   468  	var btx *bchain.Tx
   469  	if tx.BlockNumber == "" {
   470  		// mempool tx
   471  		btx, err = b.Parser.ethTxToTx(tx, 0, 0)
   472  		if err != nil {
   473  			return nil, errors.Annotatef(err, "txid %v", txid)
   474  		}
   475  	} else {
   476  		// non mempool tx - we must read the block header to get the block time
   477  		n, err := ethNumber(tx.BlockNumber)
   478  		if err != nil {
   479  			return nil, errors.Annotatef(err, "txid %v", txid)
   480  		}
   481  		h, err := b.client.HeaderByHash(ctx, *tx.BlockHash)
   482  		if err != nil {
   483  			return nil, errors.Annotatef(err, "txid %v", txid)
   484  		}
   485  		confirmations, err := b.computeConfirmations(uint64(n))
   486  		if err != nil {
   487  			return nil, errors.Annotatef(err, "txid %v", txid)
   488  		}
   489  		btx, err = b.Parser.ethTxToTx(tx, h.Time.Int64(), confirmations)
   490  		if err != nil {
   491  			return nil, errors.Annotatef(err, "txid %v", txid)
   492  		}
   493  	}
   494  	return btx, nil
   495  }
   496  
   497  // GetTransactionSpecific returns json as returned by backend, with all coin specific data
   498  func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
   499  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   500  	defer cancel()
   501  	var tx json.RawMessage
   502  	err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
   503  	if err != nil {
   504  		return nil, err
   505  	} else if tx == nil {
   506  		return nil, ethereum.NotFound
   507  	}
   508  	return tx, nil
   509  }
   510  
   511  type rpcMempoolBlock struct {
   512  	Transactions []string `json:"transactions"`
   513  }
   514  
   515  func (b *EthereumRPC) GetMempool() ([]string, error) {
   516  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   517  	defer cancel()
   518  	var raw json.RawMessage
   519  	var err error
   520  	err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", "pending", false)
   521  	if err != nil {
   522  		return nil, err
   523  	} else if len(raw) == 0 {
   524  		return nil, bchain.ErrBlockNotFound
   525  	}
   526  	var body rpcMempoolBlock
   527  	if err := json.Unmarshal(raw, &body); err != nil {
   528  		return nil, err
   529  	}
   530  	return body.Transactions, nil
   531  }
   532  
   533  // EstimateFee returns fee estimation
   534  func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) {
   535  	return b.EstimateSmartFee(blocks, true)
   536  }
   537  
   538  // EstimateSmartFee returns fee estimation
   539  func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
   540  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   541  	defer cancel()
   542  	// TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed
   543  	a := ethcommon.HexToAddress("0x1234567890123456789012345678901234567890")
   544  	msg := ethereum.CallMsg{
   545  		To: &a,
   546  	}
   547  	g, err := b.client.EstimateGas(ctx, msg)
   548  	var r big.Int
   549  	if err != nil {
   550  		return r, err
   551  	}
   552  	r.SetUint64(g)
   553  	return r, nil
   554  }
   555  
   556  // SendRawTransaction sends raw transaction.
   557  func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
   558  	ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
   559  	defer cancel()
   560  	var raw json.RawMessage
   561  	err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex)
   562  	if err != nil {
   563  		return "", err
   564  	} else if len(raw) == 0 {
   565  		return "", errors.New("SendRawTransaction: failed")
   566  	}
   567  	var result string
   568  	if err := json.Unmarshal(raw, &result); err != nil {
   569  		return "", errors.Annotatef(err, "raw result %v", raw)
   570  	}
   571  	if result == "" {
   572  		return "", errors.New("SendRawTransaction: failed, empty result")
   573  	}
   574  	return result, nil
   575  }
   576  
   577  func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
   578  	return b.Mempool.Resync(onNewTxAddr)
   579  }
   580  
   581  // GetMempoolTransactions returns slice of mempool transactions for given address
   582  func (b *EthereumRPC) GetMempoolTransactions(address string) ([]string, error) {
   583  	return b.Mempool.GetTransactions(address)
   584  }
   585  
   586  // GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
   587  func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
   588  	return b.Mempool.GetAddrDescTransactions(addrDesc)
   589  }
   590  
   591  func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
   592  	return nil, errors.New("GetMempoolEntry: not implemented")
   593  }
   594  
   595  func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser {
   596  	return b.Parser
   597  }