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/accounts"
10 "github.com/ethereum/go-ethereum/accounts/abi/bind"
11 ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
12 ethCommon "github.com/ethereum/go-ethereum/common"
13 "github.com/ethereum/go-ethereum/core/types"
14 "github.com/ethereum/go-ethereum/ethclient"
15 "github.com/hermeznetwork/hermez-node/common"
16 HEZ "github.com/hermeznetwork/hermez-node/eth/contracts/tokenHEZ"
17 "github.com/hermeznetwork/hermez-node/log"
18 "github.com/hermeznetwork/tracerr"
19)
20
21// ERC20Consts are the constants defined in a particular ERC20 Token instance
22type ERC20Consts struct {
23 Name string
24 Symbol string
25 Decimals uint64
26}
27
28// EthereumInterface is the interface to Ethereum
29type EthereumInterface interface {
30 EthLastBlock() (int64, error)
31 // EthHeaderByNumber(context.Context, *big.Int) (*types.Header, error)
32 EthBlockByNumber(context.Context, int64) (*common.Block, error)
33 EthAddress() (*ethCommon.Address, error)
34 EthTransactionReceipt(context.Context, ethCommon.Hash) (*types.Receipt, error)
35
36 EthERC20Consts(ethCommon.Address) (*ERC20Consts, error)
37 EthChainID() (*big.Int, error)
38}
39
40var (
41 // ErrAccountNil is used when the calls can not be made because the account is nil
42 ErrAccountNil = fmt.Errorf("Authorized calls can't be made when the account is nil")
43 // ErrBlockHashMismatchEvent is used when there's a block hash mismatch
44 // beetween different events of the same block
45 ErrBlockHashMismatchEvent = fmt.Errorf("block hash mismatch in event log")
46)
47
48const (
49 // default values
50 defaultCallGasLimit = 300000
51 defaultGasPriceDiv = 100
52)
53
54// EthereumConfig defines the configuration parameters of the EthereumClient
55type EthereumConfig struct {
56 CallGasLimit uint64
57 GasPriceDiv uint64
58}
59
60// EthereumClient is an ethereum client to call Smart Contract methods and check blockchain information.
61type EthereumClient struct {
62 client *ethclient.Client
63 chainID *big.Int
64 account *accounts.Account
65 ks *ethKeystore.KeyStore
66 config *EthereumConfig
67 opts *bind.CallOpts
68}
69
70// NewEthereumClient creates a EthereumClient instance. The account is not mandatory (it can
71// be nil). If the account is nil, CallAuth will fail with ErrAccountNil.
72func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, config *EthereumConfig) (*EthereumClient, error) {
73 if config == nil {
74 config = &EthereumConfig{
75 CallGasLimit: defaultCallGasLimit,
76 GasPriceDiv: defaultGasPriceDiv,
77 }
78 }
79 c := &EthereumClient{
80 client: client,
81 account: account,
82 ks: ks,
83 config: config,
84 opts: newCallOpts(),
85 }
86 chainID, err := c.EthChainID()
87 if err != nil {
88 return nil, tracerr.Wrap(err)
89 }
90 c.chainID = chainID
91 return c, nil
92}
93
94// EthChainID returns the ChainID of the ethereum network
95func (c *EthereumClient) EthChainID() (*big.Int, error) {
96 chainID, err := c.client.ChainID(context.Background())
97 if err != nil {
98 return nil, tracerr.Wrap(err)
99 }
100 return chainID, nil
101}
102
103// BalanceAt retieves information about the default account
104func (c *EthereumClient) BalanceAt(addr ethCommon.Address) (*big.Int, error) {
105 return c.client.BalanceAt(context.TODO(), addr, nil)
106}
107
108// Account returns the underlying ethereum account
109func (c *EthereumClient) Account() *accounts.Account {
110 return c.account
111}
112
113// EthAddress returns the ethereum address of the account loaded into the EthereumClient
114func (c *EthereumClient) EthAddress() (*ethCommon.Address, error) {
115 if c.account == nil {
116 return nil, tracerr.Wrap(ErrAccountNil)
117 }
118 return &c.account.Address, nil
119}
120
121// CallAuth performs a Smart Contract method call that requires authorization.
122// This call requires a valid account with Ether that can be spend during the
123// call.
124func (c *EthereumClient) CallAuth(gasLimit uint64,
125 fn func(*ethclient.Client, *bind.TransactOpts) (*types.Transaction, error)) (*types.Transaction, error) {
126 if c.account == nil {
127 return nil, tracerr.Wrap(ErrAccountNil)
128 }
129
130 gasPrice, err := c.client.SuggestGasPrice(context.Background())
131 if err != nil {
132 return nil, tracerr.Wrap(err)
133 }
134 inc := new(big.Int).Set(gasPrice)
135 inc.Div(inc, new(big.Int).SetUint64(c.config.GasPriceDiv))
136 gasPrice.Add(gasPrice, inc)
137 log.Debugw("Transaction metadata", "gasPrice", gasPrice)
138
139 auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, c.chainID)
140 if err != nil {
141 return nil, tracerr.Wrap(err)
142 }
143 auth.Value = big.NewInt(0) // in wei
144 if gasLimit == 0 {
145 auth.GasLimit = c.config.CallGasLimit // in units
146 } else {
147 auth.GasLimit = gasLimit // in units
148 }
149 auth.GasPrice = gasPrice
150
151 tx, err := fn(c.client, auth)
152 if tx != nil {
153 log.Debugw("Transaction", "tx", tx.Hash().Hex(), "nonce", tx.Nonce())
154 }
155 return tx, tracerr.Wrap(err)
156}
157
158// ContractData contains the contract data
159type ContractData struct {
160 Address ethCommon.Address
161 Tx *types.Transaction
162 Receipt *types.Receipt
163}
164
165// Call performs a read only Smart Contract method call.
166func (c *EthereumClient) Call(fn func(*ethclient.Client) error) error {
167 return fn(c.client)
168}
169
170// EthTransactionReceipt returns the transaction receipt of the given txHash
171func (c *EthereumClient) EthTransactionReceipt(ctx context.Context, txHash ethCommon.Hash) (*types.Receipt, error) {
172 return c.client.TransactionReceipt(ctx, txHash)
173}
174
175// EthLastBlock returns the last block number in the blockchain
176func (c *EthereumClient) EthLastBlock() (int64, error) {
177 ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
178 defer cancel()
179 header, err := c.client.HeaderByNumber(ctx, nil)
180 if err != nil {
181 return 0, tracerr.Wrap(err)
182 }
183 return header.Number.Int64(), nil
184}
185
186// EthHeaderByNumber internally calls ethclient.Client HeaderByNumber
187// func (c *EthereumClient) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
188// return c.client.HeaderByNumber(ctx, number)
189// }
190
191// EthBlockByNumber internally calls ethclient.Client BlockByNumber and returns
192// *common.Block. If number == -1, the latests known block is returned.
193func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number int64) (*common.Block, error) {
194 blockNum := big.NewInt(number)
195 if number == -1 {
196 blockNum = nil
197 }
198 block, err := c.client.BlockByNumber(ctx, blockNum)
199 if err != nil {
200 return nil, tracerr.Wrap(err)
201 }
202 b := &common.Block{
203 Num: block.Number().Int64(),
204 Timestamp: time.Unix(int64(block.Time()), 0),
205 ParentHash: block.ParentHash(),
206 Hash: block.Hash(),
207 }
208 return b, nil
209}
210
211// EthERC20Consts returns the constants defined for a particular ERC20 Token instance.
212func (c *EthereumClient) EthERC20Consts(tokenAddress ethCommon.Address) (*ERC20Consts, error) {
213 // We use the HEZ token smart contract interfacehere because it's an
214 // ERC20, which allows us to access the standard ERC20 constants.
215 instance, err := HEZ.NewHEZ(tokenAddress, c.client)
216 if err != nil {
217 return nil, tracerr.Wrap(err)
218 }
219 name, err := instance.Name(c.opts)
220 if err != nil {
221 return nil, tracerr.Wrap(err)
222 }
223
224 symbol, err := instance.Symbol(c.opts)
225 if err != nil {
226 return nil, tracerr.Wrap(err)
227 }
228
229 decimals, err := instance.Decimals(c.opts)
230 if err != nil {
231 return nil, tracerr.Wrap(err)
232 }
233 return &ERC20Consts{
234 Name: name,
235 Symbol: symbol,
236 Decimals: uint64(decimals),
237 }, nil
238}
239
240// Client returns the internal ethclient.Client
241func (c *EthereumClient) Client() *ethclient.Client {
242 return c.client
243}
244
245// newCallOpts returns a CallOpts to be used in ethereum calls with a non-zero
246// From address. This is a workaround for a bug in ethereumjs-vm that shows up
247// in ganache: https://github.com/hermeznetwork/hermez-node/issues/317
248func newCallOpts() *bind.CallOpts {
249 return &bind.CallOpts{
250 From: ethCommon.HexToAddress("0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"),
251 }
252}