Skip to content

Commit

Permalink
tests: replica syncing (#981)
Browse files Browse the repository at this point in the history
* [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]>
  • Loading branch information
annieke and tynes committed Jun 25, 2021
1 parent 2419efb commit 4b3f6d3
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 17 deletions.
6 changes: 6 additions & 0 deletions .changeset/tidy-rivers-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/data-transport-layer': patch
---

Add replica sync test to integration tests; handle 0 L2 blocks in DTL
1 change: 1 addition & 0 deletions .github/workflows/sync-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
working-directory: ./integration-tests
run: |
yarn
yarn build:integration
yarn test:sync
- name: Collect docker logs on failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import chai, { expect } from 'chai'
import { Wallet, BigNumber, providers } from 'ethers'
import { injectL2Context } from '@eth-optimism/core-utils'

import { sleep, l2Provider, verifierProvider } from '../test/shared/utils'
import {
sleep,
l2Provider,
verifierProvider,
waitForL2Geth,
} from '../test/shared/utils'
import { OptimismEnv } from '../test/shared/env'
import { DockerComposeNetwork } from '../test/shared/docker-compose'

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

// Wait for verifier to be looping
let logs = await verifier.logs()
while (!logs.out.includes('Starting Sequencer Loop')) {
await sleep(500)
logs = await verifier.logs()
}

provider = injectL2Context(verifierProvider)
provider = await waitForL2Geth(verifierProvider)
}

