Commit b004d1ad authored by Kelvin Fichter's avatar Kelvin Fichter

feat(fd): support Bedrock networks

Updates the fault detector to support Bedrock networks. Bedrock support
is slightly hacky because we still also need to support the legacy
system until mainnet has been upgraded to Bedrock. We will want to
update the fault detector again once mainnet is upgraded to simplify the
service and remove the legacy components.
parent bb4b84c0
---
'@eth-optimism/fault-detector': minor
---
Updates the fault detector to support Bedrock networks.
ignores: [ ignores: [
"@babel/eslint-parser", "@babel/eslint-parser",
"@types/level",
"@typescript-eslint/parser", "@typescript-eslint/parser",
"eslint-plugin-import", "eslint-plugin-import",
"eslint-plugin-unicorn", "eslint-plugin-unicorn",
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
"ethers": "^5.7.0", "ethers": "^5.7.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-prom-bundle": "^6.3.6", "express-prom-bundle": "^6.3.6",
"level": "^6.0.1", "level6": "npm:level@^6.0.1",
"levelup": "^4.4.0" "levelup": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
......
/* Imports: External */ /* Imports: External */
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts' import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { LevelUp } from 'levelup' import { LevelUp } from 'levelup'
import level from 'level' import level from 'level6'
import { Counter } from 'prom-client' import { Counter } from 'prom-client'
/* Imports: Internal */ /* Imports: Internal */
......
import { Contract } from 'ethers' import { Contract, BigNumber } from 'ethers'
export interface OutputOracle<TSubmissionEventArgs> {
contract: Contract
filter: any
getTotalElements: () => Promise<BigNumber>
getEventIndex: (args: TSubmissionEventArgs) => BigNumber
}
/** /**
* Partial event interface, meant to reduce the size of the event cache to avoid * Partial event interface, meant to reduce the size of the event cache to avoid
...@@ -41,27 +48,32 @@ const getCache = ( ...@@ -41,27 +48,32 @@ const getCache = (
} }
/** /**
* Updates the event cache for the SCC. * Updates the event cache for a contract and event.
* *
* @param scc The State Commitment Chain contract. * @param contract Contract to update cache for.
* @param filter Event filter to use.
*/ */
export const updateStateBatchEventCache = async ( export const updateOracleCache = async <TSubmissionEventArgs>(
scc: Contract oracle: OutputOracle<TSubmissionEventArgs>
): Promise<void> => { ): Promise<void> => {
const cache = getCache(scc.address) const cache = getCache(oracle.contract.address)
let currentBlock = cache.highestBlock let currentBlock = cache.highestBlock
const endingBlock = await scc.provider.getBlockNumber() const endingBlock = await oracle.contract.provider.getBlockNumber()
let step = endingBlock - currentBlock let step = endingBlock - currentBlock
let failures = 0 let failures = 0
while (currentBlock < endingBlock) { while (currentBlock < endingBlock) {
try { try {
const events = await scc.queryFilter( const events = await oracle.contract.queryFilter(
scc.filters.StateBatchAppended(), oracle.filter,
currentBlock, currentBlock,
currentBlock + step currentBlock + step
) )
// Throw the events into the cache.
for (const event of events) { for (const event of events) {
cache.eventCache[event.args._batchIndex.toNumber()] = { cache.eventCache[
oracle.getEventIndex(event.args as TSubmissionEventArgs).toNumber()
] = {
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
args: event.args, args: event.args,
...@@ -97,15 +109,15 @@ export const updateStateBatchEventCache = async ( ...@@ -97,15 +109,15 @@ export const updateStateBatchEventCache = async (
/** /**
* Finds the Event that corresponds to a given state batch by index. * Finds the Event that corresponds to a given state batch by index.
* *
* @param scc StateCommitmentChain contract. * @param oracle Output oracle contract
* @param index State batch index to search for. * @param index State batch index to search for.
* @returns Event corresponding to the batch. * @returns Event corresponding to the batch.
*/ */
export const findEventForStateBatch = async ( export const findEventForStateBatch = async <TSubmissionEventArgs>(
scc: Contract, oracle: OutputOracle<TSubmissionEventArgs>,
index: number index: number
): Promise<PartialEvent> => { ): Promise<PartialEvent> => {
const cache = getCache(scc.address) const cache = getCache(oracle.contract.address)
// Try to find the event in cache first. // Try to find the event in cache first.
if (cache.eventCache[index]) { if (cache.eventCache[index]) {
...@@ -113,7 +125,7 @@ export const findEventForStateBatch = async ( ...@@ -113,7 +125,7 @@ export const findEventForStateBatch = async (
} }
// Update the event cache if we don't have the event. // Update the event cache if we don't have the event.
await updateStateBatchEventCache(scc) await updateOracleCache(oracle)
// Event better be in cache now! // Event better be in cache now!
if (cache.eventCache[index] === undefined) { if (cache.eventCache[index] === undefined) {
...@@ -126,23 +138,23 @@ export const findEventForStateBatch = async ( ...@@ -126,23 +138,23 @@ export const findEventForStateBatch = async (
/** /**
* Finds the first state batch index that has not yet passed the fault proof window. * Finds the first state batch index that has not yet passed the fault proof window.
* *
* @param scc StateCommitmentChain contract. * @param oracle Output oracle contract.
* @returns Starting state root batch index. * @returns Starting state root batch index.
*/ */
export const findFirstUnfinalizedStateBatchIndex = async ( export const findFirstUnfinalizedStateBatchIndex = async <TSubmissionEventArgs>(
scc: Contract oracle: OutputOracle<TSubmissionEventArgs>,
fpw: number
): Promise<number> => { ): Promise<number> => {
const fpw = (await scc.FRAUD_PROOF_WINDOW()).toNumber() const latestBlock = await oracle.contract.provider.getBlock('latest')
const latestBlock = await scc.provider.getBlock('latest') const totalBatches = (await oracle.getTotalElements()).toNumber()
const totalBatches = (await scc.getTotalBatches()).toNumber()
// Perform a binary search to find the next batch that will pass the challenge period. // Perform a binary search to find the next batch that will pass the challenge period.
let lo = 0 let lo = 0
let hi = totalBatches let hi = totalBatches
while (lo !== hi) { while (lo !== hi) {
const mid = Math.floor((lo + hi) / 2) const mid = Math.floor((lo + hi) / 2)
const event = await findEventForStateBatch(scc, mid) const event = await findEventForStateBatch(oracle, mid)
const block = await scc.provider.getBlock(event.blockNumber) const block = await oracle.contract.provider.getBlock(event.blockNumber)
if (block.timestamp + fpw < latestBlock.timestamp) { if (block.timestamp + fpw < latestBlock.timestamp) {
lo = mid + 1 lo = mid + 1
......
This diff is collapsed.
...@@ -12,6 +12,7 @@ import { expect } from './setup' ...@@ -12,6 +12,7 @@ import { expect } from './setup'
import { import {
findEventForStateBatch, findEventForStateBatch,
findFirstUnfinalizedStateBatchIndex, findFirstUnfinalizedStateBatchIndex,
OutputOracle,
} from '../src' } from '../src'
describe('helpers', () => { describe('helpers', () => {
...@@ -28,6 +29,7 @@ describe('helpers', () => { ...@@ -28,6 +29,7 @@ describe('helpers', () => {
let AddressManager: Contract let AddressManager: Contract
let ChainStorageContainer: Contract let ChainStorageContainer: Contract
let StateCommitmentChain: Contract let StateCommitmentChain: Contract
let oracle: OutputOracle<any>
beforeEach(async () => { beforeEach(async () => {
// Set up fakes // Set up fakes
FakeBondManager = await smock.fake(getContractInterface('BondManager')) FakeBondManager = await smock.fake(getContractInterface('BondManager'))
...@@ -67,6 +69,13 @@ describe('helpers', () => { ...@@ -67,6 +69,13 @@ describe('helpers', () => {
// Set up mock returns // Set up mock returns
FakeCanonicalTransactionChain.getTotalElements.returns(1000000000) // just needs to be large FakeCanonicalTransactionChain.getTotalElements.returns(1000000000) // just needs to be large
FakeBondManager.isCollateralized.returns(true) FakeBondManager.isCollateralized.returns(true)
oracle = {
contract: StateCommitmentChain,
filter: StateCommitmentChain.filters.StateBatchAppended(),
getTotalElements: async () => StateCommitmentChain.getTotalBatches(),
getEventIndex: (args: any) => args._batchIndex,
}
}) })
describe('findEventForStateBatch', () => { describe('findEventForStateBatch', () => {
...@@ -79,7 +88,7 @@ describe('helpers', () => { ...@@ -79,7 +88,7 @@ describe('helpers', () => {
}) })
it('should return the event', async () => { it('should return the event', async () => {
const event = await findEventForStateBatch(StateCommitmentChain, 0) const event = await findEventForStateBatch(oracle, 0)
expect(event.args._batchIndex).to.equal(0) expect(event.args._batchIndex).to.equal(0)
}) })
...@@ -88,7 +97,7 @@ describe('helpers', () => { ...@@ -88,7 +97,7 @@ describe('helpers', () => {
describe('when the event does not exist', () => { describe('when the event does not exist', () => {
it('should throw an error', async () => { it('should throw an error', async () => {
await expect( await expect(
findEventForStateBatch(StateCommitmentChain, 0) findEventForStateBatch(oracle, 0)
).to.eventually.be.rejectedWith('unable to find event for batch') ).to.eventually.be.rejectedWith('unable to find event for batch')
}) })
}) })
...@@ -119,7 +128,8 @@ describe('helpers', () => { ...@@ -119,7 +128,8 @@ describe('helpers', () => {
it('should find the first batch older than the FPW', async () => { it('should find the first batch older than the FPW', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(1) expect(first).to.equal(1)
...@@ -144,7 +154,8 @@ describe('helpers', () => { ...@@ -144,7 +154,8 @@ describe('helpers', () => {
it('should return zero', async () => { it('should return zero', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(0) expect(first).to.equal(0)
...@@ -177,7 +188,8 @@ describe('helpers', () => { ...@@ -177,7 +188,8 @@ describe('helpers', () => {
it('should return undefined', async () => { it('should return undefined', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(undefined) expect(first).to.equal(undefined)
......
...@@ -12090,7 +12090,7 @@ level-ws@^2.0.0: ...@@ -12090,7 +12090,7 @@ level-ws@^2.0.0:
readable-stream "^3.1.0" readable-stream "^3.1.0"
xtend "^4.0.1" xtend "^4.0.1"
level@^6.0.1: "level6@npm:level@^6.0.1":
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6" resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6"
integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw== integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment