From e7dcfbba60d9f015c8a24d4a9ee2e3ccc3755e6a Mon Sep 17 00:00:00 2001 From: Khaidar Kairbek Date: Sat, 9 Nov 2024 14:24:33 -0500 Subject: [PATCH] sync: buildEvents and decodeEvents modfied --- packages/core/src/sync-realtime/index.ts | 23 +- packages/core/src/sync/events.ts | 330 +++++++++++++++++------ packages/core/src/sync/index.ts | 16 +- packages/core/src/types/eth.ts | 80 ++++-- 4 files changed, 332 insertions(+), 117 deletions(-) diff --git a/packages/core/src/sync-realtime/index.ts b/packages/core/src/sync-realtime/index.ts index 7fba21a4b..3b4416fe7 100644 --- a/packages/core/src/sync-realtime/index.ts +++ b/packages/core/src/sync-realtime/index.ts @@ -46,7 +46,9 @@ export type RealtimeSync = { start(args: { syncProgress: Pick; initialChildAddresses: Map>; - }): Promise>; + }): Promise< + Queue & { traces: SyncTrace[] }> + >; kill(): Promise; unfinalizedBlocks: LightBlock[]; finalizedChildAddresses: Map>; @@ -67,7 +69,7 @@ export type BlockWithEventData = { filters: Set; logs: SyncLog[]; factoryLogs: SyncLog[]; - traces: SyncTrace[]; + traces: SyncTraceFlat[]; transactions: SyncTransaction[]; transactionReceipts: SyncTransactionReceipt[]; }; @@ -75,9 +77,7 @@ export type BlockWithEventData = { export type RealtimeSyncEvent = | ({ type: "block"; - } & Omit & { - traces: SyncTraceFlat[]; - }) + } & BlockWithEventData) | { type: "finalize"; block: LightBlock; @@ -111,7 +111,10 @@ export const createRealtimeSync = ( * `parentHash` => `hash`. */ let unfinalizedBlocks: LightBlock[] = []; - let queue: Queue>; + let queue: Queue< + void, + Omit & { traces: SyncTrace[] } + >; let consecutiveErrors = 0; let interval: NodeJS.Timeout | undefined; @@ -177,7 +180,9 @@ export const createRealtimeSync = ( traces, transactions, transactionReceipts, - }: Omit) => { + }: Omit & { + traces: SyncTrace[]; + }) => { args.common.logger.debug({ service: "realtime", msg: `Started syncing '${args.network.name}' block ${hexToNumber(block.number)}`, @@ -608,7 +613,9 @@ export const createRealtimeSync = ( */ const fetchBlockEventData = async ( block: SyncBlock, - ): Promise> => { + ): Promise< + Omit & { traces: SyncTrace[] } + > => { //////// // Logs //////// diff --git a/packages/core/src/sync/events.ts b/packages/core/src/sync/events.ts index fd1023d60..26647edf8 100644 --- a/packages/core/src/sync/events.ts +++ b/packages/core/src/sync/events.ts @@ -1,21 +1,22 @@ import type { Common } from "@/common/common.js"; import { isBlockFilterMatched, - isCallTraceFilterMatched, isLogFilterMatched, + isTransactionFilterMatched, + isTransferFilterMatched, } from "@/sync-realtime/filter.js"; import type { BlockWithEventData } from "@/sync-realtime/index.js"; import type { Block, - CallTrace, Log, + Trace, Transaction, TransactionReceipt, } from "@/types/eth.js"; import type { SyncBlock, - SyncCallTrace, SyncLog, + SyncTraceFlat, SyncTransaction, SyncTransactionReceipt, } from "@/types/sync.js"; @@ -51,10 +52,10 @@ export type RawEvent = { block: Block; transaction?: Transaction; transactionReceipt?: TransactionReceipt; - trace?: CallTrace; + trace?: Trace; }; -export type Event = LogEvent | BlockEvent | CallTraceEvent; +export type Event = LogEvent | BlockEvent | TransactionEvent | TransferEvent; export type SetupEvent = { type: "setup"; @@ -98,8 +99,26 @@ export type BlockEvent = { }; }; -export type CallTraceEvent = { - type: "callTrace"; +// export type CallTraceEvent = { +// type: "callTrace"; +// chainId: number; +// checkpoint: string; + +// /** `${source.name}.${safeName}()` */ +// name: string; + +// event: { +// args: any; +// result: any; +// trace: CallTrace; +// block: Block; +// transaction: Transaction; +// transactionReceipt?: TransactionReceipt; +// }; +// }; + +export type TransactionEvent = { + type: "transaction"; chainId: number; checkpoint: string; @@ -109,7 +128,24 @@ export type CallTraceEvent = { event: { args: any; result: any; - trace: CallTrace; + trace: Trace; + block: Block; + transaction: Transaction; + transactionReceipt?: TransactionReceipt; + }; +}; + +export type TransferEvent = { + type: "transfer"; + chainId: number; + checkpoint: string; + + /** `${source.name}/transfer()` */ + name: string; + + event: { + result: any; + trace: Trace; block: Block; transaction: Transaction; transactionReceipt?: TransactionReceipt; @@ -126,7 +162,7 @@ export const buildEvents = ({ logs, transactions, transactionReceipts, - callTraces, + traces, }, finalizedChildAddresses, unfinalizedChildAddresses, @@ -142,7 +178,7 @@ export const buildEvents = ({ const transactionCache = new Map(); const transactionReceiptCache = new Map(); - const traceByTransactionHash = new Map(); + const traceByTransactionHash = new Map(); for (const transaction of transactions) { transactionCache.set(transaction.hash, transaction); } @@ -152,11 +188,11 @@ export const buildEvents = ({ transactionReceipt, ); } - for (const callTrace of callTraces) { - if (traceByTransactionHash.has(callTrace.transactionHash) === false) { - traceByTransactionHash.set(callTrace.transactionHash, []); + for (const trace of traces) { + if (traceByTransactionHash.has(trace.transactionHash) === false) { + traceByTransactionHash.set(trace.transactionHash, []); } - traceByTransactionHash.get(callTrace.transactionHash)!.push(callTrace); + traceByTransactionHash.get(trace.transactionHash)!.push(trace); } for (let i = 0; i < sources.length; i++) { @@ -223,59 +259,122 @@ export const buildEvents = ({ break; } - case "callTrace": { - for (const callTraces of Array.from(traceByTransactionHash.values())) { - // Use lexographical sort of stringified `traceAddress`. - callTraces.sort((a, b) => { - return a.traceAddress < b.traceAddress ? -1 : 1; - }); - - let eventIndex = 0n; - for (const callTrace of callTraces) { - if ( - isCallTraceFilterMatched({ filter, block, callTrace }) && - (isAddressFactory(filter.toAddress) - ? finalizedChildAddresses - .get(filter.toAddress)! - .has(callTrace.action.to) || - unfinalizedChildAddresses - .get(filter.toAddress)! - .has(callTrace.action.to) - : true) && - callTrace.result !== null && - filter.functionSelectors.includes( - callTrace.action.input.slice(0, 10).toLowerCase() as Hex, - ) - ) { - events.push({ - chainId: filter.chainId, - sourceIndex: i, - checkpoint: encodeCheckpoint({ - blockTimestamp: hexToNumber(block.timestamp), - chainId: BigInt(filter.chainId), - blockNumber: hexToBigInt(callTrace.blockNumber), - transactionIndex: BigInt(callTrace.transactionPosition), - eventType: EVENT_TYPES.callTraces, - eventIndex: eventIndex++, - }), - log: undefined, - trace: convertCallTrace(callTrace), - block: convertBlock(block), - transaction: convertTransaction( - transactionCache.get(callTrace.transactionHash)!, - ), - transactionReceipt: filter.includeTransactionReceipts - ? convertTransactionReceipt( - transactionReceiptCache.get(callTrace.transactionHash)!, - ) - : undefined, - }); - } + case "transaction": { + for (const trace of traces) { + if ( + isTransactionFilterMatched({ filter, block, trace: trace.trace }) + ) { + // TODO: include factory + const traceTransaction = transactionCache.get( + trace.transactionHash, + )!; + events.push({ + chainId: filter.chainId, + sourceIndex: i, + checkpoint: encodeCheckpoint({ + blockTimestamp: hexToNumber(block.timestamp), + chainId: BigInt(filter.chainId), + blockNumber: hexToBigInt(block.number), + transactionIndex: BigInt(traceTransaction.transactionIndex), + eventType: EVENT_TYPES.traces, + eventIndex: BigInt(trace.position), + }), + log: undefined, + trace: convertTrace(trace, block, traceTransaction), + block: convertBlock(block), + transaction: convertTransaction( + transactionCache.get(trace.transactionHash)!, + ), + transactionReceipt: undefined, + }); } } - break; } + case "transfer": { + for (const trace of traces) { + if (isTransferFilterMatched({ filter, block, trace: trace.trace })) { + // TODO: include factory + const traceTransaction = transactionCache.get( + trace.transactionHash, + )!; + events.push({ + chainId: filter.chainId, + sourceIndex: i, + checkpoint: encodeCheckpoint({ + blockTimestamp: hexToNumber(block.timestamp), + chainId: BigInt(filter.chainId), + blockNumber: hexToBigInt(block.number), + transactionIndex: BigInt(traceTransaction.transactionIndex), + eventType: EVENT_TYPES.traces, + eventIndex: BigInt(trace.position), + }), + log: undefined, + trace: convertTrace(trace, block, traceTransaction), + block: convertBlock(block), + transaction: convertTransaction( + transactionCache.get(trace.transactionHash)!, + ), + transactionReceipt: undefined, + }); + } + } + break; + } + + // case "callTrace": { + // for (const callTraces of Array.from(traceByTransactionHash.values())) { + // // Use lexographical sort of stringified `traceAddress`. + // callTraces.sort((a, b) => { + // return a.traceAddress < b.traceAddress ? -1 : 1; + // }); + + // let eventIndex = 0n; + // for (const callTrace of callTraces) { + // if ( + // isCallTraceFilterMatched({ filter, block, callTrace }) && + // (isAddressFactory(filter.toAddress) + // ? finalizedChildAddresses + // .get(filter.toAddress)! + // .has(callTrace.action.to) || + // unfinalizedChildAddresses + // .get(filter.toAddress)! + // .has(callTrace.action.to) + // : true) && + // callTrace.result !== null && + // filter.functionSelectors.includes( + // callTrace.action.input.slice(0, 10).toLowerCase() as Hex, + // ) + // ) { + // events.push({ + // chainId: filter.chainId, + // sourceIndex: i, + // checkpoint: encodeCheckpoint({ + // blockTimestamp: hexToNumber(block.timestamp), + // chainId: BigInt(filter.chainId), + // blockNumber: hexToBigInt(callTrace.blockNumber), + // transactionIndex: BigInt(callTrace.transactionPosition), + // eventType: EVENT_TYPES.callTraces, + // eventIndex: eventIndex++, + // }), + // log: undefined, + // trace: convertCallTrace(callTrace), + // block: convertBlock(block), + // transaction: convertTransaction( + // transactionCache.get(callTrace.transactionHash)!, + // ), + // transactionReceipt: filter.includeTransactionReceipts + // ? convertTransactionReceipt( + // transactionReceiptCache.get(callTrace.transactionHash)!, + // ) + // : undefined, + // }); + // } + // } + // } + + // break; + // } default: never(filter); } @@ -361,7 +460,42 @@ export const decodeEvents = ( break; } - case "callTrace": { + case "transfer": { + try { + const result = event.trace!.output; + + events.push({ + type: "transfer", + chainId: event.chainId, + checkpoint: event.checkpoint, + + name: `${source.name}/transfer`, + + event: { + result, + trace: event.trace!, + block: event.block, + transaction: event.transaction!, + transactionReceipt: event.transactionReceipt, + }, + }); + } catch (err) { + if (source.filter.toAddress === undefined) { + common.logger.debug({ + service: "app", + msg: `Unable to decode trace, skipping it. id: ${event.trace?.id}, input: ${event.trace?.input}, output: ${event.trace?.output}`, + }); + } else { + common.logger.warn({ + service: "app", + msg: `Unable to decode trace, skipping it. id: ${event.trace?.id}, input: ${event.trace?.input}, output: ${event.trace?.output}`, + }); + } + } + break; + } + + case "transaction": { try { const selector = event .trace!.input.slice(0, 10) @@ -379,14 +513,17 @@ export const decodeEvents = ( data: event.trace!.input, }); - const result = decodeFunctionResult({ - abi: [item], - data: event.trace!.output, - functionName, - }); + const result = + event.trace!.output !== undefined + ? decodeFunctionResult({ + abi: [item], + data: event.trace!.output, + functionName, + }) + : undefined; events.push({ - type: "callTrace", + type: "transaction", chainId: event.chainId, checkpoint: event.checkpoint, @@ -644,20 +781,41 @@ const convertTransactionReceipt = ( : transactionReceipt.type, }); -const convertCallTrace = (callTrace: SyncCallTrace): CallTrace => ({ - id: `${callTrace.transactionHash}-${JSON.stringify(callTrace.traceAddress)}`, - from: checksumAddress(callTrace.action.from), - to: checksumAddress(callTrace.action.to), - gas: hexToBigInt(callTrace.action.gas), - value: hexToBigInt(callTrace.action.value), - input: callTrace.action.input, - output: callTrace.result!.output, - gasUsed: hexToBigInt(callTrace.result!.gasUsed), - subtraces: callTrace.subtraces, - traceAddress: callTrace.traceAddress, - blockHash: callTrace.blockHash, - blockNumber: hexToBigInt(callTrace.blockNumber), - transactionHash: callTrace.transactionHash, - transactionIndex: callTrace.transactionPosition, - callType: callTrace.action.callType as CallTrace["callType"], +// const convertCallTrace = (callTrace: SyncCallTrace): CallTrace => ({ +// id: `${callTrace.transactionHash}-${JSON.stringify(callTrace.traceAddress)}`, +// from: checksumAddress(callTrace.action.from), +// to: checksumAddress(callTrace.action.to), +// gas: hexToBigInt(callTrace.action.gas), +// value: hexToBigInt(callTrace.action.value), +// input: callTrace.action.input, +// output: callTrace.result!.output, +// gasUsed: hexToBigInt(callTrace.result!.gasUsed), +// subtraces: callTrace.subtraces, +// traceAddress: callTrace.traceAddress, +// blockHash: callTrace.blockHash, +// blockNumber: hexToBigInt(callTrace.blockNumber), +// transactionHash: callTrace.transactionHash, +// transactionIndex: callTrace.transactionPosition, +// callType: callTrace.action.callType as CallTrace["callType"], +// }); + +const convertTrace = ( + trace: SyncTraceFlat, + block: SyncBlock, + transaction: SyncTransaction, +): Trace => ({ + id: `${trace.transactionHash}-${trace.position}`, + type: trace.trace.type, + from: checksumAddress(trace.trace.from), + to: checksumAddress(trace.trace.to), + gas: hexToBigInt(trace.trace.gas), + value: hexToBigInt(trace.trace.value), + input: trace.trace.input, + output: trace.trace.output, + gasUsed: hexToBigInt(trace.trace.gasUsed), + subtraces: trace.trace.calls !== undefined ? trace.trace.calls.length : 0, + blockHash: block.hash, + blockNumber: hexToBigInt(block.number), + transactionHash: trace.transactionHash, + transactionIndex: hexToNumber(transaction.transactionIndex), }); diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 9a94660b0..2909656ed 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -635,7 +635,7 @@ export const createSync = async (args: CreateSyncParameters): Promise => { filters: event.filters, logs: event.logs, factoryLogs: event.factoryLogs, - callTraces: event.callTraces, + traces: event.traces, transactions: event.transactions, transactionReceipts: event.transactionReceipts, }; @@ -760,9 +760,17 @@ export const createSync = async (args: CreateSyncParameters): Promise => { ), chainId: network.chainId, }), - args.syncStore.insertCallTraces({ - callTraces: finalizedEventData.flatMap(({ callTraces, block }) => - callTraces.map((callTrace) => ({ callTrace, block })), + args.syncStore.insertTraces({ + traces: finalizedEventData.flatMap( + ({ traces, block, transactions }) => + traces.map(({ trace, position, transactionHash }) => ({ + trace, + block, + transaction: transactions.find( + (t) => t.hash === transactionHash, + )!, + position, + })), ), chainId: network.chainId, }), diff --git a/packages/core/src/types/eth.ts b/packages/core/src/types/eth.ts index e64f09323..b8624b13c 100644 --- a/packages/core/src/types/eth.ts +++ b/packages/core/src/types/eth.ts @@ -196,30 +196,74 @@ export type TransactionReceipt = { type _TraceAddress = number | _TraceAddress[]; type TraceAddress = _TraceAddress[]; -/** - * An Ethereum call trace. - */ -export type CallTrace = { - /** Globally unique identifier for this trace (`${transactionHash}-${traceAddress}`) */ +// /** +// * An Ethereum call trace. +// */ +// export type CallTrace = { +// /** Globally unique identifier for this trace (`${transactionHash}-${traceAddress}`) */ +// id: string; +// /** Message sender */ +// from: Address; +// /** Message receipient */ +// to: Address; +// /** Amount of gas allocated to this call */ +// gas: bigint; +// /** Value in wei sent with this call */ +// value: bigint; +// /** Calldata sent with this call */ +// input: Hex; +// /** Contains return data */ +// output: Hex; +// /** Total used gas by this trace */ +// gasUsed: bigint; +// /** Number of traces created by this trace */ +// subtraces: number; +// /** Description of this traces position within all traces in the transaction */ +// traceAddress: TraceAddress; +// /** Hash of block containing this trace */ +// blockHash: Hash; +// /** Number of block containing this trace */ +// blockNumber: bigint; +// /** Hash of the transaction that created this trace */ +// transactionHash: Hash; +// /** Index of the transaction that created this trace */ +// transactionIndex: number; +// /** EVM opcode used to make this call */ +// callType: "call" | "staticcall" | "delegatecall" | "callcode"; +// }; + +export type Trace = { + /** Globally unique identifier for this trace (`${transactionHash}-${tracePosition}`) */ id: string; - /** Message sender */ + /** The type of the call. */ + type: + | "CALL" + | "CALLCODE" + | "DELEGATECALL" + | "STATICCALL" + | "CREATE" + | "CREATE2" + | "SELFDESTRUCT"; + /** The address of that initiated the call. */ from: Address; - /** Message receipient */ + /** The address of the contract that was called. */ to: Address; - /** Amount of gas allocated to this call */ + /** How much gas was left before the call. */ gas: bigint; - /** Value in wei sent with this call */ - value: bigint; - /** Calldata sent with this call */ - input: Hex; - /** Contains return data */ - output: Hex; - /** Total used gas by this trace */ + /** How much gas was used by the call. */ gasUsed: bigint; + /** Calldata input. */ + input: Hex; + /** Output of the call, if any. */ + output?: Hex; + /** Error message, if any. */ + error?: string; + /** Why this call reverted, if it reverted. */ + revertReason?: string; + /** Value transferred. */ + value: bigint; /** Number of traces created by this trace */ subtraces: number; - /** Description of this traces position within all traces in the transaction */ - traceAddress: TraceAddress; /** Hash of block containing this trace */ blockHash: Hash; /** Number of block containing this trace */ @@ -228,6 +272,4 @@ export type CallTrace = { transactionHash: Hash; /** Index of the transaction that created this trace */ transactionIndex: number; - /** EVM opcode used to make this call */ - callType: "call" | "staticcall" | "delegatecall" | "callcode"; };