github.com/ethereum-optimism/optimism@v1.7.2/packages/chain-mon/src/initialized-upgraded-mon/service.ts (about) 1 import { 2 BaseServiceV2, 3 StandardOptions, 4 Gauge, 5 Counter, 6 validators, 7 waitForProvider, 8 } from '@eth-optimism/common-ts' 9 import { getChainId, compareAddrs } from '@eth-optimism/core-utils' 10 import { Provider, TransactionResponse } from '@ethersproject/abstract-provider' 11 import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json' 12 import sepoliaConfig from '@eth-optimism/contracts-bedrock/deploy-config/sepolia.json' 13 import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json' 14 15 import { version } from '../../package.json' 16 17 const networks = { 18 1: { 19 name: 'mainnet', 20 l1StartingBlockTag: mainnetConfig.l1StartingBlockTag, 21 }, 22 10: { 23 name: 'op-mainnet', 24 l1StartingBlockTag: null, 25 }, 26 11155111: { 27 name: 'sepolia', 28 l1StartingBlockTag: sepoliaConfig.l1StartingBlockTag, 29 }, 30 11155420: { 31 name: 'op-sepolia', 32 l1StartingBlockTag: null, 33 }, 34 5: { 35 name: 'goerli', 36 l1StartingBlockTag: goerliConfig.l1StartingBlockTag, 37 }, 38 420: { 39 name: 'op-goerli', 40 l1StartingBlockTag: null, 41 }, 42 } 43 44 // keccak256("Initialized(uint8)") = 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 45 const topic_initialized = 46 '0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498' 47 48 // keccak256("Upgraded(address)") = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b 49 const topic_upgraded = 50 '0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b' 51 52 type InitializedUpgradedMonOptions = { 53 rpc: Provider 54 startBlockNumber: number 55 contracts: string 56 } 57 58 type InitializedUpgradedMonMetrics = { 59 initializedCalls: Counter 60 upgradedCalls: Counter 61 unexpectedRpcErrors: Counter 62 } 63 64 type InitializedUpgradedMonState = { 65 chainId: number 66 highestUncheckedBlockNumber: number 67 contracts: Array<{ label: string; address: string }> 68 } 69 70 export class InitializedUpgradedMonService extends BaseServiceV2< 71 InitializedUpgradedMonOptions, 72 InitializedUpgradedMonMetrics, 73 InitializedUpgradedMonState 74 > { 75 constructor( 76 options?: Partial<InitializedUpgradedMonOptions & StandardOptions> 77 ) { 78 super({ 79 version, 80 name: 'initialized-upgraded-mon', 81 loop: true, 82 options: { 83 loopIntervalMs: 1000, 84 ...options, 85 }, 86 optionsSpec: { 87 rpc: { 88 validator: validators.provider, 89 desc: 'Provider for network to monitor balances on', 90 }, 91 startBlockNumber: { 92 validator: validators.num, 93 default: -1, 94 desc: 'L1 block number to start checking from', 95 public: true, 96 }, 97 contracts: { 98 validator: validators.str, 99 desc: 'JSON array of [{ label, address }] to monitor contracts for', 100 public: true, 101 }, 102 }, 103 metricsSpec: { 104 initializedCalls: { 105 type: Gauge, 106 desc: 'Successful transactions to tracked contracts emitting initialized event', 107 labels: ['label', 'address'], 108 }, 109 upgradedCalls: { 110 type: Gauge, 111 desc: 'Successful transactions to tracked contracts emitting upgraded event', 112 labels: ['label', 'address'], 113 }, 114 unexpectedRpcErrors: { 115 type: Counter, 116 desc: 'Number of unexpected RPC errors', 117 labels: ['section', 'name'], 118 }, 119 }, 120 }) 121 } 122 123 protected async init(): Promise<void> { 124 // Connect to L1. 125 await waitForProvider(this.options.rpc, { 126 logger: this.logger, 127 name: 'L1', 128 }) 129 130 this.state.chainId = await getChainId(this.options.rpc) 131 132 const l1StartingBlockTag = networks[this.state.chainId].l1StartingBlockTag 133 134 if (this.options.startBlockNumber === -1) { 135 const block_number = 136 l1StartingBlockTag != null 137 ? (await this.options.rpc.getBlock(l1StartingBlockTag)).number 138 : 0 139 this.state.highestUncheckedBlockNumber = block_number 140 } else { 141 this.state.highestUncheckedBlockNumber = this.options.startBlockNumber 142 } 143 144 try { 145 this.state.contracts = JSON.parse(this.options.contracts) 146 } catch (e) { 147 throw new Error( 148 'unable to start service because provided options is not valid json' 149 ) 150 } 151 } 152 153 protected async main(): Promise<void> { 154 if ( 155 (await this.options.rpc.getBlockNumber()) < 156 this.state.highestUncheckedBlockNumber 157 ) { 158 this.logger.info('Waiting for new blocks') 159 return 160 } 161 162 const block = await this.options.rpc.getBlock( 163 this.state.highestUncheckedBlockNumber 164 ) 165 this.logger.info('Checking block', { 166 number: block.number, 167 }) 168 169 const transactions: TransactionResponse[] = [] 170 for (const txHash of block.transactions) { 171 const t = await this.options.rpc.getTransaction(txHash) 172 transactions.push(t) 173 } 174 175 for (const transaction of transactions) { 176 for (const contract of this.state.contracts) { 177 const to = 178 transaction.to != null ? transaction.to : transaction['creates'] 179 if (compareAddrs(contract.address, to)) { 180 try { 181 const transactionReceipt = await transaction.wait() 182 for (const log of transactionReceipt.logs) { 183 if (log.topics.includes(topic_initialized)) { 184 this.metrics.initializedCalls.inc({ 185 label: contract.label, 186 address: contract.address, 187 }) 188 this.logger.info('initialized event', { 189 label: contract.label, 190 address: contract.address, 191 }) 192 } else if (log.topics.includes(topic_upgraded)) { 193 this.metrics.upgradedCalls.inc({ 194 label: contract.label, 195 address: contract.address, 196 }) 197 this.logger.info('upgraded event', { 198 label: contract.label, 199 address: contract.address, 200 }) 201 } 202 } 203 } catch (err) { 204 // If error is due to transaction failing, ignore transaction 205 if ( 206 err.message.length >= 18 && 207 err.message.slice(0, 18) === 'transaction failed' 208 ) { 209 break 210 } 211 // Otherwise, we have an unexpected RPC error 212 this.logger.info(`got unexpected RPC error`, { 213 section: 'creations', 214 name: 'NULL', 215 err, 216 }) 217 218 this.metrics.unexpectedRpcErrors.inc({ 219 section: 'creations', 220 name: 'NULL', 221 }) 222 223 return 224 } 225 } 226 } 227 } 228 this.logger.info('Checked block', { 229 number: this.state.highestUncheckedBlockNumber, 230 }) 231 this.state.highestUncheckedBlockNumber++ 232 } 233 } 234 235 if (require.main === module) { 236 const service = new InitializedUpgradedMonService() 237 service.run() 238 }