-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add transformation logic to new queue
- Loading branch information
Showing
5 changed files
with
216 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,20 @@ | ||
import { BaseQueue, QueueOptions } from './base' | ||
import { ExportQueue } from './export' | ||
import { SearchQueue } from './search' | ||
import { TransformationsQueue } from './transform' | ||
import { WebhooksQueue } from './webhooks' | ||
|
||
// Hack to fix generic constructor on abstract class. | ||
type IQueue = { | ||
new (options: QueueOptions): BaseQueue<any> | ||
} & typeof BaseQueue<any> | ||
|
||
export const queues: IQueue[] = [ExportQueue, SearchQueue, WebhooksQueue] | ||
export const queues: IQueue[] = [ | ||
ExportQueue, | ||
SearchQueue, | ||
WebhooksQueue, | ||
TransformationsQueue, | ||
] | ||
|
||
export * from './base' | ||
export * from './connection' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { Job, Queue } from 'bullmq' | ||
import { Op } from 'sequelize' | ||
|
||
import { Contract, WasmStateEvent, WasmStateEventTransformation } from '@/db' | ||
import { WasmCodeService } from '@/services' | ||
|
||
import { BaseQueue } from './base' | ||
import { closeBullQueue, getBullQueue } from './connection' | ||
|
||
export type TransformationsQueuePayload = { | ||
/** | ||
* Minimum block height. Defaults to 0. | ||
*/ | ||
minBlockHeight?: number | ||
/** | ||
* Batch size. Defaults to 5,000. | ||
*/ | ||
batchSize?: number | ||
/** | ||
* Transform set of addresses. | ||
*/ | ||
addresses?: string[] | ||
/** | ||
* Transform contracts matching code IDs keys. | ||
*/ | ||
codeIdsKeys?: string[] | ||
/** | ||
* Transform contracts matching code IDs. | ||
*/ | ||
codeIds?: number[] | ||
} | ||
|
||
export class TransformationsQueue extends BaseQueue<TransformationsQueuePayload> { | ||
static queueName = 'transformations' | ||
|
||
static getQueue = () => | ||
getBullQueue<TransformationsQueuePayload>(this.queueName) | ||
static add = async ( | ||
...params: Parameters<Queue<TransformationsQueuePayload>['add']> | ||
) => (await this.getQueue()).add(...params) | ||
static addBulk = async ( | ||
...params: Parameters<Queue<TransformationsQueuePayload>['addBulk']> | ||
) => (await this.getQueue()).addBulk(...params) | ||
static close = () => closeBullQueue(this.queueName) | ||
|
||
async process({ | ||
data: { | ||
minBlockHeight = 0, | ||
batchSize = 5000, | ||
addresses, | ||
codeIdsKeys, | ||
codeIds: _codeIds = [], | ||
}, | ||
}: Job<TransformationsQueuePayload>): Promise<void> { | ||
const foundCodeIds = WasmCodeService.getInstance().findWasmCodeIdsByKeys( | ||
...(codeIdsKeys || []) | ||
) | ||
if (codeIdsKeys?.length && !foundCodeIds.length) { | ||
throw new Error( | ||
`no code IDs found for code IDs keys: ${codeIdsKeys.join(', ')}` | ||
) | ||
} | ||
|
||
const codeIds = [..._codeIds, ...foundCodeIds] | ||
|
||
const addressFilter = addresses?.length | ||
? { | ||
contractAddress: addresses, | ||
} | ||
: undefined | ||
|
||
if (!addressFilter && !codeIds.length) { | ||
throw new Error('no contract address nor code ID filter provided') | ||
} else { | ||
console.log( | ||
`transforming events for contract addresses: ${addresses?.join( | ||
', ' | ||
)} and code IDs: ${codeIds.join(', ')}` | ||
) | ||
} | ||
|
||
const includeContract = { | ||
include: { | ||
model: Contract, | ||
required: true, | ||
where: | ||
codeIds.length > 0 | ||
? { | ||
codeId: { | ||
[Op.in]: codeIds, | ||
}, | ||
} | ||
: undefined, | ||
}, | ||
} | ||
|
||
let latestBlockHeight = minBlockHeight | ||
const total = await WasmStateEvent.count({ | ||
where: { | ||
...addressFilter, | ||
blockHeight: { | ||
[Op.gte]: latestBlockHeight, | ||
}, | ||
}, | ||
...includeContract, | ||
}) | ||
|
||
console.log(`found ${total.toLocaleString()} events to transform...`) | ||
|
||
let processed = 0 | ||
let transformed = 0 | ||
|
||
let latestBlockEventIdsSeen: number[] = [] | ||
while (processed < total) { | ||
const events = await WasmStateEvent.findAll({ | ||
where: { | ||
...addressFilter, | ||
// Since there can be multiple events per block, the fixed batch size | ||
// will likely end up leaving some events in the latest block out of | ||
// this batch. To fix this, repeat the latest block again (>=) | ||
// excluding the events we've already seen. | ||
blockHeight: { | ||
[Op.gte]: latestBlockHeight, | ||
}, | ||
...(latestBlockEventIdsSeen.length > 0 && { | ||
id: { | ||
[Op.notIn]: latestBlockEventIdsSeen, | ||
}, | ||
}), | ||
}, | ||
limit: batchSize, | ||
order: [['blockHeight', 'ASC']], | ||
...includeContract, | ||
}) | ||
|
||
// If there are no more events, we're done. | ||
if (events.length === 0) { | ||
break | ||
} | ||
|
||
const newLatestBlockHeight = events[events.length - 1].blockHeight | ||
|
||
// If the latest block height is the same as the previous latest block | ||
// height, we are still in the same block and should append the event IDs | ||
// to the list instead of replacing it. This will only happen if the batch | ||
// size is smaller than the maximum number of events in any one block. | ||
// Otherwise, we're in a new block and should reset the list. | ||
if (Number(newLatestBlockHeight) === latestBlockHeight) { | ||
latestBlockEventIdsSeen = latestBlockEventIdsSeen.concat( | ||
events.map((event) => event.id) | ||
) | ||
} else { | ||
latestBlockEventIdsSeen = events | ||
.filter((event) => event.blockHeight === newLatestBlockHeight) | ||
.map((event) => event.id) | ||
} | ||
|
||
processed += events.length | ||
latestBlockHeight = Number(newLatestBlockHeight) | ||
|
||
const transformations = | ||
await WasmStateEventTransformation.transformParsedStateEvents( | ||
events.map((event) => event.asParsedEvent) | ||
) | ||
|
||
// const { updated, destroyed } = update | ||
// ? await updateComputationValidityDependentOnChanges(transformations) | ||
// : { | ||
// updated: 0, | ||
// destroyed: 0, | ||
// } | ||
|
||
transformed += transformations.length | ||
|
||
console.log( | ||
`transformed/processed/total: ${transformed.toLocaleString()}/${processed.toLocaleString()}/${total.toLocaleString()}. latest block height: ${latestBlockHeight.toLocaleString()}` | ||
) | ||
} | ||
|
||
console.log('done!') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.