nova-ui/services/commitments/index.ts
2022-12-04 07:02:30 +01:00

184 lines
5.8 KiB
TypeScript

import { L2ChainId } from '@/types'
import {
FreshData,
CachedData,
BatchPayload,
BatchEventPayload,
BatchEventsPayload,
GetFreshCommitments,
GetCashedEventsPayload,
} from './@types'
import { TornadoPool } from '@/_contracts'
import { getTornadoPool } from '@/contracts'
import { numbers, workerEvents } from '@/constants/worker'
import { getBlocksBatches, controlledPromise } from '@/utilities'
import { Keypair, workerProvider } from '@/services'
import { CommitmentEvents } from '@/services/events/@types'
import { GetDecryptedEvents } from '@/services/worker/@types'
export interface CommitmentsService {
getCachedData: (keypair: Keypair) => Promise<CachedData>
fetchCommitments: (keypair: Keypair) => Promise<CommitmentEvents>
getFreshCommitments: (payload: GetFreshCommitments) => Promise<FreshData>
}
class Service implements CommitmentsService {
public poolContract: TornadoPool
private latestBlock: number
private commitments: CommitmentEvents
public promises: {
[key in string]: null | {
promise: Promise<CommitmentEvents>
resolve: (value: CommitmentEvents | PromiseLike<CommitmentEvents>) => void
reject: (value: Error) => void
}
}
public static async getBatchEvents({ blockFrom, blockTo, cachedEvents, keypair, index }: BatchEventPayload) {
try {
const batchEvents = await workerProvider.openEventsChannel<BatchEventsPayload, GetDecryptedEvents>(
workerEvents.GET_BATCH_EVENTS,
{ blockFrom, blockTo, cachedEvents, publicKey: keypair.pubkey, privateKey: keypair.privkey },
index,
)
return batchEvents
} catch (err) {
throw new Error(`getFreshData error: ${err}`)
}
}
public static async getBatchEventsData({ batch, cachedEvents, keypair, index }: BatchPayload): Promise<CommitmentEvents> {
const [from, to] = batch
const { commitments } = await Service.getBatchEvents({
index,
keypair,
blockTo: to,
blockFrom: from,
cachedEvents,
})
return commitments
}
public constructor(chainId: L2ChainId) {
this.poolContract = getTornadoPool(chainId)
this.promises = {}
this.commitments = []
this.latestBlock = numbers.DEPLOYED_BLOCK
}
public async getFreshCommitments({ keypair, latestBlock, commitments }: GetFreshCommitments): Promise<FreshData> {
const currentBlock = await this.poolContract.provider.getBlockNumber()
const interval = currentBlock - latestBlock
let batchesCount = workerProvider.eventsWorkers.length
if (interval <= numbers.MIN_BLOCKS_INTERVAL_LINE) {
batchesCount = numbers.TWO
}
const batches = getBlocksBatches(latestBlock, currentBlock, batchesCount).reverse()
const promises = batches.map(
// eslint-disable-next-line
(batch, index) => this.fetchCommitmentsBatch({ batch, index, keypair, cachedEvents: commitments }),
)
const freshCommitments = await Promise.all<CommitmentEvents>(promises)
return { freshCommitments: freshCommitments.flat(), lastBlock: currentBlock }
}
public async getCachedData(keypair: Keypair): Promise<CachedData> {
try {
const currentBlock = await this.poolContract.provider.getBlockNumber()
const cachedEvents = await workerProvider.openEventsChannel<GetCashedEventsPayload, GetDecryptedEvents>(
workerEvents.GET_CACHED_COMMITMENTS_EVENTS,
{
publicKey: keypair.pubkey,
privateKey: keypair.privkey,
storeName: 'commitment_events_100',
},
)
if (cachedEvents?.lastSyncBlock && cachedEvents?.commitments?.length) {
const newBlockFrom = Number(cachedEvents.lastSyncBlock) + numbers.ONE
const latestBlock = newBlockFrom > currentBlock ? currentBlock : newBlockFrom
return { ...cachedEvents, latestBlock }
}
return { latestBlock: this.latestBlock, commitments: this.commitments }
} catch (err) {
throw new Error(`getCachedData error: ${err}`)
}
}
public async fetchCommitments(keypair: Keypair): Promise<CommitmentEvents> {
const knownPromise = this.promises[keypair.pubkey._hex]?.promise
if (knownPromise) {
return await knownPromise
}
Object.keys(this.promises).forEach((promiseKey) => {
const promise = this.promises[promiseKey]
if (promise) {
promise.reject(new Error('Account was changed'))
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.promises[promiseKey]
}
})
const controlled = controlledPromise<CommitmentEvents>(this.fetchData(keypair))
this.promises[keypair.pubkey._hex] = controlled
return await controlled.promise
}
private async fetchData(keypair: Keypair): Promise<CommitmentEvents> {
try {
const { latestBlock, commitments } = await this.getCachedData(keypair)
const { freshCommitments, lastBlock } = await this.getFreshCommitments({ keypair, latestBlock, commitments })
this.latestBlock = lastBlock
this.commitments = commitments.concat(freshCommitments.flat())
return this.commitments
} catch (err) {
throw new Error(`getBalance error: ${err}`)
} finally {
this.promises[keypair.pubkey._hex] = null
}
}
private async fetchCommitmentsBatch(payload: BatchPayload): Promise<CommitmentEvents> {
const commitments = await Service.getBatchEventsData(payload)
if (!this.promises[payload.keypair.pubkey._hex]) {
return []
}
return commitments
}
}
class CommitmentsFactory {
public instances = new Map()
public getService = (chainId: L2ChainId) => {
if (this.instances.has(chainId)) {
return this.instances.get(chainId)
}
const instance = new Service(chainId)
this.instances.set(chainId, instance)
return instance
}
}
const commitmentsFactory = new CommitmentsFactory()
export { commitmentsFactory }