github.com/ethereum-optimism/optimism@v1.7.2/packages/chain-mon/src/drippie-mon/service.ts (about)

     1  import {
     2    BaseServiceV2,
     3    StandardOptions,
     4    Gauge,
     5    Counter,
     6    validators,
     7  } from '@eth-optimism/common-ts'
     8  import { Provider } from '@ethersproject/abstract-provider'
     9  import { ethers } from 'ethers'
    10  import * as DrippieArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/Drippie.sol/Drippie.json'
    11  
    12  import { version } from '../../package.json'
    13  
    14  type DrippieMonOptions = {
    15    rpc: Provider
    16    drippieAddress: string
    17  }
    18  
    19  type DrippieMonMetrics = {
    20    isExecutable: Gauge
    21    executedDripCount: Gauge
    22    unexpectedRpcErrors: Counter
    23  }
    24  
    25  type DrippieMonState = {
    26    drippie: ethers.Contract
    27  }
    28  
    29  export class DrippieMonService extends BaseServiceV2<
    30    DrippieMonOptions,
    31    DrippieMonMetrics,
    32    DrippieMonState
    33  > {
    34    constructor(options?: Partial<DrippieMonOptions & StandardOptions>) {
    35      super({
    36        version,
    37        name: 'drippie-mon',
    38        loop: true,
    39        options: {
    40          loopIntervalMs: 60_000,
    41          ...options,
    42        },
    43        optionsSpec: {
    44          rpc: {
    45            validator: validators.provider,
    46            desc: 'Provider for network where Drippie is deployed',
    47          },
    48          drippieAddress: {
    49            validator: validators.str,
    50            desc: 'Address of Drippie contract',
    51            public: true,
    52          },
    53        },
    54        metricsSpec: {
    55          isExecutable: {
    56            type: Gauge,
    57            desc: 'Whether or not the drip is currently executable',
    58            labels: ['name'],
    59          },
    60          executedDripCount: {
    61            type: Gauge,
    62            desc: 'Number of times a drip has been executed',
    63            labels: ['name'],
    64          },
    65          unexpectedRpcErrors: {
    66            type: Counter,
    67            desc: 'Number of unexpected RPC errors',
    68            labels: ['section', 'name'],
    69          },
    70        },
    71      })
    72    }
    73  
    74    protected async init(): Promise<void> {
    75      this.state.drippie = new ethers.Contract(
    76        this.options.drippieAddress,
    77        DrippieArtifact.abi,
    78        this.options.rpc
    79      )
    80    }
    81  
    82    protected async main(): Promise<void> {
    83      let dripCreatedEvents: ethers.Event[]
    84      try {
    85        dripCreatedEvents = await this.state.drippie.queryFilter(
    86          this.state.drippie.filters.DripCreated()
    87        )
    88      } catch (err) {
    89        this.logger.info(`got unexpected RPC error`, {
    90          section: 'creations',
    91          name: 'NULL',
    92          err,
    93        })
    94  
    95        this.metrics.unexpectedRpcErrors.inc({
    96          section: 'creations',
    97          name: 'NULL',
    98        })
    99  
   100        return
   101      }
   102  
   103      // Not the most efficient thing in the world. Will end up making one request for every drip
   104      // created. We don't expect there to be many drips, so this is fine for now. We can also cache
   105      // and skip any archived drips to cut down on a few requests. Worth keeping an eye on this to
   106      // see if it's a bottleneck.
   107      for (const event of dripCreatedEvents) {
   108        const name = event.args.name
   109  
   110        let drip: any
   111        try {
   112          drip = await this.state.drippie.drips(name)
   113        } catch (err) {
   114          this.logger.info(`got unexpected RPC error`, {
   115            section: 'drips',
   116            name,
   117            err,
   118          })
   119  
   120          this.metrics.unexpectedRpcErrors.inc({
   121            section: 'drips',
   122            name,
   123          })
   124  
   125          continue
   126        }
   127  
   128        this.logger.info(`getting drip executable status`, {
   129          name,
   130          count: drip.count.toNumber(),
   131        })
   132  
   133        this.metrics.executedDripCount.set(
   134          {
   135            name,
   136          },
   137          drip.count.toNumber()
   138        )
   139  
   140        let executable: boolean
   141        try {
   142          // To avoid making unnecessary RPC requests, filter out any drips that we don't expect to
   143          // be executable right now. Only active drips (status = 2) and drips that are due to be
   144          // executed are expected to be executable (but might not be based on the dripcheck).
   145          if (
   146            drip.status === 2 &&
   147            drip.last.toNumber() + drip.config.interval.toNumber() <
   148              Date.now() / 1000
   149          ) {
   150            executable = await this.state.drippie.executable(name)
   151          } else {
   152            executable = false
   153          }
   154        } catch (err) {
   155          // All reverts include the string "Drippie:", so we can check for that.
   156          if (err.message.includes('Drippie:')) {
   157            // Not executable yet.
   158            executable = false
   159          } else {
   160            this.logger.info(`got unexpected RPC error`, {
   161              section: 'executable',
   162              name,
   163              err,
   164            })
   165  
   166            this.metrics.unexpectedRpcErrors.inc({
   167              section: 'executable',
   168              name,
   169            })
   170  
   171            continue
   172          }
   173        }
   174  
   175        this.logger.info(`got drip executable status`, {
   176          name,
   177          executable,
   178        })
   179  
   180        this.metrics.isExecutable.set(
   181          {
   182            name,
   183          },
   184          executable ? 1 : 0
   185        )
   186      }
   187    }
   188  }
   189  
   190  if (require.main === module) {
   191    const service = new DrippieMonService()
   192    service.run()
   193  }