Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Jan 11, 2024
1 parent 9bd25f6 commit 88084a7
Show file tree
Hide file tree
Showing 6 changed files with 955 additions and 0 deletions.
225 changes: 225 additions & 0 deletions yarn-project/archiver/src/archiver/store/archiver_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { Fr } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { createDebugLogger } from '@aztec/foundation/log';
import { AztecKVStore } from '@aztec/kv-store';
import {
CancelledL1ToL2Message,
ContractData,
ExtendedContractData,
GetUnencryptedLogsResponse,
L1ToL2Message,
L2Block,
L2BlockL2Logs,
L2Tx,
LogFilter,
LogType,
PendingL1ToL2Message,
TxHash,
} from '@aztec/types';

import { ArchiverDataStore } from '../archiver_store.js';
import { BlockStore } from './block_store.js';
import { ContractStore } from './contract_store.js';
import { LogStore } from './log_store.js';
import { MessageStore } from './message_store.js';

/**
* LMDB implementation of the ArchiverDataStore interface.
*/
export class ArchiverStore implements ArchiverDataStore {
#blockStore: BlockStore;
#logStore: LogStore;
#contractStore: ContractStore;
#messageStore: MessageStore;

#log = createDebugLogger('aztec:archiver:lmdb');

constructor(private db: AztecKVStore, logsMaxPageSize: number = 1000) {
this.#blockStore = new BlockStore(db);
this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
this.#contractStore = new ContractStore(db, this.#blockStore);
this.#messageStore = new MessageStore(db);
}

/**
* Append new blocks to the store's list.
* @param blocks - The L2 blocks to be added to the store.
* @returns True if the operation is successful.
*/
addBlocks(blocks: L2Block[]): Promise<boolean> {
return this.#blockStore.addBlocks(blocks);
}

/**
* Gets up to `limit` amount of L2 blocks starting from `from`.
* @param start - Number of the first block to return (inclusive).
* @param limit - The number of blocks to return.
* @returns The requested L2 blocks.
*/
getBlocks(start: number, limit: number): Promise<L2Block[]> {
try {
return Promise.resolve(Array.from(this.#blockStore.getBlocks(start, limit)));
} catch (err) {
// this function is sync so if any errors are thrown we need to make sure they're passed on as rejected Promises
return Promise.reject(err);
}
}

/**
* Gets an l2 tx.
* @param txHash - The txHash of the l2 tx.
* @returns The requested L2 tx.
*/
getL2Tx(txHash: TxHash): Promise<L2Tx | undefined> {
return Promise.resolve(this.#blockStore.getL2Tx(txHash));
}

/**
* Append new logs to the store's list.
* @param encryptedLogs - The logs to be added to the store.
* @param unencryptedLogs - The type of the logs to be added to the store.
* @param blockNumber - The block for which to add the logs.
* @returns True if the operation is successful.
*/
addLogs(
encryptedLogs: L2BlockL2Logs | undefined,
unencryptedLogs: L2BlockL2Logs | undefined,
blockNumber: number,
): Promise<boolean> {
return this.#logStore.addLogs(encryptedLogs, unencryptedLogs, blockNumber);
}

/**
* Append new pending L1 to L2 messages to the store.
* @param messages - The L1 to L2 messages to be added to the store.
* @returns True if the operation is successful.
*/
addPendingL1ToL2Messages(messages: PendingL1ToL2Message[]): Promise<boolean> {
return this.#messageStore.addPendingL1ToL2Messages(messages);
}

/**
* Remove pending L1 to L2 messages from the store (if they were cancelled).
* @param messages - The message keys to be removed from the store.
* @returns True if the operation is successful.
*/
cancelPendingL1ToL2Messages(messages: CancelledL1ToL2Message[]): Promise<boolean> {
return this.#messageStore.cancelPendingL1ToL2Messages(messages);
}

/**
* Messages that have been published in an L2 block are confirmed.
* Add them to the confirmed store, also remove them from the pending store.
* @param entryKeys - The message keys to be removed from the store.
* @returns True if the operation is successful.
*/
confirmL1ToL2Messages(entryKeys: Fr[]): Promise<boolean> {
return this.#messageStore.confirmL1ToL2Messages(entryKeys);
}

/**
* Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee
* @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).
* @returns The requested L1 to L2 message keys.
*/
getPendingL1ToL2MessageKeys(limit: number): Promise<Fr[]> {
return this.#messageStore.getPendingL1ToL2MessageKeys(limit);
}

/**
* Gets the confirmed L1 to L2 message corresponding to the given message key.
* @param messageKey - The message key to look up.
* @returns The requested L1 to L2 message or throws if not found.
*/
getConfirmedL1ToL2Message(messageKey: Fr): Promise<L1ToL2Message> {
return this.#messageStore.getConfirmedL1ToL2Message(messageKey);
}

/**
* Gets up to `limit` amount of logs starting from `from`.
* @param start - Number of the L2 block to which corresponds the first logs to be returned.
* @param limit - The number of logs to return.
* @param logType - Specifies whether to return encrypted or unencrypted logs.
* @returns The requested logs.
*/
getLogs(start: number, limit: number, logType: LogType): Promise<L2BlockL2Logs[]> {
try {
return Promise.resolve(Array.from(this.#logStore.getLogs(start, limit, logType)));
} catch (err) {
return Promise.reject(err);
}
}

/**
* Gets unencrypted logs based on the provided filter.
* @param filter - The filter to apply to the logs.
* @returns The requested logs.
*/
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
try {
return Promise.resolve(this.#logStore.getUnencryptedLogs(filter));
} catch (err) {
return Promise.reject(err);
}
}

/**
* Add new extended contract data from an L2 block to the store's list.
* @param data - List of contracts' data to be added.
* @param blockNum - Number of the L2 block the contract data was deployed in.
* @returns True if the operation is successful.
*/
addExtendedContractData(data: ExtendedContractData[], blockNum: number): Promise<boolean> {
return this.#contractStore.addExtendedContractData(data, blockNum);
}

/**
* Get the extended contract data for this contract.
* @param contractAddress - The contract data address.
* @returns The extended contract data or undefined if not found.
*/
getExtendedContractData(contractAddress: AztecAddress): Promise<ExtendedContractData | undefined> {
return Promise.resolve(this.#contractStore.getExtendedContractData(contractAddress));
}

/**
* Lookup all extended contract data in an L2 block.
* @param blockNumber - The block number to get all contract data from.
* @returns All extended contract data in the block (if found).
*/
getExtendedContractDataInBlock(blockNumber: number): Promise<ExtendedContractData[]> {
return Promise.resolve(Array.from(this.#contractStore.getExtendedContractDataInBlock(blockNumber)));
}

/**
* Get basic info for an L2 contract.
* Contains contract address & the ethereum portal address.
* @param contractAddress - The contract data address.
* @returns ContractData with the portal address (if we didn't throw an error).
*/
getContractData(contractAddress: AztecAddress): Promise<ContractData | undefined> {
return Promise.resolve(this.#contractStore.getContractData(contractAddress));
}

/**
* Get basic info for an all L2 contracts deployed in a block.
* Contains contract address & the ethereum portal address.
* @param blockNumber - Number of the L2 block where contracts were deployed.
* @returns ContractData with the portal address (if we didn't throw an error).
*/
getContractDataInBlock(blockNumber: number): Promise<ContractData[]> {
return Promise.resolve(Array.from(this.#contractStore.getContractDataInBlock(blockNumber)));
}

/**
* Gets the number of the latest L2 block processed.
* @returns The number of the latest L2 block processed.
*/
getBlockNumber(): Promise<number> {
return Promise.resolve(this.#blockStore.getBlockNumber());
}

getL1BlockNumber(): Promise<bigint> {
return Promise.resolve(this.#blockStore.getL1BlockNumber());
}
}
152 changes: 152 additions & 0 deletions yarn-project/archiver/src/archiver/store/block_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { AztecAddress } from '@aztec/circuits.js';
import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer';
import { createDebugLogger } from '@aztec/foundation/log';
import { AztecKVStore, AztecMap, MapRange } from '@aztec/kv-store';
import { INITIAL_L2_BLOCK_NUM, L2Block, L2Tx, TxHash } from '@aztec/types';

/* eslint-disable */
type BlockIndexValue = [blockNumber: number, index: number];

type BlockContext = {
blockNumber: number;
l1BlockNumber: Buffer;
block: Buffer;
blockHash: Buffer;
};
/* eslint-enable */

/**
* LMDB implementation of the ArchiverDataStore interface.
*/
export class BlockStore {
/** Map block number to block data */
#blocks: AztecMap<number, BlockContext>;

/** Index mapping transaction hash (as a string) to its location in a block */
#txIndex: AztecMap<string, BlockIndexValue>;

/** Index mapping a contract's address (as a string) to its location in a block */
#contractIndex: AztecMap<string, BlockIndexValue>;

#log = createDebugLogger('aztec:archiver:block_store');

constructor(private db: AztecKVStore) {
this.#blocks = db.createMap('archiver_blocks');

this.#txIndex = db.createMap('archiver_tx_index');
this.#contractIndex = db.createMap('archiver_contract_index');
}

/**
* Append new blocks to the store's list.
* @param blocks - The L2 blocks to be added to the store.
* @returns True if the operation is successful.
*/
addBlocks(blocks: L2Block[]): Promise<boolean> {
return this.db.transaction(() => {
for (const block of blocks) {
void this.#blocks.set(block.number, {
blockNumber: block.number,
block: block.toBuffer(),
l1BlockNumber: toBufferBE(block.getL1BlockNumber(), 32),
blockHash: block.getBlockHash(),
});

for (const [i, tx] of block.getTxs().entries()) {
if (tx.txHash.isZero()) {
continue;
}
void this.#txIndex.set(tx.txHash.toString(), [block.number, i]);
}

for (const [i, contractData] of block.newContractData.entries()) {
if (contractData.contractAddress.isZero()) {
continue;
}

void this.#contractIndex.set(contractData.contractAddress.toString(), [block.number, i]);
}
}

return true;
});
}

/**
* Gets up to `limit` amount of L2 blocks starting from `from`.
* @param start - Number of the first block to return (inclusive).
* @param limit - The number of blocks to return.
* @returns The requested L2 blocks.
*/
*getBlocks(start: number, limit: number): IterableIterator<L2Block> {
for (const blockCtx of this.#blocks.values(this.#computeBlockRange(start, limit))) {
yield L2Block.fromBuffer(blockCtx.block, blockCtx.blockHash);
}
}

getBlock(blockNumber: number): L2Block | undefined {
const blockCtx = this.#blocks.get(blockNumber);
if (!blockCtx || !blockCtx.block) {
return undefined;
}

const block = L2Block.fromBuffer(blockCtx.block, blockCtx.blockHash);

return block;
}

/**
* Gets an l2 tx.
* @param txHash - The txHash of the l2 tx.
* @returns The requested L2 tx.
*/
getL2Tx(txHash: TxHash): L2Tx | undefined {
const [blockNumber, txIndex] = this.#txIndex.get(txHash.toString()) ?? [];
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
return undefined;
}

const block = this.getBlock(blockNumber);
return block?.getTx(txIndex);
}

getL2TxLocation(txHash: TxHash): [blockNumber: number, txIndex: number] | undefined {
return this.#txIndex.get(txHash.toString());
}

getContractLocation(contractAddress: AztecAddress): [blockNumber: number, index: number] | undefined {
return this.#contractIndex.get(contractAddress.toString());
}

/**
* Gets the number of the latest L2 block processed.
* @returns The number of the latest L2 block processed.
*/
getBlockNumber(): number {
const [lastBlockNumber] = this.#blocks.keys({ reverse: true, limit: 1 });
return typeof lastBlockNumber === 'number' ? lastBlockNumber : INITIAL_L2_BLOCK_NUM - 1;
}

getL1BlockNumber(): bigint {
const [lastBlock] = this.#blocks.values({ reverse: true, limit: 1 });
if (!lastBlock) {
return 0n;
} else {
return toBigIntBE(lastBlock.l1BlockNumber);
}
}

#computeBlockRange(start: number, limit: number): Required<Pick<MapRange<number>, 'start' | 'end'>> {
if (limit < 1) {
throw new Error(`Invalid limit: ${limit}`);
}

if (start < INITIAL_L2_BLOCK_NUM) {
this.#log(`Clamping start block ${start} to ${INITIAL_L2_BLOCK_NUM}`);
start = INITIAL_L2_BLOCK_NUM;
}

const end = start + limit;
return { start, end };
}
}
Loading

0 comments on commit 88084a7

Please sign in to comment.