nova-ui/assets/nullifier.worker.js

313 lines
8.8 KiB
JavaScript

/* eslint-disable @typescript-eslint/no-require-imports */
const { isEmpty } = require('lodash')
const { BigNumber } = require('ethers')
const { IndexedDB } = require('../services/idb')
const { BatchEventsService } = require('../services/batch')
const { sleep } = require('../utilities/helpers')
const { workerEvents, numbers } = require('../constants/worker')
const { ExtendedProvider } = require('../services/ether/ExtendedProvider')
const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST } = require('../constants/contracts')
const { TornadoPool__factory: TornadoPoolFactory } = require('../_contracts')
const getProviderWithSigner = (chainId) => {
return new ExtendedProvider(RPC_LIST[chainId], chainId, FALLBACK_RPC_LIST[chainId])
}
const initDataBase = async () => {
try {
const options = {
stores: [
{
keyPath: 'nullifier',
name: `nullifier_events_100`,
indexes: [{ name: 'transactionHash', unique: false }],
},
],
dbName: 'tornado_pool_nullifier',
}
const instance = new IndexedDB(options)
await instance.initDB()
self.$indexedDB = instance
} catch (err) {
console.log('err', err.message)
}
}
const initWorker = (chainId) => {
self.chainId = chainId
const provider = getProviderWithSigner(chainId)
setTornadoPool(chainId, provider)
initDataBase()
}
const setTornadoPool = (chainId, provider) => {
self.poolContract = TornadoPoolFactory.connect(POOL_CONTRACT[chainId], provider)
self.BatchEventsService = new BatchEventsService({
provider,
contract: self.poolContract
})
}
const saveEvents = async ({ events }) => {
try {
const isIdbEnable = await getIsDBEnabled()
if (isIdbEnable || !getIsWhitelistedDomain() || isEmpty(events)) {
return
}
self.$indexedDB.createMultipleTransactions({
data: events,
storeName: 'nullifier_events_100',
})
} catch (err) {
console.error(`saveEvents has error: ${err.message}`)
}
}
const checkUnspent = async (decryptedEvent, nullifierEvents) => {
try {
const { nullifierHash } = decryptedEvent
const isIdbEnable = await getIsDBEnabled()
if (isIdbEnable) {
const lastEvent = await self.$indexedDB.getFromIndex({
key: nullifierHash,
indexName: 'nullifier',
storeName: 'nullifier_events_100',
})
if (lastEvent) {
return undefined
}
}
const event = nullifierEvents.find((event) => {
return event.nullifier === nullifierHash
})
if (event) {
return undefined
}
return decryptedEvent
} catch (err) {
throw new Error(`Method getNullifierEvent has error: ${err.message}`)
}
}
const getCachedEvents = async () => {
let blockFrom = numbers.DEPLOYED_BLOCK
if (!self.$indexedDB) {
await sleep(numbers.RECALL_DELAY)
}
const cachedEvents = await self.$indexedDB.getAll({
storeName: 'nullifier_events_100',
})
if (cachedEvents && cachedEvents.length) {
const [latestEvent] = cachedEvents.sort((a, b) => b.blockNumber - a.blockNumber)
const currentBlock = await self.poolContract.provider.getBlockNumber()
const newBlockFrom = Number(latestEvent.blockNumber) + numbers.ONE
if (latestEvent.blockNumber === currentBlock) {
return { blockFrom, cachedEvents }
}
blockFrom = newBlockFrom > currentBlock ? currentBlock : newBlockFrom
}
return { blockFrom, cachedEvents }
}
const getNullifiers = async (blockFrom) => {
try {
const events = await self.BatchEventsService.getBatchEvents({
fromBlock: blockFrom,
type: 'NewNullifier'
})
return events.map(({ blockNumber, transactionHash, args }) => ({
blockNumber,
transactionHash,
nullifier: args.nullifier,
}))
} catch (err) {
console.error('getNullifiers', err.message)
return []
}
}
const getNullifierEvents = async (cachedNullifiers, withCache = true) => {
let cached = { blockFrom: numbers.DEPLOYED_BLOCK, cachedEvents: [] }
try {
if (cachedNullifiers && cachedNullifiers.length) {
const [latestEvent] = cachedNullifiers.sort((a, b) => b.blockNumber - a.blockNumber)
const currentBlock = await self.poolContract.provider.getBlockNumber()
const newBlockFrom = Number(latestEvent.blockNumber) + numbers.ONE
if (latestEvent.blockNumber === currentBlock) {
cached.blockFrom = numbers.DEPLOYED_BLOCK
}
cached.blockFrom = newBlockFrom > currentBlock ? currentBlock : newBlockFrom
cached.cachedEvents = cachedNullifiers
} else {
cached = await getCachedEvents()
}
const { blockFrom = numbers.DEPLOYED_BLOCK, cachedEvents = [] } = cached
const nullifiers = await getNullifiers(blockFrom)
if (nullifiers.length) {
saveEvents({ events: nullifiers })
}
return withCache ? cachedEvents.concat(nullifiers) : nullifiers
} catch (err) {
throw new Error(`Method getNullifierEvents has error: ${err.message}`)
}
}
const checkUnspentEvents = async ({ cachedNullifiers, decryptedEvents }) => {
try {
if (decryptedEvents.length === numbers.ZERO) {
return {
unspentUtxo: [],
totalAmount: 0,
}
}
const nullifierEvents = await getNullifierEvents(cachedNullifiers)
let totalAmount = BigNumber.from('0')
const unspentUtxo = []
const unspentEvents = await Promise.all(decryptedEvents.map((event) => checkUnspent(event, nullifierEvents)))
unspentEvents.forEach((event) => {
if (event && !BigNumber.from(event.amount).isZero()) {
unspentUtxo.push(event)
totalAmount = totalAmount.add(event.amount)
}
})
return { totalAmount, unspentUtxo }
} catch (err) {
throw new Error(`Method checkUnspentEvents has error: ${err.message}`)
}
}
const getNullifierEventsFromTxHash = async ({ cachedNullifiers, txHash }, [getUnspentPort]) => {
try {
const lastEvents = await self.$indexedDB.getAllFromIndex({
key: txHash.toLowerCase(),
indexName: 'transactionHash',
storeName: 'nullifier_events_100',
})
if (lastEvents && lastEvents.length > numbers.ZERO) {
getUnspentPort.postMessage({ result: lastEvents })
return
}
const nullifierEvents = await getNullifierEvents(cachedNullifiers, true)
const findingEvents = nullifierEvents.filter((event) => {
return event.transactionHash.toLowerCase() === txHash.toLowerCase()
})
getUnspentPort.postMessage({ result: findingEvents })
} catch (err) {
getUnspentPort.postMessage({ errorMessage: err.message })
}
}
const getNullifierEvent = async ({ cachedNullifiers, nullifierHash }, [getUnspentPort]) => {
try {
const lastEvent = await self.$indexedDB.getFromIndex({
key: nullifierHash,
indexName: 'nullifier',
storeName: 'nullifier_events_100',
})
if (lastEvent) {
getUnspentPort.postMessage({ result: lastEvent })
return
}
const events = await getNullifierEvents(cachedNullifiers)
const [event] = events.filter((event) => {
return event.nullifier === nullifierHash
})
getUnspentPort.postMessage({ result: event })
} catch (err) {
getUnspentPort.postMessage({ errorMessage: err.message })
}
}
const updateNullifierEvents = async (cachedNullifiers, [getUnspentPort]) => {
try {
const events = await getNullifierEvents(cachedNullifiers)
getUnspentPort.postMessage({ result: events })
} catch (err) {
getUnspentPort.postMessage({ errorMessage: err.message })
}
}
const getUnspentEvents = async ({ decryptedEvents, cachedNullifiers }, [getUnspentPort]) => {
try {
const unspentEvents = await checkUnspentEvents({ decryptedEvents, cachedNullifiers })
getUnspentPort.postMessage({ result: unspentEvents })
} catch (err) {
getUnspentPort.postMessage({ errorMessage: err.message })
}
}
const listener = ({ data, ports }) => {
self.postMessage(data)
switch (data.eventName) {
case workerEvents.INIT_WORKER:
initWorker(data.payload)
break
case workerEvents.GET_NULLIFIER_EVENT:
getNullifierEvent(data.payload, ports)
break
case workerEvents.UPDATE_NULLIFIER_EVENTS:
updateNullifierEvents(data.payload, ports)
break
case workerEvents.GET_UNSPENT_EVENTS:
getUnspentEvents(data.payload, ports)
break
case workerEvents.GET_NULLIFIER_EVENTS_FROM_TX_HASH:
getNullifierEventsFromTxHash(data.payload, ports)
break
}
}
// helpers
const getIsDBEnabled = async () => {
if (!self.$indexedDB) {
await sleep(numbers.RECALL_DELAY)
}
return !self.$indexedDB || self.$indexedDB.isBlocked
}
const getIsWhitelistedDomain = () => {
const domainWhiteList = ['localhost:3000', 'nova.tornadocash.eth', 'nova.tornadocash.eth.link', 'nova.tornadocash.eth.limo']
if (self.location.host.includes('compassionate-payne-b9dc6b.netlify.app')) {
return true
}
return domainWhiteList.includes(self.location.host)
}
self.addEventListener('message', listener, false)