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 }