diff --git a/bootstrap.sh b/bootstrap.sh index edc7bb5775fa..86c64db5f0ac 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -11,7 +11,7 @@ source $(git rev-parse --show-toplevel)/ci3/source_bootstrap export DENOISE=${DENOISE:-1} # Number of TXE servers to run when testing. -export NUM_TXES=8 +export NUM_TXES=1 export MAKEFLAGS="-j${MAKE_JOBS:-$(get_num_cpus)}" diff --git a/noir-projects/noir-contracts/bootstrap.sh b/noir-projects/noir-contracts/bootstrap.sh index fe6b671029c3..e38c87d5ac44 100755 --- a/noir-projects/noir-contracts/bootstrap.sh +++ b/noir-projects/noir-contracts/bootstrap.sh @@ -247,7 +247,7 @@ function test { # Starting txe servers with incrementing port numbers. # Base port is below the Linux ephemeral range (32768-60999) to avoid conflicts. local txe_base_port=14730 - export NUM_TXES=8 + export NUM_TXES=1 trap 'kill $(jobs -p) &>/dev/null || true' EXIT for i in $(seq 0 $((NUM_TXES-1))); do check_port $((txe_base_port + i)) || echo "WARNING: port $((txe_base_port + i)) is in use, TXE $i may fail to start" diff --git a/yarn-project/aztec/scripts/aztec.sh b/yarn-project/aztec/scripts/aztec.sh index 93016cf41334..aff76c6ed46a 100755 --- a/yarn-project/aztec/scripts/aztec.sh +++ b/yarn-project/aztec/scripts/aztec.sh @@ -27,7 +27,7 @@ case $cmd in trap 'kill $server_pid &>/dev/null || true' EXIT while ! nc -z 127.0.0.1 8081 &>/dev/null; do sleep 0.2; done export NARGO_FOREIGN_CALL_TIMEOUT=300000 - nargo test --silence-warnings --oracle-resolver http://127.0.0.1:8081 "$@" + nargo test --silence-warnings --oracle-resolver http://127.0.0.1:8081 --test-threads 16 "$@" ;; start) if [ "${1:-}" == "--local-network" ]; then diff --git a/yarn-project/aztec/src/cli/aztec_start_action.ts b/yarn-project/aztec/src/cli/aztec_start_action.ts index 9ffad783f0f1..4a9c96c08be4 100644 --- a/yarn-project/aztec/src/cli/aztec_start_action.ts +++ b/yarn-project/aztec/src/cli/aztec_start_action.ts @@ -37,11 +37,11 @@ export async function aztecStart(options: any, userLog: LogFn, debugLogger: Logg l1RpcUrls: options.l1RpcUrls, testAccounts: localNetwork.testAccounts, realProofs: false, - // Setting the epoch duration to 4 by default for local network. This allows the epoch to be "proven" faster, so + // Setting the epoch duration to 2 by default for local network. This allows the epoch to be "proven" faster, so // the users can consume out hash without having to wait for a long time. // Note: We are not proving anything in the local network (realProofs == false). But in `createLocalNetwork`, // the EpochTestSettler will set the out hash to the outbox when an epoch is complete. - aztecEpochDuration: 4, + aztecEpochDuration: 2, }, userLog, ); diff --git a/yarn-project/aztec/src/local-network/local-network.ts b/yarn-project/aztec/src/local-network/local-network.ts index 9c700ce483b7..1b3882881359 100644 --- a/yarn-project/aztec/src/local-network/local-network.ts +++ b/yarn-project/aztec/src/local-network/local-network.ts @@ -18,6 +18,7 @@ import type { LogFn } from '@aztec/foundation/log'; import { DateProvider, TestDateProvider } from '@aztec/foundation/timer'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { protocolContractsHash } from '@aztec/protocol-contracts'; +import { SequencerState } from '@aztec/sequencer-client'; import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server'; import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { @@ -181,6 +182,21 @@ export async function createLocalNetwork(config: Partial = { const blobClient = createBlobClient(); const node = await createAztecNode(aztecNodeConfig, { telemetry, blobClient, dateProvider }, { prefilledPublicData }); + // Now that the node is up, let the watcher check for pending txs so it can skip unfilled slots faster when + // transactions are waiting in the mempool. Also let it check if the sequencer is actively building, to avoid + // warping time out from under an in-progress block. + watcher?.setGetPendingTxCount(() => node.getPendingTxCount()); + const sequencer = node.getSequencer()?.getSequencer(); + if (sequencer) { + const idleStates: Set = new Set([ + SequencerState.STOPPED, + SequencerState.STOPPING, + SequencerState.IDLE, + SequencerState.SYNCHRONIZING, + ]); + watcher?.setIsSequencerBuilding(() => !idleStates.has(sequencer.getState())); + } + let epochTestSettler: EpochTestSettler | undefined; if (!aztecNodeConfig.p2pEnabled) { epochTestSettler = new EpochTestSettler( diff --git a/yarn-project/aztec/src/testing/anvil_test_watcher.ts b/yarn-project/aztec/src/testing/anvil_test_watcher.ts index bd04679a75bc..44ef6662a9c8 100644 --- a/yarn-project/aztec/src/testing/anvil_test_watcher.ts +++ b/yarn-project/aztec/src/testing/anvil_test_watcher.ts @@ -31,6 +31,15 @@ export class AnvilTestWatcher { private isMarkingAsProven = true; + // Optional callback to check if there are pending txs in the mempool. + private getPendingTxCount?: () => Promise; + + // Optional callback to check if the sequencer is actively building a block. + private isSequencerBuilding?: () => boolean; + + // Tracks when we first observed the current unfilled slot with pending txs (real wall time). + private unfilledSlotFirstSeen?: { slot: number; realTime: number }; + constructor( private cheatcodes: EthCheatCodes, rollupAddress: EthAddress, @@ -59,6 +68,16 @@ export class AnvilTestWatcher { this.isLocalNetwork = isLocalNetwork; } + /** Sets a callback to check for pending txs, used to skip unfilled slots faster when txs are waiting. */ + setGetPendingTxCount(fn: () => Promise) { + this.getPendingTxCount = fn; + } + + /** Sets a callback to check if the sequencer is actively building, to avoid warping while it works. */ + setIsSequencerBuilding(fn: () => boolean) { + this.isSequencerBuilding = fn; + } + async start() { if (this.filledRunningPromise) { throw new Error('Watcher already watching for filled slot'); @@ -131,15 +150,8 @@ export class AnvilTestWatcher { const nextSlotTimestamp = Number(await this.rollup.read.getTimestampForSlot([BigInt(nextSlot)])); if (BigInt(currentSlot) === checkpointLog.slotNumber) { - // We should jump to the next slot - try { - await this.cheatcodes.warp(nextSlotTimestamp, { - resetBlockInterval: true, - }); - } catch (e) { - this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`); - } - + // The current slot has been filled, we should jump to the next slot. + await this.warpToTimestamp(nextSlotTimestamp); this.logger.info(`Slot ${currentSlot} was filled, jumped to next slot`); return; } @@ -149,18 +161,50 @@ export class AnvilTestWatcher { return; } - const currentTimestamp = this.dateProvider?.now() ?? Date.now(); - if (currentTimestamp > nextSlotTimestamp * 1000) { - try { - await this.cheatcodes.warp(nextSlotTimestamp, { resetBlockInterval: true }); - } catch (e) { - this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`); + // If there are pending txs and the sequencer missed them, warp quickly (after a 2s real-time debounce) so the + // sequencer can retry in the next slot. Without this, we'd have to wait a full real-time slot duration (~36s) for + // the dateProvider to catch up to the next slot timestamp. We skip the warp if the sequencer is actively building + // to avoid invalidating its in-progress work. + if (this.getPendingTxCount) { + const pendingTxs = await this.getPendingTxCount(); + if (pendingTxs > 0) { + if (this.isSequencerBuilding?.()) { + this.unfilledSlotFirstSeen = undefined; + return; + } + + const realNow = Date.now(); + if (!this.unfilledSlotFirstSeen || this.unfilledSlotFirstSeen.slot !== currentSlot) { + this.unfilledSlotFirstSeen = { slot: currentSlot, realTime: realNow }; + return; + } + + if (realNow - this.unfilledSlotFirstSeen.realTime > 2000) { + await this.warpToTimestamp(nextSlotTimestamp); + this.unfilledSlotFirstSeen = undefined; + this.logger.info(`Slot ${currentSlot} was missed with pending txs, jumped to next slot`); + } + + return; } + } + // Fallback: warp when the dateProvider time has passed the next slot timestamp. + const currentTimestamp = this.dateProvider?.now() ?? Date.now(); + if (currentTimestamp > nextSlotTimestamp * 1000) { + await this.warpToTimestamp(nextSlotTimestamp); this.logger.info(`Slot ${currentSlot} was missed, jumped to next slot`); } } catch { this.logger.error('mineIfSlotFilled failed'); } } + + private async warpToTimestamp(timestamp: number) { + try { + await this.cheatcodes.warp(timestamp, { resetBlockInterval: true }); + } catch (e) { + this.logger.error(`Failed to warp to timestamp ${timestamp}: ${e}`); + } + } } diff --git a/yarn-project/cli/src/config/chain_l2_config.ts b/yarn-project/cli/src/config/chain_l2_config.ts index 932860d5fc73..75f69e615a00 100644 --- a/yarn-project/cli/src/config/chain_l2_config.ts +++ b/yarn-project/cli/src/config/chain_l2_config.ts @@ -45,7 +45,9 @@ export function enrichEnvironmentWithChainName(networkName: NetworkNames) { } // Apply generated network config from defaults.yml - const generatedConfig = NetworkConfigs[networkName]; + // For devnet iterations (v4-devnet-1, etc.), use the base devnet config + const configKey = /^v\d+-devnet-\d+$/.test(networkName) ? 'devnet' : networkName; + const generatedConfig = NetworkConfigs[configKey]; if (generatedConfig) { enrichEnvironmentWithNetworkConfig(generatedConfig); } diff --git a/yarn-project/foundation/src/config/network_name.ts b/yarn-project/foundation/src/config/network_name.ts index 7fb6f41fa29b..e00dcb200ec7 100644 --- a/yarn-project/foundation/src/config/network_name.ts +++ b/yarn-project/foundation/src/config/network_name.ts @@ -5,7 +5,8 @@ export type NetworkNames = | 'testnet' | 'mainnet' | 'next-net' - | 'devnet'; + | 'devnet' + | `v${number}-devnet-${number}`; export function getActiveNetworkName(name?: string): NetworkNames { const network = name || process.env.NETWORK; @@ -23,6 +24,8 @@ export function getActiveNetworkName(name?: string): NetworkNames { return 'next-net'; } else if (network === 'devnet') { return 'devnet'; + } else if (/^v\d+-devnet-\d+$/.test(network)) { + return network as `v${number}-devnet-${number}`; } throw new Error(`Unknown network: ${network}`); } diff --git a/yarn-project/protocol-contracts/src/auth-registry/index.ts b/yarn-project/protocol-contracts/src/auth-registry/index.ts index d1699bdb6191..9f7f38677565 100644 --- a/yarn-project/protocol-contracts/src/auth-registry/index.ts +++ b/yarn-project/protocol-contracts/src/auth-registry/index.ts @@ -10,9 +10,9 @@ let protocolContract: ProtocolContract; export const AuthRegistryArtifact: ContractArtifact = loadContractArtifact(AuthRegistryJson as NoirCompiledContract); /** Returns the canonical deployment of the auth registry. */ -export async function getCanonicalAuthRegistry(): Promise { +export function getCanonicalAuthRegistry(): Promise { if (!protocolContract) { - protocolContract = await makeProtocolContract('AuthRegistry', AuthRegistryArtifact); + protocolContract = makeProtocolContract('AuthRegistry', AuthRegistryArtifact); } - return protocolContract; + return Promise.resolve(protocolContract); } diff --git a/yarn-project/protocol-contracts/src/auth-registry/lazy.ts b/yarn-project/protocol-contracts/src/auth-registry/lazy.ts index a058a48514c6..e7b45ac528dd 100644 --- a/yarn-project/protocol-contracts/src/auth-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/auth-registry/lazy.ts @@ -23,7 +23,7 @@ export async function getAuthRegistryArtifact(): Promise { export async function getCanonicalAuthRegistry(): Promise { if (!protocolContract) { const authRegistryArtifact = await getAuthRegistryArtifact(); - protocolContract = await makeProtocolContract('AuthRegistry', authRegistryArtifact); + protocolContract = makeProtocolContract('AuthRegistry', authRegistryArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/class-registry/index.ts b/yarn-project/protocol-contracts/src/class-registry/index.ts index 7526ff0fafda..7b3db3145893 100644 --- a/yarn-project/protocol-contracts/src/class-registry/index.ts +++ b/yarn-project/protocol-contracts/src/class-registry/index.ts @@ -14,10 +14,10 @@ export const ContractClassRegistryArtifact = loadContractArtifact(ContractClassR let protocolContract: ProtocolContract; /** Returns the canonical deployment of the contract. */ -export async function getCanonicalClassRegistry(): Promise { +export function getCanonicalClassRegistry(): Promise { if (!protocolContract) { const artifact = ContractClassRegistryArtifact; - protocolContract = await makeProtocolContract('ContractClassRegistry', artifact); + protocolContract = makeProtocolContract('ContractClassRegistry', artifact); } - return protocolContract; + return Promise.resolve(protocolContract); } diff --git a/yarn-project/protocol-contracts/src/class-registry/lazy.ts b/yarn-project/protocol-contracts/src/class-registry/lazy.ts index 1a1887049d61..634a075d1947 100644 --- a/yarn-project/protocol-contracts/src/class-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/class-registry/lazy.ts @@ -27,7 +27,7 @@ export async function getContractClassRegistryArtifact(): Promise { if (!protocolContract) { const contractClassRegistryArtifact = await getContractClassRegistryArtifact(); - protocolContract = await makeProtocolContract('ContractClassRegistry', contractClassRegistryArtifact); + protocolContract = makeProtocolContract('ContractClassRegistry', contractClassRegistryArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/fee-juice/index.ts b/yarn-project/protocol-contracts/src/fee-juice/index.ts index d277eda89bb1..90b4f5902465 100644 --- a/yarn-project/protocol-contracts/src/fee-juice/index.ts +++ b/yarn-project/protocol-contracts/src/fee-juice/index.ts @@ -14,11 +14,11 @@ export const FeeJuiceArtifact = loadContractArtifact(FeeJuiceJson as NoirCompile let protocolContract: ProtocolContract; /** Returns the canonical deployment of the contract. */ -export async function getCanonicalFeeJuice(): Promise { +export function getCanonicalFeeJuice(): Promise { if (!protocolContract) { - protocolContract = await makeProtocolContract('FeeJuice', FeeJuiceArtifact); + protocolContract = makeProtocolContract('FeeJuice', FeeJuiceArtifact); } - return protocolContract; + return Promise.resolve(protocolContract); } /** diff --git a/yarn-project/protocol-contracts/src/fee-juice/lazy.ts b/yarn-project/protocol-contracts/src/fee-juice/lazy.ts index 3ac6361da3eb..6c7e98166420 100644 --- a/yarn-project/protocol-contracts/src/fee-juice/lazy.ts +++ b/yarn-project/protocol-contracts/src/fee-juice/lazy.ts @@ -23,7 +23,7 @@ export async function getFeeJuiceArtifact(): Promise { export async function getCanonicalFeeJuice(): Promise { if (!protocolContract) { const feeJuiceArtifact = await getFeeJuiceArtifact(); - protocolContract = await makeProtocolContract('FeeJuice', feeJuiceArtifact); + protocolContract = makeProtocolContract('FeeJuice', feeJuiceArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/instance-registry/index.ts b/yarn-project/protocol-contracts/src/instance-registry/index.ts index d597f68e6629..9d10cdfe6b44 100644 --- a/yarn-project/protocol-contracts/src/instance-registry/index.ts +++ b/yarn-project/protocol-contracts/src/instance-registry/index.ts @@ -15,9 +15,9 @@ export const ContractInstanceRegistryArtifact = loadContractArtifact( let protocolContract: ProtocolContract; /** Returns the canonical deployment of the contract. */ -export async function getCanonicalInstanceRegistry(): Promise { +export function getCanonicalInstanceRegistry(): Promise { if (!protocolContract) { - protocolContract = await makeProtocolContract('ContractInstanceRegistry', ContractInstanceRegistryArtifact); + protocolContract = makeProtocolContract('ContractInstanceRegistry', ContractInstanceRegistryArtifact); } - return protocolContract; + return Promise.resolve(protocolContract); } diff --git a/yarn-project/protocol-contracts/src/instance-registry/lazy.ts b/yarn-project/protocol-contracts/src/instance-registry/lazy.ts index 41d9a8fc32d5..31f554c1d061 100644 --- a/yarn-project/protocol-contracts/src/instance-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/instance-registry/lazy.ts @@ -26,7 +26,7 @@ export async function getContractInstanceRegistryArtifact(): Promise { if (!protocolContract) { const contractInstanceRegistryArtifact = await getContractInstanceRegistryArtifact(); - protocolContract = await makeProtocolContract('ContractInstanceRegistry', contractInstanceRegistryArtifact); + protocolContract = makeProtocolContract('ContractInstanceRegistry', contractInstanceRegistryArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/make_protocol_contract.ts b/yarn-project/protocol-contracts/src/make_protocol_contract.ts index 14c3e0be7dbf..87215bcb3c20 100644 --- a/yarn-project/protocol-contracts/src/make_protocol_contract.ts +++ b/yarn-project/protocol-contracts/src/make_protocol_contract.ts @@ -1,26 +1,48 @@ import type { ContractArtifact } from '@aztec/stdlib/abi'; -import { getContractClassFromArtifact, getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract'; +import { PublicKeys } from '@aztec/stdlib/keys'; import type { ProtocolContract } from './protocol_contract.js'; -import { ProtocolContractAddress, type ProtocolContractName, ProtocolContractSalt } from './protocol_contract_data.js'; +import { + ProtocolContractAddress, + ProtocolContractClassId, + ProtocolContractClassIdPreimage, + ProtocolContractInitializationHash, + type ProtocolContractName, + ProtocolContractPrivateFunctions, + ProtocolContractSalt, +} from './protocol_contract_data.js'; /** - * Returns the canonical deployment given its name and artifact. - * To be used internally within the protocol-contracts package. + * Reconstructs a ProtocolContract from precomputed data without performing any hash computations. + * Internal to the protocol-contracts package — not part of the public API. */ -export async function makeProtocolContract( - name: ProtocolContractName, - artifact: ContractArtifact, -): Promise { +export function makeProtocolContract(name: ProtocolContractName, artifact: ContractArtifact): ProtocolContract { const address = ProtocolContractAddress[name]; const salt = ProtocolContractSalt[name]; - // TODO(@spalladino): This computes the contract class from the artifact twice. - const contractClass = await getContractClassFromArtifact(artifact); - const instance = await getContractInstanceFromInstantiationParams(artifact, { salt, deployer: address }); - return { - instance: { ...instance, address }, - contractClass, - artifact, + const classId = ProtocolContractClassId[name]; + const { artifactHash, privateFunctionsRoot, publicBytecodeCommitment } = ProtocolContractClassIdPreimage[name]; + const initializationHash = ProtocolContractInitializationHash[name]; + + const contractClass = { + id: classId, + version: 1 as const, + artifactHash, + privateFunctionsRoot, + publicBytecodeCommitment, + packedBytecode: artifact.functions.find(f => f.name === 'public_dispatch')?.bytecode ?? Buffer.alloc(0), + privateFunctions: ProtocolContractPrivateFunctions[name], + }; + + const instance = { + version: 1 as const, + currentContractClassId: classId, + originalContractClassId: classId, + initializationHash, + publicKeys: PublicKeys.default(), + salt, + deployer: address, address, }; + + return { instance, contractClass, artifact, address }; } diff --git a/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts index a3e478f51867..e7dae3778f4c 100644 --- a/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts +++ b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts @@ -10,9 +10,9 @@ export const MultiCallEntrypointArtifact = loadContractArtifact(MultiCallEntrypo let protocolContract: ProtocolContract; /** Returns the canonical deployment of the contract. */ -export async function getCanonicalMultiCallEntrypoint(): Promise { +export function getCanonicalMultiCallEntrypoint(): Promise { if (!protocolContract) { - protocolContract = await makeProtocolContract('MultiCallEntrypoint', MultiCallEntrypointArtifact); + protocolContract = makeProtocolContract('MultiCallEntrypoint', MultiCallEntrypointArtifact); } - return protocolContract; + return Promise.resolve(protocolContract); } diff --git a/yarn-project/protocol-contracts/src/multi-call-entrypoint/lazy.ts b/yarn-project/protocol-contracts/src/multi-call-entrypoint/lazy.ts index 18d9c4fc94f0..d1fe22edfcce 100644 --- a/yarn-project/protocol-contracts/src/multi-call-entrypoint/lazy.ts +++ b/yarn-project/protocol-contracts/src/multi-call-entrypoint/lazy.ts @@ -23,7 +23,7 @@ export async function getMultiCallEntrypointArtifact(): Promise { if (!protocolContract) { const multiCallEntrypointArtifact = await getMultiCallEntrypointArtifact(); - protocolContract = await makeProtocolContract('MultiCallEntrypoint', multiCallEntrypointArtifact); + protocolContract = makeProtocolContract('MultiCallEntrypoint', multiCallEntrypointArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/provider/bundle.ts b/yarn-project/protocol-contracts/src/provider/bundle.ts index ad886d532a9f..18868f164420 100644 --- a/yarn-project/protocol-contracts/src/provider/bundle.ts +++ b/yarn-project/protocol-contracts/src/provider/bundle.ts @@ -22,6 +22,6 @@ export const ProtocolContractArtifact: Record { - return makeProtocolContract(name, ProtocolContractArtifact[name]); + return Promise.resolve(makeProtocolContract(name, ProtocolContractArtifact[name])); } } diff --git a/yarn-project/protocol-contracts/src/public-checks/index.ts b/yarn-project/protocol-contracts/src/public-checks/index.ts index 66280429fcc3..0016a8c50926 100644 --- a/yarn-project/protocol-contracts/src/public-checks/index.ts +++ b/yarn-project/protocol-contracts/src/public-checks/index.ts @@ -10,9 +10,9 @@ export const PublicChecksArtifact = loadContractArtifact(PublicChecksJson as Noi let protocolContract: ProtocolContract; /** Returns the canonical deployment of the contract. */ -export async function getCanonicalPublicChecks(): Promise { +export function getCanonicalPublicChecks(): Promise { if (!protocolContract) { - protocolContract = await makeProtocolContract('PublicChecks', PublicChecksArtifact); + protocolContract = makeProtocolContract('PublicChecks', PublicChecksArtifact); } - return protocolContract; + return Promise.resolve(protocolContract); } diff --git a/yarn-project/protocol-contracts/src/public-checks/lazy.ts b/yarn-project/protocol-contracts/src/public-checks/lazy.ts index 21a8eef301a4..fee24a720598 100644 --- a/yarn-project/protocol-contracts/src/public-checks/lazy.ts +++ b/yarn-project/protocol-contracts/src/public-checks/lazy.ts @@ -23,7 +23,7 @@ export async function getPublicChecksArtifact(): Promise { export async function getCanonicalPublicChecks(): Promise { if (!protocolContract) { const publicChecksArtifact = await getPublicChecksArtifact(); - protocolContract = await makeProtocolContract('PublicChecks', publicChecksArtifact); + protocolContract = makeProtocolContract('PublicChecks', publicChecksArtifact); } return protocolContract; } diff --git a/yarn-project/protocol-contracts/src/scripts/generate_data.ts b/yarn-project/protocol-contracts/src/scripts/generate_data.ts index f6648841ea11..69d62cfe7631 100644 --- a/yarn-project/protocol-contracts/src/scripts/generate_data.ts +++ b/yarn-project/protocol-contracts/src/scripts/generate_data.ts @@ -11,10 +11,15 @@ import { import { makeTuple } from '@aztec/foundation/array'; import { Fr } from '@aztec/foundation/curves/bn254'; import { createConsoleLogger } from '@aztec/foundation/log'; -import { loadContractArtifact } from '@aztec/stdlib/abi'; +import { FunctionSelector, loadContractArtifact } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract'; +import { + computeContractAddressFromInstance, + computeInitializationHash, + getContractClassFromArtifact, +} from '@aztec/stdlib/contract'; import { computeSiloedPrivateLogFirstField } from '@aztec/stdlib/hash'; +import { PublicKeys } from '@aztec/stdlib/keys'; import { type NoirCompiledContract } from '@aztec/stdlib/noir'; import { ProtocolContracts } from '@aztec/stdlib/tx'; @@ -62,9 +67,42 @@ async function copyArtifact(srcName: string, destName: string) { return artifact; } -async function computeAddress(artifact: NoirCompiledContract, deployer: AztecAddress) { - const instance = await getContractInstanceFromInstantiationParams(loadContractArtifact(artifact), { salt, deployer }); - return instance.address; +type ContractData = { + address: AztecAddress; + classId: Fr; + artifactHash: Fr; + privateFunctionsRoot: Fr; + publicBytecodeCommitment: Fr; + initializationHash: Fr; + privateFunctions: { selector: FunctionSelector; vkHash: Fr }[]; +}; + +// Precompute all the expensive contract data that can be obtained from the artifact, to avoid redundant computations in clients. +// Protocol contracts come from a trusted source, so no class verifications are needed. +async function computeContractData(artifact: NoirCompiledContract, deployer: AztecAddress): Promise { + const loaded = loadContractArtifact(artifact); + const contractClass = await getContractClassFromArtifact(loaded); + const constructorArtifact = loaded.functions.find(f => f.name === 'constructor'); + const initializationHash = await computeInitializationHash(constructorArtifact, []); + const instance = { + version: 1 as const, + currentContractClassId: contractClass.id, + originalContractClassId: contractClass.id, + initializationHash, + publicKeys: PublicKeys.default(), + salt, + deployer, + }; + const address = await computeContractAddressFromInstance(instance); + return { + address, + classId: contractClass.id, + artifactHash: contractClass.artifactHash, + privateFunctionsRoot: contractClass.privateFunctionsRoot, + publicBytecodeCommitment: contractClass.publicBytecodeCommitment, + initializationHash, + privateFunctions: contractClass.privateFunctions, + }; } async function generateDeclarationFile(destName: string) { @@ -103,15 +141,53 @@ function generateContractAddresses(names: string[]) { `; } -function generateDerivedAddresses(names: string[], derivedAddresses: AztecAddress[]) { +function generateDerivedAddresses(names: string[], contractData: ContractData[]) { return ` export const ProtocolContractDerivedAddress = { - ${derivedAddresses.map((address, i) => `${names[i]}: AztecAddress.fromString('${address.toString()}')`).join(',\n')} + ${contractData.map((d, i) => `${names[i]}: AztecAddress.fromString('${d.address.toString()}')`).join(',\n')} }; `; } -async function generateProtocolContractsList(names: string[], derivedAddresses: AztecAddress[]) { +function generateClassIdPreimages(names: string[], contractData: ContractData[]) { + return ` + export const ProtocolContractClassId: Record = { + ${contractData.map((d, i) => `${names[i]}: Fr.fromString('${d.classId.toString()}')`).join(',\n')} + }; + + export const ProtocolContractClassIdPreimage: Record = { + ${contractData + .map( + (d, i) => `${names[i]}: { + artifactHash: Fr.fromString('${d.artifactHash.toString()}'), + privateFunctionsRoot: Fr.fromString('${d.privateFunctionsRoot.toString()}'), + publicBytecodeCommitment: Fr.fromString('${d.publicBytecodeCommitment.toString()}'), + }`, + ) + .join(',\n')} + }; + + export const ProtocolContractInitializationHash: Record = { + ${contractData.map((d, i) => `${names[i]}: Fr.fromString('${d.initializationHash.toString()}')`).join(',\n')} + }; + + export const ProtocolContractPrivateFunctions: Record = { + ${contractData + .map( + (d, i) => + `${names[i]}: [${d.privateFunctions + .map( + fn => + `{ selector: FunctionSelector.fromField(Fr.fromString('${fn.selector.toField().toString()}')), vkHash: Fr.fromString('${fn.vkHash.toString()}') }`, + ) + .join(', ')}]`, + ) + .join(',\n')} + }; + `; +} + +async function generateProtocolContractsList(names: string[], contractData: ContractData[]) { const list = makeTuple(MAX_PROTOCOL_CONTRACTS, () => AztecAddress.zero()); for (let i = 0; i < names.length; i++) { const name = names[i]; @@ -120,7 +196,7 @@ async function generateProtocolContractsList(names: string[], derivedAddresses: if (!list[derivedAddressIndex].equals(AztecAddress.zero())) { throw new Error(`Duplicate protocol contract address: ${address.toString()}`); } - list[derivedAddressIndex] = derivedAddresses[i]; + list[derivedAddressIndex] = contractData[i].address; } return ` @@ -142,10 +218,11 @@ async function generateLogTags() { `; } -async function generateOutputFile(names: string[], derivedAddresses: AztecAddress[]) { +async function generateOutputFile(names: string[], contractData: ContractData[]) { const content = ` // GENERATED FILE - DO NOT EDIT. RUN \`yarn generate\` or \`yarn generate:data\` import { Fr } from '@aztec/foundation/curves/bn254'; + import { FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { ProtocolContracts } from '@aztec/stdlib/tx'; @@ -155,9 +232,11 @@ async function generateOutputFile(names: string[], derivedAddresses: AztecAddres ${generateContractAddresses(names)} - ${generateDerivedAddresses(names, derivedAddresses)} + ${generateDerivedAddresses(names, contractData)} + + ${generateClassIdPreimages(names, contractData)} - ${await generateProtocolContractsList(names, derivedAddresses)} + ${await generateProtocolContractsList(names, contractData)} ${await generateLogTags()} `; @@ -171,17 +250,19 @@ async function main() { await fs.readFile(path.join(noirContractsRoot, 'protocol_contracts.json'), 'utf8'), ) as string[]; - const derivedAddresses: AztecAddress[] = []; + const contractDataList: ContractData[] = []; const destNames = srcNames.map(n => n.split('-')[1]); for (let i = 0; i < srcNames.length; i++) { const srcName = srcNames[i]; const destName = destNames[i]; const artifact = await copyArtifact(srcName, destName); await generateDeclarationFile(destName); - derivedAddresses.push(await computeAddress(artifact, AztecAddress.fromBigInt(contractAddressMapping[destName]))); + contractDataList.push( + await computeContractData(artifact, AztecAddress.fromBigInt(BigInt(contractAddressMapping[destName]))), + ); } - await generateOutputFile(destNames, derivedAddresses); + await generateOutputFile(destNames, contractDataList); } try { diff --git a/yarn-project/pxe/src/private_kernel/private_kernel_oracle.ts b/yarn-project/pxe/src/private_kernel/private_kernel_oracle.ts index 39babbf1a6ca..6bbf8983af94 100644 --- a/yarn-project/pxe/src/private_kernel/private_kernel_oracle.ts +++ b/yarn-project/pxe/src/private_kernel/private_kernel_oracle.ts @@ -8,11 +8,7 @@ import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash } from '@aztec/stdlib/block'; -import { - type ContractInstanceWithAddress, - computeContractClassIdPreimage, - computeSaltedInitializationHash, -} from '@aztec/stdlib/contract'; +import { type ContractInstanceWithAddress, computeSaltedInitializationHash } from '@aztec/stdlib/contract'; import { DelayedPublicMutableValues, DelayedPublicMutableValuesWithHash } from '@aztec/stdlib/delayed-public-mutable'; import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; @@ -49,11 +45,15 @@ export class PrivateKernelOracle { /** Retrieves the preimage of a contract class id from the contract classes db. */ public async getContractClassIdPreimage(contractClassId: Fr) { - const contractClass = await this.contractStore.getContractClass(contractClassId); + const contractClass = await this.contractStore.getContractClassWithPreimage(contractClassId); if (!contractClass) { throw new Error(`Contract class not found when getting class id preimage. Class id: ${contractClassId}.`); } - return computeContractClassIdPreimage(contractClass); + return { + artifactHash: contractClass.artifactHash, + privateFunctionsRoot: contractClass.privateFunctionsRoot, + publicBytecodeCommitment: contractClass.publicBytecodeCommitment, + }; } /** Returns a membership witness with the sibling path and leaf index in our private functions tree. */ diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index d1d3b8fbd050..0d70a53fc1e5 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -344,9 +344,8 @@ export class PXE { async #registerProtocolContracts() { const registered: Record = {}; for (const name of protocolContractNames) { - const { address, contractClass, instance, artifact } = - await this.protocolContractsProvider.getProtocolContractArtifact(name); - await this.contractStore.addContractArtifact(contractClass.id, artifact); + const { address, instance, artifact } = await this.protocolContractsProvider.getProtocolContractArtifact(name); + await this.contractStore.addContractArtifact(artifact); await this.contractStore.addContractInstance(instance); registered[name] = address.toString(); } @@ -601,8 +600,7 @@ export class PXE { * @param artifact - The build artifact for the contract class. */ public async registerContractClass(artifact: ContractArtifact): Promise { - const { id: contractClassId } = await getContractClassFromArtifact(artifact); - await this.contractStore.addContractArtifact(contractClassId, artifact); + const contractClassId = await this.contractStore.addContractArtifact(artifact); this.log.info(`Added contract class ${artifact.name} with id ${contractClassId}`); } @@ -621,17 +619,17 @@ export class PXE { if (artifact) { // If the user provides an artifact, validate it against the expected class id and register it const contractClass = await getContractClassFromArtifact(artifact); - const contractClassId = contractClass.id; - if (!contractClassId.equals(instance.currentContractClassId)) { + if (!contractClass.id.equals(instance.currentContractClassId)) { throw new Error( - `Artifact does not match expected class id (computed ${contractClassId} but instance refers to ${instance.currentContractClassId})`, + `Artifact does not match expected class id (computed ${contractClass.id} but instance refers to ${instance.currentContractClassId})`, ); } const computedAddress = await computeContractAddressFromInstance(instance); if (!computedAddress.equals(instance.address)) { throw new Error('Added a contract in which the address does not match the contract instance.'); } - await this.contractStore.addContractArtifact(contractClass.id, artifact); + + await this.contractStore.addContractArtifact(artifact, contractClass); const publicFunctionSignatures = artifact.functions .filter(fn => fn.functionType === FunctionType.PUBLIC) @@ -680,15 +678,16 @@ export class PXE { throw new Error('Could not update contract to a class different from the current one.'); } - await this.contractStore.addContractArtifact(contractClass.id, artifact); - const publicFunctionSignatures = artifact.functions .filter(fn => fn.functionType === FunctionType.PUBLIC) .map(fn => decodeFunctionSignature(fn.name, fn.parameters)); await this.node.registerContractFunctionSignatures(publicFunctionSignatures); currentInstance.currentContractClassId = contractClass.id; - await this.contractStore.addContractInstance(currentInstance); + await Promise.all([ + this.contractStore.addContractArtifact(artifact, contractClass), + this.contractStore.addContractInstance(currentInstance), + ]); this.log.info(`Updated contract ${artifact.name} at ${contractAddress.toString()} to class ${contractClass.id}`); }); } diff --git a/yarn-project/pxe/src/storage/contract_store/contract_store.test.ts b/yarn-project/pxe/src/storage/contract_store/contract_store.test.ts index 49034ac58a75..4b5253b863b6 100644 --- a/yarn-project/pxe/src/storage/contract_store/contract_store.test.ts +++ b/yarn-project/pxe/src/storage/contract_store/contract_store.test.ts @@ -1,10 +1,11 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { BenchmarkingContractArtifact } from '@aztec/noir-test-contracts.js/Benchmarking'; import { TestContractArtifact } from '@aztec/noir-test-contracts.js/Test'; import { FunctionType } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { SerializableContractInstance } from '@aztec/stdlib/contract'; +import { SerializableContractInstance, getContractClassFromArtifact } from '@aztec/stdlib/contract'; + +import { jest } from '@jest/globals'; import { ContractStore } from './contract_store.js'; @@ -18,8 +19,7 @@ describe('ContractStore', () => { it('stores a contract artifact', async () => { const artifact = BenchmarkingContractArtifact; - const id = Fr.random(); - await contractStore.addContractArtifact(id, artifact); + const id = await contractStore.addContractArtifact(artifact); await expect(contractStore.getContractArtifact(id)).resolves.toEqual(artifact); }); @@ -30,8 +30,7 @@ describe('ContractStore', () => { const copiedFn = structuredClone(artifact.functions[index]); artifact.functions.push(copiedFn); - const id = Fr.random(); - await expect(contractStore.addContractArtifact(id, artifact)).rejects.toThrow( + await expect(contractStore.addContractArtifact(artifact)).rejects.toThrow( 'Repeated function selectors of private functions', ); }); @@ -42,4 +41,39 @@ describe('ContractStore', () => { await contractStore.addContractInstance(instance); await expect(contractStore.getContractInstance(address)).resolves.toEqual(instance); }); + + it('reconstructs contract class with correct preimage fields', async () => { + const artifact = BenchmarkingContractArtifact; + const expected = await getContractClassFromArtifact(artifact); + await contractStore.addContractArtifact(artifact); + + const result = await contractStore.getContractClassWithPreimage(expected.id); + expect(result).toBeDefined(); + expect(result!.id).toEqual(expected.id); + expect(result!.artifactHash).toEqual(expected.artifactHash); + expect(result!.privateFunctionsRoot).toEqual(expected.privateFunctionsRoot); + expect(result!.publicBytecodeCommitment).toEqual(expected.publicBytecodeCommitment); + expect(result!.packedBytecode).toEqual(expected.packedBytecode); + expect(result!.privateFunctions).toHaveLength(expected.privateFunctions.length); + for (let i = 0; i < expected.privateFunctions.length; i++) { + expect(result!.privateFunctions[i].selector).toEqual(expected.privateFunctions[i].selector); + expect(result!.privateFunctions[i].vkHash).toEqual(expected.privateFunctions[i].vkHash); + } + }); + + it('skips KV write on cache hit', async () => { + const kvStore = await openTmpStore('contract_store_cache_test'); + const store = new ContractStore(kvStore); + const spy = jest.spyOn(kvStore, 'transactionAsync'); + + const artifact = BenchmarkingContractArtifact; + await store.addContractArtifact(artifact); + expect(spy).toHaveBeenCalledTimes(1); + + // Second add of the same artifact should hit the in-memory cache and skip the KV write + await store.addContractArtifact(artifact); + expect(spy).toHaveBeenCalledTimes(1); + + spy.mockRestore(); + }); }); diff --git a/yarn-project/pxe/src/storage/contract_store/contract_store.ts b/yarn-project/pxe/src/storage/contract_store/contract_store.ts index 6b2453850499..5b7f36a88c07 100644 --- a/yarn-project/pxe/src/storage/contract_store/contract_store.ts +++ b/yarn-project/pxe/src/storage/contract_store/contract_store.ts @@ -1,6 +1,7 @@ import type { FUNCTION_TREE_HEIGHT } from '@aztec/constants'; -import type { Fr } from '@aztec/foundation/curves/bn254'; +import { Fr } from '@aztec/foundation/curves/bn254'; import { toArray } from '@aztec/foundation/iterable'; +import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize'; import type { MembershipWitness } from '@aztec/foundation/trees'; import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; import { @@ -19,7 +20,8 @@ import { } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { - type ContractClass, + type ContractClassIdPreimage, + type ContractClassWithId, type ContractInstanceWithAddress, SerializableContractInstance, getContractClassFromArtifact, @@ -27,6 +29,68 @@ import { import { PrivateFunctionsTree } from './private_functions_tree.js'; +const VERSION = 1 as const; + +/** + * All contract class data except the large packedBytecode. + * The expensive data from the ContractClass is precomputed and stored in this format to avoid redundant hashing. + * Since we have to store the artifacts anyway, the final ContractClass is reconstructed by combining this data + * with the packedBytecode obtained from the former. That way we can have quick class lookups without wasted storage. + */ +export class SerializableContractClassData { + public readonly version = VERSION; + public readonly id: Fr; + public readonly artifactHash: Fr; + public readonly privateFunctionsRoot: Fr; + public readonly publicBytecodeCommitment: Fr; + public readonly privateFunctions: { selector: FunctionSelector; vkHash: Fr }[]; + + constructor( + data: ContractClassIdPreimage & { + id: Fr; + privateFunctions: { selector: FunctionSelector; vkHash: Fr }[]; + }, + ) { + this.id = data.id; + this.artifactHash = data.artifactHash; + this.privateFunctionsRoot = data.privateFunctionsRoot; + this.publicBytecodeCommitment = data.publicBytecodeCommitment; + this.privateFunctions = data.privateFunctions; + } + + toBuffer(): Buffer { + return serializeToBuffer( + numToUInt8(this.version), + this.id, + this.artifactHash, + this.privateFunctionsRoot, + this.publicBytecodeCommitment, + this.privateFunctions.length, + ...this.privateFunctions.map(fn => serializeToBuffer(fn.selector, fn.vkHash)), + ); + } + + static fromBuffer(bufferOrReader: Buffer | BufferReader): SerializableContractClassData { + const reader = BufferReader.asReader(bufferOrReader); + const version = reader.readUInt8(); + if (version !== VERSION) { + throw new Error(`Unexpected contract class data version ${version}`); + } + return new SerializableContractClassData({ + id: reader.readObject(Fr), + artifactHash: reader.readObject(Fr), + privateFunctionsRoot: reader.readObject(Fr), + publicBytecodeCommitment: reader.readObject(Fr), + privateFunctions: reader.readVector({ + fromBuffer: (r: BufferReader) => ({ + selector: r.readObject(FunctionSelector), + vkHash: r.readObject(Fr), + }), + }), + }); + } +} + /** * ContractStore serves as a data manager and retriever for Aztec.nr contracts. * It provides methods to obtain contract addresses, function ABI, bytecode, and membership witnesses @@ -39,42 +103,68 @@ export class ContractStore { // TODO: Update it to be LRU cache so that it doesn't keep all the data all the time. #privateFunctionTrees: Map = new Map(); - /** Map from contract address to contract class id */ + /** + * In-memory cache of deserialized ContractArtifact objects, keyed by class id string. + * Avoids repeated LMDB reads + JSON.parse + Zod validation on every oracle call. + * Artifacts are large but immutable after registration — safe to cache for the lifetime of the store. + */ + // TODO: Update it to be LRU cache so that it doesn't keep all the data all the time. + #contractArtifactCache: Map = new Map(); + + /** Map from contract address to contract class id (avoids KV round-trip on hot path). */ #contractClassIdMap: Map = new Map(); #store: AztecAsyncKVStore; #contractArtifacts: AztecAsyncMap; + #contractClassData: AztecAsyncMap; #contractInstances: AztecAsyncMap; constructor(store: AztecAsyncKVStore) { this.#store = store; this.#contractArtifacts = store.openMap('contract_artifacts'); + this.#contractClassData = store.openMap('contract_classes'); this.#contractInstances = store.openMap('contracts_instances'); } // Setters - public async addContractArtifact(id: Fr, contract: ContractArtifact): Promise { - // Validation outside transactionAsync - these are not DB operations + /** + * Registers a new contract artifact and its corresponding class data. + * IMPORTANT: This method does not verify that the provided artifact matches the class data or that the class id matches the artifact. + * It is the caller's responsibility to ensure the consistency and correctness of the provided data. + * This is done to avoid redundant, expensive contract class computations + */ + public async addContractArtifact( + contract: ContractArtifact, + contractClassWithIdAndPreimage?: ContractClassWithId & ContractClassIdPreimage, + ): Promise { + const contractClass = contractClassWithIdAndPreimage ?? (await getContractClassFromArtifact(contract)); + const key = contractClass.id.toString(); + + if (this.#contractArtifactCache.has(key)) { + return contractClass.id; + } + const privateFunctions = contract.functions.filter( functionArtifact => functionArtifact.functionType === FunctionType.PRIVATE, ); - const privateSelectors = await Promise.all( - privateFunctions.map(async privateFunctionArtifact => - ( - await FunctionSelector.fromNameAndParameters(privateFunctionArtifact.name, privateFunctionArtifact.parameters) - ).toString(), + privateFunctions.map(async fn => + (await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters)).toString(), ), ); - if (privateSelectors.length !== new Set(privateSelectors).size) { throw new Error('Repeated function selectors of private functions'); } - await this.#store.transactionAsync(() => - this.#contractArtifacts.set(id.toString(), contractArtifactToBuffer(contract)), - ); + this.#contractArtifactCache.set(key, contract); + + await this.#store.transactionAsync(async () => { + await this.#contractArtifacts.set(key, contractArtifactToBuffer(contract)); + await this.#contractClassData.set(key, new SerializableContractClassData(contractClass).toBuffer()); + }); + + return contractClass.id; } async addContractInstance(contract: ContractInstanceWithAddress): Promise { @@ -89,26 +179,17 @@ export class ContractStore { // Private getters async #getContractClassId(contractAddress: AztecAddress): Promise { - if (!this.#contractClassIdMap.has(contractAddress.toString())) { + const key = contractAddress.toString(); + if (!this.#contractClassIdMap.has(key)) { const instance = await this.getContractInstance(contractAddress); if (!instance) { return; } - this.#contractClassIdMap.set(contractAddress.toString(), instance.currentContractClassId); + this.#contractClassIdMap.set(key, instance.currentContractClassId); } - return this.#contractClassIdMap.get(contractAddress.toString()); + return this.#contractClassIdMap.get(key); } - /** - * Retrieve or create a ContractTree instance based on the provided class id. - * If an existing tree with the same class id is found in the cache, it will be returned. - * Otherwise, a new ContractTree instance will be created using the contract data from the database - * and added to the cache before returning. - * - * @param classId - The class id of the contract for which the ContractTree is required. - * @returns A ContractTree instance associated with the specified contract address. - * @throws An Error if the contract is not found in the ContractDatabase. - */ async #getPrivateFunctionTreeForClassId(classId: Fr): Promise { if (!this.#privateFunctionTrees.has(classId.toString())) { const artifact = await this.getContractArtifact(classId); @@ -121,9 +202,9 @@ export class ContractStore { return this.#privateFunctionTrees.get(classId.toString())!; } - async #getContractArtifactByAddress(contractAddress: AztecAddress): Promise { - const contractClassId = await this.#getContractClassId(contractAddress); - return contractClassId && this.getContractArtifact(contractClassId); + async #getArtifactByAddress(contractAddress: AztecAddress): Promise { + const classId = await this.#getContractClassId(contractAddress); + return classId && this.getContractArtifact(classId); } // Public getters @@ -135,7 +216,7 @@ export class ContractStore { }); } - /** Returns a contract instance for a given address. Throws if not found. */ + /** Returns a contract instance for a given address. */ public getContractInstance(contractAddress: AztecAddress): Promise { return this.#store.transactionAsync(async () => { const contract = await this.#contractInstances.getAsync(contractAddress.toString()); @@ -143,18 +224,39 @@ export class ContractStore { }); } - public getContractArtifact(contractClassId: Fr): Promise { - return this.#store.transactionAsync(async () => { - const contract = await this.#contractArtifacts.getAsync(contractClassId.toString()); - // TODO(@spalladino): AztecAsyncMap lies and returns Uint8Arrays instead of Buffers, hence the extra Buffer.from. - return contract && contractArtifactFromBuffer(Buffer.from(contract)); + /** Returns the raw contract artifact for a given class id. */ + public async getContractArtifact(contractClassId: Fr): Promise { + const key = contractClassId.toString(); + const cached = this.#contractArtifactCache.get(key); + if (cached) { + return cached; + } + const artifact = await this.#store.transactionAsync(async () => { + const buf = await this.#contractArtifacts.getAsync(key); + return buf && contractArtifactFromBuffer(buf); }); + if (artifact) { + this.#contractArtifactCache.set(key, artifact); + } + return artifact; } - /** Returns a contract class for a given class id. Throws if not found. */ - public async getContractClass(contractClassId: Fr): Promise { + /** Returns a contract class for a given class id. */ + public async getContractClassWithPreimage( + contractClassId: Fr, + ): Promise<(ContractClassWithId & ContractClassIdPreimage) | undefined> { + const key = contractClassId.toString(); + const buf = await this.#contractClassData.getAsync(key); + if (!buf) { + return undefined; + } + const classData = SerializableContractClassData.fromBuffer(buf); const artifact = await this.getContractArtifact(contractClassId); - return artifact && getContractClassFromArtifact(artifact); + if (!artifact) { + return undefined; + } + const packedBytecode = artifact.functions.find(f => f.name === 'public_dispatch')?.bytecode ?? Buffer.alloc(0); + return { ...classData, packedBytecode }; } public async getContract( @@ -173,8 +275,6 @@ export class ContractStore { /** * Retrieves the artifact of a specified function within a given contract. - * The function is identified by its selector, which is a unique code generated from the function's signature. - * Throws an error if the contract address or function selector are invalid or not found. * * @param contractAddress - The AztecAddress representing the contract containing the function. * @param selector - The function selector. @@ -184,9 +284,12 @@ export class ContractStore { contractAddress: AztecAddress, selector: FunctionSelector, ): Promise { - const artifact = await this.#getContractArtifactByAddress(contractAddress); - const fnArtifact = artifact && (await this.#findFunctionArtifactBySelector(artifact, selector)); - return fnArtifact && { ...fnArtifact, contractName: artifact.name }; + const artifact = await this.#getArtifactByAddress(contractAddress); + if (!artifact) { + return undefined; + } + const fn = await this.#findFunctionArtifactBySelector(artifact, selector); + return fn && { ...fn, contractName: artifact.name }; } public async getFunctionArtifactWithDebugMetadata( @@ -207,50 +310,48 @@ export class ContractStore { public async getPublicFunctionArtifact( contractAddress: AztecAddress, ): Promise { - const artifact = await this.#getContractArtifactByAddress(contractAddress); - const fnArtifact = artifact && artifact.functions.find(fn => fn.functionType === FunctionType.PUBLIC); - return fnArtifact && { ...fnArtifact, contractName: artifact.name }; + const artifact = await this.#getArtifactByAddress(contractAddress); + const fn = artifact && artifact.functions.find(f => f.functionType === FunctionType.PUBLIC); + return fn && { ...fn, contractName: artifact.name }; } public async getFunctionAbi( contractAddress: AztecAddress, selector: FunctionSelector, ): Promise { - const artifact = await this.#getContractArtifactByAddress(contractAddress); + const artifact = await this.#getArtifactByAddress(contractAddress); return artifact && (await this.#findFunctionAbiBySelector(artifact, selector)); } /** * Retrieves the debug metadata of a specified function within a given contract. - * The function is identified by its selector, which is a unique code generated from the function's signature. - * Returns undefined if the debug metadata for the given function is not found. - * Throws if the contract has not been added to the database. * * @param contractAddress - The AztecAddress representing the contract containing the function. * @param selector - The function selector. - * @returns The corresponding function's artifact as an object. + * @returns The corresponding function's debug metadata, or undefined. */ public async getFunctionDebugMetadata( contractAddress: AztecAddress, selector: FunctionSelector, ): Promise { - const artifact = await this.#getContractArtifactByAddress(contractAddress); - const fnArtifact = artifact && (await this.#findFunctionArtifactBySelector(artifact, selector)); - return fnArtifact && getFunctionDebugMetadata(artifact, fnArtifact); + const artifact = await this.#getArtifactByAddress(contractAddress); + if (!artifact) { + return undefined; + } + const fn = await this.#findFunctionArtifactBySelector(artifact, selector); + return fn && getFunctionDebugMetadata(artifact, fn); } public async getPublicFunctionDebugMetadata( contractAddress: AztecAddress, ): Promise { - const artifact = await this.#getContractArtifactByAddress(contractAddress); - const fnArtifact = artifact && artifact.functions.find(fn => fn.functionType === FunctionType.PUBLIC); - return fnArtifact && getFunctionDebugMetadata(artifact, fnArtifact); + const artifact = await this.#getArtifactByAddress(contractAddress); + const fn = artifact && artifact.functions.find(f => f.functionType === FunctionType.PUBLIC); + return fn && getFunctionDebugMetadata(artifact, fn); } /** * Retrieve the function membership witness for the given contract class and function selector. - * The function membership witness represents a proof that the function belongs to the specified contract. - * Throws an error if the contract address or function selector is unknown. * * @param contractClassId - The id of the class. * @param selector - The function selector. @@ -265,23 +366,21 @@ export class ContractStore { } public async getDebugContractName(contractAddress: AztecAddress) { - const artifact = await this.#getContractArtifactByAddress(contractAddress); + const artifact = await this.#getArtifactByAddress(contractAddress); return artifact?.name; } public async getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector) { - const artifact = await this.#getContractArtifactByAddress(contractAddress); - const fnArtifact = artifact && (await this.#findFunctionAbiBySelector(artifact, selector)); - return `${artifact?.name ?? contractAddress}:${fnArtifact?.name ?? selector}`; + const artifact = await this.#getArtifactByAddress(contractAddress); + const fn = artifact && (await this.#findFunctionAbiBySelector(artifact, selector)); + return `${artifact?.name ?? contractAddress}:${fn?.name ?? selector}`; } async #findFunctionArtifactBySelector( artifact: ContractArtifact, selector: FunctionSelector, ): Promise { - const functions = artifact.functions; - for (let i = 0; i < functions.length; i++) { - const fn = functions[i]; + for (const fn of artifact.functions) { const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters); if (fnSelector.equals(selector)) { return fn; @@ -293,9 +392,7 @@ export class ContractStore { artifact: ContractArtifact, selector: FunctionSelector, ): Promise { - const functions = [...artifact.functions, ...(artifact.nonDispatchPublicFunctions ?? [])]; - for (let i = 0; i < functions.length; i++) { - const fn = functions[i]; + for (const fn of [...artifact.functions, ...(artifact.nonDispatchPublicFunctions ?? [])]) { const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters); if (fnSelector.equals(selector)) { return fn; @@ -316,10 +413,12 @@ export class ContractStore { throw new Error(`Unknown function ${functionName} in contract ${contract.name}.`); } + const selector = await FunctionSelector.fromNameAndParameters(functionDao.name, functionDao.parameters); + return FunctionCall.from({ name: functionDao.name, to, - selector: await FunctionSelector.fromNameAndParameters(functionDao.name, functionDao.parameters), + selector, type: functionDao.functionType, hideMsgSender: false, isStatic: functionDao.isStatic, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 071613e1c491..527f7144bf60 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -422,6 +422,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter { it('creates a contract class from a contract compilation artifact', async () => { const artifact = getBenchmarkContractArtifact(); - const contractClass = await getContractClassFromArtifact({ - ...artifact, - artifactHash: Fr.fromHexString('0x1234'), - }); + const contractClass = await getContractClassFromArtifact(artifact); // Assert bytecode has a reasonable length expect(contractClass.packedBytecode.length).toBeGreaterThan(100); diff --git a/yarn-project/stdlib/src/contract/contract_class.ts b/yarn-project/stdlib/src/contract/contract_class.ts index 8b400b45b580..68706437dbb4 100644 --- a/yarn-project/stdlib/src/contract/contract_class.ts +++ b/yarn-project/stdlib/src/contract/contract_class.ts @@ -8,7 +8,7 @@ import { type ContractClassIdPreimage, computeContractClassIdWithPreimage } from import type { ContractClass, ContractClassWithId } from './interfaces/index.js'; /** Contract artifact including its artifact hash */ -type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr }; +export type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr }; const cmpFunctionArtifacts = (a: T, b: T) => a.selector.toField().cmp(b.selector.toField()); @@ -35,8 +35,8 @@ export async function getContractClassFromArtifact( privateArtifactFunctions.sort(cmpFunctionArtifacts); - const contractClass: ContractClass = { - version: 1, + const contractClass = { + version: 1 as const, artifactHash, packedBytecode, privateFunctions: privateArtifactFunctions, diff --git a/yarn-project/txe/src/index.ts b/yarn-project/txe/src/index.ts index bdcfa01af09b..76c25b5a4751 100644 --- a/yarn-project/txe/src/index.ts +++ b/yarn-project/txe/src/index.ts @@ -9,9 +9,12 @@ import { Fr } from '@aztec/aztec.js/fields'; import { PublicKeys, deriveKeys } from '@aztec/aztec.js/keys'; import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server'; import type { Logger } from '@aztec/foundation/log'; -import { type ProtocolContract, protocolContractNames } from '@aztec/protocol-contracts'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { protocolContractNames } from '@aztec/protocol-contracts'; import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle'; +import { ContractStore } from '@aztec/pxe/server'; import { computeArtifactHash } from '@aztec/stdlib/contract'; +import type { ContractArtifactWithHash } from '@aztec/stdlib/contract'; import type { ApiSchemaFor } from '@aztec/stdlib/schemas'; import { zodFor } from '@aztec/stdlib/schemas'; @@ -33,18 +36,24 @@ import { fromSingle, toSingle, } from './util/encoding.js'; -import type { ContractArtifactWithHash } from './util/txe_contract_store.js'; const sessions = new Map(); /* * TXE typically has to load the same contract artifacts over and over again for multiple tests, - * so we cache them here to avoid both loading them from disk repeatedly and computing their artifact hashes + * so we cache them here to avoid loading from disk repeatedly. + * + * The in-flight map coalesces concurrent requests for the same cache key so that + * computeArtifactHash (very expensive) is only run once even under parallelism. */ const TXEArtifactsCache = new Map< string, { artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress } >(); +const TXEArtifactsCacheInFlight = new Map< + string, + Promise<{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }> +>(); type TXEForeignCallInput = { session_id: number; @@ -68,7 +77,7 @@ const TXEForeignCallInputSchema = zodFor()( ); class TXEDispatcher { - private protocolContracts!: ProtocolContract[]; + private contractStore!: ContractStore; constructor(private logger: Logger) {} @@ -135,29 +144,36 @@ class TXEDispatcher { this.logger.debug(`Using cached artifact for ${cacheKey}`); ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!); } else { - this.logger.debug(`Loading compiled artifact ${artifactPath}`); - const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract; - const artifactWithoutHash = loadContractArtifact(artifactJSON); - artifact = { - ...artifactWithoutHash, - // Artifact hash is *very* expensive to compute, so we do it here once - // and the TXE contract data provider can cache it - artifactHash: await computeArtifactHash(artifactWithoutHash), - }; - this.logger.debug( - `Deploy ${ - artifact.name - } with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`, - ); - instance = await getContractInstanceFromInstantiationParams(artifact, { - constructorArgs: decodedArgs, - skipArgsDecoding: true, - salt: Fr.ONE, - publicKeys, - constructorArtifact: initializer ? initializer : undefined, - deployer: AztecAddress.ZERO, - }); - TXEArtifactsCache.set(cacheKey, { artifact, instance }); + if (!TXEArtifactsCacheInFlight.has(cacheKey)) { + this.logger.debug(`Loading compiled artifact ${artifactPath}`); + const compute = async () => { + const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract; + const artifactWithoutHash = loadContractArtifact(artifactJSON); + const computedArtifact: ContractArtifactWithHash = { + ...artifactWithoutHash, + // Artifact hash is *very* expensive to compute, so we do it here once + // and the TXE contract data provider can cache it + artifactHash: await computeArtifactHash(artifactWithoutHash), + }; + this.logger.debug( + `Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`, + ); + const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, { + constructorArgs: decodedArgs, + skipArgsDecoding: true, + salt: Fr.ONE, + publicKeys, + constructorArtifact: initializer ? initializer : undefined, + deployer: AztecAddress.ZERO, + }); + const result = { artifact: computedArtifact, instance: computedInstance }; + TXEArtifactsCache.set(cacheKey, result); + TXEArtifactsCacheInFlight.delete(cacheKey); + return result; + }; + TXEArtifactsCacheInFlight.set(cacheKey, compute()); + } + ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!); } inputs.splice(0, 1, artifact, instance, toSingle(secret)); @@ -175,23 +191,35 @@ class TXEDispatcher { this.logger.debug(`Using cached artifact for ${cacheKey}`); ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!); } else { - const keys = await deriveKeys(secret); - const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y]; - artifact = { - ...SchnorrAccountContractArtifact, - // Artifact hash is *very* expensive to compute, so we do it here once - // and the TXE contract data provider can cache it - artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact), - }; - instance = await getContractInstanceFromInstantiationParams(artifact, { - constructorArgs: args, - skipArgsDecoding: true, - salt: Fr.ONE, - publicKeys: keys.publicKeys, - constructorArtifact: 'constructor', - deployer: AztecAddress.ZERO, - }); - TXEArtifactsCache.set(cacheKey, { artifact, instance }); + if (!TXEArtifactsCacheInFlight.has(cacheKey)) { + const compute = async () => { + const keys = await deriveKeys(secret); + const args = [ + keys.publicKeys.masterIncomingViewingPublicKey.x, + keys.publicKeys.masterIncomingViewingPublicKey.y, + ]; + const computedArtifact: ContractArtifactWithHash = { + ...SchnorrAccountContractArtifact, + // Artifact hash is *very* expensive to compute, so we do it here once + // and the TXE contract data provider can cache it + artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact), + }; + const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, { + constructorArgs: args, + skipArgsDecoding: true, + salt: Fr.ONE, + publicKeys: keys.publicKeys, + constructorArtifact: 'constructor', + deployer: AztecAddress.ZERO, + }); + const result = { artifact: computedArtifact, instance: computedInstance }; + TXEArtifactsCache.set(cacheKey, result); + TXEArtifactsCacheInFlight.delete(cacheKey); + return result; + }; + TXEArtifactsCacheInFlight.set(cacheKey, compute()); + } + ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!); } inputs.splice(0, 0, artifact, instance); @@ -204,12 +232,18 @@ class TXEDispatcher { if (!sessions.has(sessionId)) { this.logger.debug(`Creating new session ${sessionId}`); - if (!this.protocolContracts) { - this.protocolContracts = await Promise.all( - protocolContractNames.map(name => new BundledProtocolContractsProvider().getProtocolContractArtifact(name)), - ); + if (!this.contractStore) { + const kvStore = await openTmpStore('txe-contracts'); + this.contractStore = new ContractStore(kvStore); + const provider = new BundledProtocolContractsProvider(); + for (const name of protocolContractNames) { + const { instance, artifact } = await provider.getProtocolContractArtifact(name); + await this.contractStore.addContractArtifact(artifact); + await this.contractStore.addContractInstance(instance); + } + this.logger.debug('Registered protocol contracts in shared contract store'); } - sessions.set(sessionId, await TXESession.init(this.protocolContracts)); + sessions.set(sessionId, await TXESession.init(this.contractStore)); } switch (functionName) { diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 29ec046bec14..59947893fd7c 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -16,6 +16,7 @@ import type { AccessScopes } from '@aztec/pxe/client/lazy'; import { AddressStore, CapsuleStore, + type ContractStore, NoteStore, ORACLE_VERSION, PrivateEventStore, @@ -84,7 +85,6 @@ import { ForkCheckpoint } from '@aztec/world-state'; import { DEFAULT_ADDRESS } from '../constants.js'; import type { TXEStateMachine } from '../state_machine/index.js'; import type { TXEAccountStore } from '../util/txe_account_store.js'; -import type { TXEContractStore } from '../util/txe_contract_store.js'; import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js'; import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from '../utils/block_creation.js'; import type { ITxeExecutionOracle } from './interfaces.js'; @@ -97,7 +97,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl constructor( private stateMachine: TXEStateMachine, - private contractStore: TXEContractStore, + private contractStore: ContractStore, private noteStore: NoteStore, private keyStore: KeyStore, private addressStore: AddressStore, @@ -211,7 +211,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl await this.txeAddAccount(artifact, instance, secret); } else { await this.contractStore.addContractInstance(instance); - await this.contractStore.addContractArtifact(instance.currentContractClassId, artifact); + await this.contractStore.addContractArtifact(artifact); this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`); } } @@ -221,7 +221,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`); await this.contractStore.addContractInstance(instance); - await this.contractStore.addContractArtifact(instance.currentContractClassId, artifact); + await this.contractStore.addContractArtifact(artifact); const completeAddress = await this.keyStore.addAccount(secret, partialAddress); await this.accountStore.setAccount(completeAddress.address, completeAddress); diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 5f6f2598cd4a..157f10bb1983 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -3,12 +3,12 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import type { ProtocolContract } from '@aztec/protocol-contracts'; import type { AccessScopes } from '@aztec/pxe/client/lazy'; import { AddressStore, AnchorBlockStore, CapsuleStore, + ContractStore, JobCoordinator, NoteService, NoteStore, @@ -55,7 +55,6 @@ import { TXEArchiver } from './state_machine/archiver.js'; import { TXEStateMachine } from './state_machine/index.js'; import type { ForeignCallArgs, ForeignCallResult } from './util/encoding.js'; import { TXEAccountStore } from './util/txe_account_store.js'; -import { TXEContractStore } from './util/txe_contract_store.js'; import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from './utils/block_creation.js'; import { makeTxEffect } from './utils/tx_effect_creation.js'; @@ -132,7 +131,7 @@ export class TXESession implements TXESessionStateHandler { | IPrivateExecutionOracle | IAvmExecutionOracle | ITxeExecutionOracle, - private contractStore: TXEContractStore, + private contractStore: ContractStore, private noteStore: NoteStore, private keyStore: KeyStore, private addressStore: AddressStore, @@ -149,12 +148,11 @@ export class TXESession implements TXESessionStateHandler { private nextBlockTimestamp: bigint, ) {} - static async init(protocolContracts: ProtocolContract[]) { + static async init(contractStore: ContractStore) { const store = await openTmpStore('txe-session'); const addressStore = new AddressStore(store); const privateEventStore = new PrivateEventStore(store); - const contractStore = new TXEContractStore(store); const noteStore = new NoteStore(store); const senderTaggingStore = new SenderTaggingStore(store); const recipientTaggingStore = new RecipientTaggingStore(store); @@ -173,12 +171,6 @@ export class TXESession implements TXESessionStateHandler { noteStore, ]); - // Register protocol contracts. - for (const { contractClass, instance, artifact } of protocolContracts) { - await contractStore.addContractArtifact(contractClass.id, artifact); - await contractStore.addContractInstance(instance); - } - const archiver = new TXEArchiver(store); const anchorBlockStore = new AnchorBlockStore(store); const stateMachine = await TXEStateMachine.create(archiver, anchorBlockStore, contractStore, noteStore); diff --git a/yarn-project/txe/src/util/txe_contract_store.ts b/yarn-project/txe/src/util/txe_contract_store.ts deleted file mode 100644 index 4ca375531318..000000000000 --- a/yarn-project/txe/src/util/txe_contract_store.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { ContractArtifact } from '@aztec/aztec.js/abi'; -import { Fr } from '@aztec/aztec.js/fields'; -import { ContractStore } from '@aztec/pxe/server'; - -export type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr }; - -/* - * A contract store that stores contract artifacts with their hashes. Since - * TXE typically deploys the same contract again and again for multiple tests, caching - * the *very* expensive artifact hash computation improves testing speed significantly. - */ -export class TXEContractStore extends ContractStore { - #artifactHashes: Map = new Map(); - - public override async addContractArtifact( - id: Fr, - artifact: ContractArtifact | ContractArtifactWithHash, - ): Promise { - if ('artifactHash' in artifact) { - this.#artifactHashes.set(id.toString(), artifact.artifactHash.toBuffer()); - } - await super.addContractArtifact(id, artifact); - } - - public override async getContractArtifact( - contractClassId: Fr, - ): Promise { - const artifact = await super.getContractArtifact(contractClassId); - if (artifact && this.#artifactHashes.has(contractClassId.toString())) { - (artifact as ContractArtifactWithHash).artifactHash = Fr.fromBuffer( - this.#artifactHashes.get(contractClassId.toString())!, - ); - } - return artifact; - } -} diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts index 0ec38ae2dcd2..8573d54ee6c8 100644 --- a/yarn-project/txe/src/util/txe_public_contract_data_source.ts +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -1,19 +1,11 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { ContractStore } from '@aztec/pxe/server'; -import { type ContractArtifact, FunctionSelector, FunctionType } from '@aztec/stdlib/abi'; +import { type ContractArtifact, FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { - type ContractClassPublic, - type ContractDataSource, - type ContractInstanceWithAddress, - computePrivateFunctionsRoot, - computePublicBytecodeCommitment, - getContractClassPrivateFunctionFromArtifact, -} from '@aztec/stdlib/contract'; +import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; export class TXEPublicContractDataSource implements ContractDataSource { - #privateFunctionsRoot: Map = new Map(); constructor( private blockNumber: BlockNumber, private contractStore: ContractStore, @@ -24,42 +16,24 @@ export class TXEPublicContractDataSource implements ContractDataSource { } async getContractClass(id: Fr): Promise { - const contractClass = await this.contractStore.getContractClass(id); + const contractClass = await this.contractStore.getContractClassWithPreimage(id); if (!contractClass) { return; } - const artifact = await this.contractStore.getContractArtifact(id); - if (!artifact) { - return; - } - - let privateFunctionsRoot; - if (!this.#privateFunctionsRoot.has(id.toString())) { - const privateFunctions = await Promise.all( - artifact.functions - .filter(fn => fn.functionType === FunctionType.PRIVATE) - .map(fn => getContractClassPrivateFunctionFromArtifact(fn)), - ); - privateFunctionsRoot = await computePrivateFunctionsRoot(privateFunctions); - this.#privateFunctionsRoot.set(id.toString(), privateFunctionsRoot.toBuffer()); - } else { - privateFunctionsRoot = Fr.fromBuffer(this.#privateFunctionsRoot.get(id.toString())!); - } - return { - id, - artifactHash: contractClass!.artifactHash, - packedBytecode: contractClass!.packedBytecode, - privateFunctionsRoot, - version: contractClass!.version, + id: contractClass.id, + artifactHash: contractClass.artifactHash, + packedBytecode: contractClass.packedBytecode, + privateFunctionsRoot: contractClass.privateFunctionsRoot, + version: contractClass.version, privateFunctions: [], utilityFunctions: [], }; } async getBytecodeCommitment(id: Fr): Promise { - const contractClass = await this.contractStore.getContractClass(id); - return contractClass && computePublicBytecodeCommitment(contractClass.packedBytecode); + const contractClass = await this.contractStore.getContractClassWithPreimage(id); + return contractClass?.publicBytecodeCommitment; } async getContract(address: AztecAddress): Promise {