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: [
"@babel/eslint-parser",
"@types/level",
"@typescript-eslint/parser",
"eslint-plugin-import",
"eslint-plugin-unicorn",
......
......@@ -52,7 +52,7 @@
"ethers": "^5.7.0",
"express": "^4.17.1",
"express-prom-bundle": "^6.3.6",
"level": "^6.0.1",
"level6": "npm:level@^6.0.1",
"levelup": "^4.4.0"
},
"devDependencies": {
......
/* Imports: External */
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { LevelUp } from 'levelup'
import level from 'level'
import level from 'level6'
import { Counter } from 'prom-client'
/* 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
......@@ -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 (
scc: Contract
export const updateOracleCache = async <TSubmissionEventArgs>(
oracle: OutputOracle<TSubmissionEventArgs>
): Promise<void> => {
const cache = getCache(scc.address)
const cache = getCache(oracle.contract.address)
let currentBlock = cache.highestBlock
const endingBlock = await scc.provider.getBlockNumber()
const endingBlock = await oracle.contract.provider.getBlockNumber()
let step = endingBlock - currentBlock
let failures = 0
while (currentBlock < endingBlock) {
try {
const events = await scc.queryFilter(
scc.filters.StateBatchAppended(),
const events = await oracle.contract.queryFilter(
oracle.filter,
currentBlock,
currentBlock + step
)
// Throw the events into the cache.
for (const event of events) {
cache.eventCache[event.args._batchIndex.toNumber()] = {
cache.eventCache[
oracle.getEventIndex(event.args as TSubmissionEventArgs).toNumber()
] = {
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
args: event.args,
......@@ -97,15 +109,15 @@ export const updateStateBatchEventCache = async (
/**
* 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.
* @returns Event corresponding to the batch.
*/
export const findEventForStateBatch = async (
scc: Contract,
export const findEventForStateBatch = async <TSubmissionEventArgs>(
oracle: OutputOracle<TSubmissionEventArgs>,
index: number
): Promise<PartialEvent> => {
const cache = getCache(scc.address)
const cache = getCache(oracle.contract.address)
// Try to find the event in cache first.
if (cache.eventCache[index]) {
......@@ -113,7 +125,7 @@ export const findEventForStateBatch = async (
}
// Update the event cache if we don't have the event.
await updateStateBatchEventCache(scc)
await updateOracleCache(oracle)
// Event better be in cache now!
if (cache.eventCache[index] === undefined) {
......@@ -126,23 +138,23 @@ export const findEventForStateBatch = async (
/**
* 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.
*/
export const findFirstUnfinalizedStateBatchIndex = async (
scc: Contract
export const findFirstUnfinalizedStateBatchIndex = async <TSubmissionEventArgs>(
oracle: OutputOracle<TSubmissionEventArgs>,
fpw: number
): Promise<number> => {
const fpw = (await scc.FRAUD_PROOF_WINDOW()).toNumber()
const latestBlock = await scc.provider.getBlock('latest')
const totalBatches = (await scc.getTotalBatches()).toNumber()
const latestBlock = await oracle.contract.provider.getBlock('latest')
const totalBatches = (await oracle.getTotalElements()).toNumber()
// Perform a binary search to find the next batch that will pass the challenge period.
let lo = 0
let hi = totalBatches
while (lo !== hi) {
const mid = Math.floor((lo + hi) / 2)
const event = await findEventForStateBatch(scc, mid)
const block = await scc.provider.getBlock(event.blockNumber)
const event = await findEventForStateBatch(oracle, mid)
const block = await oracle.contract.provider.getBlock(event.blockNumber)
if (block.timestamp + fpw < latestBlock.timestamp) {
lo = mid + 1
......
This diff is collapsed.
......@@ -12,6 +12,7 @@ import { expect } from './setup'
import {
findEventForStateBatch,
findFirstUnfinalizedStateBatchIndex,
OutputOracle,
} from '../src'
describe('helpers', () => {
......@@ -28,6 +29,7 @@ describe('helpers', () => {
let AddressManager: Contract
let ChainStorageContainer: Contract
let StateCommitmentChain: Contract
let oracle: OutputOracle<any>
beforeEach(async () => {
// Set up fakes
FakeBondManager = await smock.fake(getContractInterface('BondManager'))
......@@ -67,6 +69,13 @@ describe('helpers', () => {
// Set up mock returns
FakeCanonicalTransactionChain.getTotalElements.returns(1000000000) // just needs to be large
FakeBondManager.isCollateralized.returns(true)
oracle = {
contract: StateCommitmentChain,
filter: StateCommitmentChain.filters.StateBatchAppended(),
getTotalElements: async () => StateCommitmentChain.getTotalBatches(),
getEventIndex: (args: any) => args._batchIndex,
}
})
describe('findEventForStateBatch', () => {
......@@ -79,7 +88,7 @@ describe('helpers', () => {
})
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)
})
......@@ -88,7 +97,7 @@ describe('helpers', () => {
describe('when the event does not exist', () => {
it('should throw an error', async () => {
await expect(
findEventForStateBatch(StateCommitmentChain, 0)
findEventForStateBatch(oracle, 0)
).to.eventually.be.rejectedWith('unable to find event for batch')
})
})
......@@ -119,7 +128,8 @@ describe('helpers', () => {
it('should find the first batch older than the FPW', async () => {
const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain
oracle,
challengeWindowSeconds
)
expect(first).to.equal(1)
......@@ -144,7 +154,8 @@ describe('helpers', () => {
it('should return zero', async () => {
const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain
oracle,
challengeWindowSeconds
)
expect(first).to.equal(0)
......@@ -177,7 +188,8 @@ describe('helpers', () => {
it('should return undefined', async () => {
const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain
oracle,
challengeWindowSeconds
)
expect(first).to.equal(undefined)
......
......@@ -12090,7 +12090,7 @@ level-ws@^2.0.0:
readable-stream "^3.1.0"
xtend "^4.0.1"
level@^6.0.1:
"level6@npm:level@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6"
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