github.com/ethereum-optimism/optimism@v1.7.2/packages/chain-mon/src/wallet-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 goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
    13  
    14  import { version } from '../../package.json'
    15  
    16  const networks = {
    17    1: {
    18      name: 'mainnet',
    19      l1StartingBlockTag: mainnetConfig.l1StartingBlockTag,
    20      accounts: [
    21        {
    22          label: 'Proposer',
    23          wallet: mainnetConfig.l2OutputOracleProposer,
    24          target: '0xdfe97868233d1aa22e815a266982f2cf17685a27',
    25        },
    26        {
    27          label: 'Batcher',
    28          wallet: mainnetConfig.batchSenderAddress,
    29          target: mainnetConfig.batchInboxAddress,
    30        },
    31      ],
    32    },
    33    10: {
    34      name: 'goerli',
    35      l1StartingBlockTag: goerliConfig.l1StartingBlockTag,
    36      accounts: [
    37        {
    38          label: 'Proposer',
    39          wallet: goerliConfig.l2OutputOracleProposer,
    40          target: '0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0',
    41        },
    42        {
    43          label: 'Batcher',
    44          wallet: goerliConfig.batchSenderAddress,
    45          target: goerliConfig.batchInboxAddress,
    46        },
    47      ],
    48    },
    49  }
    50  
    51  type WalletMonOptions = {
    52    rpc: Provider
    53    startBlockNumber: number
    54  }
    55  
    56  type WalletMonMetrics = {
    57    validatedCalls: Counter
    58    unexpectedCalls: Counter
    59    unexpectedRpcErrors: Counter
    60  }
    61  
    62  type WalletMonState = {
    63    chainId: number
    64    highestUncheckedBlockNumber: number
    65  }
    66  
    67  export class WalletMonService extends BaseServiceV2<
    68    WalletMonOptions,
    69    WalletMonMetrics,
    70    WalletMonState
    71  > {
    72    constructor(options?: Partial<WalletMonOptions & StandardOptions>) {
    73      super({
    74        version,
    75        name: 'wallet-mon',
    76        loop: true,
    77        options: {
    78          loopIntervalMs: 1000,
    79          ...options,
    80        },
    81        optionsSpec: {
    82          rpc: {
    83            validator: validators.provider,
    84            desc: 'Provider for network to monitor balances on',
    85          },
    86          startBlockNumber: {
    87            validator: validators.num,
    88            default: -1,
    89            desc: 'L1 block number to start checking from',
    90            public: true,
    91          },
    92        },
    93        metricsSpec: {
    94          validatedCalls: {
    95            type: Gauge,
    96            desc: 'Transactions from the account checked',
    97            labels: ['wallet', 'target', 'nickname'],
    98          },
    99          unexpectedCalls: {
   100            type: Counter,
   101            desc: 'Number of unexpected wallets',
   102            labels: ['wallet', 'target', 'nickname', 'transactionHash'],
   103          },
   104          unexpectedRpcErrors: {
   105            type: Counter,
   106            desc: 'Number of unexpected RPC errors',
   107            labels: ['section', 'name'],
   108          },
   109        },
   110      })
   111    }
   112  
   113    protected async init(): Promise<void> {
   114      // Connect to L1.
   115      await waitForProvider(this.options.rpc, {
   116        logger: this.logger,
   117        name: 'L1',
   118      })
   119  
   120      this.state.chainId = await getChainId(this.options.rpc)
   121  
   122      const l1StartingBlockTag = networks[this.state.chainId].l1StartingBlockTag
   123  
   124      if (this.options.startBlockNumber === -1) {
   125        const block = await this.options.rpc.getBlock(l1StartingBlockTag)
   126        this.state.highestUncheckedBlockNumber = block.number
   127      } else {
   128        this.state.highestUncheckedBlockNumber = this.options.startBlockNumber
   129      }
   130    }
   131  
   132    protected async main(): Promise<void> {
   133      if (
   134        (await this.options.rpc.getBlockNumber()) <
   135        this.state.highestUncheckedBlockNumber
   136      ) {
   137        this.logger.info('Waiting for new blocks')
   138        return
   139      }
   140  
   141      const network = networks[this.state.chainId]
   142      const accounts = network.accounts
   143  
   144      const block = await this.options.rpc.getBlock(
   145        this.state.highestUncheckedBlockNumber
   146      )
   147      this.logger.info('Checking block', {
   148        number: block.number,
   149      })
   150  
   151      const transactions: TransactionResponse[] = []
   152      for (const txHash of block.transactions) {
   153        const t = await this.options.rpc.getTransaction(txHash)
   154        transactions.push(t)
   155      }
   156  
   157      for (const transaction of transactions) {
   158        for (const account of accounts) {
   159          if (compareAddrs(account.wallet, transaction.from)) {
   160            if (compareAddrs(account.target, transaction.to)) {
   161              this.metrics.validatedCalls.inc({
   162                nickname: account.label,
   163                wallet: account.address,
   164                target: account.target,
   165              })
   166              this.logger.info('validated call', {
   167                nickname: account.label,
   168                wallet: account.address,
   169                target: account.target,
   170              })
   171            } else {
   172              this.metrics.unexpectedCalls.inc({
   173                nickname: account.label,
   174                wallet: account.address,
   175                target: transaction.to,
   176                transactionHash: transaction.hash,
   177              })
   178              this.logger.error('Unexpected call detected', {
   179                nickname: account.label,
   180                address: account.address,
   181                target: transaction.to,
   182                transactionHash: transaction.hash,
   183              })
   184            }
   185          }
   186        }
   187      }
   188      this.logger.info('Checked block', {
   189        number: this.state.highestUncheckedBlockNumber,
   190      })
   191      this.state.highestUncheckedBlockNumber++
   192    }
   193  }
   194  
   195  if (require.main === module) {
   196    const service = new WalletMonService()
   197    service.run()
   198  }