-
Notifications
You must be signed in to change notification settings - Fork 718
Expand file tree
/
Copy pathseqinbox_test.go
More file actions
456 lines (416 loc) · 16.3 KB
/
seqinbox_test.go
File metadata and controls
456 lines (416 loc) · 16.3 KB
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
// Copyright 2021-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
package arbtest
import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"math/rand"
"testing"
"time"
"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/ethclient/gethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/offchainlabs/nitro/arbcompress"
"github.com/offchainlabs/nitro/arbnode"
"github.com/offchainlabs/nitro/arbos"
"github.com/offchainlabs/nitro/arbos/l2pricing"
"github.com/offchainlabs/nitro/arbstate"
"github.com/offchainlabs/nitro/solgen/go/bridgegen"
"github.com/offchainlabs/nitro/util"
)
type blockTestState struct {
balances map[common.Address]*big.Int
nonces map[common.Address]uint64
accounts []common.Address
l2BlockNumber uint64
l1BlockNumber uint64
}
const seqInboxTestIters = 40
func encodeAddBatch(seqABI *abi.ABI, seqNum *big.Int, message []byte, afterDelayedMsgRead *big.Int, gasRefunder common.Address) ([]byte, error) {
method, ok := seqABI.Methods["addSequencerL2BatchFromOrigin0"]
if !ok {
return nil, errors.New("failed to find add addSequencerL2BatchFromOrigin0 method")
}
inputData, err := method.Inputs.Pack(
seqNum,
message,
afterDelayedMsgRead,
gasRefunder,
new(big.Int).SetUint64(uint64(1)),
new(big.Int).SetUint64(uint64(1)),
)
if err != nil {
return nil, err
}
fullData := append([]byte{}, method.ID...)
fullData = append(fullData, inputData...)
return fullData, nil
}
func diffAccessList(accessed, al types.AccessList) string {
m := make(map[common.Address]map[common.Hash]bool)
for i := 0; i < len(al); i++ {
if _, ok := m[al[i].Address]; !ok {
m[al[i].Address] = make(map[common.Hash]bool)
}
for _, slot := range al[i].StorageKeys {
m[al[i].Address][slot] = true
}
}
diff := ""
for i := 0; i < len(accessed); i++ {
addr := accessed[i].Address
if _, ok := m[addr]; !ok {
diff += fmt.Sprintf("contract address: %q wasn't accessed\n", addr)
continue
}
for j := 0; j < len(accessed[i].StorageKeys); j++ {
slot := accessed[i].StorageKeys[j]
if _, ok := m[addr][slot]; !ok {
diff += fmt.Sprintf("storage slot: %v for contract: %v wasn't accessed\n", slot, addr)
}
}
}
return diff
}
func deployGasRefunder(ctx context.Context, t *testing.T, builder *NodeBuilder) common.Address {
t.Helper()
abi, err := bridgegen.GasRefunderMetaData.GetAbi()
if err != nil {
t.Fatalf("Error getting gas refunder abi: %v", err)
}
fauOpts := builder.L1Info.GetDefaultTransactOpts("Faucet", ctx)
addr, tx, _, err := bind.DeployContract(&fauOpts, *abi, common.FromHex(bridgegen.GasRefunderBin), builder.L1.Client)
if err != nil {
t.Fatalf("Error getting gas refunder contract deployment transaction: %v", err)
}
if _, err := builder.L1.EnsureTxSucceeded(tx); err != nil {
t.Fatalf("Error deploying gas refunder contract: %v", err)
}
tx = builder.L1Info.PrepareTxTo("Faucet", &addr, 30000, big.NewInt(9223372036854775807), nil)
if err := builder.L1.Client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("Error sending gas refunder funding transaction")
}
if _, err := builder.L1.EnsureTxSucceeded(tx); err != nil {
t.Fatalf("Error funding gas refunder")
}
contract, err := bridgegen.NewGasRefunder(addr, builder.L1.Client)
if err != nil {
t.Fatalf("Error getting gas refunder contract binding: %v", err)
}
tx, err = contract.AllowContracts(&fauOpts, []common.Address{builder.L1Info.GetAddress("SequencerInbox")})
if err != nil {
t.Fatalf("Error creating transaction for altering allowlist in refunder: %v", err)
}
if _, err := builder.L1.EnsureTxSucceeded(tx); err != nil {
t.Fatalf("Error addting sequencer inbox in gas refunder allowlist: %v", err)
}
tx, err = contract.AllowRefundees(&fauOpts, []common.Address{builder.L1Info.GetAddress("Sequencer")})
if err != nil {
t.Fatalf("Error creating transaction for altering allowlist in refunder: %v", err)
}
if _, err := builder.L1.EnsureTxSucceeded(tx); err != nil {
t.Fatalf("Error addting sequencer in gas refunder allowlist: %v", err)
}
return addr
}
func testSequencerInboxReaderImpl(t *testing.T, validator bool) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise().WithTakeOwnership(false)
if validator {
builder.nodeConfig.BlockValidator.Enable = true
}
builder.isSequencer = false
cleanup := builder.Build(t)
defer cleanup()
l2Backend := builder.L2.ExecNode.Backend
l1BlockChain := builder.L1.L1Backend.BlockChain()
rpcC := builder.L1.Stack.Attach()
gethClient := gethclient.New(rpcC)
seqInbox, err := bridgegen.NewSequencerInbox(builder.L1Info.GetAddress("SequencerInbox"), builder.L1.Client)
Require(t, err)
seqOpts := builder.L1Info.GetDefaultTransactOpts("Sequencer", ctx)
gasRefunderAddr := deployGasRefunder(ctx, t, builder)
ownerAddress := builder.L2Info.GetAddress("Owner")
var startL2BlockNumber uint64 = 0
startState, _, err := l2Backend.APIBackend().StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber)
Require(t, err)
startOwnerBalance := startState.GetBalance(ownerAddress)
startOwnerNonce := startState.GetNonce(ownerAddress)
var blockStates []blockTestState
blockStates = append(blockStates, blockTestState{
balances: map[common.Address]*big.Int{
ownerAddress: startOwnerBalance.ToBig(),
},
nonces: map[common.Address]uint64{
ownerAddress: startOwnerNonce,
},
accounts: []common.Address{ownerAddress},
l2BlockNumber: startL2BlockNumber,
})
accountName := func(x int) string {
if x == 0 {
return "Owner"
}
return fmt.Sprintf("Account%v", x)
}
var accounts []string
for i := 1; i <= (seqInboxTestIters-1)/10; i++ {
accounts = append(accounts, fmt.Sprintf("ReorgSacrifice%v", i))
}
var faucetTxs []*types.Transaction
for _, acct := range accounts {
builder.L1Info.GenerateAccount(acct)
faucetTxs = append(faucetTxs, builder.L1Info.PrepareTx("Faucet", acct, 30000, big.NewInt(1e16), nil))
}
builder.L1.SendWaitTestTransactions(t, faucetTxs)
seqABI, err := bridgegen.SequencerInboxMetaData.GetAbi()
if err != nil {
t.Fatalf("Error getting sequencer inbox abi: %v", err)
}
for i := 1; i < seqInboxTestIters; i++ {
if i%10 == 0 {
reorgTo := rand.Int() % len(blockStates)
if reorgTo == 0 {
reorgTo = 1
}
// Make the reorg larger to force the miner to discard transactions.
// The miner usually collects transactions from deleted blocks and puts them in the mempool.
// However, this code doesn't run on reorgs larger than 64 blocks for performance reasons.
// Therefore, we make a bunch of small blocks to prevent the code from running.
reorgTargetNumber := blockStates[reorgTo].l1BlockNumber
currentHeader, err := builder.L1.Client.HeaderByNumber(ctx, nil)
Require(t, err)
blocksToPad := 65 - (currentHeader.Number.Uint64() - reorgTargetNumber)
builder.L1.RecalibrateNonce(t, "Faucet", builder.L1Info)
// #nosec G115
builder.L1.AdvanceBlocks(t, "Faucet", int(blocksToPad), builder.L1Info)
currentHeader, err = builder.L1.Client.HeaderByNumber(ctx, nil)
Require(t, err)
// #nosec G115
if currentHeader.Number.Int64()-int64(reorgTargetNumber) < 65 {
Fatal(t, "Less than 65 blocks of difference between current block", currentHeader.Number, "and target", reorgTargetNumber)
}
t.Logf("Reorganizing to L1 block %v", reorgTargetNumber)
reorgTarget := l1BlockChain.GetBlockByNumber(reorgTargetNumber)
err = l1BlockChain.ReorgToOldBlock(reorgTarget)
Require(t, err)
blockStates = blockStates[:(reorgTo + 1)]
// Geth's miner's mempool might not immediately process the reorg.
// Sometimes, this causes it to drop the next tx.
// To work around this, we create a sacrificial tx, which may or may not succeed.
// Whichever happens, by the end of this block, the miner will have processed the reorg.
tx := builder.L1Info.PrepareTx(fmt.Sprintf("ReorgSacrifice%v", i/10), "Faucet", 30000, big.NewInt(0), nil)
err = builder.L1.Client.SendTransaction(ctx, tx)
Require(t, err)
_, _ = WaitForTx(ctx, builder.L1.Client, tx.Hash(), time.Second)
// Advance L1 to currentHeader+1 block so that MEL can detect reorg
builder.L1.RecalibrateNonce(t, "Faucet", builder.L1Info)
// #nosec G115
builder.L1.AdvanceBlocks(t, "Faucet", int(currentHeader.Number.Uint64()-reorgTargetNumber+1), builder.L1Info)
} else {
state := blockStates[len(blockStates)-1]
newBalances := make(map[common.Address]*big.Int)
for k, v := range state.balances {
newBalances[k] = new(big.Int).Set(v)
}
state.balances = newBalances
newNonces := make(map[common.Address]uint64)
for k, v := range state.nonces {
newNonces[k] = v
}
state.nonces = newNonces
batchBuffer := bytes.NewBuffer([]byte{})
numMessages := 1 + rand.Int()%5
for j := 0; j < numMessages; j++ {
sourceNum := rand.Int() % len(state.accounts)
source := state.accounts[sourceNum]
// #nosec G115
amount := new(big.Int).SetUint64(uint64(rand.Int()) % state.balances[source].Uint64())
reserveAmount := new(big.Int).SetUint64(l2pricing.InitialBaseFeeWei * 100000000000)
if state.balances[source].Cmp(new(big.Int).Add(amount, reserveAmount)) < 0 {
// Leave enough funds for gas
amount = big.NewInt(1)
}
var dest common.Address
if j == 0 && amount.Cmp(reserveAmount) >= 0 {
name := accountName(len(state.accounts))
if !builder.L2Info.HasAccount(name) {
builder.L2Info.GenerateAccount(name)
}
dest = builder.L2Info.GetAddress(name)
state.accounts = append(state.accounts, dest)
state.balances[dest] = big.NewInt(0)
} else {
dest = state.accounts[rand.Int()%len(state.accounts)]
}
rawTx := &types.DynamicFeeTx{
To: &dest,
Gas: util.NormalizeL2GasForL1GasInitial(210000, params.GWei),
GasFeeCap: big.NewInt(l2pricing.InitialBaseFeeWei * 2),
Value: amount,
Nonce: state.nonces[source],
}
state.nonces[source]++
tx := builder.L2Info.SignTxAs(accountName(sourceNum), rawTx)
txData, err := tx.MarshalBinary()
Require(t, err)
var segment []byte
segment = append(segment, arbstate.BatchSegmentKindL2Message)
segment = append(segment, arbos.L2MessageKind_SignedTx)
segment = append(segment, txData...)
err = rlp.Encode(batchBuffer, segment)
Require(t, err)
state.balances[source].Sub(state.balances[source], amount)
state.balances[dest].Add(state.balances[dest], amount)
}
compressed, err := arbcompress.CompressWell(batchBuffer.Bytes())
Require(t, err)
batchData := append([]byte{0}, compressed...)
seqNonce := len(blockStates) - 1
for j := 0; ; j++ {
haveNonce, err := builder.L1.Client.PendingNonceAt(ctx, seqOpts.From)
Require(t, err)
// #nosec G115
if haveNonce == uint64(seqNonce) {
break
}
if j >= 10 {
t.Fatal("timed out with sequencer nonce", haveNonce, "waiting for expected nonce", seqNonce)
}
time.Sleep(time.Millisecond * 100)
}
seqOpts.Nonce = big.NewInt(int64(seqNonce))
var tx *types.Transaction
before, err := builder.L1.Client.BalanceAt(ctx, seqOpts.From, nil)
if err != nil {
t.Fatalf("BalanceAt(%v) unexpected error: %v", seqOpts.From, err)
}
data, err := encodeAddBatch(seqABI, big.NewInt(int64(len(blockStates))), batchData, big.NewInt(1), gasRefunderAddr)
if err != nil {
t.Fatalf("Error encoding batch data: %v", err)
}
si := builder.L1Info.GetAddress("SequencerInbox")
wantAL, _, _, err := gethClient.CreateAccessList(ctx, ethereum.CallMsg{
From: seqOpts.From,
To: &si,
Data: data,
})
if err != nil {
t.Fatalf("Error creating access list: %v", err)
}
accessed := arbnode.AccessList(&arbnode.AccessListOpts{
SequencerInboxAddr: builder.L1Info.GetAddress("SequencerInbox"),
BridgeAddr: builder.L1Info.GetAddress("Bridge"),
DataPosterAddr: seqOpts.From,
GasRefunderAddr: gasRefunderAddr,
SequencerInboxAccs: uint64(len(blockStates)),
AfterDelayedMessagesRead: 1,
})
if diff := diffAccessList(accessed, *wantAL); diff != "" {
t.Errorf("Access list mismatch:\n%s\n", diff)
}
if i%5 == 0 {
tx, err = seqInbox.AddSequencerL2Batch(&seqOpts, big.NewInt(int64(len(blockStates))), batchData, big.NewInt(1), gasRefunderAddr, big.NewInt(0), big.NewInt(0))
} else {
tx, err = seqInbox.AddSequencerL2BatchFromOrigin8f111f3c(&seqOpts, big.NewInt(int64(len(blockStates))), batchData, big.NewInt(1), gasRefunderAddr, common.Big0, common.Big0)
}
Require(t, err)
txRes, err := builder.L1.EnsureTxSucceeded(tx)
if err != nil {
// Geth's clique miner is finicky.
// Unfortunately this is so rare that I haven't had an opportunity to test this workaround.
// Specifically, I suspect there's a race where it thinks there's no txs to put in the new block,
// if a new tx arrives at the same time as it tries to create a block.
// Resubmit the transaction in an attempt to get the miner going again.
_ = builder.L1.Client.SendTransaction(ctx, tx)
txRes, err = builder.L1.EnsureTxSucceeded(tx)
Require(t, err)
}
after, err := builder.L1.Client.BalanceAt(ctx, seqOpts.From, nil)
if err != nil {
t.Fatalf("BalanceAt(%v) unexpected error: %v", seqOpts.From, err)
}
txCost := txRes.EffectiveGasPrice.Uint64() * txRes.GasUsed
// #nosec G115
if diff := before.Int64() - after.Int64(); diff >= int64(txCost) {
t.Errorf("Transaction: %v was not refunded, balance diff: %v, cost: %v", tx.Hash(), diff, txCost)
}
// #nosec G115
state.l2BlockNumber += uint64(numMessages)
state.l1BlockNumber = txRes.BlockNumber.Uint64()
blockStates = append(blockStates, state)
}
t.Logf("Iteration %v: state %v block %v", i, len(blockStates)-1, blockStates[len(blockStates)-1].l2BlockNumber)
// Wait for the on-chain batch count to reflect the batch we just posted.
// Under -race the simulated L1 RPC calls become 5-10x slower, and after
// reorg iterations (every 10th) the node must reprocess 65+ blocks.
for i := 0; ; i++ {
batchCount, err := seqInbox.BatchCount(&bind.CallOpts{})
if err != nil {
Fatal(t, err)
}
if batchCount.Cmp(big.NewInt(int64(len(blockStates)))) == 0 {
break
} else if i >= 500 {
Fatal(t, "timed out waiting for l1 batch count update; have", batchCount, "want", len(blockStates)-1)
}
if i > 0 && i%50 == 0 {
t.Logf("still waiting for batch count: have %v, want %v (poll %d)", batchCount, len(blockStates), i)
}
time.Sleep(20 * time.Millisecond)
}
expectedBlockNumber := blockStates[len(blockStates)-1].l2BlockNumber
for i := 0; ; i++ {
blockNumber := l2Backend.APIBackend().CurrentHeader().Number.Uint64()
if blockNumber >= expectedBlockNumber {
break
} else if i >= 1000 {
Fatal(t, "timed out waiting for l2 block update; have", blockNumber, "want", expectedBlockNumber)
}
time.Sleep(10 * time.Millisecond)
}
if validator && i%15 == 0 {
for i := 0; ; i++ {
expectedPos, err := builder.L2.ExecNode.ExecEngine.BlockNumberToMessageIndex(expectedBlockNumber)
Require(t, err)
lastValidated := builder.L2.ConsensusNode.BlockValidator.Validated(t)
if lastValidated == expectedPos+1 {
break
} else if i >= 1000 {
Fatal(t, "timed out waiting for block validator; have", lastValidated, "want", expectedPos+1)
}
time.Sleep(time.Second)
}
}
for _, state := range blockStates {
// #nosec G115
block, err := l2Backend.APIBackend().BlockByNumber(ctx, rpc.BlockNumber(state.l2BlockNumber))
Require(t, err)
if block == nil {
Fatal(t, "missing state block", state.l2BlockNumber)
}
// #nosec G115
stateDb, _, err := l2Backend.APIBackend().StateAndHeaderByNumber(ctx, rpc.BlockNumber(state.l2BlockNumber))
Require(t, err)
for acct, expectedBalance := range state.balances {
haveBalance := stateDb.GetBalance(acct)
if expectedBalance.Cmp(haveBalance.ToBig()) < 0 {
Fatal(t, "unexpected balance for account", acct, "; expected", expectedBalance, "got", haveBalance)
}
}
}
}
}
func TestSequencerInboxReader(t *testing.T) {
testSequencerInboxReaderImpl(t, false)
}