Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Oct 9, 2023
1 parent 565d3a4 commit 02e7c2c
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 21 deletions.
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
* This is a problem for example when setting the last block number marker for L1 to L2 messages -
* this.lastProcessedBlockNumber = currentBlockNumber;
* It's possible that we actually received messages in block currentBlockNumber + 1 meaning the next time
* we do this sync we get the same message again. Addtionally, the call to get cancelled L1 to L2 messages
* we do this sync we get the same message again. Additionally, the call to get cancelled L1 to L2 messages
* could read from a block not present when retrieving pending messages. If a message was added and cancelled
* in the same eth block then we could try and cancel a non-existent pending message.
*
Expand Down
29 changes: 21 additions & 8 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
LogType,
TxHash,
UnencryptedL2Log,
validateLogFilter,
} from '@aztec/types';

import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js';
Expand Down Expand Up @@ -370,13 +371,17 @@ export class MemoryArchiverStore implements ArchiverDataStore {
* @throws If txHash and block range are both defined.
*/
getUnencryptedLogs(filter: LogFilter): Promise<ExtendedUnencryptedL2Log[]> {
if (filter.txHash && (filter.fromBlock || filter.toBlock)) {
throw new Error('Cannot filter by txHash and block range at the same time');
}
validateLogFilter(filter);

const txHash = filter.txHash;

const fromBlockIndex = filter.fromBlock === undefined ? 0 : filter.fromBlock - INITIAL_L2_BLOCK_NUM;
let fromBlockIndex = 0;
if (filter.fromBlock !== undefined) {
fromBlockIndex = filter.fromBlock - INITIAL_L2_BLOCK_NUM;
} else if (filter.fromLog !== undefined) {
fromBlockIndex = filter.fromLog.blockNumber - INITIAL_L2_BLOCK_NUM;
}

if (fromBlockIndex < 0) {
throw new Error(`"fromBlock" (${filter.fromBlock}) smaller than genesis block number (${INITIAL_L2_BLOCK_NUM}).`);
}
Expand All @@ -393,26 +398,34 @@ export class MemoryArchiverStore implements ArchiverDataStore {
const contractAddress = filter.contractAddress;
const selector = filter.selector;

let txIndexInBlock = 0;
if (filter.fromLog !== undefined) {
txIndexInBlock = filter.fromLog.logIndex;
}

const logs: ExtendedUnencryptedL2Log[] = [];

for (let i = fromBlockIndex; i < toBlockIndex; i++) {
const blockContext = this.l2BlockContexts[i];
const blockLogs = this.unencryptedLogsPerBlock[i];
for (let j = 0; j < blockLogs.txLogs.length; j++) {
const txLogs = blockLogs.txLogs[j].unrollLogs().map(log => UnencryptedL2Log.fromBuffer(log));
for (; txIndexInBlock < blockLogs.txLogs.length; txIndexInBlock++) {
const txLogs = blockLogs.txLogs[txIndexInBlock].unrollLogs().map(log => UnencryptedL2Log.fromBuffer(log));
for (const log of txLogs) {
if (
(!txHash || blockContext.getTxHash(j).equals(txHash)) &&
(!txHash || blockContext.getTxHash(txIndexInBlock).equals(txHash)) &&
(!contractAddress || log.contractAddress.equals(contractAddress)) &&
(!selector || log.selector.equals(selector))
) {
logs.push(new ExtendedUnencryptedL2Log(blockContext.block.number, blockContext.getTxHash(j), log));
logs.push(
new ExtendedUnencryptedL2Log(blockContext.block.number, blockContext.getTxHash(txIndexInBlock), log),
);
if (logs.length === this.maxLogs) {
return Promise.resolve(logs);
}
}
}
}
txIndexInBlock = 0;
}

return Promise.resolve(logs);
Expand Down
23 changes: 14 additions & 9 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DebugLogger, LogFn } from '@aztec/foundation/log';
import { sleep } from '@aztec/foundation/sleep';
import { fileURLToPath } from '@aztec/foundation/url';
import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli';
import { CompleteAddress, ContractData, LogFilter } from '@aztec/types';
import { CompleteAddress, ContractData, LogFilter, validateLogFilter } from '@aztec/types';

import { createSecp256k1PeerId } from '@libp2p/peer-id-factory';
import { Command, Option } from 'commander';
Expand All @@ -37,6 +37,7 @@ import {
parseFields,
parseOptionalAztecAddress,
parseOptionalInteger,
parseOptionalLogId,
parseOptionalSelector,
parseOptionalTxHash,
parsePartialAddress,
Expand Down Expand Up @@ -285,27 +286,30 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets all the unencrypted logs from L2 blocks in the range specified.')
.option('-tx, --tx-hash <txHash>', 'A transaction hash to get the receipt for.', parseOptionalTxHash)
.option(
'-f, --from-block <blockNum>',
'-fb, --from-block <blockNum>',
'Initial block number for getting logs (defaults to 1).',
parseOptionalInteger,
)
.option('-t, --to-block <blockNum>', 'Up to which block to fetch logs (defaults to latest).', parseOptionalInteger)
.option('-tb, --to-block <blockNum>', 'Up to which block to fetch logs (defaults to latest).', parseOptionalInteger)
.option('-fl --from-log <logId>', 'Initial log id for getting logs.', parseOptionalLogId)
.option('-ca, --contract-address <address>', 'Contract address to filter logs by.', parseOptionalAztecAddress)
.option('-s, --selector <hex string>', 'Event selector to filter logs by.', parseOptionalSelector)
.addOption(pxeOption)
.option('--follow', 'If set, will keep polling for new logs until interrupted.')
.action(async ({ txHash, fromBlock, toBlock, contractAddress, selector, rpcUrl, follow }) => {
const client = await createCompatibleClient(rpcUrl, debugLogger);
.action(async ({ txHash, fromBlock, toBlock, fromLog, contractAddress, selector, rpcUrl, follow }) => {
const pxe = await createCompatibleClient(rpcUrl, debugLogger);

if (follow) {
if (txHash) throw Error('Cannot use --follow with --tx-hash');
if (toBlock) throw Error('Cannot use --follow with --to-block');
}

const filter: LogFilter = { txHash, fromBlock, toBlock, contractAddress, selector };
const filter: LogFilter = { txHash, fromBlock, toBlock, fromLog, contractAddress, selector };

validateLogFilter(filter);

const fetchLogs = async () => {
const logs = await client.getUnencryptedLogs(filter);
const logs = await pxe.getUnencryptedLogs(filter);

if (!logs.length) {
const filterOptions = Object.entries(filter)
Expand All @@ -316,8 +320,9 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
} else {
if (!follow) log('Logs found: \n');
logs.forEach(unencryptedLog => log(unencryptedLog.toHumanReadable()));
// Set `fromBlock` to the block number of the next block after the block of last log we fetched.
filter.fromBlock = logs[logs.length - 1].blockNumber + 1;
// Disable `fromBlock` and continue using the `fromLog` filter.
filter.fromBlock = undefined;
filter.fromLog = await logs[logs.length - 1].getLogId(pxe);
}
};

Expand Down
14 changes: 14 additions & 0 deletions yarn-project/cli/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RollupAbi,
RollupBytecode,
} from '@aztec/l1-artifacts';
import { LogId, parseLogId } from '@aztec/types';

import { InvalidArgumentError } from 'commander';
import fs from 'fs';
Expand Down Expand Up @@ -225,6 +226,19 @@ export function parseOptionalAztecAddress(address: string): AztecAddress | undef
return parseAztecAddress(address);
}

/**
* Parses an optional log ID string into a LogId object.
*
* @param logId - The log ID string to parse.
* @returns The parsed LogId object, or undefined if the log ID is missing or empty.
*/
export function parseOptionalLogId(logId: string): LogId | undefined {
if (!logId) {
return undefined;
}
return parseLogId(logId);
}

/**
* Parses a selector from a string.
* @param selector - A serialised selector.
Expand Down
31 changes: 30 additions & 1 deletion yarn-project/types/src/logs/extended_unencrypted_l2_log.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
import { BufferReader } from '@aztec/foundation/serialize';

import { TxHash, UnencryptedL2Log } from '../index.js';
import isEqual from 'lodash.isequal';

import { LogId, PXE, TxHash, UnencryptedL2Log } from '../index.js';

/**
* Represents an individual unencrypted log entry extended with info about the block and tx it was emitted in.
Expand Down Expand Up @@ -40,6 +42,33 @@ export class ExtendedUnencryptedL2Log {
return `${this.log.toHumanReadable()} (blockNumber: ${this.blockNumber}, txHash: ${this.txHash.toString()})`;
}

/**
* Checks if two ExtendedUnencryptedL2Log objects are equal.
* @param other - Another ExtendedUnencryptedL2Log object to compare with.
* @returns True if the two objects are equal, false otherwise.
*/
public equals(other: ExtendedUnencryptedL2Log): boolean {
return isEqual(this, other);
}

/**
* Gets a globally unique log id.
* @param pxe - The PXE instance to use for retrieving logs.
* @returns A globally unique log id.
*/
public async getLogId(pxe: PXE): Promise<LogId> {
const txLogs = await pxe.getUnencryptedLogs({ txHash: this.txHash });
const logIndex = txLogs.findIndex(log => log.equals(this));
if (logIndex === -1) {
throw new Error(`Log ${this.toHumanReadable()} not found in tx ${this.txHash.toString()}`);
}
return {
blockNumber: this.blockNumber,
txIndex: this.txHash,
logIndex: logIndex,
};
}

/**
* Deserializes log from a buffer.
* @param buffer - The buffer or buffer reader containing the log.
Expand Down
1 change: 1 addition & 0 deletions yarn-project/types/src/logs/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './function_l2_logs.js';
export * from './l2_block_l2_logs.js';
export * from './l2_logs_source.js';
export * from './log_id.js';
export * from './log_type.js';
export * from './log_filter.js';
export * from './note_spending_info/index.js';
Expand Down
27 changes: 25 additions & 2 deletions yarn-project/types/src/logs/log_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AztecAddress, FunctionSelector } from '@aztec/circuits.js';

import { TxHash } from '../index.js';
import { LogId, TxHash } from '../index.js';

/**
* Log filter used to fetch L2 logs.
Expand All @@ -11,10 +11,18 @@ export type LogFilter = {
* @remarks If this is set, `fromBlock` and `toBlock` can't be defined.
*/
txHash?: TxHash;
/** The block number from which to start fetching logs (inclusive). */
/**
* The block number from which to start fetching logs (inclusive).
* @remarks If this is set, `txHash` and `fromLog` can't be defined.
*/
fromBlock?: number;
/** The block number until which to fetch logs (not inclusive). */
toBlock?: number;
/**
* Log id from which to start fetching logs (inclusive).
* @remarks If this is set, `fromBlock` and `txHash` can't be defined.
*/
fromLog?: LogId;
/** The contract address to filter logs by. */
contractAddress?: AztecAddress;
/**
Expand All @@ -23,3 +31,18 @@ export type LogFilter = {
*/
selector?: FunctionSelector;
};

/**
* Validates a log filter.
* @param filter - Log filter to validate.
* @throws If the filter is invalid.
*/
export function validateLogFilter(filter: LogFilter) {
if (filter.txHash && (filter.fromBlock || filter.toBlock || filter.fromLog)) {
throw new Error("If txHash is set, fromBlock, toBlock, and fromLog can't be defined.");
}

if (filter.fromBlock !== undefined && filter.fromLog) {
throw new Error("If fromBlock is set, fromLog can't be defined.");
}
}
38 changes: 38 additions & 0 deletions yarn-project/types/src/logs/log_id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

/** A globally unique log id. */
export type LogId = {
/** The block number in which the tx containing the log was included. */
blockNumber: number;
/** The index of a tx in a block. */
txIndex: number;
/** The index of the log within a transaction. */
logIndex: number;
};

/**
* Parses a log id from a string.
* @param logId - A string representation of a log id.
* @returns A log id.
*/
export function parseLogId(logId: string): LogId {
const [rawBlockNumber, rawTxIndex, rawLogIndex] = logId.split('-');
const blockNumber = Number(rawBlockNumber);
const txIndex = Number(rawTxIndex);
const logIndex = Number(rawLogIndex);

if (!Number.isInteger(blockNumber)) {
throw new Error(`Invalid block number in log id: ${logId}`);
}
if (!Number.isInteger(txIndex)) {
throw new Error(`Invalid tx index in log id: ${logId}`);
}
if (!Number.isInteger(logIndex)) {
throw new Error(`Invalid log index in log id: ${logId}`);
}

return {
blockNumber,
txIndex,
logIndex,
};
}

0 comments on commit 02e7c2c

Please sign in to comment.