Commit f0aaa471 authored by Diego's avatar Diego Committed by GitHub

chain-mon: create initialized-upgraded-mon (#9416)

* chain-mon: create initialize-mon

* chain-mon: implemente logic for processing transactions in initialize-mon

* chain-mon: improve logging for initialize-mon

* chain-mon: add initialize-mon to index

* chain-mon: add networks contracts for initialize-mon

* chain-mon: rename initialize-mon to initialized-mon

* chain-mon: remove trailing tab character in initialized-mon
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chain-mon: remove trailing tab character in initialized-mon
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chain-mon: track upgraded event in initialized-mon

* chain-mon: rename initialized-mon to initialized-upgraded-mon

* chain-mon: lint

* chain-mon: pass contracts as option to initialized-upgraded-mon

* chain-mon: add support for goerli in initialized-upgraded-mon

* chain-mon: add error message in initialized-upgraded-mon
Co-authored-by: default avatarWill Cory <willcory10@gmail.com>

* chain-mon: add context to hashed topics in initialized-upgraded-mon

* chain-mon: lint

---------
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: default avatarWill Cory <willcory10@gmail.com>
parent 309c9bfd
......@@ -63,6 +63,20 @@ FAULT_DETECTOR__L2_RPC_PROVIDER=
# --bedrock Whether or not the service is running against a Bedrock chain (env: FAULT_DETECTOR__BEDROCK)
BEDROCK=true
###############################################################################
# ↓ initialized-upgraded-mon ↓ #
###############################################################################
# RPC pointing to network to monitor
INITIALIZED_UPGRADED_MON__RPC=
# The block number to start monitoring from
# Defaults to the first bedrock block if unset.
INITIALIZED_UPGRADED_MON__START_BLOCK_NUMBER=
# JSON array in the format [{ "label": <string>, "address": <address> }, ... ]
INITIALIZED_UPGRADED_MON__CONTRACTS=
# Optional Params
# --startbatchindex Batch index to start checking from. For bedrock chains, this is the L2 height to start from (env: FAULT_DETECTOR__START_BATCH_INDEX)
......
......@@ -16,6 +16,7 @@
"dev:replica-mon": "tsx watch ./src/replica-mon/service.ts",
"dev:wallet-mon": "tsx watch ./src/wallet-mon/service.ts",
"dev:wd-mon": "tsx watch ./src/wd-mon/service.ts",
"dev:initialized-upgraded-mon": "tsx watch ./src/initialized-upgraded-mon/service.ts",
"start:balance-mon": "tsx ./src/balance-mon/service.ts",
"start:drippie-mon": "tsx ./src/drippie-mon/service.ts",
"start:fault-mon": "tsx ./src/fault-mon/service.ts",
......@@ -23,6 +24,7 @@
"start:replica-mon": "tsx ./src/replica-mon/service.ts",
"start:wallet-mon": "tsx ./src/wallet-mon/service.ts",
"start:wd-mon": "tsx ./src/wd-mon/service.ts",
"start:initialized-upgraded-mon": "tsx ./src/initialized-upgraded-mon/service.ts",
"test": "hardhat test",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json",
"build": "tsc -p ./tsconfig.json",
......@@ -65,4 +67,4 @@
"ts-node": "^10.9.2",
"tsx": "^4.7.0"
}
}
}
\ No newline at end of file
......@@ -4,3 +4,4 @@ export * from './fault-mon/index'
export * from './multisig-mon/service'
export * from './wd-mon/service'
export * from './wallet-mon/service'
export * from './initialized-upgraded-mon/service'
import {
BaseServiceV2,
StandardOptions,
Gauge,
Counter,
validators,
waitForProvider,
} from '@eth-optimism/common-ts'
import { getChainId, compareAddrs } from '@eth-optimism/core-utils'
import { Provider, TransactionResponse } from '@ethersproject/abstract-provider'
import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json'
import sepoliaConfig from '@eth-optimism/contracts-bedrock/deploy-config/sepolia.json'
import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
import { version } from '../../package.json'
const networks = {
1: {
name: 'mainnet',
l1StartingBlockTag: mainnetConfig.l1StartingBlockTag,
},
10: {
name: 'op-mainnet',
l1StartingBlockTag: null,
},
11155111: {
name: 'sepolia',
l1StartingBlockTag: sepoliaConfig.l1StartingBlockTag,
},
11155420: {
name: 'op-sepolia',
l1StartingBlockTag: null,
},
5: {
name: 'goerli',
l1StartingBlockTag: goerliConfig.l1StartingBlockTag,
},
420: {
name: 'op-goerli',
l1StartingBlockTag: null,
},
}
// keccak256("Initialized(uint8)") = 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498
const topic_initialized =
'0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498'
// keccak256("Upgraded(address)") = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b
const topic_upgraded =
'0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b'
type InitializedUpgradedMonOptions = {
rpc: Provider
startBlockNumber: number
contracts: string
}
type InitializedUpgradedMonMetrics = {
initializedCalls: Counter
upgradedCalls: Counter
unexpectedRpcErrors: Counter
}
type InitializedUpgradedMonState = {
chainId: number
highestUncheckedBlockNumber: number
contracts: Array<{ label: string; address: string }>
}
export class InitializedUpgradedMonService extends BaseServiceV2<
InitializedUpgradedMonOptions,
InitializedUpgradedMonMetrics,
InitializedUpgradedMonState
> {
constructor(
options?: Partial<InitializedUpgradedMonOptions & StandardOptions>
) {
super({
version,
name: 'initialized-upgraded-mon',
loop: true,
options: {
loopIntervalMs: 1000,
...options,
},
optionsSpec: {
rpc: {
validator: validators.provider,
desc: 'Provider for network to monitor balances on',
},
startBlockNumber: {
validator: validators.num,
default: -1,
desc: 'L1 block number to start checking from',
public: true,
},
contracts: {
validator: validators.str,
desc: 'JSON array of [{ label, address }] to monitor contracts for',
public: true,
},
},
metricsSpec: {
initializedCalls: {
type: Gauge,
desc: 'Successful transactions to tracked contracts emitting initialized event',
labels: ['label', 'address'],
},
upgradedCalls: {
type: Gauge,
desc: 'Successful transactions to tracked contracts emitting upgraded event',
labels: ['label', 'address'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
labels: ['section', 'name'],
},
},
})
}
protected async init(): Promise<void> {
// Connect to L1.
await waitForProvider(this.options.rpc, {
logger: this.logger,
name: 'L1',
})
this.state.chainId = await getChainId(this.options.rpc)
const l1StartingBlockTag = networks[this.state.chainId].l1StartingBlockTag
if (this.options.startBlockNumber === -1) {
const block_number =
l1StartingBlockTag != null
? (await this.options.rpc.getBlock(l1StartingBlockTag)).number
: 0
this.state.highestUncheckedBlockNumber = block_number
} else {
this.state.highestUncheckedBlockNumber = this.options.startBlockNumber
}
try {
this.state.contracts = JSON.parse(this.options.contracts)
} catch (e) {
throw new Error(
'unable to start service because provided options is not valid json'
)
}
}
protected async main(): Promise<void> {
if (
(await this.options.rpc.getBlockNumber()) <
this.state.highestUncheckedBlockNumber
) {
this.logger.info('Waiting for new blocks')
return
}
const block = await this.options.rpc.getBlock(
this.state.highestUncheckedBlockNumber
)
this.logger.info('Checking block', {
number: block.number,
})
const transactions: TransactionResponse[] = []
for (const txHash of block.transactions) {
const t = await this.options.rpc.getTransaction(txHash)
transactions.push(t)
}
for (const transaction of transactions) {
for (const contract of this.state.contracts) {
const to =
transaction.to != null ? transaction.to : transaction['creates']
if (compareAddrs(contract.address, to)) {
try {
const transactionReceipt = await transaction.wait()
for (const log of transactionReceipt.logs) {
if (log.topics.includes(topic_initialized)) {
this.metrics.initializedCalls.inc({
label: contract.label,
address: contract.address,
})
this.logger.info('initialized event', {
label: contract.label,
address: contract.address,
})
} else if (log.topics.includes(topic_upgraded)) {
this.metrics.upgradedCalls.inc({
label: contract.label,
address: contract.address,
})
this.logger.info('upgraded event', {
label: contract.label,
address: contract.address,
})
}
}
} catch (err) {
// If error is due to transaction failing, ignore transaction
if (
err.message.length >= 18 &&
err.message.slice(0, 18) === 'transaction failed'
) {
break
}
// Otherwise, we have an unexpected RPC error
this.logger.info(`got unexpected RPC error`, {
section: 'creations',
name: 'NULL',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'creations',
name: 'NULL',
})
return
}
}
}
}
this.logger.info('Checked block', {
number: this.state.highestUncheckedBlockNumber,
})
this.state.highestUncheckedBlockNumber++
}
}
if (require.main === module) {
const service = new InitializedUpgradedMonService()
service.run()
}
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