classic-ui/services/events.js

490 lines
13 KiB
JavaScript
Raw Normal View History

2022-04-21 23:05:56 -04:00
import Web3 from 'web3'
import graph from '@/services/graph'
2022-04-21 23:05:56 -04:00
import { download } from '@/store/snark'
import networkConfig from '@/networkConfig'
import InstanceABI from '@/abis/Instance.abi.json'
2022-11-23 07:05:23 -05:00
import { CONTRACT_INSTANCES, eventsType, httpConfig } from '@/constants'
2022-11-10 21:01:46 -05:00
import { sleep, flattenNArray, formatEvents, capitalizeFirstLetter } from '@/utils'
2022-04-21 23:05:56 -04:00
const supportedCaches = ['1', '56', '100', '137']
2022-11-13 01:32:56 -05:00
let store
if (process.browser) {
window.onNuxtReady(({ $store }) => {
store = $store
})
}
2022-04-21 23:05:56 -04:00
class EventService {
constructor({ netId, amount, currency, factoryMethods }) {
2022-06-14 06:02:45 -04:00
this.idb = window.$nuxt.$indexedDB(netId)
2022-04-21 23:05:56 -04:00
const { nativeCurrency } = networkConfig[`netId${netId}`]
2022-09-21 00:24:44 -04:00
const hasCache = supportedCaches.includes(netId.toString())
2022-04-21 23:05:56 -04:00
this.netId = netId
this.amount = amount
this.currency = currency
this.factoryMethods = factoryMethods
this.contract = this.getContract({ netId, amount, currency })
this.isNative = nativeCurrency === this.currency
this.hasCache = this.isNative && hasCache
2022-04-21 23:05:56 -04:00
}
2022-06-14 06:02:45 -04:00
getInstanceName(type) {
return `${type}s_${this.currency}_${this.amount}`
2022-04-21 23:05:56 -04:00
}
2022-11-13 01:32:56 -05:00
updateEventProgress(percentage, type) {
2022-11-14 16:25:34 -05:00
if (store) {
store.dispatch('loading/updateProgress', {
message: `Fetching past ${type} events`,
progress: Math.ceil(percentage * 100)
})
}
2022-11-13 01:32:56 -05:00
}
2022-04-21 23:05:56 -04:00
async getEvents(type) {
let cachedEvents = await this.getEventsFromDB(type)
if (!cachedEvents && this.hasCache) {
cachedEvents = await this.getEventsFromCache(type)
}
2022-09-21 00:24:44 -04:00
2022-04-21 23:05:56 -04:00
return cachedEvents
}
2022-11-14 16:25:34 -05:00
2022-07-05 04:29:31 -04:00
async updateEvents(type, cachedEvents) {
2022-04-21 23:05:56 -04:00
const { deployedBlock } = networkConfig[`netId${this.netId}`]
2022-07-05 04:29:31 -04:00
const savedEvents = cachedEvents || (await this.getEvents(type))
2022-04-21 23:05:56 -04:00
let fromBlock = deployedBlock
2022-09-21 00:24:44 -04:00
2022-04-21 23:05:56 -04:00
if (savedEvents) {
fromBlock = savedEvents.lastBlock + 1
}
const newEvents = await this.getEventsFromBlock({
type,
fromBlock,
graphMethod: `getAll${capitalizeFirstLetter(type)}s`
})
const allEvents = [].concat(savedEvents?.events || [], newEvents?.events || []).sort((a, b) => {
if (a.leafIndex && b.leafIndex) {
return a.leafIndex - b.leafIndex
}
return a.blockNumber - b.blockNumber
})
const lastBlock = allEvents[allEvents.length - 1].blockNumber
this.saveEvents({ events: allEvents, lastBlock, type })
return {
events: allEvents,
lastBlock
}
}
async findEvent({ eventName, eventToFind, type }) {
2022-06-14 06:02:45 -04:00
const instanceName = this.getInstanceName(type)
2022-04-21 23:05:56 -04:00
let event = await this.idb.getFromIndex({
2022-06-14 06:02:45 -04:00
storeName: instanceName,
2022-04-21 23:05:56 -04:00
indexName: eventName,
key: eventToFind
})
if (event) {
return event
}
const savedEvents = await this.getEvents(type)
if (savedEvents) {
event = savedEvents.events.find((event) => event[eventName] === eventToFind)
if (event) {
return event
}
}
const freshEvents = await this.updateEvents(type)
event = freshEvents && freshEvents?.events.find((event) => event[eventName] === eventToFind)
return event
}
getContract({ netId, amount, currency }) {
const config = networkConfig[`netId${netId}`]
const address = config.tokens[currency].instanceAddress[amount]
return this.factoryMethods.getContract(address)
}
async getEventsFromCache(type) {
try {
2022-06-14 06:02:45 -04:00
const instanceName = this.getInstanceName(type)
2022-04-21 23:05:56 -04:00
if (!CONTRACT_INSTANCES.includes(String(this.amount))) {
console.error(`Amount doesn't includes in contract instances`)
return
}
const module = await download({
contentType: 'string',
name: `events/${instanceName}.json.gz`
2022-04-21 23:05:56 -04:00
})
if (module) {
const events = JSON.parse(module)
return {
events,
lastBlock: events[events.length - 1].blockNumber
}
}
return {
events: [],
lastBlock: ''
}
} catch (err) {
return undefined
}
}
async getEventsFromDB(type) {
try {
2022-06-14 06:02:45 -04:00
const instanceName = this.getInstanceName(type)
const savedEvents = await this.idb.getAll({ storeName: instanceName })
2022-04-21 23:05:56 -04:00
if (!savedEvents || !savedEvents.length) {
return undefined
}
return {
events: savedEvents,
2022-09-21 00:24:44 -04:00
lastBlock: savedEvents[savedEvents.length - 1].blockNumber
2022-04-21 23:05:56 -04:00
}
} catch (err) {
return undefined
}
}
2022-07-05 04:29:31 -04:00
async getStatisticsRpc({ eventsCount }) {
const { deployedBlock } = networkConfig[`netId${this.netId}`]
const savedEvents = await this.getEvents(eventsType.DEPOSIT)
if (savedEvents.events.length) {
const { events } = await this.updateEvents(eventsType.DEPOSIT, savedEvents)
return events
}
2022-04-21 23:05:56 -04:00
const blockRange = 4950
2022-07-05 04:29:31 -04:00
const fromBlock = deployedBlock
2022-04-21 23:05:56 -04:00
const { blockDifference, currentBlockNumber } = await this.getBlocksDiff({ fromBlock })
let numberParts = blockDifference === 0 ? 1 : Math.ceil(blockDifference / blockRange)
const part = Math.ceil(blockDifference / numberParts)
let events = []
let toBlock = currentBlockNumber
if (fromBlock < currentBlockNumber) {
for (let i = 0; i < numberParts; i++) {
try {
await sleep(200)
const partOfEvents = await this.getEventsPartFromRpc({
fromBlock: toBlock - part,
toBlock,
type: eventsType.DEPOSIT
})
if (partOfEvents) {
events = events.concat(partOfEvents.events)
if (eventsCount <= events.length) {
break
}
}
toBlock -= part
} catch {
numberParts = numberParts + 1
}
}
if (eventsCount !== events.length) {
const savedEvents = await this.getEvents(eventsType.DEPOSIT)
events = events.concat(savedEvents?.events || [])
}
}
return events
}
async getEventsFromGraph({ fromBlock, methodName }) {
try {
const { events, lastSyncBlock } = await graph[methodName]({
fromBlock,
netId: this.netId,
amount: this.amount,
currency: this.currency
})
return {
events,
lastBlock: lastSyncBlock
}
} catch (err) {
return undefined
}
}
async getBlocksDiff({ fromBlock }) {
const currentBlockNumber = await this.factoryMethods.getBlockNumber()
return {
currentBlockNumber,
blockDifference: Math.ceil(currentBlockNumber - fromBlock)
}
}
2022-11-14 16:25:34 -05:00
getPastEvents({ fromBlock, toBlock, type }) {
return new Promise((resolve, reject) => {
const repsonse = this.contract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock,
toBlock
})
if (repsonse) {
resolve(repsonse)
} else {
reject(new Error())
}
})
}
2022-11-10 21:01:46 -05:00
async getEventsPartFromRpc({ fromBlock, toBlock, type }, shouldRetry = false, i = 0) {
2022-04-21 23:05:56 -04:00
try {
2022-07-05 04:29:31 -04:00
const { currentBlockNumber } = await this.getBlocksDiff({ fromBlock })
if (fromBlock > currentBlockNumber) {
return {
events: [],
lastBlock: fromBlock
}
}
2022-11-10 21:01:46 -05:00
let events = []
2022-11-14 16:25:34 -05:00
try {
events = await this.getPastEvents({ fromBlock, toBlock, type })
} catch (e) {
if (shouldRetry) {
i = i + 1
2023-03-22 16:50:13 -04:00
// maximum 5 second buffer for rate-limiting
await sleep(1000 * i)
2022-11-10 21:01:46 -05:00
events = await this.getEventsPartFromRpc(
{
fromBlock,
toBlock,
type
},
i !== 5,
i
)
2022-12-02 16:02:43 -05:00
} else {
throw new Error(`Failed to fetch block ${toBlock}`)
2022-11-10 21:01:46 -05:00
}
}
2022-04-21 23:05:56 -04:00
if (!events?.length) {
return {
events: [],
lastBlock: fromBlock
}
}
return {
events: formatEvents(events, type),
lastBlock: events[events.length - 1].blockNumber
}
} catch (err) {
return undefined
}
}
2022-12-02 16:02:43 -05:00
createBatchRequest(batchArray) {
return batchArray.map(
(e, i) =>
2022-11-10 21:01:46 -05:00
new Promise(async (resolve) => {
2022-12-02 16:02:43 -05:00
try {
2023-03-22 16:50:13 -04:00
sleep(20 * i)
2022-12-02 16:02:43 -05:00
const { events } = await this.getEventsPartFromRpc({ ...e }, true)
resolve(events)
} catch (e) {
resolve({ isFailedBatch: true, ...e })
}
2022-11-10 21:01:46 -05:00
})
)
}
2022-04-21 23:05:56 -04:00
async getBatchEventsFromRpc({ fromBlock, type }) {
try {
2022-11-10 21:01:46 -05:00
const batchSize = 10
2022-09-21 00:24:44 -04:00
const blockRange = 10000
2022-04-21 23:05:56 -04:00
2022-12-02 16:02:43 -05:00
let [events, failed] = [[], []]
let lastBlock = fromBlock
const { blockDifference, currentBlockNumber } = await this.getBlocksDiff({ fromBlock })
2022-11-10 21:01:46 -05:00
const batchDigest = blockDifference === 0 ? 1 : Math.ceil(blockDifference / blockRange)
2022-12-02 16:02:43 -05:00
2022-11-10 21:01:46 -05:00
const blockDenom = Math.ceil(blockDifference / batchDigest)
const batchCount = Math.ceil(batchDigest / batchSize)
2022-04-21 23:05:56 -04:00
if (fromBlock < currentBlockNumber) {
2022-12-02 16:02:43 -05:00
await this.updateEventProgress(0, type)
2022-11-23 07:05:23 -05:00
2022-11-10 21:01:46 -05:00
for (let batchIndex = 0; batchIndex < batchCount; batchIndex++) {
2022-12-02 16:02:43 -05:00
const isLastBatch = batchIndex === batchCount - 1
const params = new Array(batchSize).fill('').map((_, i) => {
const toBlock = (i + 1) * blockDenom + lastBlock
const fromBlock = toBlock - blockDenom
return { fromBlock, toBlock, type }
})
const batch = await Promise.all(this.createBatchRequest(params))
2022-04-21 23:05:56 -04:00
2022-12-02 16:02:43 -05:00
lastBlock = params[batchSize - 1].toBlock
events = events.concat(batch.filter((e) => !e.isFailedBatch))
failed = failed.concat(batch.filter((e) => e.isFailedBatch))
const progressIndex = batchIndex - failed.length / batchSize
if (isLastBatch && failed.length !== 0) {
const fbatch = await Promise.all(this.createBatchRequest(failed))
const isFailedBatch = fbatch.filter((e) => e.isFailedBatch).length !== 0
if (isFailedBatch) {
throw new Error('Failed to batch events')
} else {
events = events.concat(fbatch)
}
}
await this.updateEventProgress(progressIndex / batchCount, type)
2022-04-21 23:05:56 -04:00
}
2022-11-10 21:01:46 -05:00
events = flattenNArray(events)
return {
lastBlock: events[events.length - 1].blockNumber,
events
2022-04-21 23:05:56 -04:00
}
}
return undefined
} catch (err) {
return undefined
}
}
async getEventsFromRpc({ fromBlock, type }) {
try {
2022-09-21 00:24:44 -04:00
const { blockDifference } = await this.getBlocksDiff({ fromBlock })
2022-11-23 07:05:23 -05:00
const blockRange = 10000
2022-11-10 21:01:46 -05:00
2022-04-21 23:05:56 -04:00
let events
2022-11-23 07:05:23 -05:00
if (blockDifference < blockRange) {
const rpcEvents = await this.getEventsPartFromRpc({ fromBlock, toBlock: 'latest', type })
2022-04-21 23:05:56 -04:00
events = rpcEvents?.events || []
} else {
const rpcEvents = await this.getBatchEventsFromRpc({ fromBlock, type })
2022-04-21 23:05:56 -04:00
events = rpcEvents?.events || []
}
2022-09-21 00:24:44 -04:00
2022-04-21 23:05:56 -04:00
return events
} catch (err) {
return []
}
}
async getEventsFromBlock({ fromBlock, graphMethod, type }) {
try {
// ToDo think about undefined
const rpcEvents = await this.getEventsFromRpc({ fromBlock, type })
2022-04-21 23:05:56 -04:00
const allEvents = [].concat(rpcEvents || [])
2022-09-21 00:24:44 -04:00
2022-04-21 23:05:56 -04:00
if (allEvents.length) {
return {
events: allEvents,
lastBlock: allEvents[allEvents.length - 1].blockNumber
}
}
return undefined
} catch (err) {
return undefined
}
}
async saveEvents({ events, lastBlock, type }) {
try {
if (!events || !events.length || this.idb.isBlocked) {
return
}
2022-06-14 06:02:45 -04:00
const instanceName = this.getInstanceName(type)
2022-04-21 23:05:56 -04:00
await this.idb.createMultipleTransactions({
data: events,
2022-06-14 06:02:45 -04:00
storeName: instanceName
2022-04-21 23:05:56 -04:00
})
await this.idb.putItem({
data: {
blockNumber: lastBlock,
name: instanceName
},
2022-06-14 06:02:45 -04:00
storeName: 'lastEvents'
2022-04-21 23:05:56 -04:00
})
} catch (err) {
console.error('saveEvents has error:', err.message)
}
}
}
class EventsFactory {
instances = new Map()
constructor(rpcUrl) {
2022-11-23 07:05:23 -05:00
const httpProvider = new Web3.providers.HttpProvider(rpcUrl, httpConfig)
2022-11-21 06:07:23 -05:00
this.provider = new Web3(httpProvider).eth
2022-04-21 23:05:56 -04:00
}
getBlockNumber = () => {
return this.provider.getBlockNumber()
}
getContract = (address) => {
return new this.provider.Contract(InstanceABI, address)
}
getService = (payload) => {
const instanceName = `${payload.currency}_${payload.amount}`
2022-04-21 23:05:56 -04:00
if (this.instances.has(instanceName)) {
return this.instances.get(instanceName)
}
const instance = new EventService({
...payload,
factoryMethods: {
getContract: this.getContract,
getBlockNumber: this.getBlockNumber
}
})
this.instances.set(instanceName, instance)
return instance
}
}
2022-10-28 03:51:47 -04:00
export { EventsFactory }