forked from tornadocash/nova-ui
Tornado Contrib
355e1e88ce
Either ts-loader or babel-loader to bundle workers didn't work properly so I transpiled them by hand
311 lines
8.7 KiB
JavaScript
311 lines
8.7 KiB
JavaScript
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
const { isEmpty } = require('lodash')
|
|
const { BigNumber, Contract } = require('ethers')
|
|
|
|
const { IndexedDB } = require('./services/idb')
|
|
const { BatchEventsService } = require('./services/batch')
|
|
const { ExtendedProvider } = require('./services/provider')
|
|
const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } = require('./services/constants')
|
|
const { sleep } = require('./services/utilities')
|
|
const { poolAbi } = require('./services/pool')
|
|
|
|
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 = new Contract(POOL_CONTRACT[chainId], poolAbi, 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)
|