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
4 changes: 4 additions & 0 deletions docs/docs/developers/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Aztec is in full-speed development. Literally every version breaks compatibility
This release includes a major architectural change to the system.
The PXE JSON RPC Server has been removed, and PXE is now available only as a library to be used by wallets.

## [Aztec node]

Network config. The node now pulls default configuration from the public repository [AztecProtocol/networks](https://github.com/AztecProtocol/networks) after it applies the configuration it takes from the running environment and the configuration values baked into the source code. See associated [Design document](https://github.com/AztecProtocol/engineering-designs/blob/15415a62a7c8e901acb8e523625e91fc6f71dce4/docs/network-config/dd.md)

## [Aztec.js]

### CLI Wallet commands dropped from `aztec` command
Expand Down
6 changes: 4 additions & 2 deletions yarn-project/aztec/src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
import { injectCommands as injectBuilderCommands } from '@aztec/builder';
import { injectCommands as injectAztecNodeCommands } from '@aztec/cli/aztec_node';
import { enrichEnvironmentWithChainConfig } from '@aztec/cli/config';
import { enrichEnvironmentWithChainConfig, enrichEnvironmentWithNetworkConfig } from '@aztec/cli/config';
import { injectCommands as injectContractCommands } from '@aztec/cli/contracts';
import { injectCommands as injectDevnetCommands } from '@aztec/cli/devnet';
import { injectCommands as injectInfrastructureCommands } from '@aztec/cli/infrastructure';
Expand Down Expand Up @@ -38,7 +38,9 @@ async function main() {
networkValue = args[networkIndex].split('=')[1] || args[networkIndex + 1];
}

await enrichEnvironmentWithChainConfig(getActiveNetworkName(networkValue));
const networkName = getActiveNetworkName(networkValue);
await enrichEnvironmentWithChainConfig(networkName);
await enrichEnvironmentWithNetworkConfig(networkName);

const cliVersion = getCliVersion();
let program = new Command('aztec');
Expand Down
67 changes: 67 additions & 0 deletions yarn-project/cli/src/config/cached_fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createLogger } from '@aztec/aztec.js';

import { mkdir, readFile, stat, writeFile } from 'fs/promises';
import { dirname } from 'path';

export interface CachedFetchOptions {
/** Cache duration in milliseconds */
cacheDurationMs: number;
/** The cache file */
cacheFile?: string;
}

/**
* Fetches data from a URL with file-based caching support.
* This utility can be used by both remote config and bootnodes fetching.
*
* @param url - The URL to fetch from
* @param networkName - Network name for cache directory structure
* @param options - Caching and error handling options
* @param cacheDir - Optional cache directory (defaults to no caching)
* @returns The fetched and parsed JSON data, or undefined if fetch fails and throwOnError is false
*/
export async function cachedFetch<T = any>(
url: string,
options: CachedFetchOptions,
fetch = globalThis.fetch,
log = createLogger('cached_fetch'),
): Promise<T | undefined> {
const { cacheDurationMs, cacheFile } = options;

// Try to read from cache first
try {
if (cacheFile) {
const info = await stat(cacheFile);
if (info.mtimeMs + cacheDurationMs > Date.now()) {
const cachedData = JSON.parse(await readFile(cacheFile, 'utf-8'));
return cachedData;
}
}
} catch {
log.trace('Failed to read data from cache');
}

try {
const response = await fetch(url);
if (!response.ok) {
log.warn(`Failed to fetch from ${url}: ${response.status} ${response.statusText}`);
return undefined;
}

const data = await response.json();

try {
if (cacheFile) {
await mkdir(dirname(cacheFile), { recursive: true });
await writeFile(cacheFile, JSON.stringify(data), 'utf-8');
}
} catch (err) {
log.warn('Failed to cache data on disk: ' + cacheFile, { cacheFile, err });
}

return data;
} catch (err) {
log.warn(`Failed to fetch from ${url}`, { err });
return undefined;
}
}
68 changes: 14 additions & 54 deletions yarn-project/cli/src/config/chain_l2_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { EthAddress } from '@aztec/foundation/eth-address';
import type { SharedNodeConfig } from '@aztec/node-lib/config';
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';

import { mkdir, readFile, stat, writeFile } from 'fs/promises';
import path, { dirname, join } from 'path';
import path, { join } from 'path';

import publicIncludeMetrics from '../../public_include_metric_prefixes.json' with { type: 'json' };
import { cachedFetch } from './cached_fetch.js';
import { enrichEthAddressVar, enrichVar } from './enrich_env.js';

