Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use tree snapshots in aztec-node/pxe/oracles #3504

Merged
merged 4 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 76 additions & 30 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ContractDataSource,
ExtendedContractData,
GetUnencryptedLogsResponse,
INITIAL_L2_BLOCK_NUM,
L1ToL2MessageAndIndex,
L1ToL2MessageSource,
L2Block,
Expand Down Expand Up @@ -301,42 +302,59 @@ export class AztecNodeService implements AztecNode {

/**
* Find the index of the given leaf in the given tree.
* @param blockNumber - The block number at which to get the data
* @param treeId - The tree to search in.
* @param leafValue - The value to search for
* @returns The index of the given leaf in the given tree or undefined if not found.
*/
public async findLeafIndex(treeId: MerkleTreeId, leafValue: Fr): Promise<bigint | undefined> {
const committedDb = await this.#getWorldState();
public async findLeafIndex(
blockNumber: number | 'latest',
treeId: MerkleTreeId,
leafValue: Fr,
): Promise<bigint | undefined> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.findLeafIndex(treeId, leafValue.toBuffer());
}

/**
* Returns a sibling path for the given index in the contract tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - The index of the leaf for which the sibling path is required.
* @returns The sibling path for the leaf index.
*/
public async getContractSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof CONTRACT_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getContractSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof CONTRACT_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.CONTRACT_TREE, leafIndex);
}

/**
* Returns a sibling path for the given index in the nullifier tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - The index of the leaf for which the sibling path is required.
* @returns The sibling path for the leaf index.
*/
public async getNullifierTreeSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getNullifierTreeSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
}

/**
* Returns a sibling path for the given index in the data tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - The index of the leaf for which the sibling path is required.
* @returns The sibling path for the leaf index.
*/
public async getNoteHashSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getNoteHashSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
}

Expand All @@ -348,38 +366,50 @@ export class AztecNodeService implements AztecNode {
*/
public async getL1ToL2MessageAndIndex(messageKey: Fr): Promise<L1ToL2MessageAndIndex> {
// todo: #697 - make this one lookup.
const index = (await this.findLeafIndex(MerkleTreeId.L1_TO_L2_MESSAGES_TREE, messageKey))!;
const index = (await this.findLeafIndex('latest', MerkleTreeId.L1_TO_L2_MESSAGES_TREE, messageKey))!;
const message = await this.l1ToL2MessageSource.getConfirmedL1ToL2Message(messageKey);
return Promise.resolve(new L1ToL2MessageAndIndex(index, message));
}

/**
* Returns a sibling path for a leaf in the committed l1 to l2 data tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - Index of the leaf in the tree.
* @returns The sibling path.
*/
public async getL1ToL2MessageSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getL1ToL2MessageSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGES_TREE, leafIndex);
}

/**
* Returns a sibling path for a leaf in the committed blocks tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - Index of the leaf in the tree.
* @returns The sibling path.
*/
public async getBlocksTreeSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof BLOCKS_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getBlocksTreeSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof BLOCKS_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.BLOCKS_TREE, leafIndex);
}

/**
* Returns a sibling path for a leaf in the committed public data tree.
* @param blockNumber - The block number at which to get the data.
* @param leafIndex - Index of the leaf in the tree.
* @returns The sibling path.
*/
public async getPublicDataTreeSiblingPath(leafIndex: bigint): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState();
public async getPublicDataTreeSiblingPath(
blockNumber: number | 'latest',
leafIndex: bigint,
): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
const committedDb = await this.#getWorldState(blockNumber);
return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
}

