Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
26 changes: 13 additions & 13 deletions l1-contracts/gas_benchmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@

## No Validators

| Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas |
|----------------------|---------|---------|---------------|--------------|
| propose | 195,988 | 222,201 | 932 | 14,912 |
| submitEpochRootProof | 697,655 | 743,529 | 2,820 | 45,120 |
| setupEpoch | 31,998 | 113,793 | - | - |
| Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas |
|----------------------|-----------|-----------|---------------|--------------|
| propose | 196,596 | 222,810 | 964 | 15,424 |
| submitEpochRootProof | 1,000,225 | 1,038,384 | 14,084 | 225,344 |
| setupEpoch | 32,042 | 113,837 | - | - |

**Avg Gas Cost per Second**: 3,341.5 gas/second
**Avg Gas Cost per Second**: 3,612.7 gas/second
*Epoch duration*: 0h 38m 24s

## Validators

| Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas |
|----------------------|---------|---------|---------------|--------------|
| propose | 324,449 | 351,604 | 4,452 | 71,232 |
| submitEpochRootProof | 896,101 | 941,944 | 5,316 | 85,056 |
| aggregate3 | 373,118 | 386,457 | - | - |
| setupEpoch | 46,459 | 547,626 | - | - |
| Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas |
|----------------------|-----------|-----------|---------------|--------------|
| propose | 324,981 | 352,180 | 4,484 | 71,744 |
| submitEpochRootProof | 1,581,276 | 1,678,797 | 16,580 | 265,280 |
| aggregate3 | 373,892 | 387,303 | - | - |
| setupEpoch | 46,504 | 547,670 | - | - |

**Avg Gas Cost per Second**: 5,304.3 gas/second
**Avg Gas Cost per Second**: 5,906.4 gas/second
*Epoch duration*: 0h 38m 24s

72 changes: 36 additions & 36 deletions l1-contracts/gas_benchmark_results.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,62 @@
"no_validators": {
"propose": {
"calls": 150,
"min": 182373,
"mean": 195988,
"median": 191762,
"max": 222201,
"calldata_size": 932,
"calldata_gas": 14912
"min": 182982,
"mean": 196596,
"median": 192371,
"max": 222810,
"calldata_size": 964,
"calldata_gas": 15424
},
"setupEpoch": {
"calls": 150,
"min": 29264,
"mean": 31998,
"median": 29264,
"max": 113793
"min": 29309,
"mean": 32042,
"median": 29309,
"max": 113837
},
"submitEpochRootProof": {
"calls": 4,
"min": 676491,
"mean": 697655,
"median": 685300,
"max": 743529,
"calldata_size": 2820,
"calldata_gas": 45120
"min": 981633,
"mean": 1000225,
"median": 990442,
"max": 1038384,
"calldata_size": 14084,
"calldata_gas": 225344
}
},
"validators": {
"propose": {
"calls": 150,
"min": 302105,
"mean": 324449,
"median": 323910,
"max": 351604,
"calldata_size": 4452,
"calldata_gas": 71232
"min": 302633,
"mean": 324981,
"median": 324474,
"max": 352180,
"calldata_size": 4484,
"calldata_gas": 71744
},
"setupEpoch": {
"calls": 150,
"min": 29264,
"mean": 46459,
"median": 29264,
"max": 547626
"min": 29309,
"mean": 46504,
"median": 29309,
"max": 547670
},
"submitEpochRootProof": {
"calls": 4,
"min": 874924,
"mean": 896101,
"median": 883769,
"max": 941944,
"calldata_size": 5316,
"calldata_gas": 85056
"min": 1469608,
"mean": 1581276,
"median": 1588350,
"max": 1678797,
"calldata_size": 16580,
"calldata_gas": 265280
},
"aggregate3": {
"calls": 55,
"min": 362015,
"mean": 373118,
"median": 372828,
"max": 386457
"min": 362764,
"mean": 373892,
"median": 373649,
"max": 387303
}
}
}
8 changes: 7 additions & 1 deletion yarn-project/aztec-node/src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ async function main() {
middlewares: [getOtelJsonRpcServerMetricsMiddleware(), getOtelJsonRpcPropagationMiddleware()],
maxBatchSize: aztecNodeConfig.rpcMaxBatchSize,
maxBodySizeBytes: aztecNodeConfig.rpcMaxBodySize,
corsAllowedHeaders: aztecNodeConfig.rpcCorsAllowedHeaders,
corsAllowedOrigins: getRpcCorsAllowedOrigins(aztecNodeConfig),
});
await startHttpRpcServer(rpcServer, { port: +AZTEC_NODE_PORT, apiPrefix: API_PREFIX });
await startHttpRpcServer(rpcServer, {
port: +AZTEC_NODE_PORT,
apiPrefix: API_PREFIX,
keepAliveTimeoutMs: aztecNodeConfig.rpcHttpKeepAliveTimeoutMs,
headersTimeoutMs: aztecNodeConfig.rpcHttpHeadersTimeoutMs,
});
logger.info(`Aztec Node JSON-RPC Server listening on port ${AZTEC_NODE_PORT}`);
}