const SNAPSHOT_URL = 'https://pub-f4a8c34d4bb7441ebf8f48d904512180.r2.dev/snapshots';

Expand Down Expand Up @@ -79,9 +80,9 @@ export const stagingIgnitionL2ChainConfig: L2ChainConfig = {
sponsoredFPC: false,
p2pEnabled: true,
p2pBootstrapNodes: [],
registryAddress: '0x5f85fa0f40bc4b5ccd53c9f34258aa55d25cdde8',
slashFactoryAddress: '0x257db2ca1471b7f76f414d2997404bfbe916c8c9',
feeAssetHandlerAddress: '0x67d645b0a3e053605ea861d7e8909be6669812c4',
registryAddress: '0x53e2c2148da04fd0e8dd282f016f627a187c292c',
slashFactoryAddress: '0x56448efb139ef440438dfa445333734ea5ed60a0',
feeAssetHandlerAddress: '0x3613b834544030c166a4d47eca14b910f4816f57',
seqMinTxsPerBlock: 0,
seqMaxTxsPerBlock: 0,
realProofs: true,
Expand Down Expand Up @@ -160,9 +161,9 @@ export const stagingPublicL2ChainConfig: L2ChainConfig = {
sponsoredFPC: true,
p2pEnabled: true,
p2pBootstrapNodes: [],
registryAddress: '0x2e48addca360da61e4d6c21ff2b1961af56eb83b',
slashFactoryAddress: '0xe19410632fd00695bc5a08dd82044b7b26317742',
feeAssetHandlerAddress: '0xb46dc3d91f849999330b6dd93473fa29fc45b076',
registryAddress: '0xe83067689f3cf837ccbf8a3966f0e0fe985dcb3e',
slashFactoryAddress: '0x8b87a1812162d4890f01bb40f410047f37d3ceb8',
feeAssetHandlerAddress: '0xa8159159a9e2a57c6e8c59fd5b3dd94c6dbddfe3',
seqMinTxsPerBlock: 0,
seqMaxTxsPerBlock: 20,
realProofs: true,
Expand Down Expand Up @@ -265,37 +266,13 @@ export const testnetL2ChainConfig: L2ChainConfig = {
const BOOTNODE_CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour;

export async function getBootnodes(networkName: NetworkNames, cacheDir?: string) {
const cacheFile = cacheDir ? join(cacheDir, networkName, 'bootnodes.json') : undefined;
try {
if (cacheFile) {
const info = await stat(cacheFile);
if (info.mtimeMs + BOOTNODE_CACHE_DURATION_MS > Date.now()) {
return JSON.parse(await readFile(cacheFile, 'utf-8'))['bootnodes'];
}
}
} catch {
// no-op. Get the remote-file
}

const url = `http://static.aztec.network/${networkName}/bootnodes.json`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch basic contract addresses from ${url}. Check you are using a correct network name.`,
);
}
const json = await response.json();

try {
if (cacheFile) {
await mkdir(dirname(cacheFile), { recursive: true });
await writeFile(cacheFile, JSON.stringify(json), 'utf-8');
}
} catch {
// no-op
}
const data = await cachedFetch(url, {
cacheDurationMs: BOOTNODE_CACHE_DURATION_MS,
cacheFile: cacheDir ? join(cacheDir, networkName, 'bootnodes.json') : undefined,
});

return json['bootnodes'];
return data?.bootnodes;
}

export async function getL2ChainConfig(
Expand All @@ -321,23 +298,6 @@ export async function getL2ChainConfig(
return config;
}

function enrichVar(envVar: EnvVar, value: string | undefined) {
// Don't override
if (process.env[envVar] || value === undefined) {
return;
}
process.env[envVar] = value;
}

function enrichEthAddressVar(envVar: EnvVar, value: string) {
// EthAddress doesn't like being given empty strings
if (value === '') {
enrichVar(envVar, EthAddress.ZERO.toString());
return;
}
enrichVar(envVar, value);
}

function getDefaultDataDir(networkName: NetworkNames): string {
return path.join(process.env.HOME || '~', '.aztec', networkName, 'data');
}
Expand Down
15 changes: 15 additions & 0 deletions yarn-project/cli/src/config/enrich_env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { EthAddress } from '@aztec/aztec.js';
import type { EnvVar } from '@aztec/foundation/config';

export function enrichVar(envVar: EnvVar, value: string | undefined) {
// Don't override
if (process.env[envVar] || value === undefined) {
return;
}
process.env[envVar] = value;
}

export function enrichEthAddressVar(envVar: EnvVar, value: string) {
// EthAddress doesn't like being given empty strings
enrichVar(envVar, value || EthAddress.ZERO.toString());
}
2 changes: 2 additions & 0 deletions yarn-project/cli/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './cached_fetch.js';
export * from './chain_l2_config.js';
export * from './get_l1_config.js';
export * from './network_config.js';
108 changes: 108 additions & 0 deletions yarn-project/cli/src/config/network_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { type NetworkConfig, NetworkConfigMapSchema, type NetworkNames } from '@aztec/foundation/config';

import { readFile } from 'fs/promises';
import { join } from 'path';

import { cachedFetch } from './cached_fetch.js';
import { enrichEthAddressVar, enrichVar } from './enrich_env.js';

const DEFAULT_CONFIG_URL =
'https://raw.githubusercontent.com/AztecProtocol/networks/refs/heads/main/network_config.json';
const NETWORK_CONFIG_CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour

/**
* Fetches remote network configuration from GitHub with caching support.
* Uses the reusable cachedFetch utility.
*
* @param networkName - The network name to fetch config for
* @param cacheDir - Optional cache directory for storing fetched config
* @returns Remote configuration for the specified network, or undefined if not found/error
*/
export async function getNetworkConfig(
networkName: NetworkNames,
cacheDir?: string,
): Promise<NetworkConfig | undefined> {
let url: URL | undefined;
const configLocation = process.env.NETWORK_CONFIG_LOCATION || DEFAULT_CONFIG_URL;

if (!configLocation) {
return undefined;
}

try {
if (configLocation.includes('://')) {
url = new URL(configLocation);
} else {
url = new URL(`file://${configLocation}`);
}
} catch {
/* no-op */
}

if (!url) {
return undefined;
}

try {
let rawConfig: any;

if (url.protocol === 'http:' || url.protocol === 'https:') {
rawConfig = await cachedFetch(url.href, {
cacheDurationMs: NETWORK_CONFIG_CACHE_DURATION_MS,
cacheFile: cacheDir ? join(cacheDir, networkName, 'network_config.json') : undefined,
});
} else if (url.protocol === 'file:') {
rawConfig = JSON.parse(await readFile(url.pathname, 'utf-8'));
} else {
throw new Error('Unsupported Aztec network config protocol: ' + url.href);
}

if (!rawConfig) {
return undefined;
}

const networkConfigMap = NetworkConfigMapSchema.parse(rawConfig);
if (networkName in networkConfigMap) {
return networkConfigMap[networkName];
} else {
return undefined;
}
} catch {
return undefined;
}
}

/**
* Enriches environment variables with remote network configuration.
* This function is called before node config initialization to set env vars
* from the remote config, following the same pattern as enrichEnvironmentWithChainConfig().
*
* @param networkName - The network name to fetch remote config for
*/
export async function enrichEnvironmentWithNetworkConfig(networkName: NetworkNames) {
if (networkName === 'local') {
return; // No remote config for local development
}

const cacheDir = process.env.DATA_DIRECTORY ? join(process.env.DATA_DIRECTORY, 'cache') : undefined;
const networkConfig = await getNetworkConfig(networkName, cacheDir);

if (!networkConfig) {
return;
}

enrichVar('BOOTSTRAP_NODES', networkConfig.bootnodes.join(','));
enrichVar('L1_CHAIN_ID', String(networkConfig.l1ChainId));

// Snapshot synch only supports a single source. Take the first
// See A-101 for more details
const firstSource = networkConfig[0];
if (firstSource) {
enrichVar('SYNC_SNAPSHOTS_URL', networkConfig.snapshots.join(','));
}

enrichEthAddressVar('REGISTRY_CONTRACT_ADDRESS', networkConfig.registryAddress.toString());
if (networkConfig.feeAssetHandlerAddress) {
enrichEthAddressVar('FEE_ASSET_HANDLER_CONTRACT_ADDRESS', networkConfig.feeAssetHandlerAddress.toString());
}
}
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export type EnvVar =
| 'LOG_LEVEL'
| 'MNEMONIC'
| 'NETWORK'
| 'NETWORK_CONFIG_LOCATION'
| 'NO_PXE'
| 'USE_GCLOUD_LOGGING'
| 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/foundation/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { SecretValue } from './secret_value.js';

export { SecretValue, getActiveNetworkName };
export type { EnvVar, NetworkNames };
export type { NetworkConfig, NetworkConfigMap } from './network_config.js';
export { NetworkConfigMapSchema, NetworkConfigSchema } from './network_config.js';

export interface ConfigMapping {
env?: EnvVar;
Expand Down
Loading
Loading