From acfee0ee69d7695271444cf3f38654678a736e11 Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:46:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9blockdata=E6=8A=93=E5=8F=96,?= =?UTF-8?q?=20=E9=85=8D=E7=BD=AE=E4=BB=8Ejson=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/scription_list.json | 19 +++++++ src/config/scriptions_cfg.ts | 30 ----------- src/interface/IScriptionCfg.ts | 38 ++++++++++++++ src/models/CheckIn.ts | 4 +- src/scriptions.ts | 22 ++++++-- src/service/block.sync.service.ts | 18 +++++-- src/utils/block.util.ts | 85 +++++++++++++++++++++++++++++++ src/utils/event.util.ts | 2 +- 8 files changed, 177 insertions(+), 41 deletions(-) create mode 100644 config/scription_list.json delete mode 100644 src/config/scriptions_cfg.ts create mode 100644 src/interface/IScriptionCfg.ts diff --git a/config/scription_list.json b/config/scription_list.json new file mode 100644 index 0000000..55dd75a --- /dev/null +++ b/config/scription_list.json @@ -0,0 +1,19 @@ +[ + { + "chain": 421614, + "rpc": "https://arb-sepolia.g.alchemy.com/v2/mHoYM0SyjeizxvdjShcdOHiCrXOM_mlg", + "fromBlock": 5624211, + "filters": [{ + "key": "input", + "op": "eq", + "type": "utf8_data", + "value": "data:,{\"p\":\"cf-20\",\"op\":\"check\"}" + },{ + "key": "to", + "op": "eq", + "type": "address", + "value": "0x50a8e60041a206acaa5f844a1104896224be6f39" + }], + "dataModel": "CheckIn" + } +] \ No newline at end of file diff --git a/src/config/scriptions_cfg.ts b/src/config/scriptions_cfg.ts deleted file mode 100644 index 5d006f6..0000000 --- a/src/config/scriptions_cfg.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CheckIn } from "models/CheckIn" -import { utf8ToHex } from "utils/string.util" - -export interface IScriptionCfg { - chain: number, - rpc?: string, - fromBlock: number, - filter: (event: any) => boolean, - process: (event: any) => Promise, -} - -const CHECKIN_DATA_STR = 'data:,{"p":"cf-20","op":"check"}' -const CHECKIN_DATA_HEX = '0x'+utf8ToHex(CHECKIN_DATA_STR) -const CHECKIN_ADDRESS = '0x50a8e60041a206acaa5f844a1104896224be6f39' - -export const SCRIPTIONS_CFG: IScriptionCfg[] = [ - { - chain: 421614, - // rpc: 'https://arbitrum-sepolia.infura.io/v3/25559ac58e714177b31ff48d507e7ac9', - rpc: 'https://arb-sepolia.g.alchemy.com/v2/EKR1je8ZGia332kkemNc4mtXQuFskIq3', - fromBlock: 5624211, - filter: (event: any) => { - return ( event.input === CHECKIN_DATA_HEX - && event.to.toLowerCase() === CHECKIN_ADDRESS) - }, - process: async (tx: any) => { - return CheckIn.saveEvent(tx) - } - } -] \ No newline at end of file diff --git a/src/interface/IScriptionCfg.ts b/src/interface/IScriptionCfg.ts new file mode 100644 index 0000000..6b50058 --- /dev/null +++ b/src/interface/IScriptionCfg.ts @@ -0,0 +1,38 @@ +export enum FilterEnum { + eq = 'eq', + ne = 'ne', + gt = 'gt', + gte = 'gte', + lt = 'lt', + lte = 'lte', + in = 'in', + nin = 'nin', + like = 'like', + nlike = 'nlike', + isNull = 'isNull', + isNotNull = 'isNotNull' +} + +export enum FilterValueTypeEnum { + string = 'string', + number = 'number', + boolean = 'boolean', + utf8_data = 'utf8_data', + hex_data = 'hex_data', + address = 'address' +} + +export interface IFilter { + key: string, + type: string, + value: string, + op: FilterEnum +} +export interface IScriptionCfg { + chain: number, + rpc?: string, + fromBlock: number, + filters?: IFilter[], + filter: (event: any) => boolean, + process: (event: any) => Promise, +} \ No newline at end of file diff --git a/src/models/CheckIn.ts b/src/models/CheckIn.ts index aa971dd..27cfdfc 100644 --- a/src/models/CheckIn.ts +++ b/src/models/CheckIn.ts @@ -42,9 +42,9 @@ export class CheckInClass extends BaseModule { event.count = preDayEvent.count + 1 } try { - await CheckIn.insertOrUpdate({ from: event.from, dataTag: event.dataTag }, event) + await CheckIn.insertOrUpdate({ from: event.from, dateTag: event.dateTag }, event) } catch (err) { - logger.info('save check record error: ', event.dataTag, event.from, err.message || err) + logger.info('save check record error: ', event.dateTag, event.from, err.message || err) } } diff --git a/src/scriptions.ts b/src/scriptions.ts index 7b29ded..704b595 100644 --- a/src/scriptions.ts +++ b/src/scriptions.ts @@ -3,20 +3,36 @@ import logger from 'logger/logger' import { RedisClient } from 'redis/RedisClient' const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' dotenv.config({ path: envFile }) -import {IScriptionCfg, SCRIPTIONS_CFG} from 'config/scriptions_cfg' - +const scriptions = require('../config/scription_list.json') import 'common/Extend' import { AllChains, IChain } from 'chain/allchain' import { BlockSyncSvr } from 'service/block.sync.service' +import { IScriptionCfg } from 'interface/IScriptionCfg' +import { buildScriptionFilters } from 'utils/block.util' +import { CheckIn } from 'models/CheckIn' let svrs: any[] = [] let lock = false +let eventProcessers = { + CheckIn: CheckIn, +} async function initEventSvrs() { const cfgMap: Map = new Map(); - for (let cfg of SCRIPTIONS_CFG) { + for (let cfg of scriptions) { + if (!cfg.filter && cfg.filters) { + cfg.filter = buildScriptionFilters(cfg) + } + cfg.process = async (event: any) => { + let processer = eventProcessers[cfg.dataModel] + if (!processer) { + logger.error('processer not found: ' + cfg.dataModel) + return + } + await processer.saveEvent(event) + } const chainCfg = AllChains.find((chain) => chain.id === cfg.chain) if (!chainCfg) { logger.error('chainCfg not found: ' + cfg.chain) diff --git a/src/service/block.sync.service.ts b/src/service/block.sync.service.ts index 4a8871b..1ea7e21 100644 --- a/src/service/block.sync.service.ts +++ b/src/service/block.sync.service.ts @@ -1,10 +1,13 @@ import { IChain } from "chain/allchain"; -import { ethBlockNumber, retryEthBlockNumber } from "chain/chain.api"; -import { IScriptionCfg } from "config/scriptions_cfg"; +import { retryEthBlockNumber } from "chain/chain.api"; +import { IScriptionCfg } from "interface/IScriptionCfg"; +import logger from "logger/logger"; +import { CheckIn } from "models/CheckIn"; import { RedisClient } from "redis/RedisClient"; import { getPastBlocksIter } from "utils/block.util"; import { formatDate } from "utils/date.util"; + export class BlockSyncSvr { chainCfg: IChain scriptionCfgs: IScriptionCfg[] = [] @@ -66,9 +69,14 @@ export class BlockSyncSvr { } for (let j = 0; j < this.scriptionCfgs.length; j++) { const cfg = this.scriptionCfgs[j]; - if (cfg.filter(tx)) { - await cfg.process(tx) - } + + if (cfg.filter && cfg.filter(tx) ) { + try { + await cfg.process(tx) + } catch (err) { + logger.error('process tx error: ', err.message || err) + } + } } } } diff --git a/src/utils/block.util.ts b/src/utils/block.util.ts index 59c2f23..8b304b7 100644 --- a/src/utils/block.util.ts +++ b/src/utils/block.util.ts @@ -1,6 +1,8 @@ import { batchEthBlocks } from "chain/chain.api"; +import { IScriptionCfg } from "interface/IScriptionCfg"; import logger from "logger/logger"; import { RedisClient } from "redis/RedisClient"; +import { utf8ToHex } from "./string.util"; const MAX_BATCH_AMOUNT = 500 const REQUEST_INTERVAL = 0.5 * 1000 @@ -84,3 +86,86 @@ export function* getPastBlocksIter({chainId, rpc, fromBlock, amount} remain -= MAX_BATCH_AMOUNT } } + + +export const buildScriptionFilters = (cfg: IScriptionCfg) => { + if (cfg.filter) { + return cfg.filter + } + if (cfg.filters) { + let body = '' + for (let i = 0; i < cfg.filters.length; i++) { + if (i > 0) { + body += ' && ' + } + let filter = cfg.filters[i] + let value: any = filter.value + let op = '' + switch (filter.op) { + case 'eq': + op = '===' + break + case 'ne': + op = '!==' + break + case 'gt': + op = '>' + break + case 'gte': + op = '>=' + break + case 'lt': + op = '<' + break + case 'lte': + op = '<=' + break + case 'in': + op = 'in' + break + case 'nin': + op = 'nin' + break + case 'like': + op = 'like' + break + case 'nlike': + op = 'nlike' + break + case 'isNull': + body += `!event.${filter.key}` + break + case 'isNotNull': + body += `!!event.${filter.key}` + break + } + if (filter.type === 'address') { + value = `'${value.toLowerCase()}'` + } else if (filter.type === 'utf8_data') { + value = `'0x${utf8ToHex(value)}'` + } else if (filter.type === 'hex_data') { + value = `'${value.indexOf('0x') === 0 ? value : '0x'+value}'` + } else if (filter.type === 'number') { + value = parseInt(value) + } else if (filter.type === 'boolean') { + value = !!value + } else { + value = `'${value}'` + } + if (op) { + if (op === 'in') { + body += `event.${filter.key}.indexOf(${value}) >= 0` + } else if (op === 'nin') { + body += `!(event.${filter.key}.indexOf(${value}) >= 0)` + } else if (op === 'nlike') { + body += `!new RegExp(${value}).test(event.${filter.key})` + } else if (op === 'like') { + body += new RegExp(value).test(`event.${filter.key}`) + } else { + body += `event.${filter.key}${op}${value}` + } + } + } + return new Function('event', `return ${body}`) + } +} \ No newline at end of file diff --git a/src/utils/event.util.ts b/src/utils/event.util.ts index 0c1a7b6..ebe887a 100644 --- a/src/utils/event.util.ts +++ b/src/utils/event.util.ts @@ -43,4 +43,4 @@ export const decodeEvent = (cfg: IEventCfg, eventData: {data: string, topics: st } } return decodedData -} \ No newline at end of file +}