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: block header block number oracle #3648

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ export class Oracle {
return blockHeader.toArray().map(toACVMField);
}

// TODO(#3564) - Nuke this oracle and inject the number directly to context
async getNullifierRootBlockNumber([nullifierTreeRoot]: ACVMField[]): Promise<ACVMField> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come NullifierRootblockNumber, would it not be the same for all of them?

const parsedRoot = fromACVMField(nullifierTreeRoot);

const blockNumber = await this.typedOracle.getNullifierRootBlockNumber(parsedRoot);
if (!blockNumber) {
throw new Error(`Block header not found for block ${parsedRoot}.`);
}
return toACVMField(blockNumber);
}

async getAuthWitness([messageHash]: ACVMField[]): Promise<ACVMField[]> {
const messageHashField = fromACVMField(messageHash);
const witness = await this.typedOracle.getAuthWitness(messageHashField);
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

// TODO(#3564) - Nuke this oracle and inject the number directly to context
getNullifierRootBlockNumber(_nullifierTreeRoot: Fr): Promise<number | undefined> {
throw new Error('Not available.');
}

getCompleteAddress(_address: AztecAddress): Promise<CompleteAddress> {
throw new Error('Not available.');
}
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,10 @@ export interface DBOracle extends CommitmentsDB {
* @returns - The block corresponding to the given block number. Undefined if it does not exist.
*/
getBlock(blockNumber: number): Promise<L2Block | undefined>;

/**
* Fetches the current block number.
* @returns The block number.
*/
getBlockNumber(): Promise<number>;
}
33 changes: 32 additions & 1 deletion yarn-project/acir-simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { computeGlobalsHash, siloNullifier } from '@aztec/circuits.js/abis';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { AuthWitness, AztecNode, CompleteAddress, MerkleTreeId, NullifierMembershipWitness } from '@aztec/types';
import {
AuthWitness,
AztecNode,
CompleteAddress,
INITIAL_L2_BLOCK_NUM,
MerkleTreeId,
NullifierMembershipWitness,
} from '@aztec/types';

import { NoteData, TypedOracle } from '../acvm/index.js';
import { DBOracle } from './db_oracle.js';
Expand Down Expand Up @@ -113,6 +120,30 @@ export class ViewDataOracle extends TypedOracle {
);
}

/**
* Gets number of a block in which a given nullifier tree root was included.
* @param nullifierTreeRoot - The nullifier tree root to get the block number for.
* @returns The block number.
*
* TODO(#3564) - Nuke this oracle and inject the number directly to context
*/
public async getNullifierRootBlockNumber(nullifierTreeRoot: Fr): Promise<number | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As before, not clear to me why it should be the nullifier root, but think it was just to have one?

const currentBlockNumber = await this.db.getBlockNumber();
for (let i = currentBlockNumber; i >= INITIAL_L2_BLOCK_NUM; i -= 2) {
const block = await this.db.getBlock(i);
if (!block) {
throw new Error(`Block ${i} not found`);
}
if (block.endNullifierTreeSnapshot.root.equals(nullifierTreeRoot)) {
return i;
}
if (block.startNullifierTreeSnapshot.root.equals(nullifierTreeRoot)) {
return i - 1;
}
}
throw new Error(`Failed to find block containing nullifier tree root ${nullifierTreeRoot}`);
}

