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

feat(avm): gzip avm bytecode #6475

Merged
merged 2 commits into from
May 16, 2024
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
1 change: 1 addition & 0 deletions avm-transpiler/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion avm-transpiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ regex = "1.10"
env_logger = "0.11"
log = "0.4"
serde_json = "1.0"
serde = { version = "1.0.136", features = ["derive"]}
serde = { version = "1.0.136", features = ["derive"] }
flate2 = "1.0"
20 changes: 18 additions & 2 deletions avm-transpiler/src/transpile_contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::io::Read;

use base64::Engine;
use log::info;
use serde::{Deserialize, Serialize};

use acvm::acir::circuit::Program;
use noirc_errors::debug_info::DebugInfo;
use noirc_errors::debug_info::ProgramDebugInfo;

use crate::transpile::{brillig_to_avm, map_brillig_pcs_to_avm_pcs, patch_debug_info_pcs};
Expand Down Expand Up @@ -104,6 +105,21 @@ impl From<CompiledAcirContractArtifact> for TranspiledContractArtifact {
// Transpile to AVM
let avm_bytecode = brillig_to_avm(brillig_bytecode, &brillig_pcs_to_avm_pcs);

// Gzip AVM bytecode. This has to be removed once we need to do bytecode verification.
let mut compressed_avm_bytecode = Vec::new();
let mut encoder =
flate2::read::GzEncoder::new(&avm_bytecode[..], flate2::Compression::best());
let _ = encoder.read_to_end(&mut compressed_avm_bytecode);

log::info!(
"{}::{}: compressed {} to {} bytes ({}% reduction)",
contract.name,
function.name,
avm_bytecode.len(),
compressed_avm_bytecode.len(),
100 - (compressed_avm_bytecode.len() * 100 / avm_bytecode.len())
);

// Patch the debug infos with updated PCs
let debug_infos = patch_debug_info_pcs(
&function.debug_symbols.debug_infos,
Expand All @@ -117,7 +133,7 @@ impl From<CompiledAcirContractArtifact> for TranspiledContractArtifact {
is_unconstrained: function.is_unconstrained,
custom_attributes: function.custom_attributes,
abi: function.abi,
bytecode: base64::prelude::BASE64_STANDARD.encode(avm_bytecode),
bytecode: base64::prelude::BASE64_STANDARD.encode(compressed_avm_bytecode),
debug_symbols: ProgramDebugInfo { debug_infos },
},
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const size_t MAX_PUBLIC_DATA_READS_PER_CALL = 16;
const size_t MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32;
const size_t MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2;
const size_t MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2;
const size_t MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1;
const size_t MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 16;
const size_t MAX_NOTE_ENCRYPTED_LOGS_PER_CALL = 16;
const size_t MAX_ENCRYPTED_LOGS_PER_CALL = 4;
const size_t MAX_UNENCRYPTED_LOGS_PER_CALL = 4;
Expand All @@ -27,7 +27,7 @@ const size_t MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2;
const size_t MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128;
const size_t MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8;
const size_t MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8;
const size_t MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4;
const size_t MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 64;
const size_t MAX_NOTE_ENCRYPTED_LOGS_PER_TX = 64;
const size_t MAX_ENCRYPTED_LOGS_PER_TX = 8;
const size_t MAX_UNENCRYPTED_LOGS_PER_TX = 8;
Expand Down Expand Up @@ -64,7 +64,7 @@ const size_t MAX_ARGS_LENGTH = ARGS_HASH_CHUNK_COUNT * ARGS_HASH_CHUNK_LENGTH;
const size_t INITIAL_L2_BLOCK_NUM = 1;
const size_t BLOB_SIZE_IN_BYTES = 31 * 4096;
const size_t NESTED_CALL_L2_GAS_BUFFER = 20000;
const size_t MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 32000;
const size_t MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 20000;
const size_t MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000;
const size_t MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 3000;
const size_t REGISTERER_PRIVATE_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS = 19;
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ library Constants {
uint256 internal constant INITIAL_L2_BLOCK_NUM = 1;
uint256 internal constant BLOB_SIZE_IN_BYTES = 31 * 4096;
uint256 internal constant NESTED_CALL_L2_GAS_BUFFER = 20000;
uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 32000;
uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 20000;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 3000;
uint256 internal constant REGISTERER_PRIVATE_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS = 19;
Expand Down
3 changes: 1 addition & 2 deletions noir-projects/noir-contracts/scripts/transpile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
set -eu

TRANSPILER=${TRANSPILER:-../../avm-transpiler/target/release/avm-transpiler}
ls target/*.json | parallel "$TRANSPILER {} {}"

ls target/*.json | parallel "$TRANSPILER {} {}"
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ global BLOB_SIZE_IN_BYTES: Field = 31 * 4096;
global NESTED_CALL_L2_GAS_BUFFER = 20000;

// CONTRACT CLASS CONSTANTS
global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: u64 = 32000;
global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: u64 = 20000;
// Bytecode size for private functions is per function, not for the entire contract.
// Note that private functions bytecode includes a mix of acir and brillig.
global MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS: u64 = 3000;
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const INITIALIZATION_SLOT_SEPARATOR = 1000_000_000;
export const INITIAL_L2_BLOCK_NUM = 1;
export const BLOB_SIZE_IN_BYTES = 31 * 4096;
export const NESTED_CALL_L2_GAS_BUFFER = 20000;
export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 32000;
export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 20000;
export const MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000;
export const MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 3000;
export const REGISTERER_PRIVATE_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS = 19;
Expand Down
14 changes: 7 additions & 7 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AvmNestedCallsTestContractArtifact, AvmTestContractArtifact } from '@az
import { jest } from '@jest/globals';
import { strict as assert } from 'assert';

import { isAvmBytecode } from '../public/transitional_adaptors.js';
import { isAvmBytecode, markBytecodeAsAvm } from '../public/transitional_adaptors.js';
import { AvmMachineState } from './avm_machine_state.js';
import { type MemoryValue, TypeTag, type Uint8 } from './avm_memory_types.js';
import { AvmSimulator } from './avm_simulator.js';
Expand Down Expand Up @@ -39,14 +39,14 @@ describe('AVM simulator: injected bytecode', () => {
]);
});

it('Should not be recognized as AVM bytecode (magic missing)', () => {
expect(!isAvmBytecode(bytecode));
it('Should not be recognized as AVM bytecode (magic missing)', async () => {
expect(!(await isAvmBytecode(bytecode)));
});

it('Should execute bytecode that performs basic addition', async () => {
const context = initContext({ env: initExecutionEnvironment({ calldata }) });
const { l2GasLeft: initialL2GasLeft } = AvmMachineState.fromState(context.machineState);
const results = await new AvmSimulator(context).executeBytecode(bytecode);
const results = await new AvmSimulator(context).executeBytecode(markBytecodeAsAvm(bytecode));

expect(results.reverted).toBe(false);
expect(results.output).toEqual([new Fr(3)]);
Expand All @@ -59,7 +59,7 @@ describe('AVM simulator: injected bytecode', () => {
machineState: initMachineState({ l2GasLeft: 5 }),
});

const results = await new AvmSimulator(context).executeBytecode(bytecode);
const results = await new AvmSimulator(context).executeBytecode(markBytecodeAsAvm(bytecode));
expect(results.reverted).toBe(true);
expect(results.output).toEqual([]);
expect(results.revertReason?.message).toEqual('Not enough L2GAS gas left');
Expand Down Expand Up @@ -91,9 +91,9 @@ describe('AVM simulator: transpiled Noir contracts', () => {
expect(results.output).toEqual([new Fr(0)]);
});

it('Should be recognized as AVM bytecode (magic present)', () => {
it('Should be recognized as AVM bytecode (magic present)', async () => {
const bytecode = getAvmTestContractBytecode('add_args_return');
expect(isAvmBytecode(bytecode));
expect(await isAvmBytecode(bytecode));
});

describe('U128 addition and overflows', () => {
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';

import { strict as assert } from 'assert';

import { isAvmBytecode } from '../public/transitional_adaptors.js';
import { decompressBytecodeIfCompressed, isAvmBytecode } from '../public/transitional_adaptors.js';
import type { AvmContext } from './avm_context.js';
import { AvmContractCallResults } from './avm_message_call_result.js';
import {
Expand Down Expand Up @@ -39,7 +39,6 @@ export class AvmSimulator {
if (!bytecode) {
throw new NoBytecodeForContractError(this.context.environment.address);
}
assert(isAvmBytecode(bytecode), "AVM simulator can't execute non-AVM bytecode");

return await this.executeBytecode(bytecode);
}
Expand All @@ -49,7 +48,10 @@ export class AvmSimulator {
* This method is useful for testing and debugging.
*/
public async executeBytecode(bytecode: Buffer): Promise<AvmContractCallResults> {
return await this.executeInstructions(decodeFromBytecode(bytecode));
const decompressedBytecode = await decompressBytecodeIfCompressed(bytecode);
assert(isAvmBytecode(decompressedBytecode), "AVM simulator can't execute non-AVM bytecode");

return await this.executeInstructions(decodeFromBytecode(decompressedBytecode));
}

/**
Expand Down
15 changes: 12 additions & 3 deletions yarn-project/simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { createDebugLogger } from '@aztec/foundation/log';

import { spawn } from 'child_process';
import { assert } from 'console';
import fs from 'fs/promises';
import path from 'path';

Expand All @@ -27,7 +28,12 @@ import { PackedValuesCache } from '../common/packed_values_cache.js';
import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from './db.js';
import { type PublicExecution, type PublicExecutionResult, checkValidStaticCall } from './execution.js';
import { PublicExecutionContext } from './public_execution_context.js';
import { convertAvmResultsToPxResult, createAvmExecutionEnvironment, isAvmBytecode } from './transitional_adaptors.js';
import {
convertAvmResultsToPxResult,
createAvmExecutionEnvironment,
decompressBytecodeIfCompressed,
isAvmBytecode,
} from './transitional_adaptors.js';

/**
* Execute a public function and return the execution result.
Expand All @@ -46,7 +52,7 @@ export async function executePublicFunction(
);
}

if (isAvmBytecode(bytecode)) {
if (await isAvmBytecode(bytecode)) {
return await executeTopLevelPublicFunctionAvm(context, bytecode);
} else {
return await executePublicFunctionAcvm(context, bytecode, nested);
Expand Down Expand Up @@ -355,7 +361,10 @@ export class PublicExecutor {
const proofPath = path.join(artifactsPath, 'proof');

const { args, functionData, contractAddress } = avmExecution;
const bytecode = await this.contractsDb.getBytecode(contractAddress, functionData.selector);
let bytecode = await this.contractsDb.getBytecode(contractAddress, functionData.selector);
assert(!!bytecode, `Bytecode not found for ${contractAddress}:${functionData.selector}`);
// This should be removed once we do bytecode validation.
bytecode = await decompressBytecodeIfCompressed(bytecode!);
// Write call data and bytecode to files.
await fs.writeFile(
calldataPath,
Expand Down
19 changes: 17 additions & 2 deletions yarn-project/simulator/src/public/transitional_adaptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
} from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';

import { promisify } from 'util';
import { gunzip } from 'zlib';

import { type AvmContext } from '../avm/avm_context.js';
import { AvmExecutionEnvironment } from '../avm/avm_execution_environment.js';
import { type AvmContractCallResults } from '../avm/avm_message_call_result.js';
Expand Down Expand Up @@ -111,7 +114,19 @@ export function markBytecodeAsAvm(bytecode: Buffer): Buffer {
return Buffer.concat([bytecode, AVM_MAGIC_SUFFIX]);
}

export function isAvmBytecode(bytecode: Buffer): boolean {
// This is just a helper function for the AVM circuit.
export async function decompressBytecodeIfCompressed(bytecode: Buffer): Promise<Buffer> {
try {
return await promisify(gunzip)(bytecode);
} catch {
// If the bytecode is not compressed, the gunzip call will throw an error
// In this case, we assume the bytecode is not compressed and continue.
return Promise.resolve(bytecode);
}
}

export async function isAvmBytecode(bytecode: Buffer): Promise<boolean> {
const decompressedBytecode = await decompressBytecodeIfCompressed(bytecode);
const magicSize = AVM_MAGIC_SUFFIX.length;
return bytecode.subarray(-magicSize).equals(AVM_MAGIC_SUFFIX);
return decompressedBytecode.subarray(-magicSize).equals(AVM_MAGIC_SUFFIX);
}
Loading