You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
7.5 KiB

  1. package eth
  2. import (
  3. "context"
  4. "fmt"
  5. "math/big"
  6. "time"
  7. "github.com/ethereum/go-ethereum/accounts"
  8. "github.com/ethereum/go-ethereum/accounts/abi/bind"
  9. ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
  10. ethCommon "github.com/ethereum/go-ethereum/common"
  11. "github.com/ethereum/go-ethereum/core/types"
  12. "github.com/ethereum/go-ethereum/ethclient"
  13. "github.com/hermeznetwork/hermez-node/common"
  14. "github.com/hermeznetwork/hermez-node/log"
  15. )
  16. var (
  17. ErrAccountNil = fmt.Errorf("Authorized calls can't be made when the account is nil")
  18. // ErrReceiptStatusFailed when receiving a failed transaction
  19. ErrReceiptStatusFailed = fmt.Errorf("receipt status is failed")
  20. // ErrReceiptNotRecieved when unable to retrieve a transaction
  21. ErrReceiptNotReceived = fmt.Errorf("receipt not available")
  22. )
  23. const (
  24. errStrDeploy = "deployment of %s failed: %w"
  25. errStrWaitReceipt = "wait receipt of %s deploy failed: %w"
  26. // default values
  27. defaultCallGasLimit = 300000
  28. defaultDeployGasLimit = 1000000
  29. defaultGasPriceDiv = 100
  30. defaultReceiptTimeout = 60
  31. defaultIntervalReceiptLoop = 200
  32. )
  33. type Config struct {
  34. CallGasLimit uint64
  35. DeployGasLimit uint64
  36. GasPriceDiv uint64
  37. ReceiptTimeout time.Duration // in seconds
  38. IntervalReceiptLoop time.Duration // in milliseconds
  39. }
  40. // Client is an ethereum client to call Smart Contract methods.
  41. type Client struct {
  42. client *ethclient.Client
  43. account *accounts.Account
  44. ks *ethKeystore.KeyStore
  45. ReceiptTimeout time.Duration
  46. config *Config
  47. }
  48. // NewClient creates a Client instance. The account is not mandatory (it can
  49. // be nil). If the account is nil, CallAuth will fail with ErrAccountNil.
  50. func NewClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, config *Config) *Client {
  51. if config == nil {
  52. config = &Config{
  53. CallGasLimit: defaultCallGasLimit,
  54. DeployGasLimit: defaultDeployGasLimit,
  55. GasPriceDiv: defaultGasPriceDiv,
  56. ReceiptTimeout: defaultReceiptTimeout,
  57. IntervalReceiptLoop: defaultIntervalReceiptLoop,
  58. }
  59. }
  60. return &Client{client: client, account: account, ks: ks, ReceiptTimeout: config.ReceiptTimeout * time.Second, config: config}
  61. }
  62. // BalanceAt retieves information about the default account
  63. func (c *Client) BalanceAt(addr ethCommon.Address) (*big.Int, error) {
  64. return c.client.BalanceAt(context.TODO(), addr, nil)
  65. }
  66. // Account returns the underlying ethereum account
  67. func (c *Client) Account() *accounts.Account {
  68. return c.account
  69. }
  70. // CallAuth performs a Smart Contract method call that requires authorization.
  71. // This call requires a valid account with Ether that can be spend during the
  72. // call.
  73. func (c *Client) CallAuth(gasLimit uint64,
  74. fn func(*ethclient.Client, *bind.TransactOpts) (*types.Transaction, error)) (*types.Transaction, error) {
  75. if c.account == nil {
  76. return nil, ErrAccountNil
  77. }
  78. gasPrice, err := c.client.SuggestGasPrice(context.Background())
  79. if err != nil {
  80. return nil, err
  81. }
  82. inc := new(big.Int).Set(gasPrice)
  83. inc.Div(inc, new(big.Int).SetUint64(c.config.GasPriceDiv))
  84. gasPrice.Add(gasPrice, inc)
  85. log.Debug("Transaction metadata", "gasPrice", gasPrice)
  86. auth, err := bind.NewKeyStoreTransactor(c.ks, *c.account)
  87. if err != nil {
  88. return nil, err
  89. }
  90. auth.Value = big.NewInt(0) // in wei
  91. if gasLimit == 0 {
  92. auth.GasLimit = c.config.CallGasLimit // in units
  93. } else {
  94. auth.GasLimit = gasLimit // in units
  95. }
  96. auth.GasPrice = gasPrice
  97. tx, err := fn(c.client, auth)
  98. if tx != nil {
  99. log.Debug("Transaction", "tx", tx.Hash().Hex(), "nonce", tx.Nonce())
  100. }
  101. return tx, err
  102. }
  103. type ContractData struct {
  104. Address ethCommon.Address
  105. Tx *types.Transaction
  106. Receipt *types.Receipt
  107. }
  108. // Deploy a smart contract. `name` is used to log deployment information. fn
  109. // is a wrapper to the deploy function generated by abigen. In case of error,
  110. // the returned `ContractData` may have some parameters filled depending on the
  111. // kind of error that occurred.
  112. func (c *Client) Deploy(name string,
  113. fn func(c *ethclient.Client, auth *bind.TransactOpts) (ethCommon.Address, *types.Transaction, interface{}, error)) (ContractData, error) {
  114. var contractData ContractData
  115. log.Info("Deploying", "contract", name)
  116. tx, err := c.CallAuth(
  117. c.config.DeployGasLimit,
  118. func(client *ethclient.Client, auth *bind.TransactOpts) (*types.Transaction, error) {
  119. addr, tx, _, err := fn(client, auth)
  120. if err != nil {
  121. return nil, err
  122. }
  123. contractData.Address = addr
  124. return tx, nil
  125. },
  126. )
  127. if err != nil {
  128. return contractData, fmt.Errorf(errStrDeploy, name, err)
  129. }
  130. log.Info("Waiting receipt", "tx", tx.Hash().Hex(), "contract", name)
  131. contractData.Tx = tx
  132. receipt, err := c.WaitReceipt(tx)
  133. if err != nil {
  134. return contractData, fmt.Errorf(errStrWaitReceipt, name, err)
  135. }
  136. contractData.Receipt = receipt
  137. return contractData, nil
  138. }
  139. // Call performs a read only Smart Contract method call.
  140. func (c *Client) Call(fn func(*ethclient.Client) error) error {
  141. return fn(c.client)
  142. }
  143. // WaitReceipt will block until a transaction is confirmed. Internally it
  144. // polls the state every 200 milliseconds.
  145. func (c *Client) WaitReceipt(tx *types.Transaction) (*types.Receipt, error) {
  146. return c.waitReceipt(tx, context.TODO(), c.ReceiptTimeout)
  147. }
  148. // GetReceipt will check if a transaction is confirmed and return
  149. // immediately, waiting at most 1 second and returning error if the transaction
  150. // is still pending.
  151. func (c *Client) GetReceipt(tx *types.Transaction) (*types.Receipt, error) {
  152. ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
  153. defer cancel()
  154. return c.waitReceipt(tx, ctx, 0)
  155. }
  156. func (c *Client) waitReceipt(tx *types.Transaction, ctx context.Context, timeout time.Duration) (*types.Receipt, error) {
  157. var err error
  158. var receipt *types.Receipt
  159. txid := tx.Hash()
  160. log.Debug("Waiting for receipt", "tx", txid.Hex())
  161. start := time.Now()
  162. for {
  163. receipt, err = c.client.TransactionReceipt(ctx, txid)
  164. if receipt != nil || time.Since(start) >= timeout {
  165. break
  166. }
  167. time.Sleep(c.config.IntervalReceiptLoop * time.Millisecond)
  168. }
  169. if receipt != nil && receipt.Status == types.ReceiptStatusFailed {
  170. log.Error("Failed transaction", "tx", txid.Hex())
  171. return receipt, ErrReceiptStatusFailed
  172. }
  173. if receipt == nil {
  174. log.Debug("Pendingtransaction / Wait receipt timeout", "tx", txid.Hex(), "lasterr", err)
  175. return receipt, ErrReceiptNotReceived
  176. }
  177. log.Debug("Successful transaction", "tx", txid.Hex())
  178. return receipt, err
  179. }
  180. // CurrentBlock returns the current block number in the blockchain
  181. func (c *Client) CurrentBlock() (*big.Int, error) {
  182. ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
  183. defer cancel()
  184. header, err := c.client.HeaderByNumber(ctx, nil)
  185. if err != nil {
  186. return nil, err
  187. }
  188. return header.Number, nil
  189. }
  190. // HeaderByNumber internally calls ethclient.Client HeaderByNumber
  191. func (c *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
  192. return c.client.HeaderByNumber(ctx, number)
  193. }
  194. // BlockByNumber internally calls ethclient.Client BlockByNumber and returns *common.Block
  195. func (c *Client) BlockByNumber(ctx context.Context, number *big.Int) (*common.Block, error) {
  196. block, err := c.client.BlockByNumber(ctx, number)
  197. if err != nil {
  198. return nil, err
  199. }
  200. b := &common.Block{
  201. EthBlockNum: block.Number().Uint64(),
  202. Timestamp: time.Unix(int64(block.Time()), 0),
  203. Hash: block.Hash(),
  204. }
  205. return b, nil
  206. }
  207. func (c *Client) ForgeCall(callData *common.CallDataForge) ([]byte, error) {
  208. // TODO this depends on the smart contracts, once are ready this will be updated
  209. return nil, nil
  210. }