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  }