Skip to content

Commit b8e2d68

Browse files
annieketynes
andauthored
tests: replica syncing (#981)
* [wip] add l2_dtl and replica images * passing basic dummy tx test * add erc20 test * add sync test to ci Co-authored-by: Mark Tyneway <[email protected]>
1 parent 57ca21a commit b8e2d68

File tree

9 files changed

+192
-17
lines changed

9 files changed

+192
-17
lines changed

.changeset/tidy-rivers-press.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@eth-optimism/integration-tests': patch
3+
'@eth-optimism/data-transport-layer': patch
4+
---
5+
6+
Add replica sync test to integration tests; handle 0 L2 blocks in DTL

.github/workflows/sync-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
working-directory: ./integration-tests
3535
run: |
3636
yarn
37+
yarn build:integration
3738
yarn test:sync
3839
3940
- name: Collect docker logs on failure

integration-tests/sync-tests/sync-verifier.spec.ts renamed to integration-tests/sync-tests/1-sync-verifier.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import chai, { expect } from 'chai'
22
import { Wallet, BigNumber, providers } from 'ethers'
33
import { injectL2Context } from '@eth-optimism/core-utils'
44

5-
import { sleep, l2Provider, verifierProvider } from '../test/shared/utils'
5+
import {
6+
sleep,
7+
l2Provider,
8+
verifierProvider,
9+
waitForL2Geth,
10+
} from '../test/shared/utils'
611
import { OptimismEnv } from '../test/shared/env'
712
import { DockerComposeNetwork } from '../test/shared/docker-compose'
813

@@ -33,14 +38,7 @@ describe('Syncing a verifier', () => {
3338
verifier = new DockerComposeNetwork(['verifier'])
3439
await verifier.up({ commandOptions: ['--scale', 'verifier=1'] })
3540

36-
// Wait for verifier to be looping
37-
let logs = await verifier.logs()
38-
while (!logs.out.includes('Starting Sequencer Loop')) {
39-
await sleep(500)
40-
logs = await verifier.logs()
41-
}
42-
43-
provider = injectL2Context(verifierProvider)
41+
provider = await waitForL2Geth(verifierProvider)
4442
}
4543

4644
const syncVerifier = async (sequencerBlockNumber: number) => {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import chai, { expect } from 'chai'
2+
import { Wallet, Contract, ContractFactory, providers } from 'ethers'
3+
import { ethers } from 'hardhat'
4+
import { injectL2Context } from '@eth-optimism/core-utils'
5+
6+
import {
7+
sleep,
8+
l2Provider,
9+
replicaProvider,
10+
waitForL2Geth,
11+
} from '../test/shared/utils'
12+
import { OptimismEnv } from '../test/shared/env'
13+
import { DockerComposeNetwork } from '../test/shared/docker-compose'
14+
15+
describe('Syncing a replica', () => {
16+
let env: OptimismEnv
17+
let wallet: Wallet
18+
let replica: DockerComposeNetwork
19+
let provider: providers.JsonRpcProvider
20+
21+
const sequencerProvider = injectL2Context(l2Provider)
22+
23+
/* Helper functions */
24+
25+
const startReplica = async () => {
26+
// Bring up new replica
27+
replica = new DockerComposeNetwork(['replica'])
28+
await replica.up({
29+
commandOptions: ['--scale', 'replica=1'],
30+
})
31+
32+
provider = await waitForL2Geth(replicaProvider)
33+
}
34+
35+
const syncReplica = async (sequencerBlockNumber: number) => {
36+
// Wait until replica has caught up to the sequencer
37+
let latestReplicaBlock = (await provider.getBlock('latest')) as any
38+
while (latestReplicaBlock.number < sequencerBlockNumber) {
39+
await sleep(500)
40+
latestReplicaBlock = (await provider.getBlock('latest')) as any
41+
}
42+
43+
return provider.getBlock(sequencerBlockNumber)
44+
}
45+
46+
before(async () => {
47+
env = await OptimismEnv.new()
48+
wallet = env.l2Wallet
49+
})
50+
51+
after(async () => {
52+
await replica.stop('replica')
53+
await replica.rm()
54+
})
55+
56+
describe('Basic transactions and ERC20s', () => {
57+
const initialAmount = 1000
58+
const tokenName = 'OVM Test'
59+
const tokenDecimals = 8
60+
const TokenSymbol = 'OVM'
61+
62+
let other: Wallet
63+
let Factory__ERC20: ContractFactory
64+
let ERC20: Contract
65+
66+
before(async () => {
67+
other = Wallet.createRandom().connect(ethers.provider)
68+
Factory__ERC20 = await ethers.getContractFactory('ERC20', wallet)
69+
})
70+
71+
it('should sync dummy transaction', async () => {
72+
const tx = {
73+
to: '0x' + '1234'.repeat(10),
74+
gasLimit: 4000000,
75+
gasPrice: 0,
76+
data: '0x',
77+
value: 0,
78+
}
79+
const result = await wallet.sendTransaction(tx)
80+
await result.wait()
81+
82+
const latestSequencerBlock = (await sequencerProvider.getBlock(
83+
'latest'
84+
)) as any
85+
86+
await startReplica()
87+
88+
const matchingReplicaBlock = (await syncReplica(
89+
latestSequencerBlock.number
90+
)) as any
91+
92+
expect(matchingReplicaBlock.stateRoot).to.eq(
93+
latestSequencerBlock.stateRoot
94+
)
95+
})
96+
97+
it('should sync ERC20 deployment and transfer', async () => {
98+
ERC20 = await Factory__ERC20.deploy(
99+
initialAmount,
100+
tokenName,
101+
tokenDecimals,
102+
TokenSymbol
103+
)
104+
105+
const transfer = await ERC20.transfer(other.address, 100)
106+
await transfer.wait()
107+
108+
const latestSequencerBlock = (await provider.getBlock('latest')) as any
109+
110+
const matchingReplicaBlock = (await syncReplica(
111+
latestSequencerBlock.number
112+
)) as any
113+
114+
expect(matchingReplicaBlock.stateRoot).to.eq(
115+
latestSequencerBlock.stateRoot
116+
)
117+
})
118+
})
119+
})

integration-tests/test/shared/docker-compose.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type ServiceNames =
88
| 'l2geth'
99
| 'relayer'
1010
| 'verifier'
11+
| 'replica'
1112

1213
const OPS_DIRECTORY = path.join(process.cwd(), '../ops')
1314
const DEFAULT_SERVICES: ServiceNames[] = [

integration-tests/test/shared/utils.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
getContractFactory,
55
getContractInterface,
66
} from '@eth-optimism/contracts'
7-
import { remove0x, Watcher } from '@eth-optimism/core-utils'
7+
import { injectL2Context, remove0x, Watcher } from '@eth-optimism/core-utils'
88
import {
99
Contract,
1010
Wallet,
@@ -22,9 +22,11 @@ const env = cleanEnv(process.env, {
2222
L1_URL: str({ default: 'http://localhost:9545' }),
2323
L2_URL: str({ default: 'http://localhost:8545' }),
2424
VERIFIER_URL: str({ default: 'http://localhost:8547' }),
25+
REPLICA_URL: str({ default: 'http://localhost:8549' }),
2526
L1_POLLING_INTERVAL: num({ default: 10 }),
2627
L2_POLLING_INTERVAL: num({ default: 10 }),
2728
VERIFIER_POLLING_INTERVAL: num({ default: 10 }),
29+
REPLICA_POLLING_INTERVAL: num({ default: 10 }),
2830
PRIVATE_KEY: str({
2931
default:
3032
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
@@ -44,6 +46,9 @@ l2Provider.pollingInterval = env.L2_POLLING_INTERVAL
4446
export const verifierProvider = new providers.JsonRpcProvider(env.VERIFIER_URL)
4547
verifierProvider.pollingInterval = env.VERIFIER_POLLING_INTERVAL
4648

49+
export const replicaProvider = new providers.JsonRpcProvider(env.REPLICA_URL)
50+
replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL
51+
4752
// The sequencer private key which is funded on L1
4853
export const l1Wallet = new Wallet(env.PRIVATE_KEY, l1Provider)
4954

@@ -111,3 +116,18 @@ const abiCoder = new utils.AbiCoder()
111116
export const encodeSolidityRevertMessage = (_reason: string): string => {
112117
return '0x08c379a0' + remove0x(abiCoder.encode(['string'], [_reason]))
113118
}
119+
120+
export const waitForL2Geth = async (
121+
provider: providers.JsonRpcProvider
122+
): Promise<providers.JsonRpcProvider> => {
123+
let ready: boolean = false
124+
while (!ready) {
125+
try {
126+
await provider.getNetwork()
127+
ready = true
128+
} catch (error) {
129+
await sleep(1000)
130+
}
131+
}
132+
return injectL2Context(provider)
133+
}

ops/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ docker-compose \
3030
up --build --detach
3131
```
3232

33-
Optionally, run a verifier along the rest of the stack.
33+
Optionally, run a verifier along the rest of the stack. Run a replica with the same command by switching the service name!
3434
```
35-
docker-compose up --scale verifier=1 \
35+
docker-compose up --scale \
36+
verifier=1 \
3637
--build --detach
3738
```
3839

40+
3941
A Makefile has been provided for convience. The following targets are available.
4042
- make up
4143
- make down

ops/docker-compose.yml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ services:
6262
# connect to the 2 layers
6363
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT: http://l1_chain:8545
6464
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT: http://l2geth:8545
65+
DATA_TRANSPORT_LAYER__SYNC_FROM_L2: 'true'
6566
DATA_TRANSPORT_LAYER__L2_CHAIN_ID: 420
6667
ports:
6768
- ${DTL_PORT:-7878}:7878
@@ -141,23 +142,46 @@ services:
141142
build:
142143
context: ..
143144
dockerfile: ./ops/docker/Dockerfile.geth
144-
# override with the geth script and the env vars required for it
145145
entrypoint: sh ./geth.sh
146146
env_file:
147147
- ./envs/geth.env
148148
environment:
149149
ETH1_HTTP: http://l1_chain:8545
150150
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
151-
# used for getting the addresses
152151
URL: http://deployer:8081/addresses.json
153-
# connecting to the DTL
154152
ROLLUP_CLIENT_HTTP: http://dtl:7878
153+
ROLLUP_BACKEND: 'l1'
155154
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
156155
RETRIES: 60
157-
IS_VERIFIER: "true"
156+
ROLLUP_VERIFIER_ENABLE: 'true'
158157
ports:
159158
- ${VERIFIER_HTTP_PORT:-8547}:8545
160159
- ${VERIFIER_WS_PORT:-8548}:8546
160+
161+
replica:
162+
depends_on:
163+
- dtl
164+
image: ethereumoptimism/l2geth
165+
deploy:
166+
replicas: 0
167+
build:
168+
context: ..
169+
dockerfile: ./ops/docker/Dockerfile.geth
170+
entrypoint: sh ./geth.sh
171+
env_file:
172+
- ./envs/geth.env
173+
environment:
174+
ETH1_HTTP: http://l1_chain:8545
175+
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
176+
URL: http://deployer:8081/addresses.json
177+
ROLLUP_CLIENT_HTTP: http://dtl:7878
178+
ROLLUP_BACKEND: 'l2'
179+
ROLLUP_VERIFIER_ENABLE: 'true'
180+
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
181+
RETRIES: 60
182+
ports:
183+
- ${L2GETH_HTTP_PORT:-8549}:8545
184+
- ${L2GETH_WS_PORT:-8550}:8546
161185

162186
integration_tests:
163187
image: ethereumoptimism/integration-tests

packages/data-transport-layer/src/services/l2-ingestion/service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ export class L2IngestionService extends BaseService<L2IngestionServiceOptions> {
8888
)
8989

9090
// We're already at the head, so no point in attempting to sync.
91-
if (highestSyncedL2BlockNumber === targetL2Block) {
91+
// Also wait on edge case of no L2 transactions
92+
if (
93+
highestSyncedL2BlockNumber === targetL2Block ||
94+
currentL2Block === 0
95+
) {
9296
await sleep(this.options.pollingInterval)
9397
continue
9498
}

0 commit comments

Comments
 (0)