Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)}"

Expand Down
2 changes: 1 addition & 1 deletion noir-projects/noir-contracts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec/scripts/aztec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec/src/cli/aztec_start_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/aztec/src/local-network/local-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -181,6 +182,21 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
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<string> = 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(
Expand Down
74 changes: 59 additions & 15 deletions yarn-project/aztec/src/testing/anvil_test_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>;

// 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,
Expand Down Expand Up @@ -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<number>) {
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');
Expand Down Expand Up @@ -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;
}
Expand All @@ -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}`);
}
}
}
4 changes: 3 additions & 1 deletion yarn-project/cli/src/config/chain_l2_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/foundation/src/config/network_name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}`);
}
6 changes: 3 additions & 3 deletions yarn-project/protocol-contracts/src/auth-registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProtocolContract> {
export function getCanonicalAuthRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
protocolContract = await makeProtocolContract('AuthRegistry', AuthRegistryArtifact);
protocolContract = makeProtocolContract('AuthRegistry', AuthRegistryArtifact);
}
return protocolContract;
return Promise.resolve(protocolContract);
}
2 changes: 1 addition & 1 deletion yarn-project/protocol-contracts/src/auth-registry/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getAuthRegistryArtifact(): Promise<ContractArtifact> {
export async function getCanonicalAuthRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
const authRegistryArtifact = await getAuthRegistryArtifact();
protocolContract = await makeProtocolContract('AuthRegistry', authRegistryArtifact);
protocolContract = makeProtocolContract('AuthRegistry', authRegistryArtifact);
}
return protocolContract;
}
6 changes: 3 additions & 3 deletions yarn-project/protocol-contracts/src/class-registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export const ContractClassRegistryArtifact = loadContractArtifact(ContractClassR
let protocolContract: ProtocolContract;

/** Returns the canonical deployment of the contract. */
export async function getCanonicalClassRegistry(): Promise<ProtocolContract> {
export function getCanonicalClassRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
const artifact = ContractClassRegistryArtifact;
protocolContract = await makeProtocolContract('ContractClassRegistry', artifact);
protocolContract = makeProtocolContract('ContractClassRegistry', artifact);
}
return protocolContract;
return Promise.resolve(protocolContract);
}
2 changes: 1 addition & 1 deletion yarn-project/protocol-contracts/src/class-registry/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function getContractClassRegistryArtifact(): Promise<ContractArtifa
export async function getCanonicalClassRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
const contractClassRegistryArtifact = await getContractClassRegistryArtifact();
protocolContract = await makeProtocolContract('ContractClassRegistry', contractClassRegistryArtifact);
protocolContract = makeProtocolContract('ContractClassRegistry', contractClassRegistryArtifact);
}
return protocolContract;
}
6 changes: 3 additions & 3 deletions yarn-project/protocol-contracts/src/fee-juice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProtocolContract> {
export function getCanonicalFeeJuice(): Promise<ProtocolContract> {
if (!protocolContract) {
protocolContract = await makeProtocolContract('FeeJuice', FeeJuiceArtifact);
protocolContract = makeProtocolContract('FeeJuice', FeeJuiceArtifact);
}
return protocolContract;
return Promise.resolve(protocolContract);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/protocol-contracts/src/fee-juice/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getFeeJuiceArtifact(): Promise<ContractArtifact> {
export async function getCanonicalFeeJuice(): Promise<ProtocolContract> {
if (!protocolContract) {
const feeJuiceArtifact = await getFeeJuiceArtifact();
protocolContract = await makeProtocolContract('FeeJuice', feeJuiceArtifact);
protocolContract = makeProtocolContract('FeeJuice', feeJuiceArtifact);
}
return protocolContract;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export const ContractInstanceRegistryArtifact = loadContractArtifact(
let protocolContract: ProtocolContract;

/** Returns the canonical deployment of the contract. */
export async function getCanonicalInstanceRegistry(): Promise<ProtocolContract> {
export function getCanonicalInstanceRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
protocolContract = await makeProtocolContract('ContractInstanceRegistry', ContractInstanceRegistryArtifact);
protocolContract = makeProtocolContract('ContractInstanceRegistry', ContractInstanceRegistryArtifact);
}
return protocolContract;
return Promise.resolve(protocolContract);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function getContractInstanceRegistryArtifact(): Promise<ContractArt
export async function getCanonicalInstanceRegistry(): Promise<ProtocolContract> {
if (!protocolContract) {
const contractInstanceRegistryArtifact = await getContractInstanceRegistryArtifact();
protocolContract = await makeProtocolContract('ContractInstanceRegistry', contractInstanceRegistryArtifact);
protocolContract = makeProtocolContract('ContractInstanceRegistry', contractInstanceRegistryArtifact);
}
return protocolContract;
}
52 changes: 37 additions & 15 deletions yarn-project/protocol-contracts/src/make_protocol_contract.ts
Original file line number Diff line number Diff line change
@@ -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<ProtocolContract> {
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 };
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export const MultiCallEntrypointArtifact = loadContractArtifact(MultiCallEntrypo
let protocolContract: ProtocolContract;

/** Returns the canonical deployment of the contract. */
export async function getCanonicalMultiCallEntrypoint(): Promise<ProtocolContract> {
export function getCanonicalMultiCallEntrypoint(): Promise<ProtocolContract> {
if (!protocolContract) {
protocolContract = await makeProtocolContract('MultiCallEntrypoint', MultiCallEntrypointArtifact);
protocolContract = makeProtocolContract('MultiCallEntrypoint', MultiCallEntrypointArtifact);
}
return protocolContract;
return Promise.resolve(protocolContract);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getMultiCallEntrypointArtifact(): Promise<ContractArtifact
export async function getCanonicalMultiCallEntrypoint(): Promise<ProtocolContract> {
if (!protocolContract) {
const multiCallEntrypointArtifact = await getMultiCallEntrypointArtifact();
protocolContract = await makeProtocolContract('MultiCallEntrypoint', multiCallEntrypointArtifact);
protocolContract = makeProtocolContract('MultiCallEntrypoint', multiCallEntrypointArtifact);
}
return protocolContract;
}
2 changes: 1 addition & 1 deletion yarn-project/protocol-contracts/src/provider/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export const ProtocolContractArtifact: Record<ProtocolContractName, ContractArti

export class BundledProtocolContractsProvider implements ProtocolContractsProvider {
getProtocolContractArtifact(name: ProtocolContractName): Promise<ProtocolContract> {
return makeProtocolContract(name, ProtocolContractArtifact[name]);
return Promise.resolve(makeProtocolContract(name, ProtocolContractArtifact[name]));
}
}
Loading
Loading