Expand Down
7 changes: 6 additions & 1 deletion yarn-project/aztec/src/cli/aztec_start_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@ export async function aztecStart(options: any, userLog: LogFn, debugLogger: Logg
],
maxBatchSize: options.rpcMaxBatchSize,
maxBodySizeBytes: options.rpcMaxBodySize,
corsAllowedHeaders: options.rpcCorsAllowedHeaders,
corsAllowedOrigins: getRpcCorsAllowedOrigins(options),
});
const { port } = await startHttpRpcServer(rpcServer, { port: options.port });
const { port } = await startHttpRpcServer(rpcServer, {
port: options.port,
keepAliveTimeoutMs: options.rpcHttpKeepAliveTimeoutMs,
headersTimeoutMs: options.rpcHttpHeadersTimeoutMs,
});
debugLogger.info(`Aztec Server listening on port ${port}`, versions);
}

Expand Down
26 changes: 26 additions & 0 deletions yarn-project/aztec/src/cli/aztec_start_options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ describe('aztec_start_options commander integration', () => {
expect(cmd.opts().rpcCorsAllowedOrigins).toEqual(['https://app1.example.com', 'https://app2.example.com']);
});

it('parses RPC CORS allowed headers from the environment', () => {
process.env.RPC_CORS_ALLOWED_HEADERS = 'content-type, x-api-key';
const cmd = buildCommandWith(['API']);
cmd.parse(['node', 'cli']);

expect(cmd.opts().rpcCorsAllowedHeaders).toEqual(['content-type', 'x-api-key']);
});

it('parses RPC HTTP timeouts from the environment', () => {
process.env.RPC_HTTP_KEEP_ALIVE_TIMEOUT_MS = '65000';
process.env.RPC_HTTP_HEADERS_TIMEOUT_MS = '66000';
const cmd = buildCommandWith(['API']);
cmd.parse(['node', 'cli']);

expect(cmd.opts().rpcHttpKeepAliveTimeoutMs).toBe(65_000);
expect(cmd.opts().rpcHttpHeadersTimeoutMs).toBe(66_000);
});

it('uses the Node.js defaults for RPC HTTP timeouts', () => {
const cmd = buildCommandWith(['API']);
cmd.parse(['node', 'cli']);

expect(cmd.opts().rpcHttpKeepAliveTimeoutMs).toBe(5_000);
expect(cmd.opts().rpcHttpHeadersTimeoutMs).toBe(60_000);
});

