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

chore(benchmark): Measure time to decrypt notes in pxe #2714

Merged
merged 1 commit into from
Oct 6, 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
15 changes: 15 additions & 0 deletions scripts/ci/aggregate_e2e_benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const {
CIRCUIT_OUTPUT_SIZE,
CIRCUIT_INPUT_SIZE,
CIRCUIT_SIMULATED,
NOTE_SUCCESSFUL_DECRYPTING_TIME,
NOTE_TRIAL_DECRYPTING_TIME,
NOTE_PROCESSOR_CAUGHT_UP,
ROLLUP_SIZES,
BENCHMARK_FILE_JSON,
} = require("./benchmark_shared.js");
Expand Down Expand Up @@ -75,6 +78,16 @@ function processCircuitSimulation(entry, results) {
append(results, CIRCUIT_OUTPUT_SIZE, bucket, entry.outputSize);
}

// Processes an entry with event name 'note-processor-caught-up' and updates results
// Buckets are rollup sizes
function processNoteProcessorCaughtUp(entry, results) {
const { seen, decrypted } = entry;
if (ROLLUP_SIZES.includes(decrypted))
append(results, NOTE_SUCCESSFUL_DECRYPTING_TIME, decrypted, entry.duration);
if (ROLLUP_SIZES.includes(seen) && decrypted === 0)
append(results, NOTE_TRIAL_DECRYPTING_TIME, seen, entry.duration);
}

