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}