Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-starfishes-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/data-transport-layer': patch
---

Updates DTL to correctly parse L1 to L2 tx timestamps after the first bss hardfork
5 changes: 5 additions & 0 deletions .changeset/forty-dancers-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/integration-tests': patch
---

Updates integration tests to include a test for syncing a Verifier from L1
5 changes: 5 additions & 0 deletions .changeset/green-donuts-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/l2geth': patch
---

Fixes incorrect timestamp handling for L1 syncing verifiers
5 changes: 5 additions & 0 deletions .changeset/quick-drinks-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/batch-submitter': patch
---

Updates batch submitter to also include separate timestamps for deposit transactions"
5 changes: 5 additions & 0 deletions .changeset/smooth-bags-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/integration-tests': patch
---

Add verifier integration tests
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.github

node_modules
.env
**/.env

test
**/*_test.go
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/test/shared/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
l1Provider,
l2Provider,
replicaProvider,
verifierProvider,
l1Wallet,
l2Wallet,
gasPriceOracleWallet,
Expand Down Expand Up @@ -57,6 +58,7 @@ export class OptimismEnv {
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider
verifierProvider: providers.JsonRpcProvider

constructor(args: any) {
this.addressManager = args.addressManager
Expand All @@ -74,6 +76,7 @@ export class OptimismEnv {
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.verifierProvider = args.verifierProvider
this.ctc = args.ctc
this.scc = args.scc
}
Expand Down Expand Up @@ -140,6 +143,7 @@ export class OptimismEnv {
l2Wallet,
l1Provider,
l2Provider,
verifierProvider,
replicaProvider,
})
}
Expand Down
10 changes: 10 additions & 0 deletions integration-tests/test/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const procEnv = cleanEnv(process.env, {
REPLICA_URL: str({ default: 'http://localhost:8549' }),
REPLICA_POLLING_INTERVAL: num({ default: 10 }),

VERIFIER_URL: str({ default: 'http://localhost:8547' }),

PRIVATE_KEY: str({
default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
Expand Down Expand Up @@ -96,6 +98,9 @@ const procEnv = cleanEnv(process.env, {
RUN_NIGHTLY_TESTS: bool({
default: false,
}),
RUN_VERIFIER_TESTS: bool({
default: true,
}),

MOCHA_TIMEOUT: num({
default: 120_000,
Expand All @@ -121,6 +126,11 @@ export const replicaProvider = injectL2Context(
)
replicaProvider.pollingInterval = procEnv.REPLICA_POLLING_INTERVAL

export const verifierProvider = injectL2Context(
new providers.JsonRpcProvider(procEnv.VERIFIER_URL)
)
verifierProvider.pollingInterval = procEnv.L2_POLLING_INTERVAL

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

Expand Down
103 changes: 103 additions & 0 deletions integration-tests/test/verifier.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { TransactionReceipt } from '@ethersproject/abstract-provider'

import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
gasPriceForL2,
sleep,
envConfig,
} from './shared/utils'

describe('Verifier Tests', () => {
let env: OptimismEnv

before(async function () {
if (!envConfig.RUN_VERIFIER_TESTS) {
this.skip()
return
}

env = await OptimismEnv.new()
})

describe('Matching blocks', () => {
it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const result = await env.l2Wallet.sendTransaction(tx)

let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}

const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any

const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any

expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})

it('sync an unprotected tx (eip155)', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.l2Provider.sendTransaction(signed)

let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}

const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any

const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any

expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})

it('should forward tx to sequencer', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.verifierProvider.sendTransaction(signed)

let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}

const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any

const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any

expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})
})
})
13 changes: 1 addition & 12 deletions l2geth/miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,18 +931,7 @@ func (w *worker) commitNewTx(tx *types.Transaction) error {
// Preserve liveliness as best as possible. Must panic on L1 to L2
// transactions as the timestamp cannot be malleated
if parent.Time() > tx.L1Timestamp() {
log.Error("Monotonicity violation", "index", num)
if tx.QueueOrigin() == types.QueueOriginSequencer {
tx.SetL1Timestamp(parent.Time())
prev := parent.Transactions()
if len(prev) == 1 {
tx.SetL1BlockNumber(prev[0].L1BlockNumber().Uint64())
} else {
log.Error("Cannot recover L1 Blocknumber")
}
} else {
log.Error("Cannot recover from monotonicity violation")
}
log.Error("Monotonicity violation", "index", num, "parent", parent.Time(), "tx", tx.L1Timestamp())
}

// Fill in the index field in the tx meta if it is `nil`.
Expand Down
5 changes: 3 additions & 2 deletions l2geth/rollup/sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,13 +825,14 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
if now.Sub(current) > s.timestampRefreshThreshold {
current = now
}
log.Info("Updating latest timestamp", "timestamp", current, "unix", current.Unix())
tx.SetL1Timestamp(uint64(current.Unix()))
} else if tx.L1Timestamp() == 0 && s.verifier {
// This should never happen
log.Error("No tx timestamp found when running as verifier", "hash", tx.Hash().Hex())
} else if tx.L1Timestamp() < s.GetLatestL1Timestamp() {
} else if tx.L1Timestamp() < ts {
// This should never happen, but sometimes does
log.Error("Timestamp monotonicity violation", "hash", tx.Hash().Hex())
log.Error("Timestamp monotonicity violation", "hash", tx.Hash().Hex(), "latest", ts, "tx", tx.L1Timestamp())
}

l1BlockNumber := tx.L1BlockNumber()
Expand Down
4 changes: 3 additions & 1 deletion ops/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ services:
- l1_chain
- deployer
- dtl
- l2geth
deploy:
replicas: 0
replicas: 1
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
Expand All @@ -146,6 +147,7 @@ services:
- ./envs/geth.env
environment:
ETH1_HTTP: http://l1_chain:8545
SEQUENCER_CLIENT_HTTP: http://l2geth:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l1'
Expand Down
1 change: 1 addition & 0 deletions ops/envs/dtl.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL=2000
DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS=true
DATA_TRANSPORT_LAYER__SERVER_HOSTNAME=0.0.0.0
DATA_TRANSPORT_LAYER__L1_START_HEIGHT=1
DATA_TRANSPORT_LAYER__BSS_HARDFORK_1_INDEX=0

DATA_TRANSPORT_LAYER__ADDRESS_MANAGER=
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=
Expand Down
84 changes: 43 additions & 41 deletions packages/data-transport-layer/src/db/transport-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ interface Indexed {
index: number
}

interface ExtraTransportDBOptions {
bssHardfork1Index?: number
}

export class TransportDB {
public db: SimpleDB
public opts: ExtraTransportDBOptions

constructor(leveldb: LevelUp) {
constructor(leveldb: LevelUp, opts?: ExtraTransportDBOptions) {
this.db = new SimpleDB(leveldb)
this.opts = opts || {}
}

public async putEnqueueEntries(entries: EnqueueEntry[]): Promise<void> {
Expand Down Expand Up @@ -254,26 +260,7 @@ export class TransportDB {
return null
}

if (transaction.queueOrigin === 'l1') {
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}

return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp: enqueue.timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
}
} else {
return transaction
}
return this._makeFullTransaction(transaction)
}

public async getLatestFullTransaction(): Promise<TransactionEntry> {
Expand All @@ -293,31 +280,46 @@ export class TransportDB {

const fullTransactions = []
for (const transaction of transactions) {
if (transaction.queueOrigin === 'l1') {
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}

fullTransactions.push({
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp: enqueue.timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
})
} else {
fullTransactions.push(transaction)
}
fullTransactions.push(await this._makeFullTransaction(transaction))
}

return fullTransactions
}

private async _makeFullTransaction(
transaction: TransactionEntry
): Promise<TransactionEntry> {
// We only need to do extra work for L1 to L2 transactions.
if (transaction.queueOrigin !== 'l1') {
return transaction
}

const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}

let timestamp = enqueue.timestamp
if (
typeof this.opts.bssHardfork1Index === 'number' &&
transaction.index >= this.opts.bssHardfork1Index
) {
timestamp = transaction.timestamp
}

return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
}
}

private async _getLatestEntryIndex(key: string): Promise<number> {
return this.db.get<number>(`${key}:latest`, 0) || 0
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
.toNumber(),
batchIndex: extraData.batchIndex.toNumber(),
blockNumber: BigNumber.from(0).toNumber(),
timestamp: BigNumber.from(0).toNumber(),
timestamp: context.timestamp,
gasLimit: BigNumber.from(0).toString(),
target: constants.AddressZero,
origin: constants.AddressZero,
Expand Down
Loading