Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Top level init bb.js, but better scoped imports to not incur cost too early #3629

Merged
merged 9 commits into from
Dec 10, 2023
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: 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