Commit 0023a477 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #2181 from ethereum-optimism/sc/remove-watcher

feat(core-utils): remove Watcher and injectL2Context
parents ed1bb684 0b4453f7
---
'@eth-optimism/core-utils': minor
---
Deletes the Watcher and injectL2Context functions. Use the SDK instead.
---
'@eth-optimism/batch-submitter': patch
'@eth-optimism/replica-healthcheck': patch
---
Use asL2Provider instead of injectL2Context in bss and healthcheck service.
......@@ -36,6 +36,7 @@
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.14",
"@eth-optimism/core-utils": "0.7.7",
"@eth-optimism/sdk": "^0.2.1",
"@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.5.3",
......
/* External Imports */
import { exit } from 'process'
import { injectL2Context, Bcfg } from '@eth-optimism/core-utils'
import { Bcfg } from '@eth-optimism/core-utils'
import { asL2Provider } from '@eth-optimism/sdk'
import * as Sentry from '@sentry/node'
import { Logger, Metrics, createMetricsServer } from '@eth-optimism/common-ts'
import { Signer, Wallet } from 'ethers'
......@@ -346,7 +347,7 @@ export const run = async () => {
const clearPendingTxs = requiredEnvVars.CLEAR_PENDING_TXS
const l2Provider = injectL2Context(
const l2Provider = asL2Provider(
new StaticJsonRpcProvider({
url: requiredEnvVars.L2_NODE_WEB3_URL,
headers: { 'User-Agent': 'batch-submitter' },
......
......@@ -37,12 +37,10 @@
"@ethersproject/providers": "^5.5.3",
"@ethersproject/web": "^5.5.1",
"chai": "^4.3.4",
"ethers": "^5.5.4",
"lodash": "^4.17.21"
"ethers": "^5.5.4"
},
"devDependencies": {
"@types/chai": "^4.2.18",
"@types/lodash": "^4.14.168",
"@types/mocha": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
......
......@@ -5,6 +5,4 @@
export * from './alias'
export * from './batch-encoding'
export * from './fees'
export * from './l2context'
export * from './rollup-types'
export * from './watcher'
import cloneDeep from 'lodash/cloneDeep'
import { providers, BigNumber } from 'ethers'
const parseNumber = (n: string | number): number => {
if (typeof n === 'string' && n.startsWith('0x')) {
return parseInt(n, 16)
}
if (typeof n === 'number') {
return n
}
return parseInt(n, 10)
}
/**
* Helper for adding additional L2 context to transactions
*/
export const injectL2Context = (l1Provider: providers.JsonRpcProvider) => {
const provider = cloneDeep(l1Provider)
// Pass through the state root
const blockFormat = provider.formatter.block.bind(provider.formatter)
provider.formatter.block = (block) => {
const b = blockFormat(block)
b.stateRoot = block.stateRoot
return b
}
// Pass through the state root and additional tx data
const blockWithTransactions = provider.formatter.blockWithTransactions.bind(
provider.formatter
)
provider.formatter.blockWithTransactions = (block) => {
const b = blockWithTransactions(block)
b.stateRoot = block.stateRoot
for (let i = 0; i < b.transactions.length; i++) {
b.transactions[i].l1BlockNumber = block.transactions[i].l1BlockNumber
if (b.transactions[i].l1BlockNumber != null) {
b.transactions[i].l1BlockNumber = parseNumber(
b.transactions[i].l1BlockNumber
)
}
b.transactions[i].l1Timestamp = block.transactions[i].l1Timestamp
if (b.transactions[i].l1Timestamp != null) {
b.transactions[i].l1Timestamp = parseNumber(
b.transactions[i].l1Timestamp
)
}
b.transactions[i].l1TxOrigin = block.transactions[i].l1TxOrigin
b.transactions[i].queueOrigin = block.transactions[i].queueOrigin
b.transactions[i].rawTransaction = block.transactions[i].rawTransaction
}
return b
}
// Handle additional tx data
const formatTxResponse = provider.formatter.transactionResponse.bind(
provider.formatter
)
provider.formatter.transactionResponse = (transaction) => {
const tx = formatTxResponse(transaction) as any
tx.txType = transaction.txType
tx.queueOrigin = transaction.queueOrigin
tx.rawTransaction = transaction.rawTransaction
tx.l1BlockNumber = transaction.l1BlockNumber
if (tx.l1BlockNumber != null) {
tx.l1BlockNumber = parseInt(tx.l1BlockNumber, 16)
}
tx.l1TxOrigin = transaction.l1TxOrigin
return tx
}
const formatReceiptResponse = provider.formatter.receipt.bind(
provider.formatter
)
provider.formatter.receipt = (receipt) => {
const r = formatReceiptResponse(receipt)
r.l1GasPrice = BigNumber.from(receipt.l1GasPrice)
r.l1GasUsed = BigNumber.from(receipt.l1GasUsed)
r.l1Fee = BigNumber.from(receipt.l1Fee)
r.l1FeeScalar = parseFloat(receipt.l1FeeScalar)
return r
}
return provider
}
/* External Imports */
import { ethers } from 'ethers'
import { Provider, TransactionReceipt } from '@ethersproject/abstract-provider'
const RELAYED_MESSAGE = ethers.utils.id(`RelayedMessage(bytes32)`)
const FAILED_RELAYED_MESSAGE = ethers.utils.id(`FailedRelayedMessage(bytes32)`)
export interface Layer {
provider: Provider
messengerAddress: string
blocksToFetch?: number
}
export interface WatcherOptions {
l1: Layer
l2: Layer
pollInterval?: number
blocksToFetch?: number
pollForPending?: boolean
}
export class Watcher {
public l1: Layer
public l2: Layer
public pollInterval = 3000
public blocksToFetch = 1500
public pollForPending = true
constructor(opts: WatcherOptions) {
this.l1 = opts.l1
this.l2 = opts.l2
if (typeof opts.pollInterval === 'number') {
this.pollInterval = opts.pollInterval
}
if (typeof opts.blocksToFetch === 'number') {
this.blocksToFetch = opts.blocksToFetch
}
if (typeof opts.pollForPending === 'boolean') {
this.pollForPending = opts.pollForPending
}
}
public async getMessageHashesFromL1Tx(l1TxHash: string): Promise<string[]> {
return this.getMessageHashesFromTx(this.l1, l1TxHash)
}
public async getMessageHashesFromL2Tx(l2TxHash: string): Promise<string[]> {
return this.getMessageHashesFromTx(this.l2, l2TxHash)
}
public async getL1TransactionReceipt(
l2ToL1MsgHash: string,
pollForPending?
): Promise<TransactionReceipt> {
return this.getTransactionReceipt(this.l1, l2ToL1MsgHash, pollForPending)
}
public async getL2TransactionReceipt(
l1ToL2MsgHash: string,
pollForPending?
): Promise<TransactionReceipt> {
return this.getTransactionReceipt(this.l2, l1ToL2MsgHash, pollForPending)
}
public async getMessageHashesFromTx(
layer: Layer,
txHash: string
): Promise<string[]> {
const receipt = await layer.provider.getTransactionReceipt(txHash)
if (!receipt) {
return []
}
const msgHashes = []
const sentMessageEventId = ethers.utils.id(
'SentMessage(address,address,bytes,uint256,uint256)'
)
const l2CrossDomainMessengerRelayAbi = [
'function relayMessage(address _target,address _sender,bytes memory _message,uint256 _messageNonce)',
]
const l2CrossDomainMessengerRelayinterface = new ethers.utils.Interface(
l2CrossDomainMessengerRelayAbi
)
for (const log of receipt.logs) {
if (
log.address === layer.messengerAddress &&
log.topics[0] === sentMessageEventId
) {
const [sender, message, messageNonce] =
ethers.utils.defaultAbiCoder.decode(
['address', 'bytes', 'uint256'],
log.data
)
const [target] = ethers.utils.defaultAbiCoder.decode(
['address'],
log.topics[1]
)
const encodedMessage =
l2CrossDomainMessengerRelayinterface.encodeFunctionData(
'relayMessage',
[target, sender, message, messageNonce]
)
msgHashes.push(
ethers.utils.solidityKeccak256(['bytes'], [encodedMessage])
)
}
}
return msgHashes
}
public async getTransactionReceipt(
layer: Layer,
msgHash: string,
pollForPending?
): Promise<TransactionReceipt> {
if (typeof pollForPending !== 'boolean') {
pollForPending = this.pollForPending
}
let matches: ethers.providers.Log[] = []
let blocksToFetch = layer.blocksToFetch
if (typeof blocksToFetch !== 'number') {
blocksToFetch = this.blocksToFetch
}
// scan for transaction with specified message
while (matches.length === 0) {
const blockNumber = await layer.provider.getBlockNumber()
const startingBlock = Math.max(blockNumber - blocksToFetch, 0)
const successFilter: ethers.providers.Filter = {
address: layer.messengerAddress,
topics: [RELAYED_MESSAGE],
fromBlock: startingBlock,
}
const failureFilter: ethers.providers.Filter = {
address: layer.messengerAddress,
topics: [FAILED_RELAYED_MESSAGE],
fromBlock: startingBlock,
}
const successLogs = await layer.provider.getLogs(successFilter)
const failureLogs = await layer.provider.getLogs(failureFilter)
const logs = successLogs.concat(failureLogs)
matches = logs.filter(
(log: ethers.providers.Log) => log.topics[1] === msgHash
)
// exit loop after first iteration if not polling
if (!pollForPending) {
break
}
// pause awhile before trying again
await new Promise((r) => setTimeout(r, this.pollInterval))
}
// Message was relayed in the past
if (matches.length > 0) {
if (matches.length > 1) {
throw Error(
'Found multiple transactions relaying the same message hash.'
)
}
return layer.provider.getTransactionReceipt(matches[0].transactionHash)
} else {
return Promise.resolve(undefined)
}
}
}
......@@ -34,6 +34,7 @@
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/core-utils": "0.7.7",
"@eth-optimism/sdk": "^0.2.1",
"dotenv": "^10.0.0",
"ethers": "^5.5.4",
"express": "^4.17.1",
......
......@@ -6,7 +6,8 @@ import { Gauge, Histogram } from 'prom-client'
import cron from 'node-cron'
import { providers, Wallet } from 'ethers'
import { Metrics, Logger } from '@eth-optimism/common-ts'
import { injectL2Context, sleep } from '@eth-optimism/core-utils'
import { sleep } from '@eth-optimism/core-utils'
import { asL2Provider } from '@eth-optimism/sdk'
import { binarySearchForMismatch } from './helpers'
......@@ -49,7 +50,7 @@ export class HealthcheckServer {
init = () => {
this.metrics = this.initMetrics()
this.server = this.initServer()
this.replicaProvider = injectL2Context(
this.replicaProvider = asL2Provider(
new providers.StaticJsonRpcProvider({
url: this.options.replicaRpcProvider,
headers: { 'User-Agent': 'replica-healthcheck' },
......@@ -180,7 +181,7 @@ export class HealthcheckServer {
}
runSyncCheck = async () => {
const sequencerProvider = injectL2Context(
const sequencerProvider = asL2Provider(
new providers.StaticJsonRpcProvider({
url: this.options.sequencerRpcProvider,
headers: { 'User-Agent': 'replica-healthcheck' },
......
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