Skip to content
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 docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ tags: [migration, updating, sandbox]

Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them.

## TBD

## [Aztec.nr] Modified `get_log_by_tag` function

`get_log_by_tag` function has been renamed to `get_public_log_by_tag` and now it accepts contract address along with a tag as input and it only returns public logs:

```diff
- let maybe_log = get_log_by_tag(pending_partial_note.note_completion_log_tag);
+ let maybe_log = get_public_log_by_tag(pending_partial_note.note_completion_log_tag, contract_address);
```

## 0.87.0

## [Aztec.js/TS libraries]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
discovery::{ComputeNoteHashAndNullifier, nonce_discovery::attempt_note_nonce_discovery},
encoding::MAX_MESSAGE_CONTENT_LEN,
},
oracle::message_discovery::{deliver_note, get_log_by_tag},
oracle::message_discovery::{deliver_note, get_public_log_by_tag},
utils::array,
};

Expand Down Expand Up @@ -93,7 +93,10 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
);

pending_partial_notes.for_each(|i, pending_partial_note: DeliveredPendingPartialNote| {
let maybe_log = get_log_by_tag(pending_partial_note.note_completion_log_tag);
let maybe_log = get_public_log_by_tag(
pending_partial_note.note_completion_log_tag,
contract_address,
);
if maybe_log.is_none() {
debug_log_format(
"Found no completion logs for partial note with tag {}",
Expand Down
24 changes: 13 additions & 11 deletions noir-projects/aztec-nr/aztec/src/oracle/message_discovery.nr
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ pub unconstrained fn deliver_note(

/// The contents of a public log, plus contextual information about the transaction in which the log was emitted. This
/// is the data required in order to discover notes that are being delivered in a log.
// TODO(#11639): this could also be used to fetch private logs, but the `BoundedVec` maximum length is that of a public
// log.
pub struct LogWithTxData {
pub struct PublicLogWithTxData {
// The log fields length is PUBLIC_LOG_SIZE_IN_FIELDS. + 1 because the contract address is prepended to the content.
pub log_content: BoundedVec<Field, PUBLIC_LOG_SIZE_IN_FIELDS + 1>,
pub tx_hash: Field,
Expand All @@ -62,14 +60,15 @@ pub struct LogWithTxData {
pub first_nullifier_in_tx: Field,
}

/// Fetches a log from the node that has the corresponding `tag`. The log can be either a public or a private log, and
/// the tag is the first field in the log's content. Returns `Option::none` if no such log exists. Throws if more than
/// one log with that tag exists.
/// Public logs have an extra field included at the beginning with the address of the contract that emitted them.
/// Fetches a public log emitted by `contract_address` that has the corresponding `tag`. The tag is the first field in the log's content.
/// Returns `Option::none` if no such log exists. Throws if more than one log with that tag exists.
// TODO(#11627): handle multiple logs with the same tag.
// TODO(#10273): improve contract siloing of logs, don't introduce an extra field.
pub unconstrained fn get_log_by_tag(tag: Field) -> Option<LogWithTxData> {
get_log_by_tag_oracle(tag)
pub unconstrained fn get_public_log_by_tag(
tag: Field,
contract_address: AztecAddress,
) -> Option<PublicLogWithTxData> {
get_public_log_by_tag_oracle(tag, contract_address)
}

#[oracle(deliverNote)]
Expand All @@ -84,5 +83,8 @@ unconstrained fn deliver_note_oracle(
recipient: AztecAddress,
) -> bool {}

#[oracle(getLogByTag)]
unconstrained fn get_log_by_tag_oracle(tag: Field) -> Option<LogWithTxData> {}
#[oracle(getPublicLogByTag)]
unconstrained fn get_public_log_by_tag_oracle(
tag: Field,
contract_address: AztecAddress,
) -> Option<PublicLogWithTxData> {}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { PRIVATE_LOG_SIZE_IN_FIELDS, PUBLIC_LOG_SIZE_IN_FIELDS } from '@aztec/constants';
import { PUBLIC_LOG_SIZE_IN_FIELDS } from '@aztec/constants';
import { padArrayEnd, timesParallel } from '@aztec/foundation/collection';
import { randomInt } from '@aztec/foundation/crypto';
import { Fq, Fr } from '@aztec/foundation/fields';
import type { Tuple } from '@aztec/foundation/serialize';
import { KeyStore } from '@aztec/key-store';
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
Expand Down Expand Up @@ -147,7 +146,7 @@ describe('PXEOracleInterface', () => {
}
// Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2

// Set up the getTaggedLogs mock
// Set up the getPrivateLogsByTags mock
aztecNode.getLogsByTags.mockImplementation(tags => {
return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? []));
});
Expand Down Expand Up @@ -434,28 +433,6 @@ describe('PXEOracleInterface', () => {
await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1);
});

it('should not sync public tagged logs', async () => {
const logs: { [k: string]: TxScopedL2Log[] } = {};
const tag = await computeSiloedTagForIndex(senders[0], recipient.address, contractAddress, 0);

// Create a public log with the correct tag
const logContent = [Fr.ONE, tag, ...Array(PUBLIC_LOG_SIZE_IN_FIELDS - 2).fill(Fr.random())] as Tuple<
Fr,
typeof PUBLIC_LOG_SIZE_IN_FIELDS
>;
const log = new PublicLog(await AztecAddress.random(), logContent, PUBLIC_LOG_SIZE_IN_FIELDS);
const scopedLog = new TxScopedL2Log(TxHash.random(), 1, 0, 0, log);

logs[tag.toString()] = [scopedLog];
aztecNode.getLogsByTags.mockImplementation(tags => {
return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? []));
});
await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT);

// We expect the above log to be discarded, and so none to be synced
await expectPendingTaggedLogArrayLengthToBe(contractAddress, 0);
});

const expectPendingTaggedLogArrayLengthToBe = async (contractAddress: AztecAddress, expectedLength: number) => {
// Capsule array length is stored in the array base slot.
const capsule = await capsuleDataProvider.loadCapsule(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT);
Expand Down Expand Up @@ -635,7 +612,7 @@ describe('PXEOracleInterface', () => {
});
});

describe('getLogByTag', () => {
describe('getPublicLogByTag', () => {
const tag = Fr.random();

beforeEach(() => {
Expand All @@ -646,31 +623,23 @@ describe('PXEOracleInterface', () => {
it('returns null if no logs found for tag', async () => {
aztecNode.getLogsByTags.mockResolvedValue([[]]);

const result = await pxeOracleInterface.getLogByTag(tag);
const result = await pxeOracleInterface.getPublicLogByTag(tag, contractAddress);
expect(result).toBeNull();
});

it.each<[string, boolean]>([
['public log', true],
['private log', false],
])('returns log data for %s when single log found', async (_, isFromPublic) => {
const scopedLog = await TxScopedL2Log.random(isFromPublic);
it('returns log data when single log found', async () => {
const scopedLog = await TxScopedL2Log.random(true);
const logContractAddress = (scopedLog.log as PublicLog).contractAddress;

aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]);
const indexedTxEffect = await randomIndexedTxEffect();
aztecNode.getTxEffect.mockImplementation((txHash: TxHash) =>
txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined),
);

const result = (await pxeOracleInterface.getLogByTag(tag))!;
const result = (await pxeOracleInterface.getPublicLogByTag(tag, logContractAddress))!;

if (isFromPublic) {
expect(result.logContent).toEqual(
[(scopedLog.log as PublicLog).contractAddress.toField()].concat(scopedLog.log.getEmittedFields()),
);
} else {
expect(result.logContent).toEqual(scopedLog.log.getEmittedFields());
}
expect(result.logContent).toEqual([logContractAddress.toField()].concat(scopedLog.log.getEmittedFields()));
expect(result.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes);
expect(result.txHash).toEqual(scopedLog.txHash);
expect(result.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]);
Expand All @@ -680,24 +649,33 @@ describe('PXEOracleInterface', () => {
});

it('throws if multiple logs found for tag', async () => {
const scopedLog = await TxScopedL2Log.random();
const scopedLog = await TxScopedL2Log.random(true);
aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]);
const logContractAddress = (scopedLog.log as PublicLog).contractAddress;

await expect(pxeOracleInterface.getLogByTag(tag)).rejects.toThrow(/Got 2 logs for tag/);
await expect(pxeOracleInterface.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow(/Got 2 logs for tag/);
});

it('throws if tx effect not found', async () => {
const scopedLog = await TxScopedL2Log.random();
const scopedLog = await TxScopedL2Log.random(true);
aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]);
aztecNode.getTxEffect.mockResolvedValue(undefined);
const logContractAddress = (scopedLog.log as PublicLog).contractAddress;

await expect(pxeOracleInterface.getLogByTag(tag)).rejects.toThrow(/failed to retrieve tx effects/);
await expect(pxeOracleInterface.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow(
/failed to retrieve tx effects/,
);
});

it('returns log fields that are actually emitted', async () => {
const logContent = [Fr.random(), Fr.random()];
const logContractAddress = await AztecAddress.random();

const log = new PrivateLog(padArrayEnd(logContent, Fr.ZERO, PRIVATE_LOG_SIZE_IN_FIELDS), logContent.length);
const log = PublicLog.from({
contractAddress: logContractAddress,
fields: padArrayEnd(logContent, Fr.ZERO, PUBLIC_LOG_SIZE_IN_FIELDS),
emittedLength: logContent.length,
});
const scopedLogWithPadding = new TxScopedL2Log(
TxHash.random(),
randomInt(100),
Expand All @@ -709,9 +687,9 @@ describe('PXEOracleInterface', () => {
aztecNode.getLogsByTags.mockResolvedValue([[scopedLogWithPadding]]);
aztecNode.getTxEffect.mockResolvedValue(await randomIndexedTxEffect());

const result = await pxeOracleInterface.getLogByTag(tag);
const result = await pxeOracleInterface.getPublicLogByTag(tag, logContractAddress);

expect(result?.logContent).toEqual(logContent);
expect(result?.logContent).toEqual([log.contractAddress.toField(), ...logContent]);
});
});

Expand Down
47 changes: 29 additions & 18 deletions yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import type { KeyValidationRequest } from '@aztec/stdlib/kernel';
import { computeAddressSecret, computeAppTaggingSecret } from '@aztec/stdlib/keys';
import {
IndexedTaggingSecret,
LogWithTxData,
PendingTaggedLog,
PublicLog,
PublicLogWithTxData,
TxScopedL2Log,
deriveEcdhSharedSecret,
} from '@aztec/stdlib/logs';
Expand Down Expand Up @@ -392,7 +392,7 @@ export class PXEOracleInterface implements ExecutionDataProvider {
});

// We fetch the logs for the tags
const possibleLogs = await this.aztecNode.getLogsByTags(currentTags);
const possibleLogs = await this.#getPrivateLogsByTags(currentTags);

// We find the index of the last log in the window that is not empty
const indexOfLastLog = possibleLogs.findLastIndex(possibleLog => possibleLog.length !== 0);
Expand Down Expand Up @@ -429,8 +429,8 @@ export class PXEOracleInterface implements ExecutionDataProvider {
}

/**
* Synchronizes the logs tagged with scoped addresses and all the senders in the address book. Stores the found logs
* in CapsuleArray ready for a later retrieval in Aztec.nr.
* Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found
* logs in CapsuleArray ready for a later retrieval in Aztec.nr.
* @param contractAddress - The address of the contract that the logs are tagged for.
* @param pendingTaggedLogArrayBaseSlot - The base slot of the pending tagged logs capsule array in which
* found logs will be stored.
Expand Down Expand Up @@ -490,20 +490,14 @@ export class PXEOracleInterface implements ExecutionDataProvider {
// a new set of secrets and windows to fetch logs for.
const newLargestIndexMapForIteration: { [k: string]: number } = {};

// Fetch the logs for the tags and iterate over them
const logsByTags = await this.aztecNode.getLogsByTags(tagsForTheWholeWindow);
// Fetch the private logs for the tags and iterate over them
const logsByTags = await this.#getPrivateLogsByTags(tagsForTheWholeWindow);

for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) {
const logsByTag = logsByTags[logIndex];
if (logsByTag.length > 0) {
// Discard public logs
const filteredLogsByTag = logsByTag.filter(l => !l.isFromPublic);
if (filteredLogsByTag.length < logsByTag.length) {
this.log.warn(`Discarded ${logsByTag.filter(l => l.isFromPublic).length} public logs with matching tags`);
}

// We filter out the logs that are newer than the historical block number of the tx currently being constructed
const filteredLogsByBlockNumber = filteredLogsByTag.filter(l => l.blockNumber <= maxBlockNumber);
const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber);

// We store the logs in capsules (to later be obtained in Noir)
await this.#storePendingTaggedLogs(
Expand All @@ -518,7 +512,7 @@ export class PXEOracleInterface implements ExecutionDataProvider {
const secretCorrespondingToLog = secretsForTheWholeWindow[logIndex];
const initialIndex = initialIndexesMap[secretCorrespondingToLog.appTaggingSecret.toString()];

this.log.debug(`Found ${filteredLogsByTag.length} logs as recipient ${recipient}`, {
this.log.debug(`Found ${logsByTags.length} logs as recipient ${recipient}`, {
recipient,
secret: secretCorrespondingToLog.appTaggingSecret,
contractName,
Expand Down Expand Up @@ -700,8 +694,8 @@ export class PXEOracleInterface implements ExecutionDataProvider {
}
}

public async getLogByTag(tag: Fr): Promise<LogWithTxData | null> {
const logs = await this.aztecNode.getLogsByTags([tag]);
public async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise<PublicLogWithTxData | null> {
const logs = await this.#getPublicLogsByTagsFromContract([tag], contractAddress);
const logsForTag = logs[0];

this.log.debug(`Got ${logsForTag.length} logs for tag ${tag}`);
Expand All @@ -711,7 +705,7 @@ export class PXEOracleInterface implements ExecutionDataProvider {
} else if (logsForTag.length > 1) {
// TODO(#11627): handle this case
throw new Error(
`Got ${logsForTag.length} logs for tag ${tag}. getLogByTag currently only supports a single log per tag`,
`Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`,
);
}

Expand All @@ -729,7 +723,7 @@ export class PXEOracleInterface implements ExecutionDataProvider {
scopedLog.log.getEmittedFields(),
);

return new LogWithTxData(logContent, scopedLog.txHash, txEffect.data.noteHashes, txEffect.data.nullifiers[0]);
return new PublicLogWithTxData(logContent, scopedLog.txHash, txEffect.data.noteHashes, txEffect.data.nullifiers[0]);
}

public async removeNullifiedNotes(contractAddress: AztecAddress) {
Expand Down Expand Up @@ -832,4 +826,21 @@ export class PXEOracleInterface implements ExecutionDataProvider {
blockNumber,
);
}

// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This
// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change.
async #getPrivateLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
const allLogs = await this.aztecNode.getLogsByTags(tags);
return allLogs.map(logs => logs.filter(log => !log.isFromPublic));
}

// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This
// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change.
async #getPublicLogsByTagsFromContract(tags: Fr[], contractAddress: AztecAddress): Promise<TxScopedL2Log[][]> {
const allLogs = await this.aztecNode.getLogsByTags(tags);
const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic));
return allPublicLogs.map(logs =>
logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)),
);
}
}
8 changes: 4 additions & 4 deletions yarn-project/simulator/src/private/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fr, Point } from '@aztec/foundation/fields';
import { EventSelector, FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { ContractClassLog, ContractClassLogFields, LogWithTxData } from '@aztec/stdlib/logs';
import { ContractClassLog, ContractClassLogFields, PublicLogWithTxData } from '@aztec/stdlib/logs';
import { MerkleTreeId } from '@aztec/stdlib/trees';
import { TxHash } from '@aztec/stdlib/tx';

Expand Down Expand Up @@ -412,11 +412,11 @@ export class Oracle {
return [toACVMField(true)];
}

async getLogByTag([tag]: ACVMField[]): Promise<(ACVMField | ACVMField[])[]> {
const log = await this.typedOracle.getLogByTag(Fr.fromString(tag));
async getPublicLogByTag([tag]: ACVMField[], [contractAddress]: ACVMField[]): Promise<(ACVMField | ACVMField[])[]> {
const log = await this.typedOracle.getPublicLogByTag(Fr.fromString(tag), AztecAddress.fromString(contractAddress));

if (log == null) {
return [toACVMField(0), ...LogWithTxData.noirSerializationOfEmpty().map(toACVMFieldSingleOrArray)];
return [toACVMField(0), ...PublicLogWithTxData.noirSerializationOfEmpty().map(toACVMFieldSingleOrArray)];
} else {
return [toACVMField(1), ...log.toNoirSerialization().map(toACVMFieldSingleOrArray)];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { EventSelector, FunctionSelector, NoteSelector } from '@aztec/stdli
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract';
import type { KeyValidationRequest } from '@aztec/stdlib/kernel';
import type { ContractClassLog, IndexedTaggingSecret, LogWithTxData } from '@aztec/stdlib/logs';
import type { ContractClassLog, IndexedTaggingSecret, PublicLogWithTxData } from '@aztec/stdlib/logs';
import type { Note, NoteStatus } from '@aztec/stdlib/note';
import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
import type { BlockHeader, TxHash } from '@aztec/stdlib/tx';
Expand Down Expand Up @@ -231,8 +231,8 @@ export abstract class TypedOracle {
return Promise.reject(new OracleMethodNotAvailableError('deliverNote'));
}

getLogByTag(_tag: Fr): Promise<LogWithTxData | null> {
throw new OracleMethodNotAvailableError('getLogByTag');
getPublicLogByTag(_tag: Fr, _contractAddress: AztecAddress): Promise<PublicLogWithTxData | null> {
throw new OracleMethodNotAvailableError('getPublicLogByTag');
}

storeCapsule(_contractAddress: AztecAddress, _key: Fr, _capsule: Fr[]): Promise<void> {
Expand Down
Loading
Loading