Skip to content

Commit

Permalink
fix: Top level init bb.js, but better scoped imports to not incur cos…
Browse files Browse the repository at this point in the history
…t too early (AztecProtocol#3629)

Related to this issue:
AztecProtocol#3618
Gets `aztec-cli help` down to 0.01s!!!!
  • Loading branch information
charlielye authored Dec 10, 2023
1 parent c7f1878 commit cea862d
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 273 deletions.
2 changes: 2 additions & 0 deletions barretenberg/ts/scripts/cjs_postprocess.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ DIR="./dest/node-cjs"
for FILE in $(find "$DIR" -name "*.js"); do
# Use sed to replace 'import.meta.url' with '""'
sed -i.bak 's/import\.meta\.url/""/g' "$FILE" && rm "$FILE.bak"
# Use sed to remove any lines postfixed // POSTPROCESS ESM ONLY
sed -i.bak '/\/\/ POSTPROCESS ESM ONLY$/d' "$FILE" && rm "$FILE.bak"
done
14 changes: 6 additions & 8 deletions barretenberg/ts/src/barretenberg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class BarretenbergSync extends BarretenbergApiSync {

static getSingleton() {
if (!barretenbergSyncSingleton) {
throw new Error('Initialise first via initSingleton().');
throw new Error('First call BarretenbergSync.initSingleton() on @aztec/bb.js module.');
}
return barretenbergSyncSingleton;
}
Expand All @@ -75,10 +75,8 @@ export class BarretenbergSync extends BarretenbergApiSync {
}
}

// If we're loading this module in a test environment, just init the singleton immediately for convienience.
if (process.env.NODE_ENV === 'test') {
// Need to ignore for cjs build.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await BarretenbergSync.initSingleton();
}
// If we're in ESM environment, use top level await. CJS users need to call it manually.
// Need to ignore for cjs build.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await BarretenbergSync.initSingleton(); // POSTPROCESS ESM ONLY
11 changes: 10 additions & 1 deletion yarn-project/aztec.js/src/api/init.ts
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
export { init as initAztecJs } from '@aztec/foundation/crypto';
import { init } from '@aztec/foundation/crypto';

/**
* This should only be needed to be called in CJS environments that don't have top level await.
* Initializes any asynchronous subsystems required to use the library.
* At time of writing, this is just our foundation crypto lib.
*/
export async function initAztecJs() {
await init();
}
10 changes: 8 additions & 2 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* import { AztecAddress } from '@aztec/aztec.js/aztec_address';
* import { EthAddress } from '@aztec/aztec.js/eth_address';
* ```
*
* TODO: Ultimately reimplement this mega exporter by mega exporting a granular api (then deprecate it).
*/
export {
WaitOpts,
Expand Down Expand Up @@ -118,7 +120,7 @@ export {
// External devs will almost certainly have their own methods of doing these things.
// If we want to use them in our own "aztec.js consuming code", import them from foundation as needed.
export { ContractArtifact, FunctionArtifact, encodeArguments } from '@aztec/foundation/abi';
export { sha256, init } from '@aztec/foundation/crypto';
export { sha256 } from '@aztec/foundation/crypto';
export { DebugLogger, createDebugLogger, onLog } from '@aztec/foundation/log';
export { retry, retryUntil } from '@aztec/foundation/retry';
export { sleep } from '@aztec/foundation/sleep';
Expand All @@ -127,6 +129,7 @@ export { fileURLToPath } from '@aztec/foundation/url';
export { to2Fields, toBigInt } from '@aztec/foundation/serialize';
export { toBigIntBE } from '@aztec/foundation/bigint-buffer';
export { makeFetch } from '@aztec/foundation/json-rpc/client';
export { FieldsOf } from '@aztec/foundation/types';

export {
DeployL1Contracts,
Expand All @@ -135,4 +138,7 @@ export {
deployL1Contracts,
} from '@aztec/ethereum';

export { FieldsOf } from '@aztec/foundation/types';
// Start of section that exports public api via granular api.
// Here you *can* do `export *` as the granular api defacto exports things explicitly.
// This entire index file will be deprecated at some point after we're satisfied.
export * from './api/init.js';
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { init } from '@aztec/foundation/crypto';

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

import { Aes128 } from './index.js';

describe('aes128', () => {
let aes128!: Aes128;

beforeAll(async () => {
await init();
beforeAll(() => {
aes128 = new Aes128();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { init } from '@aztec/foundation/crypto';
import { createDebugLogger } from '@aztec/foundation/log';

import { GrumpkinScalar, Point } from '../../../index.js';
Expand All @@ -9,8 +8,7 @@ const debug = createDebugLogger('bb:grumpkin_test');
describe('grumpkin', () => {
let grumpkin!: Grumpkin;

beforeAll(async () => {
await init();
beforeAll(() => {
grumpkin = new Grumpkin();
});

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/cli/src/cmds/add_note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DebugLogger } from '@aztec/foundation/log';
import { ExtendedNote, Note, TxHash } from '@aztec/types';

import { createCompatibleClient } from '../client.js';
import { parseFields } from '../utils.js';
import { parseFields } from '../parse_args.js';

/**
*
Expand Down
5 changes: 1 addition & 4 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { initAztecJs } from '@aztec/aztec.js/init';
import { DebugLogger, LogFn } from '@aztec/foundation/log';
import { fileURLToPath } from '@aztec/foundation/url';
import { addNoirCompilerCommanderActions } from '@aztec/noir-compiler/cli';
Expand All @@ -22,7 +21,7 @@ import {
parsePublicKey,
parseSaltFromHexString,
parseTxHash,
} from './utils.js';
} from './parse_args.js';

/**
* If we can successfully resolve 'host.docker.internal', then we are running in a container, and we should treat
Expand Down Expand Up @@ -63,8 +62,6 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.argParser(parsePrivateKey)
.makeOptionMandatory(mandatory);

program.hook('preAction', initAztecJs);

program
.command('deploy-l1-contracts')
.description('Deploys all necessary Ethereum contracts for Aztec.')
Expand Down
248 changes: 248 additions & 0 deletions yarn-project/cli/src/parse_args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { FunctionSelector } from '@aztec/aztec.js/abi';
import { AztecAddress } from '@aztec/aztec.js/aztec_address';
import { EthAddress } from '@aztec/aztec.js/eth_address';
import { Fr, GrumpkinScalar, Point } from '@aztec/aztec.js/fields';
import { LogId } from '@aztec/aztec.js/log_id';
import { TxHash } from '@aztec/aztec.js/tx_hash';

import { InvalidArgumentError } from 'commander';

/**
* Removes the leading 0x from a hex string. If no leading 0x is found the string is returned unchanged.
* @param hex - A hex string
* @returns A new string with leading 0x removed
*/
const stripLeadingHex = (hex: string) => {
if (hex.length > 2 && hex.startsWith('0x')) {
return hex.substring(2);
}
return hex;
};

/**
* Parses a hex encoded string to an Fr integer to be used as salt
* @param str - Hex encoded string
* @returns A integer to be used as salt
*/
export function parseSaltFromHexString(str: string): Fr {
const hex = stripLeadingHex(str);

// ensure it's a hex string
if (!hex.match(/^[0-9a-f]+$/i)) {
throw new InvalidArgumentError('Invalid hex string');
}

// pad it so that we may read it as a buffer.
// Buffer needs _exactly_ two hex characters per byte
const padded = hex.length % 2 === 1 ? '0' + hex : hex;

// finally, turn it into an integer
return Fr.fromBuffer(Buffer.from(padded, 'hex'));
}

/**
* Parses an AztecAddress from a string.
* @param address - A serialized Aztec address
* @returns An Aztec address
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseAztecAddress(address: string): AztecAddress {
try {
return AztecAddress.fromString(address);
} catch {
throw new InvalidArgumentError(`Invalid address: ${address}`);
}
}

/**
* Parses an Ethereum address from a string.
* @param address - A serialized Ethereum address
* @returns An Ethereum address
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseEthereumAddress(address: string): EthAddress {
try {
return EthAddress.fromString(address);
} catch {
throw new InvalidArgumentError(`Invalid address: ${address}`);
}
}

/**
* Parses an AztecAddress from a string.
* @param address - A serialized Aztec address
* @returns An Aztec address
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseOptionalAztecAddress(address: string): AztecAddress | undefined {
if (!address) {
return undefined;
}
return parseAztecAddress(address);
}

/**
* Parses an optional log ID string into a LogId object.
*
* @param logId - The log ID string to parse.
* @returns The parsed LogId object, or undefined if the log ID is missing or empty.
*/
export function parseOptionalLogId(logId: string): LogId | undefined {
if (!logId) {
return undefined;
}
return LogId.fromString(logId);
}

/**
* Parses a selector from a string.
* @param selector - A serialized selector.
* @returns A selector.
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseOptionalSelector(selector: string): FunctionSelector | undefined {
if (!selector) {
return undefined;
}
try {
return FunctionSelector.fromString(selector);
} catch {
throw new InvalidArgumentError(`Invalid selector: ${selector}`);
}
}

/**
* Parses a string into an integer or returns undefined if the input is falsy.
*
* @param value - The string to parse into an integer.
* @returns The parsed integer, or undefined if the input string is falsy.
* @throws If the input is not a valid integer.
*/
export function parseOptionalInteger(value: string): number | undefined {
if (!value) {
return undefined;
}
const parsed = Number(value);
if (!Number.isInteger(parsed)) {
throw new InvalidArgumentError('Invalid integer.');
}
return parsed;
}

/**
* Parses a TxHash from a string.
* @param txHash - A transaction hash
* @returns A TxHash instance
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseTxHash(txHash: string): TxHash {
try {
return TxHash.fromString(txHash);
} catch {
throw new InvalidArgumentError(`Invalid transaction hash: ${txHash}`);
}
}

/**
* Parses an optional TxHash from a string.
* Calls parseTxHash internally.
* @param txHash - A transaction hash
* @returns A TxHash instance, or undefined if the input string is falsy.
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseOptionalTxHash(txHash: string): TxHash | undefined {
if (!txHash) {
return undefined;
}
return parseTxHash(txHash);
}

/**
* Parses a public key from a string.
* @param publicKey - A public key
* @returns A Point instance
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parsePublicKey(publicKey: string): Point {
try {
return Point.fromString(publicKey);
} catch (err) {
throw new InvalidArgumentError(`Invalid public key: ${publicKey}`);
}
}

/**
* Parses a partial address from a string.
* @param address - A partial address
* @returns A Fr instance
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parsePartialAddress(address: string): Fr {
try {
return Fr.fromString(address);
} catch (err) {
throw new InvalidArgumentError(`Invalid partial address: ${address}`);
}
}

/**
* Parses a private key from a string.
* @param privateKey - A string
* @returns A private key
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parsePrivateKey(privateKey: string): GrumpkinScalar {
try {
const value = GrumpkinScalar.fromString(privateKey);
// most likely a badly formatted key was passed
if (value.isZero()) {
throw new Error('Private key must not be zero');
}

return value;
} catch (err) {
throw new InvalidArgumentError(`Invalid private key: ${privateKey}`);
}
}

/**
* Parses a field from a string.
* @param field - A string representing the field.
* @returns A field.
* @throws InvalidArgumentError if the input string is not valid.
*/
export function parseField(field: string): Fr {
try {
const isHex = field.startsWith('0x') || field.match(new RegExp(`^[0-9a-f]{${Fr.SIZE_IN_BYTES * 2}}$`, 'i'));
if (isHex) {
return Fr.fromString(field);
}

if (['true', 'false'].includes(field)) {
return new Fr(field === 'true');
}

const isNumber = +field || field === '0';
if (isNumber) {
return new Fr(BigInt(field));
}

const isBigInt = field.endsWith('n');
if (isBigInt) {
return new Fr(BigInt(field.replace(/n$/, '')));
}

return new Fr(BigInt(field));
} catch (err) {
throw new InvalidArgumentError(`Invalid field: ${field}`);
}
}

/**
* Parses an array of strings to Frs.
* @param fields - An array of strings representing the fields.
* @returns An array of Frs.
*/
export function parseFields(fields: string[]): Fr[] {
return fields.map(parseField);
}
Loading

0 comments on commit cea862d

Please sign in to comment.