const syncVerifier = async (sequencerBlockNumber: number) => {
Expand Down
119 changes: 119 additions & 0 deletions integration-tests/sync-tests/2-sync-replica.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import chai, { expect } from 'chai'
import { Wallet, Contract, ContractFactory, providers } from 'ethers'
import { ethers } from 'hardhat'
import { injectL2Context } from '@eth-optimism/core-utils'

import {
sleep,
l2Provider,
replicaProvider,
waitForL2Geth,
} from '../test/shared/utils'
import { OptimismEnv } from '../test/shared/env'
import { DockerComposeNetwork } from '../test/shared/docker-compose'

describe('Syncing a replica', () => {
let env: OptimismEnv
let wallet: Wallet
let replica: DockerComposeNetwork
let provider: providers.JsonRpcProvider

const sequencerProvider = injectL2Context(l2Provider)

/* Helper functions */

const startReplica = async () => {
// Bring up new replica
replica = new DockerComposeNetwork(['replica'])
await replica.up({
commandOptions: ['--scale', 'replica=1'],
})

provider = await waitForL2Geth(replicaProvider)
}

const syncReplica = async (sequencerBlockNumber: number) => {
// Wait until replica has caught up to the sequencer
let latestReplicaBlock = (await provider.getBlock('latest')) as any
while (latestReplicaBlock.number < sequencerBlockNumber) {
await sleep(500)
latestReplicaBlock = (await provider.getBlock('latest')) as any
}

return provider.getBlock(sequencerBlockNumber)
}

before(async () => {
env = await OptimismEnv.new()
wallet = env.l2Wallet
})

after(async () => {
await replica.stop('replica')
await replica.rm()
})

describe('Basic transactions and ERC20s', () => {
const initialAmount = 1000
const tokenName = 'OVM Test'
const tokenDecimals = 8
const TokenSymbol = 'OVM'

let other: Wallet
let Factory__ERC20: ContractFactory
let ERC20: Contract

before(async () => {
other = Wallet.createRandom().connect(ethers.provider)
Factory__ERC20 = await ethers.getContractFactory('ERC20', wallet)
})

it('should sync dummy transaction', async () => {
const tx = {
to: '0x' + '1234'.repeat(10),
gasLimit: 4000000,
gasPrice: 0,
data: '0x',
value: 0,
}
const result = await wallet.sendTransaction(tx)
await result.wait()

const latestSequencerBlock = (await sequencerProvider.getBlock(
'latest'
)) as any

await startReplica()

const matchingReplicaBlock = (await syncReplica(
latestSequencerBlock.number
)) as any

expect(matchingReplicaBlock.stateRoot).to.eq(
latestSequencerBlock.stateRoot
)
})

it('should sync ERC20 deployment and transfer', async () => {
ERC20 = await Factory__ERC20.deploy(
initialAmount,
tokenName,
tokenDecimals,
TokenSymbol
)

const transfer = await ERC20.transfer(other.address, 100)
await transfer.wait()

const latestSequencerBlock = (await provider.getBlock('latest')) as any

const matchingReplicaBlock = (await syncReplica(
latestSequencerBlock.number
)) as any

expect(matchingReplicaBlock.stateRoot).to.eq(
latestSequencerBlock.stateRoot
)
})
})
})
1 change: 1 addition & 0 deletions integration-tests/test/shared/docker-compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ServiceNames =
| 'l2geth'
| 'relayer'
| 'verifier'
| 'replica'

const OPS_DIRECTORY = path.join(process.cwd(), '../ops')
const DEFAULT_SERVICES: ServiceNames[] = [
Expand Down
22 changes: 21 additions & 1 deletion integration-tests/test/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getContractInterface,
predeploys,
} from '@eth-optimism/contracts'
import { remove0x, Watcher } from '@eth-optimism/core-utils'
import { injectL2Context, remove0x, Watcher } from '@eth-optimism/core-utils'
import {
Contract,
Wallet,
Expand All @@ -25,9 +25,11 @@ const env = cleanEnv(process.env, {
L1_URL: str({ default: 'http://localhost:9545' }),
L2_URL: str({ default: 'http://localhost:8545' }),
VERIFIER_URL: str({ default: 'http://localhost:8547' }),
REPLICA_URL: str({ default: 'http://localhost:8549' }),
L1_POLLING_INTERVAL: num({ default: 10 }),
L2_POLLING_INTERVAL: num({ default: 10 }),
VERIFIER_POLLING_INTERVAL: num({ default: 10 }),
REPLICA_POLLING_INTERVAL: num({ default: 10 }),
PRIVATE_KEY: str({
default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
Expand All @@ -47,6 +49,9 @@ l2Provider.pollingInterval = env.L2_POLLING_INTERVAL
export const verifierProvider = new providers.JsonRpcProvider(env.VERIFIER_URL)
verifierProvider.pollingInterval = env.VERIFIER_POLLING_INTERVAL

export const replicaProvider = new providers.JsonRpcProvider(env.REPLICA_URL)
replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL

// The sequencer private key which is funded on L1
export const l1Wallet = new Wallet(env.PRIVATE_KEY, l1Provider)

Expand Down Expand Up @@ -168,3 +173,18 @@ export const expectApprox = (
`Actual value is more than ${lowerDeviation}% less than target`
).to.be.true
}

export const waitForL2Geth = async (
provider: providers.JsonRpcProvider
): Promise<providers.JsonRpcProvider> => {
let ready: boolean = false
while (!ready) {
try {
await provider.getNetwork()
ready = true
} catch (error) {
await sleep(1000)
}
}
return injectL2Context(provider)
}
6 changes: 4 additions & 2 deletions ops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ docker-compose \
up --build --detach
```

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


A Makefile has been provided for convience. The following targets are available.
- make up
- make down
Expand Down
32 changes: 28 additions & 4 deletions ops/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ services:
# connect to the 2 layers
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT: http://l1_chain:8545
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT: http://l2geth:8545
DATA_TRANSPORT_LAYER__SYNC_FROM_L2: 'true'
DATA_TRANSPORT_LAYER__L2_CHAIN_ID: 420
ports:
- ${DTL_PORT:-7878}:7878
Expand Down Expand Up @@ -141,23 +142,46 @@ services:
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
# override with the geth script and the env vars required for it
entrypoint: sh ./geth.sh
env_file:
- ./envs/geth.env
environment:
ETH1_HTTP: http://l1_chain:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
# used for getting the addresses
URL: http://deployer:8081/addresses.json
# connecting to the DTL
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l1'
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
IS_VERIFIER: "true"
ROLLUP_VERIFIER_ENABLE: 'true'
ports:
- ${VERIFIER_HTTP_PORT:-8547}:8545
- ${VERIFIER_WS_PORT:-8548}:8546

replica:
depends_on:
- dtl
image: ethereumoptimism/l2geth
deploy:
replicas: 0
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
entrypoint: sh ./geth.sh
env_file:
- ./envs/geth.env
environment:
ETH1_HTTP: http://l1_chain:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
URL: http://deployer:8081/addresses.json
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l2'
ROLLUP_VERIFIER_ENABLE: 'true'
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
ports:
- ${L2GETH_HTTP_PORT:-8549}:8545
- ${L2GETH_WS_PORT:-8550}:8546

integration_tests:
image: ethereumoptimism/integration-tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ export class L2IngestionService extends BaseService<L2IngestionServiceOptions> {
)

// We're already at the head, so no point in attempting to sync.
if (highestSyncedL2BlockNumber === targetL2Block) {
// Also wait on edge case of no L2 transactions
if (
highestSyncedL2BlockNumber === targetL2Block ||
currentL2Block === 0
) {
await sleep(this.options.pollingInterval)
continue
}
Expand Down

0 comments on commit 4b3f6d3

Please sign in to comment.