forked from Layr-Labs/eigenpod-proofs-generation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchain_client.go
191 lines (165 loc) · 5.84 KB
/
chain_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package eigenpodproofs
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/rs/zerolog/log"
)
var (
FallbackGasTipCap = big.NewInt(15000000000)
ErrCannotGetECDSAPubKey = errors.New("ErrCannotGetECDSAPubKey")
ErrTransactionFailed = errors.New("ErrTransactionFailed")
)
type ChainClient struct {
*ethclient.Client
privateKey *ecdsa.PrivateKey
AccountAddress common.Address
NoSendTransactOpts *bind.TransactOpts
Contracts map[common.Address]*bind.BoundContract
}
func NewChainClient(ethClient *ethclient.Client, privateKeyString string) (*ChainClient, error) {
var (
accountAddress common.Address
privateKey *ecdsa.PrivateKey
opts *bind.TransactOpts
err error
)
if len(privateKeyString) != 0 {
privateKey, err = crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, fmt.Errorf("NewClient: cannot parse private key: %w", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Error().Msg("NewClient: cannot get publicKeyECDSA")
return nil, ErrCannotGetECDSAPubKey
}
accountAddress = crypto.PubkeyToAddress(*publicKeyECDSA)
chainIDBigInt, err := ethClient.ChainID(context.Background())
if err != nil {
return nil, fmt.Errorf("NewClient: cannot get chainId: %w", err)
}
// generate and memoize NoSendTransactOpts
opts, err = bind.NewKeyedTransactorWithChainID(privateKey, chainIDBigInt)
if err != nil {
return nil, fmt.Errorf("NewClient: cannot create NoSendTransactOpts: %w", err)
}
opts.NoSend = true
}
c := &ChainClient{
privateKey: privateKey,
AccountAddress: accountAddress,
Client: ethClient,
Contracts: make(map[common.Address]*bind.BoundContract),
}
c.NoSendTransactOpts = opts
return c, nil
}
func (c *ChainClient) GetCurrentBlockNumber(ctx context.Context) (uint32, error) {
bn, err := c.Client.BlockNumber(ctx)
return uint32(bn), err
}
func (c *ChainClient) GetAccountAddress() common.Address {
return c.AccountAddress
}
func (c *ChainClient) GetNoSendTransactOpts() *bind.TransactOpts {
return c.NoSendTransactOpts
}
// EstimateGasPriceAndLimitAndSendTx sends and returns an otherwise identical txn
// to the one provided but with updated gas prices sampled from the existing network
// conditions and an accurate gasLimit
//
// Note: tx must be a to a contract, not an EOA
//
// Slightly modified from: https://github.com/ethereum-optimism/optimism/blob/ec266098641820c50c39c31048aa4e953bece464/batch-submitter/drivers/sequencer/driver.go#L314
func (c *ChainClient) EstimateGasPriceAndLimitAndSendTx(
ctx context.Context,
tx *types.Transaction,
tag string,
) (*types.Receipt, error) {
gasTipCap, err := c.SuggestGasTipCap(ctx)
if err != nil {
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
log.Debug().Msgf("EstimateGasPriceAndLimitAndSendTx: cannot get gasTipCap: %v", err)
gasTipCap = FallbackGasTipCap
}
header, err := c.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
}
gasFeeCap := new(big.Int).Add(header.BaseFee, gasTipCap)
// The estimated gas limits performed by RawTransact fail semi-regularly
// with out of gas exceptions. To remedy this we extract the internal calls
// to perform gas price/gas limit estimation here and add a buffer to
// account for any network variability.
gasLimit, err := c.Client.EstimateGas(ctx, ethereum.CallMsg{
From: c.AccountAddress,
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: nil,
Data: tx.Data(),
})
if err != nil {
return nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(c.privateKey, tx.ChainId())
if err != nil {
return nil, fmt.Errorf("EstimateGasPriceAndLimitAndSendTx: cannot create transactOpts: %w", err)
}
opts.Context = ctx
opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasTipCap = gasTipCap
opts.GasFeeCap = gasFeeCap
opts.GasLimit = addGasBuffer(gasLimit)
contract := c.Contracts[*tx.To()]
// if the contract has not been cached
if contract == nil {
// create a dummy bound contract tied to the `to` address of the transaction
contract = bind.NewBoundContract(*tx.To(), abi.ABI{}, c.Client, c.Client, c.Client)
// cache the contract for later use
c.Contracts[*tx.To()] = contract
}
tx, err = contract.RawTransact(opts, tx.Data())
if err != nil {
return nil, fmt.Errorf("EstimateGasPriceAndLimitAndSendTx: failed to send txn (%s): %w", tag, err)
}
receipt, err := c.EnsureTransactionEvaled(
tx,
tag,
)
if err != nil {
return nil, err
}
return receipt, err
}
func (c *ChainClient) EnsureTransactionEvaled(tx *types.Transaction, tag string) (*types.Receipt, error) {
receipt, err := bind.WaitMined(context.Background(), c.Client, tx)
if err != nil {
return nil, fmt.Errorf("EnsureTransactionEvaled: failed to wait for transaction (%s) to mine: %w", tag, err)
}
if receipt.Status != 1 {
log.Debug().Msgf("EnsureTransactionEvaled: transaction (%s) failed: %v", tag, receipt)
return nil, ErrTransactionFailed
}
log.Debug().Msgf("EnsureTransactionEvaled: transaction (%s) succeeded: %v", tag, receipt)
return receipt, nil
}
func addGasBuffer(gasLimit uint64) uint64 {
return 6 * gasLimit / 5 // add 20% buffer to gas limit
}