diff --git a/scripts/ci/aggregate_e2e_benchmark.js b/scripts/ci/aggregate_e2e_benchmark.js index 133c3a32ce9..d59a1330910 100644 --- a/scripts/ci/aggregate_e2e_benchmark.js +++ b/scripts/ci/aggregate_e2e_benchmark.js @@ -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"); @@ -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) { @@ -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; } diff --git a/scripts/ci/benchmark_shared.js b/scripts/ci/benchmark_shared.js index d3d32f0404f..df0548eab1c 100644 --- a/scripts/ci/benchmark_shared.js +++ b/scripts/ci/benchmark_shared.js @@ -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, diff --git a/yarn-project/aztec.js/src/account/manager/index.ts b/yarn-project/aztec.js/src/account/manager/index.ts index f82aafdb3fa..3e8248d935b 100644 --- a/yarn-project/aztec.js/src/account/manager/index.ts +++ b/yarn-project/aztec.js/src/account/manager/index.ts @@ -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'; @@ -73,9 +79,9 @@ export class AccountManager { * instances to be interacted with from this account. * @returns A Wallet instance. */ - public async getWallet(): Promise { + public async getWallet(): Promise { const entrypoint = await this.getAccount(); - return new AccountWallet(this.pxe, entrypoint); + return new AccountWalletWithPrivateKey(this.pxe, entrypoint, this.encryptionPrivateKey); } /** @@ -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 { + public async register(): Promise { const completeAddress = await this.getCompleteAddress(); await this.pxe.registerAccount(this.encryptionPrivateKey, completeAddress.partialAddress); return this.getWallet(); @@ -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 { + public async waitDeploy(opts: WaitOpts = {}): Promise { await this.deploy().then(tx => tx.wait(opts)); return this.getWallet(); } diff --git a/yarn-project/aztec.js/src/account/utils.ts b/yarn-project/aztec.js/src/account/utils.ts index 593ca2cdd8a..0890a3eff46 100644 --- a/yarn-project/aztec.js/src/account/utils.ts +++ b/yarn-project/aztec.js/src/account/utils.ts @@ -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 { +export function createAccount(pxe: PXE): Promise { return getSchnorrAccount(pxe, GrumpkinScalar.random(), GrumpkinScalar.random()).waitDeploy(); } @@ -30,7 +30,7 @@ export async function createRecipient(pxe: PXE): Promise { * @param numberOfAccounts - How many accounts to create. * @returns The created account wallets. */ -export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise { +export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise { const accounts = []; // Prepare deployments diff --git a/yarn-project/aztec.js/src/sandbox/index.ts b/yarn-project/aztec.js/src/sandbox/index.ts index a6494cd05d9..89470f7ea96 100644 --- a/yarn-project/aztec.js/src/sandbox/index.ts +++ b/yarn-project/aztec.js/src/sandbox/index.ts @@ -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'), @@ -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 { +export function getSandboxAccountsWallets(pxe: PXE): Promise { 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(), diff --git a/yarn-project/aztec.js/src/wallet/account_wallet.ts b/yarn-project/aztec.js/src/wallet/account_wallet.ts index f51fb13307b..829f7e16bc4 100644 --- a/yarn-project/aztec.js/src/wallet/account_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/account_wallet.ts @@ -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'; @@ -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; + } +} diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index a6804a1b116..fb7949db8b3 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -32,7 +32,7 @@ export abstract class BaseWallet implements Wallet { abstract createAuthWitness(message: Fr): Promise; - registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise { + registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise { return this.pxe.registerAccount(privKey, partialAddress); } registerRecipient(account: CompleteAddress): Promise { diff --git a/yarn-project/end-to-end/src/benchmarks/bench_publish_rollup.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_publish_rollup.test.ts index 8855939931a..91791aeedb1 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_publish_rollup.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_publish_rollup.test.ts @@ -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'; @@ -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, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index d3ada644a9a..35b5693b385 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -1,6 +1,6 @@ import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; import { - AccountWallet, + AccountWalletWithPrivateKey, AztecAddress, CheatCodes, CompleteAddress, @@ -156,7 +156,7 @@ export async function setupPXEService( /** * The wallets to be used. */ - wallets: AccountWallet[]; + wallets: AccountWalletWithPrivateKey[]; /** * Logger instance named as the current test. */ @@ -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. */ diff --git a/yarn-project/noir-contracts/src/contracts/benchmarking_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/benchmarking_contract/src/main.nr index a66a958f992..a731c29827d 100644 --- a/yarn-project/noir-contracts/src/contracts/benchmarking_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/benchmarking_contract/src/main.nr @@ -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}, @@ -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) + } } diff --git a/yarn-project/pxe/src/config/index.ts b/yarn-project/pxe/src/config/index.ts index cf07799dab6..8c511ce4fa0 100644 --- a/yarn-project/pxe/src/config/index.ts +++ b/yarn-project/pxe/src/config/index.ts @@ -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'; @@ -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, }; } diff --git a/yarn-project/pxe/src/note_processor/note_processor.test.ts b/yarn-project/pxe/src/note_processor/note_processor.test.ts index 063e7d05ca9..24823478199 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.test.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.test.ts @@ -6,6 +6,7 @@ import { ConstantKeyPair } from '@aztec/key-store'; import { AztecNode, FunctionL2Logs, + INITIAL_L2_BLOCK_NUM, KeyPair, KeyStore, L2Block, @@ -125,7 +126,14 @@ describe('Note Processor', () => { keyStore = mock(); simulator = mock(); 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({ diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 5c2e8d97bbd..01caa613732 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -3,6 +3,7 @@ import { computeCommitmentNonce, siloNullifier } from '@aztec/circuits.js/abis'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; +import { Timer } from '@aztec/foundation/timer'; import { AztecNode, KeyStore, L2BlockContext, L2BlockL2Logs, NoteSpendingInfo, PublicKey } from '@aztec/types'; import { Database, NoteSpendingInfoDao } from '../database/index.js'; @@ -22,16 +23,30 @@ interface ProcessedData { noteSpendingInfoDaos: NoteSpendingInfoDao[]; } +/** Accumulated stats for a note processor. */ +type NoteProcessorStats = { + /** How many notes have been seen and trial-decrypted. */ + seen: number; + /** How many notes were successfully decrypted. */ + decrypted: number; + /** How many notes failed processing. */ + failed: number; +}; + /** * NoteProcessor is responsible for decrypting logs and converting them to notes via their originating contracts * before storing them against their owner. */ export class NoteProcessor { - /** - * The latest L2 block number that the note processor has synchronized to. - */ + /** The latest L2 block number that the note processor has synchronized to. */ private syncedToBlock = 0; + /** Keeps track of processing time since an instance is created. */ + public readonly timer: Timer = new Timer(); + + /** Stats accumulated for this processor. */ + public readonly stats: NoteProcessorStats = { seen: 0, decrypted: 0, failed: 0 }; + constructor( /** * The public counterpart to the private key to be used in note decryption. @@ -40,9 +55,12 @@ export class NoteProcessor { private keyStore: KeyStore, private db: Database, private node: AztecNode, + private startingBlock: number, private simulator = getAcirSimulator(db, node, keyStore), - private log = createDebugLogger('aztec:aztec_note_processor'), - ) {} + private log = createDebugLogger('aztec:note_processor'), + ) { + this.syncedToBlock = this.startingBlock - 1; + } /** * Check if the NoteProcessor is synchronized with the remote block number. @@ -114,6 +132,7 @@ export class NoteProcessor { const excludedIndices: Set = new Set(); for (const functionLogs of txFunctionLogs) { for (const logs of functionLogs.logs) { + this.stats.seen++; const noteSpendingInfo = NoteSpendingInfo.fromEncryptedBuffer(logs, privateKey, curve); if (noteSpendingInfo) { // We have successfully decrypted the data. @@ -134,7 +153,9 @@ export class NoteProcessor { index, publicKey: this.publicKey, }); + this.stats.decrypted++; } catch (e) { + this.stats.failed++; this.log.warn(`Could not process note because of "${e}". Skipping note...`); } } diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 2edcae3d43f..9aeb1c38bb8 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -32,7 +32,6 @@ import { DeployedContract, ExtendedContractData, FunctionCall, - INITIAL_L2_BLOCK_NUM, KeyStore, L2Block, L2BlockL2Logs, @@ -93,7 +92,8 @@ export class PXEService implements PXE { * @returns A promise that resolves when the server has started successfully. */ public async start() { - await this.synchronizer.start(INITIAL_L2_BLOCK_NUM, 1, this.config.l2BlockPollingIntervalMS); + const { l2BlockPollingIntervalMS, l2StartingBlock } = this.config; + await this.synchronizer.start(l2StartingBlock, 1, l2BlockPollingIntervalMS); const info = await this.getNodeInfo(); this.log.info(`Started PXE connected to chain ${info.chainId} version ${info.protocolVersion}`); } @@ -114,17 +114,18 @@ export class PXEService implements PXE { return this.db.addAuthWitness(witness.requestHash, witness.witness); } - public async registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress) { + public async registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise { const completeAddress = await CompleteAddress.fromPrivateKeyAndPartialAddress(privKey, partialAddress); const wasAdded = await this.db.addCompleteAddress(completeAddress); if (wasAdded) { const pubKey = this.keyStore.addAccount(privKey); - this.synchronizer.addAccount(pubKey, this.keyStore); + this.synchronizer.addAccount(pubKey, this.keyStore, this.config.l2StartingBlock); this.log.info(`Registered account ${completeAddress.address.toString()}`); this.log.debug(`Registered account\n ${completeAddress.toReadableString()}`); } else { this.log.info(`Account:\n "${completeAddress.address.toString()}"\n already registered.`); } + return completeAddress; } public async getRegisteredAccounts(): Promise { @@ -647,4 +648,8 @@ export class PXEService implements PXE { public getSyncStatus() { return Promise.resolve(this.synchronizer.getSyncStatus()); } + + public getKeyStore() { + return this.keyStore; + } } diff --git a/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts b/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts index a6fde8a0f36..a980b0efa18 100644 --- a/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts +++ b/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts @@ -2,7 +2,7 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { L1ContractAddresses } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; import { TestKeyStore } from '@aztec/key-store'; -import { AztecNode, L2Tx, PXE, mockTx } from '@aztec/types'; +import { AztecNode, INITIAL_L2_BLOCK_NUM, L2Tx, PXE, mockTx } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -15,9 +15,7 @@ async function createPXEService(): Promise { const keyStore = new TestKeyStore(await Grumpkin.new()); const node = mock(); const db = new MemoryDB(); - const config: PXEServiceConfig = { - l2BlockPollingIntervalMS: 100, - }; + const config: PXEServiceConfig = { l2BlockPollingIntervalMS: 100, l2StartingBlock: INITIAL_L2_BLOCK_NUM }; // Setup the relevant mocks node.getBlockNumber.mockResolvedValue(2); @@ -48,9 +46,7 @@ describe('PXEService', () => { keyStore = new TestKeyStore(await Grumpkin.new()); node = mock(); db = new MemoryDB(); - config = { - l2BlockPollingIntervalMS: 100, - }; + config = { l2BlockPollingIntervalMS: 100, l2StartingBlock: INITIAL_L2_BLOCK_NUM }; }); it('throws when submitting a tx with a nullifier of already settled tx', async () => { diff --git a/yarn-project/pxe/src/synchronizer/synchronizer.test.ts b/yarn-project/pxe/src/synchronizer/synchronizer.test.ts index 1f16d15e4fc..d4e90b53aad 100644 --- a/yarn-project/pxe/src/synchronizer/synchronizer.test.ts +++ b/yarn-project/pxe/src/synchronizer/synchronizer.test.ts @@ -1,7 +1,7 @@ import { CompleteAddress, Fr, GrumpkinScalar, HistoricBlockData } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { TestKeyStore } from '@aztec/key-store'; -import { AztecNode, L2Block, MerkleTreeId } from '@aztec/types'; +import { AztecNode, INITIAL_L2_BLOCK_NUM, L2Block, MerkleTreeId } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; import omit from 'lodash.omit'; @@ -109,7 +109,7 @@ describe('Synchronizer', () => { await database.addCompleteAddress(completeAddress); // Add the account which will add the note processor to the synchronizer - synchronizer.addAccount(completeAddress.publicKey, keyStore); + synchronizer.addAccount(completeAddress.publicKey, keyStore, INITIAL_L2_BLOCK_NUM); await synchronizer.workNoteProcessorCatchUp(); diff --git a/yarn-project/pxe/src/synchronizer/synchronizer.ts b/yarn-project/pxe/src/synchronizer/synchronizer.ts index d3deb73eab9..092d2d69ba9 100644 --- a/yarn-project/pxe/src/synchronizer/synchronizer.ts +++ b/yarn-project/pxe/src/synchronizer/synchronizer.ts @@ -2,7 +2,7 @@ import { AztecAddress, CircuitsWasm, Fr, HistoricBlockData, PublicKey } from '@a import { computeGlobalsHash } from '@aztec/circuits.js/abis'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { InterruptableSleep } from '@aztec/foundation/sleep'; -import { AztecNode, INITIAL_L2_BLOCK_NUM, KeyStore, L2BlockContext, LogType } from '@aztec/types'; +import { AztecNode, INITIAL_L2_BLOCK_NUM, KeyStore, L2BlockContext, L2BlockL2Logs, LogType } from '@aztec/types'; import { Database } from '../database/index.js'; import { NoteProcessor } from '../note_processor/index.js'; @@ -119,9 +119,8 @@ export class Synchronizer { const latestBlock = blockContexts[blockContexts.length - 1]; await this.setBlockDataFromBlock(latestBlock); - this.log( - `Forwarding ${encryptedLogs.length} encrypted logs and blocks to ${this.noteProcessors.length} note processors`, - ); + const logCount = L2BlockL2Logs.getTotalLogCount(encryptedLogs); + this.log(`Forwarding ${logCount} encrypted logs and blocks to ${this.noteProcessors.length} note processors`); for (const noteProcessor of this.noteProcessors) { await noteProcessor.process(blockContexts, encryptedLogs); } @@ -173,11 +172,18 @@ export class Synchronizer { const blockContexts = blocks.map(block => new L2BlockContext(block)); - this.log(`Forwarding ${encryptedLogs.length} encrypted logs and blocks to note processor in catch up mode`); + const logCount = L2BlockL2Logs.getTotalLogCount(encryptedLogs); + this.log(`Forwarding ${logCount} encrypted logs and blocks to note processor in catch up mode`); await noteProcessor.process(blockContexts, encryptedLogs); if (noteProcessor.status.syncedToBlock === this.synchedToBlock) { // Note processor caught up, move it to `noteProcessors` from `noteProcessorsToCatchUp`. + this.log(`Note processor for ${noteProcessor.publicKey.toString()} has caught up`, { + eventName: 'note-processor-caught-up', + publicKey: noteProcessor.publicKey.toString(), + duration: noteProcessor.timer.ms(), + ...noteProcessor.stats, + }); this.noteProcessorsToCatchUp.shift(); this.noteProcessors.push(noteProcessor); } @@ -228,15 +234,14 @@ export class Synchronizer { * * @param publicKey - The public key for the account. * @param keyStore - The key store. + * @param startingBlock - The block where to start scanning for notes for this accounts. * @returns A promise that resolves once the account is added to the Synchronizer. */ - public addAccount(publicKey: PublicKey, keyStore: KeyStore) { + public addAccount(publicKey: PublicKey, keyStore: KeyStore, startingBlock: number) { const processor = this.noteProcessors.find(x => x.publicKey.equals(publicKey)); - if (processor) { - return; - } + if (processor) return; - this.noteProcessorsToCatchUp.push(new NoteProcessor(publicKey, keyStore, this.db, this.node)); + this.noteProcessorsToCatchUp.push(new NoteProcessor(publicKey, keyStore, this.db, this.node, startingBlock)); } /** diff --git a/yarn-project/sequencer-client/src/publisher/index.ts b/yarn-project/sequencer-client/src/publisher/index.ts index 8f4a3a3b3e1..97cef6b9915 100644 --- a/yarn-project/sequencer-client/src/publisher/index.ts +++ b/yarn-project/sequencer-client/src/publisher/index.ts @@ -24,13 +24,13 @@ export type L1PublishStats = { /** Number of the L2 block. */ blockNumber: number; /** Number of encrypted logs. */ - encryptedLogCount: number; + encryptedLogCount?: number; /** Number of unencrypted logs. */ - unencryptedLogCount: number; + unencryptedLogCount?: number; /** Serialised size of encrypted logs. */ - encryptedLogSize: number; + encryptedLogSize?: number; /** Serialised size of unencrypted logs. */ - unencryptedLogSize: number; + unencryptedLogSize?: number; }; /** diff --git a/yarn-project/types/src/interfaces/pxe.ts b/yarn-project/types/src/interfaces/pxe.ts index cd61e82b5ba..b3907ebab6d 100644 --- a/yarn-project/types/src/interfaces/pxe.ts +++ b/yarn-project/types/src/interfaces/pxe.ts @@ -42,13 +42,14 @@ export interface PXE { /** * Registers a user account in PXE given its master encryption private key. * Once a new account is registered, the PXE Service will trial-decrypt all published notes on - * the chain and store those that correspond to the registered account. + * the chain and store those that correspond to the registered account. Will do nothing if the + * account is already registered. * * @param privKey - Private key of the corresponding user master public key. * @param partialAddress - The partial address of the account contract corresponding to the account being registered. - * @throws If the account is already registered. + * @returns The complete address of the account. */ - registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise; + registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise; /** * Registers a recipient in PXE. This is required when sending encrypted notes to diff --git a/yarn-project/types/src/l2_block.ts b/yarn-project/types/src/l2_block.ts index 166dec34f37..3890bbf1817 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -500,7 +500,7 @@ export class L2Block { throw new Error(`Trying to attach different ${logFieldName} logs to block ${this.number}.`); } - L2Block.logger(`Attaching ${logFieldName} logs`); + L2Block.logger(`Attaching ${logFieldName} ${logs.getTotalLogCount()} logs to block ${this.number}`); const numTxs = this.newCommitments.length / MAX_NEW_COMMITMENTS_PER_TX; @@ -747,14 +747,20 @@ export class L2Block { * @returns Stats on tx count, number, and log size and count. */ getStats() { - return { - txCount: this.numberOfTxs, - blockNumber: this.number, + const encryptedLogsStats = this.newEncryptedLogs && { encryptedLogCount: this.newEncryptedLogs?.getTotalLogCount() ?? 0, - unencryptedLogCount: this.newUnencryptedLogs?.getTotalLogCount() ?? 0, encryptedLogSize: this.newEncryptedLogs?.getSerializedLength() ?? 0, + }; + const unencryptedLogsStats = this.newUnencryptedLogs && { + unencryptedLogCount: this.newUnencryptedLogs?.getTotalLogCount() ?? 0, unencryptedLogSize: this.newUnencryptedLogs?.getSerializedLength() ?? 0, }; + return { + txCount: this.numberOfTxs, + blockNumber: this.number, + ...encryptedLogsStats, + ...unencryptedLogsStats, + }; } /** diff --git a/yarn-project/types/src/logs/l2_block_l2_logs.ts b/yarn-project/types/src/logs/l2_block_l2_logs.ts index 5668fab26d4..8f4824a4fde 100644 --- a/yarn-project/types/src/logs/l2_block_l2_logs.ts +++ b/yarn-project/types/src/logs/l2_block_l2_logs.ts @@ -122,4 +122,13 @@ export class L2BlockL2Logs { const txLogs = obj.txLogs.map((log: any) => TxL2Logs.fromJSON(log)); return new L2BlockL2Logs(txLogs); } + + /** + * Returns the total number of log entries across an array of L2BlockL2Logs. + * @param l2BlockL2logs - L2BlockL2Logs to sum over. + * @returns Total sum of log entries. + */ + public static getTotalLogCount(l2BlockL2logs: L2BlockL2Logs[]): number { + return l2BlockL2logs.reduce((sum, log) => sum + log.getTotalLogCount(), 0); + } }