it('enables public credentialed CORS from the environment', () => {
process.env.RPC_CORS_ALLOW_ANY_ORIGIN = 'true';
const cmd = buildCommandWith(['API']);
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec/src/cli/aztec_start_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ export const aztecStartOptions: { [key: string]: AztecStartOption[] } = {
},
configToFlag('--rpcMaxBatchSize', nodeRpcConfigMappings.rpcMaxBatchSize),
configToFlag('--rpcMaxBodySize', nodeRpcConfigMappings.rpcMaxBodySize),
configToFlag('--rpc-http-keep-alive-timeout-ms', nodeRpcConfigMappings.rpcHttpKeepAliveTimeoutMs),
configToFlag('--rpc-http-headers-timeout-ms', nodeRpcConfigMappings.rpcHttpHeadersTimeoutMs),
configToFlag('--rpc-cors-allowed-headers', nodeRpcConfigMappings.rpcCorsAllowedHeaders),
configToFlag('--rpc-cors-allowed-origins', nodeRpcConfigMappings.rpcCorsAllowedOrigins),
configToFlag('--rpc-cors-allow-any-origin', nodeRpcConfigMappings.rpcCorsAllowAnyOrigin),
],
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ export type EnvVar =
| 'PXE_SYNC_CHAIN_TIP'
| 'RPC_MAX_BATCH_SIZE'
| 'RPC_MAX_BODY_SIZE'
| 'RPC_HTTP_HEADERS_TIMEOUT_MS'
| 'RPC_HTTP_KEEP_ALIVE_TIMEOUT_MS'
| 'RPC_CORS_ALLOW_ANY_ORIGIN'
| 'RPC_CORS_ALLOWED_HEADERS'
| 'RPC_CORS_ALLOWED_ORIGINS'
| 'RPC_SIMULATE_PUBLIC_MAX_GAS_LIMIT'
| 'RPC_SIMULATE_PUBLIC_MAX_DEBUG_LOG_MEMORY_READS'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import http from 'http';
import request from 'supertest';

import { times } from '../../collection/array.js';
Expand All @@ -7,6 +8,7 @@ import {
createNamespacedSafeJsonRpcServer,
createSafeJsonRpcServer,
makeHandler,
startHttpRpcServer,
} from './safe_json_rpc_server.js';

const jsonrpc = '2.0';
Expand Down Expand Up @@ -111,6 +113,21 @@ describe('SafeJsonRpcServer', () => {
expect(middlewareCalled).toBe(false);
});

it('restricts preflight requests to configured headers', async () => {
server = createSafeJsonRpcServer<TestStateApi>(testState, TestStateSchema, {
corsAllowedHeaders: ['content-type', 'x-api-key'],
});

const response = await request(server.getApp().callback())
.options('/')
.set('origin', 'https://app.example.com')
.set('access-control-request-method', 'POST')
.set('access-control-request-headers', 'content-type,x-api-key,x-unauthorized');

expect(response.status).toBe(204);
expect(response.headers['access-control-allow-headers']).toBe('content-type,x-api-key');
});

it('reflects any request origin on preflight under the wildcard policy', async () => {
server = createSafeJsonRpcServer<TestStateApi>(testState, TestStateSchema, {
corsAllowedOrigins: ['*'],
Expand All @@ -128,6 +145,30 @@ describe('SafeJsonRpcServer', () => {
});
});

describe('HTTP timeouts', () => {
beforeEach(() => {
server = createSafeJsonRpcServer<TestStateApi>(testState, TestStateSchema);
});

it('preserves the Node.js defaults', async () => {
const defaultHttpServer = http.createServer();
await using httpServer = await startHttpRpcServer(server);

expect(httpServer.keepAliveTimeout).toBe(defaultHttpServer.keepAliveTimeout);
expect(httpServer.headersTimeout).toBe(defaultHttpServer.headersTimeout);
});

it('configures keep-alive and headers timeouts', async () => {
await using httpServer = await startHttpRpcServer(server, {
keepAliveTimeoutMs: 65_000,
headersTimeoutMs: 66_000,
});

expect(httpServer.keepAliveTimeout).toBe(65_000);
expect(httpServer.headersTimeout).toBe(66_000);
});
});

describe('single', () => {
beforeEach(() => {
server = createSafeJsonRpcServer<TestStateApi>(testState, TestStateSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,21 @@ export type SafeJsonRpcServerConfig = {
maxBodySizeBytes: string;
/** Origins allowed to make credentialed cross-origin requests. An empty list preserves wildcard CORS. */
corsAllowedOrigins?: string[];
/** Headers allowed in cross-origin requests. An empty list reflects the requested headers. */
corsAllowedHeaders?: string[];
};

type ResolvedSafeJsonRpcServerConfig = Omit<SafeJsonRpcServerConfig, 'corsAllowedOrigins'> & {
type ResolvedSafeJsonRpcServerConfig = Omit<SafeJsonRpcServerConfig, 'corsAllowedOrigins' | 'corsAllowedHeaders'> & {
corsAllowedOrigins: string[];
corsAllowedHeaders: string[];
};

const defaultServerConfig: ResolvedSafeJsonRpcServerConfig = {
http200OnError: false,
maxBatchSize: 100,
maxBodySizeBytes: '1mb',
corsAllowedOrigins: [],
corsAllowedHeaders: [],
};

function normalizeCorsOrigin(origin: string): string {
Expand Down Expand Up @@ -151,8 +155,10 @@ export class SafeJsonRpcServer {

app.use(compress({ br: false }));
app.use(jsonResponse);
const corsAllowedHeaders =
this.config.corsAllowedHeaders.length > 0 ? { allowHeaders: this.config.corsAllowedHeaders } : {};
if (this.config.corsAllowedOrigins.length === 0) {
app.use(cors());
app.use(cors(corsAllowedHeaders));
} else {
const allowedOrigins = new Set(this.config.corsAllowedOrigins);
const allowAnyOrigin = allowedOrigins.has('*');
Expand All @@ -175,6 +181,7 @@ export class SafeJsonRpcServer {
return '';
},
credentials: true,
...corsAllowedHeaders,
}),
);
}
Expand Down Expand Up @@ -540,6 +547,8 @@ export async function startHttpRpcServer(
port?: number | string;
apiPrefix?: string;
timeoutMs?: number;
keepAliveTimeoutMs?: number;
headersTimeoutMs?: number;
noWait?: boolean;
} = {},
): Promise<http.Server & { port: number }> {
Expand All @@ -553,6 +562,12 @@ export async function startHttpRpcServer(
if (options.timeoutMs) {
httpServer.timeout = options.timeoutMs;
}
if (options.keepAliveTimeoutMs !== undefined) {
httpServer.keepAliveTimeout = options.keepAliveTimeoutMs;
}
if (options.headersTimeoutMs !== undefined) {
httpServer.headersTimeout = options.headersTimeoutMs;
}

const { promise, resolve } = promiseWithResolvers<void>();
const listenPort = options.port ? (typeof options.port === 'string' ? parseInt(options.port) : options.port) : 0;
Expand Down
Loading
Loading