// Processes a parsed entry from a logfile and updates results
function processEntry(entry, results) {
switch (entry.eventName) {
Expand All @@ -84,6 +97,8 @@ function processEntry(entry, results) {
return processRollupBlockSynced(entry, results);
case CIRCUIT_SIMULATED:
return processCircuitSimulation(entry, results);
case NOTE_PROCESSOR_CAUGHT_UP:
return processNoteProcessorCaughtUp(entry, results);
default:
return;
}
Expand Down
3 changes: 3 additions & 0 deletions scripts/ci/benchmark_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ module.exports = {
CIRCUIT_SIMULATION_TIME: "circuit_simulation_time_in_ms",
CIRCUIT_INPUT_SIZE: "circuit_input_size_in_bytes",
CIRCUIT_OUTPUT_SIZE: "circuit_output_size_in_bytes",
NOTE_SUCCESSFUL_DECRYPTING_TIME: "note_successful_decrypting_time",
NOTE_TRIAL_DECRYPTING_TIME: "note_unsuccessful_decrypting_time",
// Events to track
L2_BLOCK_PUBLISHED_TO_L1: "rollup-published-to-l1",
L2_BLOCK_SYNCED: "l2-block-handled",
CIRCUIT_SIMULATED: "circuit-simulation",
NOTE_PROCESSOR_CAUGHT_UP: "note-processor-caught-up",
// Other
ROLLUP_SIZES,
BENCHMARK_FILE_JSON,
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/aztec.js/src/account/manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { PublicKey, getContractDeploymentInfo } from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';
import { CompleteAddress, GrumpkinPrivateKey, PXE } from '@aztec/types';

import { AccountWallet, ContractDeployer, DeployMethod, WaitOpts, generatePublicKey } from '../../index.js';
import {
AccountWalletWithPrivateKey,
ContractDeployer,
DeployMethod,
WaitOpts,
generatePublicKey,
} from '../../index.js';
import { AccountContract, Salt } from '../index.js';
import { AccountInterface } from '../interface.js';
import { DeployAccountSentTx } from './deploy_account_sent_tx.js';
Expand Down Expand Up @@ -73,9 +79,9 @@ export class AccountManager {
* instances to be interacted with from this account.
* @returns A Wallet instance.
*/
public async getWallet(): Promise<AccountWallet> {
public async getWallet(): Promise<AccountWalletWithPrivateKey> {
const entrypoint = await this.getAccount();
return new AccountWallet(this.pxe, entrypoint);
return new AccountWalletWithPrivateKey(this.pxe, entrypoint, this.encryptionPrivateKey);
}

/**
Expand All @@ -84,7 +90,7 @@ export class AccountManager {
* Use the returned wallet to create Contract instances to be interacted with from this account.
* @returns A Wallet instance.
*/
public async register(): Promise<AccountWallet> {
public async register(): Promise<AccountWalletWithPrivateKey> {
const completeAddress = await this.getCompleteAddress();
await this.pxe.registerAccount(this.encryptionPrivateKey, completeAddress.partialAddress);
return this.getWallet();
Expand Down Expand Up @@ -132,7 +138,7 @@ export class AccountManager {
* @param opts - Options to wait for the tx to be mined.
* @returns A Wallet instance.
*/
public async waitDeploy(opts: WaitOpts = {}): Promise<AccountWallet> {
public async waitDeploy(opts: WaitOpts = {}): Promise<AccountWalletWithPrivateKey> {
await this.deploy().then(tx => tx.wait(opts));
return this.getWallet();
}
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/aztec.js/src/account/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { CompleteAddress, GrumpkinScalar } from '@aztec/circuits.js';
import { PXE } from '@aztec/types';

import { getSchnorrAccount } from '../index.js';
import { AccountWallet } from '../wallet/account_wallet.js';
import { AccountWalletWithPrivateKey } from '../wallet/account_wallet.js';

/**
* Deploys and registers a new account using random private keys and returns the associated Schnorr account wallet. Useful for testing.
* @param pxe - PXE.
* @returns - A wallet for a fresh account.
*/
export function createAccount(pxe: PXE): Promise<AccountWallet> {
export function createAccount(pxe: PXE): Promise<AccountWalletWithPrivateKey> {
return getSchnorrAccount(pxe, GrumpkinScalar.random(), GrumpkinScalar.random()).waitDeploy();
}

Expand All @@ -30,7 +30,7 @@ export async function createRecipient(pxe: PXE): Promise<CompleteAddress> {
* @param numberOfAccounts - How many accounts to create.
* @returns The created account wallets.
*/
export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise<AccountWallet[]> {
export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise<AccountWalletWithPrivateKey[]> {
const accounts = [];

// Prepare deployments
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { sleep } from '@aztec/foundation/sleep';
import zip from 'lodash.zip';

import SchnorrAccountContractAbi from '../abis/schnorr_account_contract.json' assert { type: 'json' };
import { AccountWallet, PXE, createPXEClient, getSchnorrAccount } from '../index.js';
import { AccountWalletWithPrivateKey, PXE, createPXEClient, getSchnorrAccount } from '../index.js';

export const INITIAL_SANDBOX_ENCRYPTION_KEYS = [
GrumpkinScalar.fromString('2153536ff6628eee01cf4024889ff977a18d9fa61d0e414422f7681cf085c281'),
Expand All @@ -25,7 +25,7 @@ export const { PXE_URL = 'http://localhost:8080' } = process.env;
* @param pxe - PXE instance.
* @returns A set of AccountWallet implementations for each of the initial accounts.
*/
export function getSandboxAccountsWallets(pxe: PXE): Promise<AccountWallet[]> {
export function getSandboxAccountsWallets(pxe: PXE): Promise<AccountWalletWithPrivateKey[]> {
return Promise.all(
zip(INITIAL_SANDBOX_ENCRYPTION_KEYS, INITIAL_SANDBOX_SIGNING_KEYS, INITIAL_SANDBOX_SALTS).map(
([encryptionKey, signingKey, salt]) => getSchnorrAccount(pxe, encryptionKey!, signingKey!, salt).getWallet(),
Expand Down
18 changes: 17 additions & 1 deletion yarn-project/aztec.js/src/wallet/account_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fr } from '@aztec/circuits.js';
import { Fr, GrumpkinPrivateKey } from '@aztec/circuits.js';
import { ABIParameterVisibility, FunctionAbiHeader, FunctionType } from '@aztec/foundation/abi';
import { AuthWitness, FunctionCall, PXE, TxExecutionRequest } from '@aztec/types';

Expand Down Expand Up @@ -68,3 +68,19 @@ export class AccountWallet extends BaseWallet {
};
}
}

/**
* Extends {@link AccountWallet} with the encryption private key. Not required for
* implementing the wallet interface but useful for testing purposes or exporting
* an account to another pxe.
*/
export class AccountWalletWithPrivateKey extends AccountWallet {
constructor(pxe: PXE, account: AccountInterface, private encryptionPrivateKey: GrumpkinPrivateKey) {
super(pxe, account);
}

/** Returns the encryption private key associated with this account. */
public getEncryptionPrivateKey() {
return this.encryptionPrivateKey;
}
}
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export abstract class BaseWallet implements Wallet {

abstract createAuthWitness(message: Fr): Promise<AuthWitness>;

registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise<void> {
registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise<CompleteAddress> {
return this.pxe.registerAccount(privKey, partialAddress);
}
registerRecipient(account: CompleteAddress): Promise<void> {
Expand Down
49 changes: 39 additions & 10 deletions yarn-project/end-to-end/src/benchmarks/bench_publish_rollup.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-disable camelcase */
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecAddress, BatchCall } from '@aztec/aztec.js';
import { EthAddress, Fr, GrumpkinScalar } from '@aztec/circuits.js';
import { retryUntil } from '@aztec/foundation/retry';
import { sleep } from '@aztec/foundation/sleep';
import { BenchmarkingContract } from '@aztec/noir-contracts/types';
import { createPXEService } from '@aztec/pxe';
import { SequencerClient } from '@aztec/sequencer-client';

import times from 'lodash.times';
Expand All @@ -21,44 +24,70 @@ describe('benchmarks/publish_rollup', () => {
context = await setup(2, { maxTxsPerBlock: 1024 });
[owner] = context.accounts.map(a => a.address);
contract = await BenchmarkingContract.deploy(context.wallet).send().deployed();
context.logger(`Deployed benchmarking contract at ${contract.address}`);
sequencer = (context.aztecNode as AztecNodeService).getSequencer()!;
await sequencer.stop();
}, 60_000);

// Each tx has a private execution (account entrypoint), a nested private call (create_note),
// a public call (increment_balance), and a nested public call (broadcast). These include
// emitting one private note and one unencrypted log, two storage reads and one write.
const makeBatchCall = (i: number) =>
new BatchCall(context.wallet, [
contract.methods.create_note(owner, i).request(),
contract.methods.increment_balance(owner, i).request(),
contract.methods.create_note(owner, i + 1).request(),
contract.methods.increment_balance(owner, i + 1).request(),
]);

it.each(ROLLUP_SIZES)(
`publishes a rollup with %d txs`,
async (txCount: number) => {
context.logger(`Assembling rollup with ${txCount} txs`);
// Simulate and simultaneously send %d txs. These should not yet be processed since sequencer is stopped.
// Each tx has a private execution (account entrypoint), a nested private call (create_note),
// a public call (increment_balance), and a nested public call (broadcast). These include
// emitting one private note and one unencrypted log, two storage reads and one write.
// Simulate and simultaneously send ROLLUP_SIZE txs. These should not yet be processed since sequencer is stopped.
const calls = times(txCount, makeBatchCall);
calls.forEach(call => call.simulate({ skipPublicSimulation: true }));
const sentTxs = calls.map(call => call.send());

// Awaiting txHash waits until the aztec node has received the tx into its p2p pool
await Promise.all(sentTxs.map(tx => tx.getTxHash()));
// And then wait a bit more just in case
await sleep(100);

// Restart sequencer to process all txs together
sequencer.restart();
// Wait for the last tx to be processed and finish the current node
await sentTxs[sentTxs.length - 1].wait({ timeout: 600_00 });

// Wait for the last tx to be processed and stop the current node
const { blockNumber } = await sentTxs[sentTxs.length - 1].wait({ timeout: 5 * 60_000 });
await context.teardown();

// Create a new aztec node to measure sync time of the block
// and call getTreeRoots to force a sync with world state to ensure the node has caught up
context.logger(`Starting new aztec node`);
const node = await AztecNodeService.createAndSync({ ...context.config, disableSequencer: true });
// Force a sync with world state to ensure new node has caught up before killing it
await node.getTreeRoots();

// Spin up a new pxe and sync it, we'll use it to test sync times of new accounts for the last block
context.logger(`Starting new pxe`);
const pxe = await createPXEService(node, { l2BlockPollingIntervalMS: 100, l2StartingBlock: blockNumber! - 1 });
await pxe.addContracts([{ ...contract, portalContract: EthAddress.ZERO }]);
await retryUntil(() => pxe.isGlobalStateSynchronized(), 'pxe-global-sync');
const { publicKey, partialAddress } = context.wallet.getCompleteAddress();
const privateKey = context.wallet.getEncryptionPrivateKey();
const l2Block = await node.getBlockNumber();

// Register the owner account and wait until it's synced so we measure how much time it took
context.logger(`Registering owner account on new pxe`);
await pxe.registerAccount(privateKey, partialAddress);
const isOwnerSynced = async () => (await pxe.getSyncStatus()).notes[publicKey.toString()] === l2Block;
await retryUntil(isOwnerSynced, 'pxe-owner-sync');

// Repeat for another account that didn't receive any notes for them, so we measure trial-decrypts
context.logger(`Registering fresh account on new pxe`);
const newAccount = await pxe.registerAccount(GrumpkinScalar.random(), Fr.random());
const isNewAccountSynced = async () =>
(await pxe.getSyncStatus()).notes[newAccount.publicKey.toString()] === l2Block;
await retryUntil(isNewAccountSynced, 'pxe-new-account-sync');

// Stop the external node and pxe
await pxe.stop();
await node.stop();
},
10 * 60_000,
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
import {
AccountWallet,
AccountWalletWithPrivateKey,
AztecAddress,
CheatCodes,
CompleteAddress,
Expand Down Expand Up @@ -156,7 +156,7 @@ export async function setupPXEService(
/**
* The wallets to be used.
*/
wallets: AccountWallet[];
wallets: AccountWalletWithPrivateKey[];
/**
* Logger instance named as the current test.
*/
Expand Down Expand Up @@ -242,9 +242,9 @@ export type EndToEndContext = {
/** The Aztec Node configuration. */
config: AztecNodeConfig;
/** The first wallet to be used. */
wallet: AccountWallet;
wallet: AccountWalletWithPrivateKey;
/** The wallets to be used. */
wallets: AccountWallet[];
wallets: AccountWalletWithPrivateKey[];
/** Logger instance named as the current test. */
logger: DebugLogger;
/** The cheat codes. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract Benchmarking {

use dep::aztec::{
context::{Context},
note::note_getter_options::NoteGetterOptions,
note::{utils as note_utils, note_getter_options::NoteGetterOptions, note_header::NoteHeader},
selector::compute_selector,
log::emit_unencrypted_log,
state_vars::{map::Map, public_state::PublicState, set::Set},
Expand Down Expand Up @@ -71,4 +71,9 @@ contract Benchmarking {
fn broadcast(owner: Field) {
emit_unencrypted_log(&mut context, storage.balances.at(owner).read());
}

unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, preimage: [Field; VALUE_NOTE_LEN]) -> [Field; 4] {
let note_header = NoteHeader::new(contract_address, nonce, storage_slot);
note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, preimage)
}
}
11 changes: 7 additions & 4 deletions yarn-project/pxe/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { INITIAL_L2_BLOCK_NUM } from '@aztec/types';

import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
Expand All @@ -6,20 +8,21 @@ import { fileURLToPath } from 'url';
* Configuration settings for the PXE Service.
*/
export interface PXEServiceConfig {
/**
* The interval to wait between polling for new blocks.
*/
/** The interval to wait between polling for new blocks. */
l2BlockPollingIntervalMS: number;
/** L2 block to start scanning from */
l2StartingBlock: number;
}

/**
* Creates an instance of PXEServiceConfig out of environment variables using sensible defaults for integration testing if not set.
*/
export function getPXEServiceConfig(): PXEServiceConfig {
const { PXE_BLOCK_POLLING_INTERVAL_MS } = process.env;
const { PXE_BLOCK_POLLING_INTERVAL_MS, PXE_L2_STARTING_BLOCK } = process.env;

return {
l2BlockPollingIntervalMS: PXE_BLOCK_POLLING_INTERVAL_MS ? +PXE_BLOCK_POLLING_INTERVAL_MS : 1000,
l2StartingBlock: PXE_L2_STARTING_BLOCK ? +PXE_L2_STARTING_BLOCK : INITIAL_L2_BLOCK_NUM,
};
}

Expand Down
10 changes: 9 additions & 1 deletion yarn-project/pxe/src/note_processor/note_processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ConstantKeyPair } from '@aztec/key-store';
import {
AztecNode,
FunctionL2Logs,
INITIAL_L2_BLOCK_NUM,
KeyPair,
KeyStore,
L2Block,
Expand Down Expand Up @@ -125,7 +126,14 @@ describe('Note Processor', () => {
keyStore = mock<KeyStore>();
simulator = mock<AcirSimulator>();
keyStore.getAccountPrivateKey.mockResolvedValue(owner.getPrivateKey());
noteProcessor = new NoteProcessor(owner.getPublicKey(), keyStore, database, aztecNode, simulator);
noteProcessor = new NoteProcessor(
owner.getPublicKey(),
keyStore,
database,
aztecNode,
INITIAL_L2_BLOCK_NUM,
simulator,
);

simulator.computeNoteHashAndNullifier.mockImplementation((...args) =>
Promise.resolve({
Expand Down
Loading