arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/eth/ethereum.go
eth/ethereum.goRaw
  1package eth
  2
  3import (
  4	"context"
  5	"fmt"
  6	"math/big"
  7	"time"
  8
  9	"github.com/ethereum/go-ethereum"
 10	"github.com/ethereum/go-ethereum/accounts"
 11	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 12	ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
 13	ethCommon "github.com/ethereum/go-ethereum/common"
 14	"github.com/ethereum/go-ethereum/core/types"
 15	"github.com/ethereum/go-ethereum/ethclient"
 16	"github.com/hermeznetwork/hermez-node/common"
 17	HEZ "github.com/hermeznetwork/hermez-node/eth/contracts/tokenHEZ"
 18	"github.com/hermeznetwork/hermez-node/log"
 19	"github.com/hermeznetwork/tracerr"
 20)
 21
 22// ERC20Consts are the constants defined in a particular ERC20 Token instance
 23type ERC20Consts struct {
 24	Name     string
 25	Symbol   string
 26	Decimals uint64
 27}
 28
 29// EthereumInterface is the interface to Ethereum
 30type EthereumInterface interface {
 31	EthLastBlock() (int64, error)
 32	// EthHeaderByNumber(context.Context, *big.Int) (*types.Header, error)
 33	EthBlockByNumber(context.Context, int64) (*common.Block, error)
 34	EthAddress() (*ethCommon.Address, error)
 35	EthTransactionReceipt(context.Context, ethCommon.Hash) (*types.Receipt, error)
 36
 37	EthERC20Consts(ethCommon.Address) (*ERC20Consts, error)
 38	EthChainID() (*big.Int, error)
 39
 40	EthPendingNonceAt(ctx context.Context, account ethCommon.Address) (uint64, error)
 41	EthNonceAt(ctx context.Context, account ethCommon.Address, blockNumber *big.Int) (uint64, error)
 42	EthSuggestGasPrice(ctx context.Context) (*big.Int, error)
 43	EthKeyStore() *ethKeystore.KeyStore
 44	EthCall(ctx context.Context, tx *types.Transaction, blockNum *big.Int) ([]byte, error)
 45}
 46
 47var (
 48	// ErrAccountNil is used when the calls can not be made because the account is nil
 49	ErrAccountNil = fmt.Errorf("Authorized calls can't be made when the account is nil")
 50	// ErrBlockHashMismatchEvent is used when there's a block hash mismatch
 51	// beetween different events of the same block
 52	ErrBlockHashMismatchEvent = fmt.Errorf("block hash mismatch in event log")
 53)
 54
 55const (
 56	// default values
 57	defaultCallGasLimit = 300000
 58	defaultGasPriceDiv  = 100
 59)
 60
 61// EthereumConfig defines the configuration parameters of the EthereumClient
 62type EthereumConfig struct {
 63	CallGasLimit uint64
 64	GasPriceDiv  uint64
 65}
 66
 67// EthereumClient is an ethereum client to call Smart Contract methods and check blockchain
 68// information.
 69type EthereumClient struct {
 70	client  *ethclient.Client
 71	chainID *big.Int
 72	account *accounts.Account
 73	ks      *ethKeystore.KeyStore
 74	config  *EthereumConfig
 75	opts    *bind.CallOpts
 76}
 77
 78// NewEthereumClient creates a EthereumClient instance.  The account is not mandatory (it can
 79// be nil).  If the account is nil, CallAuth will fail with ErrAccountNil.
 80func NewEthereumClient(client *ethclient.Client, account *accounts.Account,
 81	ks *ethKeystore.KeyStore, config *EthereumConfig) (*EthereumClient, error) {
 82	if config == nil {
 83		config = &EthereumConfig{
 84			CallGasLimit: defaultCallGasLimit,
 85			GasPriceDiv:  defaultGasPriceDiv,
 86		}
 87	}
 88	c := &EthereumClient{
 89		client:  client,
 90		account: account,
 91		ks:      ks,
 92		config:  config,
 93		opts:    newCallOpts(),
 94	}
 95	chainID, err := c.EthChainID()
 96	if err != nil {
 97		return nil, tracerr.Wrap(err)
 98	}
 99	c.chainID = chainID
100	return c, nil
101}
102
103// EthChainID returns the ChainID of the ethereum network
104func (c *EthereumClient) EthChainID() (*big.Int, error) {
105	chainID, err := c.client.ChainID(context.Background())
106	if err != nil {
107		return nil, tracerr.Wrap(err)
108	}
109	return chainID, nil
110}
111
112// BalanceAt retieves information about the default account
113func (c *EthereumClient) BalanceAt(addr ethCommon.Address) (*big.Int, error) {
114	return c.client.BalanceAt(context.TODO(), addr, nil)
115}
116
117// Account returns the underlying ethereum account
118func (c *EthereumClient) Account() *accounts.Account {
119	return c.account
120}
121
122// EthAddress returns the ethereum address of the account loaded into the EthereumClient
123func (c *EthereumClient) EthAddress() (*ethCommon.Address, error) {
124	if c.account == nil {
125		return nil, tracerr.Wrap(ErrAccountNil)
126	}
127	return &c.account.Address, nil
128}
129
130// EthSuggestGasPrice retrieves the currently suggested gas price to allow a
131// timely execution of a transaction.
132func (c *EthereumClient) EthSuggestGasPrice(ctx context.Context) (*big.Int, error) {
133	return c.client.SuggestGasPrice(ctx)
134}
135
136// EthKeyStore returns the keystore in the EthereumClient
137func (c *EthereumClient) EthKeyStore() *ethKeystore.KeyStore {
138	return c.ks
139}
140
141// NewAuth builds a new auth object to make a transaction
142func (c *EthereumClient) NewAuth() (*bind.TransactOpts, error) {
143	if c.account == nil {
144		return nil, tracerr.Wrap(ErrAccountNil)
145	}
146
147	gasPrice, err := c.client.SuggestGasPrice(context.Background())
148	if err != nil {
149		return nil, tracerr.Wrap(err)
150	}
151	inc := new(big.Int).Set(gasPrice)
152	inc.Div(inc, new(big.Int).SetUint64(c.config.GasPriceDiv))
153	gasPrice.Add(gasPrice, inc)
154	log.Debugw("Transaction metadata", "gasPrice", gasPrice)
155
156	auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, c.chainID)
157	if err != nil {
158		return nil, tracerr.Wrap(err)
159	}
160	auth.Value = big.NewInt(0) // in wei
161	auth.GasLimit = c.config.CallGasLimit
162	auth.GasPrice = gasPrice
163
164	return auth, nil
165}
166
167// CallAuth performs a Smart Contract method call that requires authorization.
168// This call requires a valid account with Ether that can be spend during the
169// call.
170func (c *EthereumClient) CallAuth(gasLimit uint64,
171	fn func(*ethclient.Client, *bind.TransactOpts) (*types.Transaction, error)) (*types.Transaction,
172	error) {
173	if c.account == nil {
174		return nil, tracerr.Wrap(ErrAccountNil)
175	}
176
177	gasPrice, err := c.client.SuggestGasPrice(context.Background())
178	if err != nil {
179		return nil, tracerr.Wrap(err)
180	}
181	inc := new(big.Int).Set(gasPrice)
182	inc.Div(inc, new(big.Int).SetUint64(c.config.GasPriceDiv))
183	gasPrice.Add(gasPrice, inc)
184	log.Debugw("Transaction metadata", "gasPrice", gasPrice)
185
186	auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, c.chainID)
187	if err != nil {
188		return nil, tracerr.Wrap(err)
189	}
190	auth.Value = big.NewInt(0) // in wei
191	if gasLimit == 0 {
192		auth.GasLimit = c.config.CallGasLimit // in units
193	} else {
194		auth.GasLimit = gasLimit // in units
195	}
196	auth.GasPrice = gasPrice
197
198	tx, err := fn(c.client, auth)
199	if tx != nil {
200		log.Debugw("Transaction", "tx", tx.Hash().Hex(), "nonce", tx.Nonce())
201	}
202	return tx, tracerr.Wrap(err)
203}
204
205// ContractData contains the contract data
206type ContractData struct {
207	Address ethCommon.Address
208	Tx      *types.Transaction
209	Receipt *types.Receipt
210}
211
212// Call performs a read only Smart Contract method call.
213func (c *EthereumClient) Call(fn func(*ethclient.Client) error) error {
214	return fn(c.client)
215}
216
217// EthTransactionReceipt returns the transaction receipt of the given txHash
218func (c *EthereumClient) EthTransactionReceipt(ctx context.Context,
219	txHash ethCommon.Hash) (*types.Receipt, error) {
220	return c.client.TransactionReceipt(ctx, txHash)
221}
222
223// EthLastBlock returns the last block number in the blockchain
224func (c *EthereumClient) EthLastBlock() (int64, error) {
225	ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
226	defer cancel()
227	header, err := c.client.HeaderByNumber(ctx, nil)
228	if err != nil {
229		return 0, tracerr.Wrap(err)
230	}
231	return header.Number.Int64(), nil
232}
233
234// EthHeaderByNumber internally calls ethclient.Client HeaderByNumber
235// func (c *EthereumClient) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header,
236// 	error) {
237// 	return c.client.HeaderByNumber(ctx, number)
238// }
239
240// EthBlockByNumber internally calls ethclient.Client BlockByNumber and returns
241// *common.Block.  If number == -1, the latests known block is returned.
242func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number int64) (*common.Block,
243	error) {
244	blockNum := big.NewInt(number)
245	if number == -1 {
246		blockNum = nil
247	}
248	block, err := c.client.BlockByNumber(ctx, blockNum)
249	if err != nil {
250		return nil, tracerr.Wrap(err)
251	}
252	b := &common.Block{
253		Num:        block.Number().Int64(),
254		Timestamp:  time.Unix(int64(block.Time()), 0),
255		ParentHash: block.ParentHash(),
256		Hash:       block.Hash(),
257	}
258	return b, nil
259}
260
261// EthERC20Consts returns the constants defined for a particular ERC20 Token instance.
262func (c *EthereumClient) EthERC20Consts(tokenAddress ethCommon.Address) (*ERC20Consts, error) {
263	// We use the HEZ token smart contract interfacehere because it's an
264	// ERC20, which allows us to access the standard ERC20 constants.
265	instance, err := HEZ.NewHEZ(tokenAddress, c.client)
266	if err != nil {
267		return nil, tracerr.Wrap(err)
268	}
269	name, err := instance.Name(c.opts)
270	if err != nil {
271		return nil, tracerr.Wrap(err)
272	}
273
274	symbol, err := instance.Symbol(c.opts)
275	if err != nil {
276		return nil, tracerr.Wrap(err)
277	}
278
279	decimals, err := instance.Decimals(c.opts)
280	if err != nil {
281		return nil, tracerr.Wrap(err)
282	}
283	return &ERC20Consts{
284		Name:     name,
285		Symbol:   symbol,
286		Decimals: uint64(decimals),
287	}, nil
288}
289
290// Client returns the internal ethclient.Client
291func (c *EthereumClient) Client() *ethclient.Client {
292	return c.client
293}
294
295// newCallOpts returns a CallOpts to be used in ethereum calls with a non-zero
296// From address.  This is a workaround for a bug in ethereumjs-vm that shows up
297// in ganache: https://github.com/hermeznetwork/hermez-node/issues/317
298func newCallOpts() *bind.CallOpts {
299	return &bind.CallOpts{
300		From: ethCommon.HexToAddress("0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"),
301	}
302}
303
304// EthPendingNonceAt returns the account nonce of the given account in the pending
305// state. This is the nonce that should be used for the next transaction.
306func (c *EthereumClient) EthPendingNonceAt(ctx context.Context,
307	account ethCommon.Address) (uint64, error) {
308	return c.client.PendingNonceAt(ctx, account)
309}
310
311// EthNonceAt returns the account nonce of the given account. The block number can
312// be nil, in which case the nonce is taken from the latest known block.
313func (c *EthereumClient) EthNonceAt(ctx context.Context,
314	account ethCommon.Address, blockNumber *big.Int) (uint64, error) {
315	return c.client.NonceAt(ctx, account, blockNumber)
316}
317
318// EthCall runs the transaction as a call (without paying) in the local node at
319// blockNum.
320func (c *EthereumClient) EthCall(ctx context.Context, tx *types.Transaction,
321	blockNum *big.Int) ([]byte, error) {
322	if c.account == nil {
323		return nil, tracerr.Wrap(ErrAccountNil)
324	}
325	msg := ethereum.CallMsg{
326		From:     c.account.Address,
327		To:       tx.To(),
328		Gas:      tx.Gas(),
329		GasPrice: tx.GasPrice(),
330		Value:    tx.Value(),
331		Data:     tx.Data(),
332	}
333	result, err := c.client.CallContract(ctx, msg, blockNum)
334	return result, tracerr.Wrap(err)
335}