/**
* Retrieve the complete address associated to a given address.
* @param address - Address to fetch the complete address for.
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec-nr/aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl PrivateContext {
self.inputs.call_context.function_selector
}

pub fn get_block_header(self, block_number: Field) -> BlockHeader {
pub fn get_block_header(self, block_number: u32) -> BlockHeader {
get_block_header(block_number, self)
}

Expand Down
42 changes: 26 additions & 16 deletions yarn-project/aztec-nr/aztec/src/oracle/get_block_header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,44 @@ use crate::{
},
};

// TODO(#3564) - Nuke this oracle and Inject the number directly to context
#[oracle(getNullifierRootBlockNumber)]
fn get_nullifier_root_block_number_oracle(_nullifier_tree_root: Field) -> Field {}

unconstrained pub fn get_nullifier_root_block_number(nullifier_tree_root: Field) -> u32 {
get_nullifier_root_block_number_oracle(nullifier_tree_root) as u32
}

#[oracle(getBlockHeader)]
fn get_block_header_oracle(_block_number: Field) -> [Field; BLOCK_HEADER_LENGTH] {}
fn get_block_header_oracle(_block_number: u32) -> [Field; BLOCK_HEADER_LENGTH] {}

unconstrained pub fn get_block_header_internal(block_number: Field) -> BlockHeader {
unconstrained pub fn get_block_header_internal(block_number: u32) -> BlockHeader {
let block_header = get_block_header_oracle(block_number);
BlockHeader::deserialize(block_header)
}

pub fn get_block_header(block_number: Field, context: PrivateContext) -> BlockHeader {
// 1) Get block header of a given block from oracle
pub fn get_block_header(block_number: u32, context: PrivateContext) -> BlockHeader {
Copy link
Contributor

@rahul-kothari rahul-kothari Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name reads weirdly given the comment in the next line.
Looking at the fn name, I assume we want to get a block header from the block number. but the next comment does the opposite. While I actually know the nuance, was wondering if there is a better name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how we should do the wording much different. Its ok to me, but I might be stupid and have too much context.

// 1) Get block number corresponding to block header inside context
// Using nullifier tree root to get the block header block number because that changes in every block (every tx emits a nullifier).
let block_header_block_number = get_nullifier_root_block_number(context.block_header.nullifier_tree_root);

// 2) Check that the block header block number is more than or equal to the block number we want to prove against
// We could not perform the proof otherwise because the archive root from the header would not "contain" the block we want to prove against
assert(block_header_block_number >= block_number, "Block header block number is smaller than the block number we want to prove against");

// 3) Get block header of a given block from oracle
let block_header = get_block_header_internal(block_number);

// 2) Compute the block hash from the block header
// 4) Compute the block hash from the block header
let block_hash = block_header.block_hash();

// 3) Get the membership witness of the block in the archive
// 5) Get the membership witness of the block in the archive
let archive_id = 5; // TODO(#3443)

// Using `block_number` here for path is incorrect and it will break if we pass in an incorrect block number on input.
// Instead here should be the block number corresponding to `context.block_header.blocks_tree_root`
// This is not currently available in private context. See issue #3564
let path_block_number = block_number;

let witness: MembershipWitness<ARCHIVE_HEIGHT, ARCHIVE_HEIGHT + 1> = get_membership_witness(path_block_number, archive_id, block_hash);

// 4) Check that the block is in the archive (i.e. the witness is valid)
let witness: MembershipWitness<ARCHIVE_HEIGHT, ARCHIVE_HEIGHT + 1> = get_membership_witness(block_header_block_number, archive_id, block_hash);

// 6) Check that the block is in the archive (i.e. the witness is valid)
assert(context.block_header.archive_root == compute_merkle_root(block_hash, witness.index, witness.path), "Proving membership of a block in archive failed");

// 5) Return the block header
// 7) Return the block header
block_header
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ struct MembershipWitness<N, M> {
}

#[oracle(getMembershipWitness)]
fn get_membership_witness_oracle<M>(_block_number: Field, _tree_id: Field, _leaf_value: Field) -> [Field; M] {}
fn get_membership_witness_oracle<M>(_block_number: u32, _tree_id: Field, _leaf_value: Field) -> [Field; M] {}

unconstrained pub fn get_membership_witness<N, M>(block_number: Field, tree_id: Field, leaf_value: Field) -> MembershipWitness<N, M> {
unconstrained pub fn get_membership_witness<N, M>(block_number: u32, tree_id: Field, leaf_value: Field) -> MembershipWitness<N, M> {
let fields: [Field; M] = get_membership_witness_oracle(block_number, tree_id, leaf_value);
MembershipWitness { index: fields[0], path: arr_copy_slice(fields, [0; N], 1) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ struct NullifierMembershipWitness {
}

#[oracle(getLowNullifierMembershipWitness)]
fn get_low_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}
fn get_low_nullifier_membership_witness_oracle(_block_number: u32, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}

// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower
// nullifier's next_value is bigger than the nullifier)
unconstrained pub fn get_low_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness {
unconstrained pub fn get_low_nullifier_membership_witness(block_number: u32, nullifier: Field) -> NullifierMembershipWitness {
let fields = get_low_nullifier_membership_witness_oracle(block_number, nullifier);
NullifierMembershipWitness {
index: fields[0],
Expand All @@ -46,11 +46,11 @@ unconstrained pub fn get_low_nullifier_membership_witness(block_number: Field, n
}

#[oracle(getNullifierMembershipWitness)]
fn get_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}
fn get_nullifier_membership_witness_oracle(_block_number: u32, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}

// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower
// nullifier's next_value is bigger than the nullifier)
unconstrained pub fn get_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness {
unconstrained pub fn get_nullifier_membership_witness(block_number: u32, nullifier: Field) -> NullifierMembershipWitness {
let fields = get_nullifier_membership_witness_oracle(block_number, nullifier);
NullifierMembershipWitness {
index: fields[0],
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use dep::protocol_types::constants::NOTE_HASH_TREE_HEIGHT;
use crate::utils::arr_copy_slice;

#[oracle(getSiblingPath)]
fn get_sibling_path_oracle<N>(_block_number: Field, _tree_id: Field, _leaf_index: Field) -> [Field; N] {}
fn get_sibling_path_oracle<N>(_block_number: u32, _tree_id: Field, _leaf_index: Field) -> [Field; N] {}

unconstrained pub fn get_sibling_path<N>(block_number: Field, tree_id: Field, leaf_index: Field) -> [Field; N] {
unconstrained pub fn get_sibling_path<N>(block_number: u32, tree_id: Field, leaf_index: Field) -> [Field; N] {
let value: [Field; N] = get_sibling_path_oracle(block_number, tree_id, leaf_index);
value
}
35 changes: 26 additions & 9 deletions yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,31 @@ describe('e2e_inclusion_proofs_contract', () => {
let accounts: CompleteAddress[];

let contract: InclusionProofsContract;
let deploymentBlockNumber: number;
const publicValue = 236n;

beforeAll(async () => {
({ pxe, teardown, wallets, accounts } = await setup(1));

contract = await InclusionProofsContract.deploy(wallets[0], publicValue).send().deployed();
const receipt = await InclusionProofsContract.deploy(wallets[0], publicValue).send().wait();
contract = receipt.contract;
deploymentBlockNumber = receipt.blockNumber!;
}, 100_000);

afterAll(() => teardown());

it('proves note existence and its nullifier non-existence and nullifier non-existence failure case', async () => {
// Owner of a note
const owner = accounts[0].address;
let noteCreationBlockNumber: number;
{
// Create a note
const value = 100n;
const receipt = await contract.methods.create_note(owner, value).send().wait({ debug: true });

noteCreationBlockNumber = receipt.blockNumber!;
const { newCommitments, visibleNotes } = receipt.debugInfo!;

expect(newCommitments.length).toBe(1);
expect(visibleNotes.length).toBe(1);
const [receivedValue, receivedOwner, _randomness] = visibleNotes[0].note.items;
Expand All @@ -46,17 +53,14 @@ describe('e2e_inclusion_proofs_contract', () => {

{
// Prove note inclusion in a given block.
// TODO: Use here note block number from the creation note tx to test archival node. This is currently not
// possible because of issue #3564
const blockNumber = await pxe.getBlockNumber();
const ignoredCommitment = 0; // Not ignored only when the note doesn't exist
await contract.methods.proveNoteInclusion(owner, blockNumber, ignoredCommitment).send().wait();
await contract.methods.proveNoteInclusion(owner, noteCreationBlockNumber, ignoredCommitment).send().wait();
}

{
// Prove that the note has not been nullified
// TODO: Use here note block number from the creation note tx to test archival node. This is currently not
// possible because of issue #3564
// TODO(#3535): Prove the nullifier non-inclusion at older block to test archival node. This is currently not
// possible because of issue https://github.com/AztecProtocol/aztec-packages/issues/3535
const blockNumber = await pxe.getBlockNumber();
const ignoredNullifier = 0; // Not ignored only when the note doesn't exist
await contract.methods.proveNullifierNonInclusion(owner, blockNumber, ignoredNullifier).send().wait();
Expand Down Expand Up @@ -93,19 +97,25 @@ describe('e2e_inclusion_proofs_contract', () => {
});

it('proves an existence of a public value in private context', async () => {
const blockNumber = await pxe.getBlockNumber();
// Chose random block number between deployment and current block number to test archival node
const blockNumber = await getRandomBlockNumberSinceDeployment();

await contract.methods.provePublicValueInclusion(publicValue, blockNumber).send().wait();
});

it('public value existence failure case', async () => {
const blockNumber = await pxe.getBlockNumber();
// Chose random block number between deployment and current block number to test archival node
const blockNumber = await getRandomBlockNumberSinceDeployment();

const randomPublicValue = Fr.random();
await expect(
contract.methods.provePublicValueInclusion(randomPublicValue, blockNumber).send().wait(),
).rejects.toThrow(/Proving public value inclusion failed/);
});

it('proves existence of a nullifier in private context', async () => {
// TODO(#3535): Test this at "random" block to test archival node. This is currently not possible because of
// issue https://github.com/AztecProtocol/aztec-packages/issues/3535
const blockNumber = await pxe.getBlockNumber();
const block = await pxe.getBlock(blockNumber);
const nullifier = block?.newNullifiers[0];
Expand All @@ -114,11 +124,18 @@ describe('e2e_inclusion_proofs_contract', () => {
});

it('nullifier existence failure case', async () => {
// TODO(#3535): Test this at "random" block to test archival node. This is currently not possible because of
// issue https://github.com/AztecProtocol/aztec-packages/issues/3535
const blockNumber = await pxe.getBlockNumber();
const randomNullifier = Fr.random();

await expect(contract.methods.proveNullifierInclusion(randomNullifier, blockNumber).send().wait()).rejects.toThrow(
/Low nullifier witness not found for nullifier 0x[0-9a-fA-F]+ at block/,
);
});

const getRandomBlockNumberSinceDeployment = async () => {
const currentBlockNumber = await pxe.getBlockNumber();
return deploymentBlockNumber + Math.floor(Math.random() * (currentBlockNumber - deploymentBlockNumber));
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,9 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNoteInclusion(
owner: AztecAddress,
block_number: Field, // The block at which we'll prove that the note exists
block_number: u32, // The block at which we'll prove that the note exists
spare_commitment: Field, // This is only used when the note is not found --> used to test the failure case
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down Expand Up @@ -142,14 +137,9 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNullifierNonInclusion(
owner: AztecAddress,
block_number: Field, // The block at which we'll prove that the nullifier does not exists
block_number: u32, // The block at which we'll prove that the nullifier does not exists
spare_nullifier: Field, // This is only used when the note is not found --> used to test the failure case
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down Expand Up @@ -215,13 +205,8 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNullifierInclusion(
nullifier: Field,
block_number: Field, // The block at which we'll prove that the nullifier not exists in the tree
block_number: u32, // The block at which we'll prove that the nullifier not exists in the tree
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand All @@ -248,13 +233,8 @@ contract InclusionProofs {
#[aztec(private)]
fn provePublicValueInclusion(
public_value: Field,
block_number: Field, // The block at which we'll prove that the public value exists
block_number: u32, // The block at which we'll prove that the public value exists
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,12 @@ export class SimulatorOracle implements DBOracle {
getBlockHeader(): Promise<BlockHeader> {
return Promise.resolve(this.db.getBlockHeader());
}

/**
* Fetches the current block number.
* @returns The block number.
*/
public async getBlockNumber(): Promise<number> {
return await this.stateInfoProvider.getBlockNumber();
}
}
6 changes: 0 additions & 6 deletions yarn-project/types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ export interface AztecNode extends StateInfoProvider {
*/
getBlocks(from: number, limit: number): Promise<L2Block[]>;

/**
* Fetches the current block number.
* @returns The block number.
*/
getBlockNumber(): Promise<number>;

/**
* Method to fetch the version of the rollup the node is connected to.
* @returns The rollup version.
Expand Down
Loading