-
Notifications
You must be signed in to change notification settings - Fork 294
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
955 additions
and
0 deletions.
There are no files selected for viewing
225 changes: 225 additions & 0 deletions
225
yarn-project/archiver/src/archiver/store/archiver_store.ts
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,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
152
yarn-project/archiver/src/archiver/store/block_store.ts
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,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 }; | ||
} | ||
} |
Oops, something went wrong.