diff --git a/app/Types.ts b/app/Types.ts index 05fd80a..facb37e 100644 --- a/app/Types.ts +++ b/app/Types.ts @@ -362,6 +362,7 @@ export interface IRandaoAgent extends IAgent { export interface IDataSource { getType(): string; getBlocksDelay(): Promise<{ diff: bigint; nodeBlockNumber: bigint; sourceBlockNumber: bigint }>; + getJob(_context, jobKey): Promise; getRegisteredJobs(_context): Promise<{ data: Map; meta: SourceMetadata }>; getOwnersBalances( context, @@ -427,7 +428,7 @@ export interface IAgent { getBaseFeePerGas(): bigint; - queryPastEvents(eventName: string, from: number, to: number): Promise; + queryPastEvents(eventName: string, from: number, to: number, filters?: [any]): Promise; buildTx(calldata: string): Promise; diff --git a/app/agents/AbstractAgent.ts b/app/agents/AbstractAgent.ts index 50192e5..be1d2be 100644 --- a/app/agents/AbstractAgent.ts +++ b/app/agents/AbstractAgent.ts @@ -18,7 +18,12 @@ import { import { BigNumber, ethers, Wallet } from 'ethers'; import { getEncryptedJson } from '../services/KeyService.js'; import { BN_ZERO, DEFAULT_SYNC_FROM_CHAINS } from '../Constants.js'; -import { filterFunctionResultObject, numberToBigInt, toChecksummedAddress, weiValueToEth } from '../Utils.js'; +import { + filterFunctionResultObject, + numberToBigInt, + toChecksummedAddress, + weiValueToEth, +} from '../Utils.js'; import { FlashbotsExecutor } from '../executors/FlashbotsExecutor.js'; import { PGAExecutor } from '../executors/PGAExecutor.js'; import { getAgentDefaultSyncFromSafe, getDefaultExecutorConfig, setConfigDefaultValues } from '../ConfigGetters.js'; @@ -355,8 +360,13 @@ export abstract class AbstractAgent implements IAgent { return this.blacklistedJobs.has(jobKey); } - public getJob(jobKey: string): RandaoJob | LightJob | null { - return this.jobs.get(jobKey); + public async getJob(jobKey: string): Promise { + let job = this.jobs.get(jobKey); + if (!job) { + job = await this.dataSource.getJob(this, jobKey); + await this.addJob(job); + } + return job; } public getJobsCount(): { total: number; interval: number; resolver: number } { const counters = { @@ -467,24 +477,24 @@ export abstract class AbstractAgent implements IAgent { } abstract _buildNewJob(event): LightJob | RandaoJob; - private async addJob(creationEvent: EventWrapper) { - const jobKey = creationEvent.args.jobKey; - const owner = creationEvent.args.owner; + private async addJobByRegisterEvent(creationEvent: EventWrapper) { + return this.addJob(this._buildNewJob(creationEvent)); + } - const job = this._buildNewJob(creationEvent); - this.jobs.set(jobKey, job); + private async addJob(job: LightJob | RandaoJob) { + this.jobs.set(job.key, job); await this.dataSource.addLensFieldsToOneJob(job); job.clearJobCredits(); - if (!this.ownerJobs.has(owner)) { - this.ownerJobs.set(owner, new Set()); + if (!this.ownerJobs.has(job.owner)) { + this.ownerJobs.set(job.owner, new Set()); } - const set = this.ownerJobs.get(owner); - set.add(jobKey); + const set = this.ownerJobs.get(job.owner); + set.add(job.key); - if (!this.ownerBalances.has(owner)) { - this.ownerBalances.set(owner, BN_ZERO); + if (!this.ownerBalances.has(job.owner)) { + this.ownerBalances.set(job.owner, BN_ZERO); } } @@ -723,7 +733,10 @@ export abstract class AbstractAgent implements IAgent { return await this.contract.ethCall('getKeeper', [keeperId]); } - public async queryPastEvents(eventName: string, from: number, to: number): Promise { + public async queryPastEvents(eventName: string, from: number, to: number, filters = []): Promise { + if (filters.length) { + eventName = this.contract[eventName](filters); + } return this.contract.getPastEvents(eventName, from, to); } @@ -733,7 +746,7 @@ export abstract class AbstractAgent implements IAgent { protected initializeListeners(blockNumber: number) { // Job events - this.on('DepositJobCredits', event => { + this.on('DepositJobCredits', async event => { const { jobKey, amount, fee } = event.args; this.clog( @@ -745,17 +758,17 @@ export abstract class AbstractAgent implements IAgent { this.clog('error', `Ignoring DepositJobCredits event due the job missing: (jobKey=${jobKey})`); } - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyJobCreditsDeposit(BigNumber.from(amount)); job.watch(); }); - this.on('WithdrawJobCredits', event => { + this.on('WithdrawJobCredits', async event => { const { jobKey, amount } = event.args; this.clog('debug', `'WithdrawJobCredits' event: (block=${event.blockNumber},jobKey=${jobKey},amount=${amount})`); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyJobCreditWithdrawal(BigNumber.from(amount)); job.watch(); }); @@ -804,7 +817,7 @@ export abstract class AbstractAgent implements IAgent { } }); - this.on('AcceptJobTransfer', event => { + this.on('AcceptJobTransfer', async event => { const { jobKey_, to_: ownerAfter } = event.args; this.clog( @@ -812,7 +825,7 @@ export abstract class AbstractAgent implements IAgent { `'AcceptJobTransfer' event: (block=${event.blockNumber},jobKey_=${jobKey_},to_=${ownerAfter})`, ); - const job = this.jobs.get(jobKey_); + const job = await this.getJob(jobKey_); const ownerBefore = job.getOwner(); this.ownerJobs.get(ownerBefore).delete(jobKey_); @@ -825,7 +838,7 @@ export abstract class AbstractAgent implements IAgent { job.watch(); }); - this.on('JobUpdate', event => { + this.on('JobUpdate', async event => { const { jobKey, maxBaseFeeGwei, rewardPct, fixedReward, jobMinCvp, intervalSeconds } = event.args; this.clog( @@ -833,12 +846,12 @@ export abstract class AbstractAgent implements IAgent { `'JobUpdate' event: (block=${event.blockNumber},jobKey=${jobKey},maxBaseFeeGwei=${maxBaseFeeGwei},reardPct=${rewardPct},fixedReward=${fixedReward},jobMinCvp=${jobMinCvp},intervalSeconds=${intervalSeconds})`, ); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyUpdate(maxBaseFeeGwei, rewardPct, fixedReward, jobMinCvp, intervalSeconds); job.watch(); }); - this.on('SetJobPreDefinedCalldata', event => { + this.on('SetJobPreDefinedCalldata', async event => { const { jobKey, preDefinedCalldata } = event.args; this.clog( @@ -846,12 +859,12 @@ export abstract class AbstractAgent implements IAgent { `'SetJobPreDefinedCalldata' event: (block=${event.blockNumber},jobKey=${jobKey},preDefinedCalldata=${preDefinedCalldata})`, ); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyPreDefinedCalldata(preDefinedCalldata); job.watch(); }); - this.on('SetJobResolver', event => { + this.on('SetJobResolver', async event => { const { jobKey, resolverAddress, resolverCalldata } = event.args; this.clog( @@ -859,7 +872,7 @@ export abstract class AbstractAgent implements IAgent { `'SetJobResolver' event: (block=${event.blockNumber},jobKey=${jobKey},resolverAddress=${resolverAddress},useJobOwnerCredits_=${resolverCalldata})`, ); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyResolver(resolverAddress, resolverCalldata); job.watch(); }); @@ -872,7 +885,7 @@ export abstract class AbstractAgent implements IAgent { `'SetJobConfig' event: (block=${event.blockNumber},jobKey=${jobKey},isActive=${isActive_},useJobOwnerCredits_=${useJobOwnerCredits_},assertResolverSelector_=${assertResolverSelector_})`, ); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); const binJob = await this.network.queryLensJobsRawBytes32(this.address, jobKey); job.applyBinJobData(binJob); job.watch(); @@ -888,10 +901,10 @@ export abstract class AbstractAgent implements IAgent { },jobKey=${jobKey},jobAddress=${jobAddress},jobId=${jobId},owner=${owner},params=${JSON.stringify(params)})`, ); - await this.addJob(event); + await this.addJobByRegisterEvent(event); }); - this.on('Execute', event => { + this.on('Execute', async event => { const { jobKey, job: jobAddress, keeperId, gasUsed, baseFee, gasPrice, compensation, binJobAfter } = event.args; this.clog( @@ -905,7 +918,7 @@ export abstract class AbstractAgent implements IAgent { )}eth/${numberToBigInt(compensation)}wei,binJobAfter=${binJobAfter})`, ); - const job = this.jobs.get(jobKey); + const job = await this.getJob(jobKey); job.applyBinJobData(binJobAfter); job.applyWasExecuted(); diff --git a/app/dataSources/BlockchainSource.ts b/app/dataSources/BlockchainSource.ts index 07f2352..1635e2b 100644 --- a/app/dataSources/BlockchainSource.ts +++ b/app/dataSources/BlockchainSource.ts @@ -16,6 +16,33 @@ export class BlockchainSource extends AbstractSource { this.type = 'blockchain'; } + /** + * Getting a RegisterJob event and initialise a job. + * Returns Map structure which key is jobKey and value is instance of RandaoJob or LightJob. Await is required. + * + * @param context - agent caller context. This can be Agent.2.2.0.light or Agent.2.3.0.randao + * @param jobKey - Job key + * + * @return Promise + */ + async getJob(context, jobKey): Promise { + const latestBock = this.network.getLatestBlockNumber(); + // TODO: check latestBlock not null + const registerLog = await this.agent + .queryPastEvents('RegisterJob', context.fullSyncFrom, Number(latestBock), [jobKey]) + .then(list => list[0]); + const newJob = context._buildNewJob(registerLog); + // fetching additional fields from lens + + const lensJob = await this.network.queryLensJobs(this.agent.address, [jobKey]).then(r => r.results[0]); + newJob.applyJob({ + ...lensJob, + owner: lensJob.owner, + config: parseConfig(BigNumber.from(lensJob.details.config)), + }); + return newJob; + } + /** * Getting a RegisterJob events and initialise a job. * Returns Map structure which key is jobKey and value is instance of RandaoJob or LightJob. Await is required. diff --git a/app/dataSources/SubgraphSource.ts b/app/dataSources/SubgraphSource.ts index cdfd416..b30e6b3 100644 --- a/app/dataSources/SubgraphSource.ts +++ b/app/dataSources/SubgraphSource.ts @@ -10,9 +10,7 @@ import { toChecksummedAddress } from '../Utils.js'; import logger from '../services/Logger.js'; import { getMaxBlocksSubgraphDelay } from '../ConfigGetters.js'; -export const QUERY_ALL_JOBS = `{ - jobs(first: 1000) { - id +const JOBS_FIELDS = `id active jobAddress jobId @@ -38,6 +36,10 @@ export const QUERY_ALL_JOBS = `{ jobNextKeeperId jobReservedSlasherId jobSlashingPossibleAfter +`; +export const QUERY_ALL_JOBS = `{ + jobs(first: 1000) { + ${JOBS_FIELDS} } }`; @@ -134,6 +136,39 @@ export class SubgraphSource extends AbstractSource { return this.query(this.subgraphUrl, QUERY_ALL_JOBS).then(res => res.jobs); } + async queryJob(jobKey) { + return this.query( + this.subgraphUrl, + `{ + jobs(where: { id: "${jobKey}" }) { + ${JOBS_FIELDS} + } + }`, + ).then(res => res.jobs[0]); + } + + async getJob(context, jobKey): Promise { + return this.queryJob(jobKey).then(job => this.buildJob(context, job)); + } + + buildJob(context, job) { + const newJob = context._buildNewJob({ + name: 'RegisterJob', + args: { + jobAddress: job.jobAddress, + jobId: BigNumber.from(job.jobId), + jobKey: job.id, + }, + }); + const lensJob = this.addLensFieldsToJobs(job); + newJob.applyJob({ + ...lensJob, + owner: lensJob.owner, + config: lensJob.config, + }); + return newJob; + } + /** * Getting a list of jobs from subgraph and initialise job. * Returns Map structure which key is jobKey and value is instance of RandaoJob or LightJob. Await is required. @@ -153,21 +188,7 @@ export class SubgraphSource extends AbstractSource { try { const jobs = await this.queryJobs(); jobs.forEach(job => { - const newJob = context._buildNewJob({ - name: 'RegisterJob', - args: { - jobAddress: job.jobAddress, - jobId: BigNumber.from(job.jobId), - jobKey: job.id, - }, - }); - const lensJob = this.addLensFieldsToJobs(job); - newJob.applyJob({ - ...lensJob, - owner: lensJob.owner, - config: lensJob.config, - }); - newJobs.set(job.id, newJob); + newJobs.set(job.id, this.buildJob(context, job)); }); } catch (e) { throw this.err(e);