Expand All @@ -390,17 +420,17 @@ export class AztecNodeService implements AztecNode {
* @returns The nullifier membership witness (if found).
*/
public async getNullifierMembershipWitness(
blockNumber: number,
blockNumber: number | 'latest',
nullifier: Fr,
): Promise<NullifierMembershipWitness | undefined> {
const committedDb = await this.#getWorldState();
const index = await committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
const db = await this.#getWorldState(blockNumber);
const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
if (!index) {
return undefined;
}

const leafDataPromise = committedDb.getLeafData(MerkleTreeId.NULLIFIER_TREE, Number(index));
const siblingPathPromise = committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
const leafDataPromise = db.getLeafData(MerkleTreeId.NULLIFIER_TREE, Number(index));
const siblingPathPromise = db.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
MerkleTreeId.NULLIFIER_TREE,
BigInt(index),
);
Expand Down Expand Up @@ -429,10 +459,10 @@ export class AztecNodeService implements AztecNode {
* TODO: This is a confusing behavior and we should eventually address that.
*/
public async getLowNullifierMembershipWitness(
blockNumber: number,
blockNumber: number | 'latest',
nullifier: Fr,
): Promise<NullifierMembershipWitness | undefined> {
const committedDb = await this.#getWorldState();
const committedDb = await this.#getWorldState(blockNumber);
const { index, alreadyPresent } = await committedDb.getPreviousValueIndex(
MerkleTreeId.NULLIFIER_TREE,
nullifier.toBigInt(),
Expand Down Expand Up @@ -462,7 +492,7 @@ export class AztecNodeService implements AztecNode {
* @returns Storage value at the given contract slot (or undefined if not found).
*/
public async getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise<Fr | undefined> {
const committedDb = await this.#getWorldState();
const committedDb = await this.#getWorldState('latest');
const leafIndex = computePublicDataTreeIndex(contract, slot);
const value = await committedDb.getLeafValue(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex.value);
return value ? Fr.fromBuffer(value) : undefined;
Expand All @@ -473,7 +503,7 @@ export class AztecNodeService implements AztecNode {
* @returns The current committed roots for the data trees.
*/
public async getTreeRoots(): Promise<Record<MerkleTreeId, Fr>> {
const committedDb = await this.#getWorldState();
const committedDb = await this.#getWorldState('latest');
const getTreeRoot = async (id: MerkleTreeId) => Fr.fromBuffer((await committedDb.getTreeInfo(id)).root);

const [noteHashTree, nullifierTree, contractTree, l1ToL2MessagesTree, blocksTree, publicDataTree] =
Expand Down Expand Up @@ -501,7 +531,7 @@ export class AztecNodeService implements AztecNode {
* @returns The current committed block header.
*/
public async getBlockHeader(): Promise<BlockHeader> {
const committedDb = await this.#getWorldState();
const committedDb = await this.#getWorldState('latest');
const [roots, globalsHash] = await Promise.all([this.getTreeRoots(), committedDb.getLatestGlobalVariablesHash()]);

return new BlockHeader(
Expand Down Expand Up @@ -555,24 +585,40 @@ export class AztecNodeService implements AztecNode {

/**
* Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
* @param blockNumber - The block number at which to get the data.
* @returns An instance of a committed MerkleTreeOperations
*/
async #getWorldState() {
async #getWorldState(blockNumber: number | 'latest') {
if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM) {
throw new Error('Invalid block number to get world state for: ' + blockNumber);
}

let blockSyncedTo: number = 0;
try {
// Attempt to sync the world state if necessary
await this.#syncWorldState();
blockSyncedTo = await this.#syncWorldState();
} catch (err) {
this.log.error(`Error getting world state: ${err}`);
}
return this.worldStateSynchronizer.getCommitted();

// using a snapshot could be less efficient than using the committed db
if (blockNumber === 'latest' || blockNumber === blockSyncedTo) {
this.log(`Using committed db for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
return this.worldStateSynchronizer.getCommitted();
} else if (blockNumber < blockSyncedTo) {
this.log(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
return this.worldStateSynchronizer.getSnapshot(blockNumber);
} else {
throw new Error(`Block ${blockNumber} not yet synced`);
}
}

/**
* Ensure we fully sync the world state
* @returns A promise that fulfils once the world state is synced
*/
async #syncWorldState() {
async #syncWorldState(): Promise<number> {
const blockSourceHeight = await this.blockSource.getBlockNumber();
await this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
return this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
}
}
8 changes: 5 additions & 3 deletions yarn-project/pxe/src/contract_tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,14 @@ export class ContractTree {
* If the witness hasn't been previously computed, this function will request the contract node
* to find the contract's index and path in order to create the membership witness.
*
* @param blockNumber - The block number at which to get the data.
*
* @returns A Promise that resolves to the MembershipWitness object for the given contract tree.
*/
public async getContractMembershipWitness() {
public async getContractMembershipWitness(blockNumber: number | 'latest' = 'latest') {
const index = await this.getContractIndex();

const siblingPath = await this.stateInfoProvider.getContractSiblingPath(index);
const siblingPath = await this.stateInfoProvider.getContractSiblingPath(blockNumber, index);
return new MembershipWitness<typeof CONTRACT_TREE_HEIGHT>(
CONTRACT_TREE_HEIGHT,
index,
Expand Down Expand Up @@ -226,7 +228,7 @@ export class ContractTree {
const root = await this.getFunctionTreeRoot();
const newContractData = new NewContractData(completeAddress.address, portalContract, root);
const commitment = computeContractLeaf(newContractData);
this.contractIndex = await this.stateInfoProvider.findLeafIndex(MerkleTreeId.CONTRACT_TREE, commitment);
this.contractIndex = await this.stateInfoProvider.findLeafIndex('latest', MerkleTreeId.CONTRACT_TREE, commitment);
if (this.contractIndex === undefined) {
throw new Error(
`Failed to find contract at ${completeAddress.address} with portal ${portalContract} resulting in commitment ${commitment}.`,
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/pxe/src/kernel_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class KernelOracle implements ProvingDataOracle {
}

async getNoteMembershipWitness(leafIndex: bigint): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT>> {
const path = await this.node.getNoteHashSiblingPath(leafIndex);
const path = await this.node.getNoteHashSiblingPath('latest', leafIndex);
return new MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT>(
path.pathSize,
leafIndex,
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,13 @@ export class PXEService implements PXE {
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386)
// This can always be `uniqueSiloedNoteHash` once notes added from public also include nonces.
const noteHashToLookUp = nonce.isZero() ? siloedNoteHash : uniqueSiloedNoteHash;
const index = await this.node.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, noteHashToLookUp);
const index = await this.node.findLeafIndex('latest', MerkleTreeId.NOTE_HASH_TREE, noteHashToLookUp);
if (index === undefined) {
throw new Error('Note does not exist.');
}

const siloedNullifier = siloNullifier(note.contractAddress, innerNullifier!);
const nullifierIndex = await this.node.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, siloedNullifier);
const nullifierIndex = await this.node.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, siloedNullifier);
if (nullifierIndex !== undefined) {
throw new Error('The note has been destroyed.');
}
Expand Down
20 changes: 8 additions & 12 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class SimulatorOracle implements DBOracle {
const messageAndIndex = await this.stateInfoProvider.getL1ToL2MessageAndIndex(msgKey);
const message = messageAndIndex.message.toFieldArray();
const index = messageAndIndex.index;
const siblingPath = await this.stateInfoProvider.getL1ToL2MessageSiblingPath(index);
const siblingPath = await this.stateInfoProvider.getL1ToL2MessageSiblingPath('latest', index);
return {
message,
siblingPath: siblingPath.toFieldArray(),
Expand All @@ -129,32 +129,28 @@ export class SimulatorOracle implements DBOracle {
* @returns - The index of the commitment. Undefined if it does not exist in the tree.
*/
async getCommitmentIndex(commitment: Fr) {
return await this.stateInfoProvider.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment);
return await this.stateInfoProvider.findLeafIndex('latest', MerkleTreeId.NOTE_HASH_TREE, commitment);
}

async getNullifierIndex(nullifier: Fr) {
return await this.stateInfoProvider.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier);
return await this.stateInfoProvider.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier);
}

public async findLeafIndex(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise<bigint | undefined> {
this.log.warn('Block number ignored in SimulatorOracle.findLeafIndex because archival node is not yet implemented');
return await this.stateInfoProvider.findLeafIndex(treeId, leafValue);
return await this.stateInfoProvider.findLeafIndex(blockNumber, treeId, leafValue);
}

public async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: bigint): Promise<Fr[]> {
this.log.warn(
'Block number ignored in SimulatorOracle.getSiblingPath because archival node is not yet implemented',
);
// @todo Doing a nasty workaround here because of https://github.com/AztecProtocol/aztec-packages/issues/3414
switch (treeId) {
case MerkleTreeId.NULLIFIER_TREE:
return (await this.stateInfoProvider.getNullifierTreeSiblingPath(leafIndex)).toFieldArray();
return (await this.stateInfoProvider.getNullifierTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
case MerkleTreeId.NOTE_HASH_TREE:
return (await this.stateInfoProvider.getNoteHashSiblingPath(leafIndex)).toFieldArray();
return (await this.stateInfoProvider.getNoteHashSiblingPath(blockNumber, leafIndex)).toFieldArray();
case MerkleTreeId.BLOCKS_TREE:
return (await this.stateInfoProvider.getBlocksTreeSiblingPath(leafIndex)).toFieldArray();
return (await this.stateInfoProvider.getBlocksTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
case MerkleTreeId.PUBLIC_DATA_TREE:
return (await this.stateInfoProvider.getPublicDataTreeSiblingPath(leafIndex)).toFieldArray();
return (await this.stateInfoProvider.getPublicDataTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
default:
throw new Error('Not implemented');
}
Expand Down
Loading