forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathl2_proposer.go
210 lines (182 loc) · 6.45 KB
/
l2_proposer.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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package actions
import (
"context"
"crypto/ecdsa"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-proposer/metrics"
"github.com/ethereum-optimism/optimism/op-proposer/proposer"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
type ProposerCfg struct {
OutputOracleAddr *common.Address
DisputeGameFactoryAddr *common.Address
ProposalInterval time.Duration
DisputeGameType uint8
ProposerKey *ecdsa.PrivateKey
AllowNonFinalized bool
}
type L2Proposer struct {
log log.Logger
l1 *ethclient.Client
driver *proposer.L2OutputSubmitter
contract *bindings.L2OutputOracleCaller
address common.Address
privKey *ecdsa.PrivateKey
contractAddr common.Address
lastTx common.Hash
}
type fakeTxMgr struct {
from common.Address
}
func (f fakeTxMgr) From() common.Address {
return f.from
}
func (f fakeTxMgr) BlockNumber(_ context.Context) (uint64, error) {
panic("unimplemented")
}
func (f fakeTxMgr) Send(_ context.Context, _ txmgr.TxCandidate) (*types.Receipt, error) {
panic("unimplemented")
}
func (f fakeTxMgr) Close() {
}
func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer {
proposerConfig := proposer.ProposerConfig{
PollInterval: time.Second,
NetworkTimeout: time.Second,
ProposalInterval: cfg.ProposalInterval,
L2OutputOracleAddr: cfg.OutputOracleAddr,
DisputeGameFactoryAddr: cfg.DisputeGameFactoryAddr,
DisputeGameType: cfg.DisputeGameType,
AllowNonFinalized: cfg.AllowNonFinalized,
}
rollupProvider, err := dial.NewStaticL2RollupProviderFromExistingRollup(rollupCl)
require.NoError(t, err)
driverSetup := proposer.DriverSetup{
Log: log,
Metr: metrics.NoopMetrics,
Cfg: proposerConfig,
Txmgr: fakeTxMgr{from: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey)},
L1Client: l1,
RollupProvider: rollupProvider,
}
if cfg.OutputOracleAddr == nil {
panic("L2OutputOracle address must be set in op-e2e test harness. The DisputeGameFactory is not yet supported as a proposal destination.")
}
dr, err := proposer.NewL2OutputSubmitter(driverSetup)
require.NoError(t, err)
contract, err := bindings.NewL2OutputOracleCaller(*cfg.OutputOracleAddr, l1)
require.NoError(t, err)
address := crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey)
proposer, err := contract.PROPOSER(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, proposer, address, "PROPOSER must be the proposer's address")
return &L2Proposer{
log: log,
l1: l1,
driver: dr,
contract: contract,
address: address,
privKey: cfg.ProposerKey,
contractAddr: *cfg.OutputOracleAddr,
}
}
// sendTx reimplements creating & sending transactions because we need to do the final send as async in
// the action tests while we do it synchronously in the real system.
func (p *L2Proposer) sendTx(t Testing, data []byte) {
gasTipCap := big.NewInt(2 * params.GWei)
pendingHeader, err := p.l1.HeaderByNumber(t.Ctx(), big.NewInt(-1))
require.NoError(t, err, "need l1 pending header for gas price estimation")
gasFeeCap := new(big.Int).Add(gasTipCap, new(big.Int).Mul(pendingHeader.BaseFee, big.NewInt(2)))
chainID, err := p.l1.ChainID(t.Ctx())
require.NoError(t, err)
nonce, err := p.l1.NonceAt(t.Ctx(), p.address, nil)
require.NoError(t, err)
gasLimit, err := estimateGasPending(t.Ctx(), p.l1, ethereum.CallMsg{
From: p.address,
To: &p.contractAddr,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: data,
})
require.NoError(t, err)
rawTx := &types.DynamicFeeTx{
Nonce: nonce,
To: &p.contractAddr,
Data: data,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Gas: gasLimit,
ChainID: chainID,
}
tx, err := types.SignNewTx(p.privKey, types.LatestSignerForChainID(chainID), rawTx)
require.NoError(t, err, "need to sign tx")
err = p.l1.SendTransaction(t.Ctx(), tx)
log.Info("Proposer sent tx", "hash", tx.Hash(), "to", p.contractAddr)
require.NoError(t, err, "need to send tx")
p.lastTx = tx.Hash()
}
// estimateGasPending calls eth_estimateGas specifying the pending block. This is required for transactions from the
// proposer because they include a reference to the latest block which isn't available via `BLOCKHASH` if `latest` is
// used. In production code, the proposer waits until another L1 block is published but in e2e tests no new L1 blocks
// will be created so pending must be used.
func estimateGasPending(ctx context.Context, ec *ethclient.Client, msg ethereum.CallMsg) (uint64, error) {
var hex hexutil.Uint64
err := ec.Client().CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg), "pending")
if err != nil {
return 0, err
}
return uint64(hex), nil
}
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}
func (p *L2Proposer) CanPropose(t Testing) bool {
_, shouldPropose, err := p.driver.FetchNextOutputInfo(t.Ctx())
require.NoError(t, err)
return shouldPropose
}
func (p *L2Proposer) ActMakeProposalTx(t Testing) {
output, shouldPropose, err := p.driver.FetchNextOutputInfo(t.Ctx())
if !shouldPropose {
return
}
require.NoError(t, err)
txData, err := p.driver.ProposeL2OutputTxData(output)
require.NoError(t, err)
// Note: Use L1 instead of the output submitter's transaction manager because
// this is non-blocking while the txmgr is blocking & deadlocks the tests
p.sendTx(t, txData)
}
func (p *L2Proposer) LastProposalTx() common.Hash {
return p.lastTx
}