Skip to content

Commit

Permalink
feat(avm): trace contract class and contract instance (#8840)
Browse files Browse the repository at this point in the history
This PR is centred around tracing and passing contract class & instance
during simulator execution and passing it to circuit. We store each
contract class & instance whenever the `simulator` calls `getBytecode`.

This changes the input interface to the bb binary - we no longer take in
a specific bytecode to execute. Instead we get a vector of
`{contract_class, contract_instance, bytecode}` which define all the
(deduplicated) contract bytecode that will be executed during this
"one-enqueued call" (actual implementation of 1-enqueued call tbd).

This doesnt do any derivation of id or address yet
  • Loading branch information
IlyasRidhuan authored Oct 25, 2024
1 parent 2bb09e5 commit 84205d8
Show file tree
Hide file tree
Showing 24 changed files with 460 additions and 177 deletions.
12 changes: 5 additions & 7 deletions barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -953,18 +953,17 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path)
* @param hints_path Path to the file containing the serialised avm circuit hints
* @param output_path Path (directory) to write the output proof and verification keys
*/
void avm_prove(const std::filesystem::path& bytecode_path,
const std::filesystem::path& calldata_path,
void avm_prove(const std::filesystem::path& calldata_path,
const std::filesystem::path& public_inputs_path,
const std::filesystem::path& hints_path,
const std::filesystem::path& output_path)
{
std::vector<uint8_t> const bytecode = read_file(bytecode_path);
std::vector<fr> const calldata = many_from_buffer<fr>(read_file(calldata_path));
std::vector<fr> const public_inputs_vec = many_from_buffer<fr>(read_file(public_inputs_path));
auto const avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path));

vinfo("bytecode size: ", bytecode.size());
// Using [0] is fine now for the top-level call, but we might need to index by address in future
vinfo("bytecode size: ", avm_hints.all_contract_bytecode[0].bytecode.size());
vinfo("calldata size: ", calldata.size());
vinfo("public_inputs size: ", public_inputs_vec.size());
vinfo("hints.storage_value_hints size: ", avm_hints.storage_value_hints.size());
Expand All @@ -979,7 +978,7 @@ void avm_prove(const std::filesystem::path& bytecode_path,

// Prove execution and return vk
auto const [verification_key, proof] =
AVM_TRACK_TIME_V("prove/all", avm_trace::Execution::prove(bytecode, calldata, public_inputs_vec, avm_hints));
AVM_TRACK_TIME_V("prove/all", avm_trace::Execution::prove(calldata, public_inputs_vec, avm_hints));

std::vector<fr> vk_as_fields = verification_key.to_field_elements();

Expand Down Expand Up @@ -1526,7 +1525,6 @@ int main(int argc, char* argv[])
write_recursion_inputs_honk<UltraFlavor>(bytecode_path, witness_path, output_path);
#ifndef DISABLE_AZTEC_VM
} else if (command == "avm_prove") {
std::filesystem::path avm_bytecode_path = get_option(args, "--avm-bytecode", "./target/avm_bytecode.bin");
std::filesystem::path avm_calldata_path = get_option(args, "--avm-calldata", "./target/avm_calldata.bin");
std::filesystem::path avm_public_inputs_path =
get_option(args, "--avm-public-inputs", "./target/avm_public_inputs.bin");
Expand All @@ -1535,7 +1533,7 @@ int main(int argc, char* argv[])
std::filesystem::path output_path = get_option(args, "-o", "./proofs");
extern std::filesystem::path avm_dump_trace_path;
avm_dump_trace_path = get_option(args, "--avm-dump-trace", "");
avm_prove(avm_bytecode_path, avm_calldata_path, avm_public_inputs_path, avm_hints_path, output_path);
avm_prove(avm_calldata_path, avm_public_inputs_path, avm_hints_path, output_path);
} else if (command == "avm_verify") {
return avm_verify(proof_path, vk_path) ? 0 : 1;
#endif
Expand Down
114 changes: 68 additions & 46 deletions barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,9 @@ class AvmExecutionTests : public ::testing::Test {
Execution::set_trace_builder_constructor([](VmPublicInputsNT public_inputs,
ExecutionHints execution_hints,
uint32_t side_effect_counter,
std::vector<FF> calldata,
const std::vector<std::vector<uint8_t>>& all_contracts_bytecode) {
return AvmTraceBuilder(std::move(public_inputs),
std::move(execution_hints),
side_effect_counter,
std::move(calldata),
all_contracts_bytecode)
std::vector<FF> calldata) {
return AvmTraceBuilder(
std::move(public_inputs), std::move(execution_hints), side_effect_counter, std::move(calldata))
.set_full_precomputed_tables(false)
.set_range_check_required(false);
});
Expand All @@ -57,7 +53,8 @@ class AvmExecutionTests : public ::testing::Test {
srs::init_crs_factory("../srs_db/ignition");
public_inputs_vec.at(DA_START_GAS_LEFT_PCPI_OFFSET) = DEFAULT_INITIAL_DA_GAS;
public_inputs_vec.at(L2_START_GAS_LEFT_PCPI_OFFSET) = DEFAULT_INITIAL_L2_GAS;
public_inputs = convert_public_inputs(public_inputs_vec);
public_inputs_vec.at(ADDRESS_KERNEL_INPUTS_COL_OFFSET) = 0xdeadbeef;
public_inputs = avm_trace::convert_public_inputs(public_inputs_vec);
};

/**
Expand All @@ -71,7 +68,21 @@ class AvmExecutionTests : public ::testing::Test {
std::vector<FF> calldata{};
std::vector<FF> returndata{};

return Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
auto execution_hints = ExecutionHints().with_avm_contract_bytecode({ bytecode });
execution_hints.all_contract_bytecode[0].contract_instance.address = 0xdeadbeef;

return AvmExecutionTests::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
}

std::vector<Row> gen_trace(std::vector<uint8_t> bytecode,
std::vector<FF> const& calldata,
std::vector<FF> const& public_inputs_vec,
std::vector<FF>& returndata,
ExecutionHints& execution_hints) const
{
execution_hints.all_contract_bytecode = { bytecode };
execution_hints.all_contract_bytecode[0].contract_instance.address = 0xdeadbeef;
return Execution::gen_trace(calldata, public_inputs_vec, returndata, execution_hints);
}

void feed_output(uint32_t output_offset, FF const& value, FF const& side_effect_counter, FF const& metadata)
Expand Down Expand Up @@ -474,8 +485,8 @@ TEST_F(AvmExecutionTests, jumpAndCalldatacopy)
Field(&Instruction::operands, ElementsAre(VariantWith<uint16_t>(5)))));

std::vector<FF> returndata;
auto trace =
Execution::gen_trace(bytecode, std::vector<FF>{ 13, 156 }, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, std::vector<FF>{ 13, 156 }, public_inputs_vec, returndata, execution_hints);

// Expected sequence of PCs during execution
std::vector<FF> pc_sequence{
Expand Down Expand Up @@ -564,10 +575,9 @@ TEST_F(AvmExecutionTests, jumpiAndCalldatacopy)
ElementsAre(VariantWith<uint8_t>(0), VariantWith<uint16_t>(6), VariantWith<uint16_t>(10)))));

std::vector<FF> returndata;
auto trace_jump =
Execution::gen_trace(bytecode, std::vector<FF>{ 9873123 }, public_inputs_vec, returndata, ExecutionHints());
auto trace_no_jump =
Execution::gen_trace(bytecode, std::vector<FF>{ 0 }, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace_jump = gen_trace(bytecode, std::vector<FF>{ 9873123 }, public_inputs_vec, returndata, execution_hints);
auto trace_no_jump = gen_trace(bytecode, std::vector<FF>{ 0 }, public_inputs_vec, returndata, execution_hints);

// Expected sequence of PCs during execution with jump
std::vector<FF> pc_sequence_jump{ 0, 1, 2, 3, 4, 6, 7 };
Expand Down Expand Up @@ -774,8 +784,9 @@ TEST_F(AvmExecutionTests, toRadixLeOpcode)

// Assign a vector that we will mutate internally in gen_trace to store the return values;
std::vector<FF> returndata;
auto trace = Execution::gen_trace(
bytecode, std::vector<FF>{ FF::modulus - FF(1) }, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace =
gen_trace(bytecode, std::vector<FF>{ FF::modulus - FF(1) }, public_inputs_vec, returndata, execution_hints);

// Find the first row enabling the TORADIXLE selector
// Expected output is bitwise decomposition of MODULUS - 1..could hardcode the result but it's a bit long
Expand Down Expand Up @@ -840,8 +851,9 @@ TEST_F(AvmExecutionTests, toRadixLeOpcodeBitsMode)

// Assign a vector that we will mutate internally in gen_trace to store the return values;
std::vector<FF> returndata;
auto trace = Execution::gen_trace(
bytecode, std::vector<FF>{ FF::modulus - FF(1) }, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace =
gen_trace(bytecode, std::vector<FF>{ FF::modulus - FF(1) }, public_inputs_vec, returndata, execution_hints);

// Find the first row enabling the TORADIXLE selector
// Expected output is bitwise decomposition of MODULUS - 1..could hardcode the result but it's a bit long
Expand Down Expand Up @@ -915,7 +927,8 @@ TEST_F(AvmExecutionTests, sha256CompressionOpcode)
// 4091010797,3974542186]),
std::vector<FF> expected_output = { 1862536192, 526086805, 2067405084, 593147560,
726610467, 813867028, 4091010797ULL, 3974542186ULL };
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

EXPECT_EQ(returndata, expected_output);

Expand Down Expand Up @@ -976,7 +989,8 @@ TEST_F(AvmExecutionTests, poseidon2PermutationOpCode)
FF(std::string("0x018555a8eb50cf07f64b019ebaf3af3c925c93e631f3ecd455db07bbb52bbdd3")),
FF(std::string("0x0cbea457c91c22c6c31fd89afd2541efc2edf31736b9f721e823b2165c90fd41"))
};
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

EXPECT_EQ(returndata, expected_output);

Expand Down Expand Up @@ -1043,7 +1057,8 @@ TEST_F(AvmExecutionTests, keccakf1600OpCode)
// Assign a vector that we will mutate internally in gen_trace to store the return values;
std::vector<FF> calldata = std::vector<FF>();
std::vector<FF> returndata = std::vector<FF>();
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

EXPECT_EQ(returndata, expected_output);

Expand Down Expand Up @@ -1110,7 +1125,8 @@ TEST_F(AvmExecutionTests, embeddedCurveAddOpCode)
// Assign a vector that we will mutate internally in gen_trace to store the return values;
std::vector<FF> returndata;
std::vector<FF> calldata = { a.x, a.y, FF(a_is_inf ? 1 : 0), b.x, b.y, FF(b_is_inf ? 1 : 0) };
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

EXPECT_EQ(returndata, expected_output);

Expand Down Expand Up @@ -1197,7 +1213,8 @@ TEST_F(AvmExecutionTests, msmOpCode)

// Assign a vector that we will mutate internally in gen_trace to store the return values;
std::vector<FF> returndata;
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

EXPECT_EQ(returndata, expected_output);

Expand Down Expand Up @@ -1355,16 +1372,16 @@ TEST_F(AvmExecutionTests, kernelInputOpcodes)
std::vector<FF> calldata;

FF sender = 1;
FF address = 2;
FF function_selector = 3;
FF transaction_fee = 4;
FF chainid = 5;
FF version = 6;
FF blocknumber = 7;
FF timestamp = 8;
FF feeperl2gas = 9;
FF feeperdagas = 10;
FF is_static_call = 11;
FF address = 0xdeadbeef;
FF function_selector = 4;
FF transaction_fee = 5;
FF chainid = 6;
FF version = 7;
FF blocknumber = 8;
FF timestamp = 9;
FF feeperl2gas = 10;
FF feeperdagas = 11;
FF is_static_call = 12;

// The return data for this test should be a the opcodes in sequence, as the opcodes dst address lines up with
// this array The returndata call above will then return this array
Expand Down Expand Up @@ -1393,7 +1410,8 @@ TEST_F(AvmExecutionTests, kernelInputOpcodes)
public_inputs_vec[FEE_PER_L2_GAS_PCPI_OFFSET] = feeperl2gas;

std::vector<FF> returndata;
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

// Validate returndata
EXPECT_EQ(returndata, expected_returndata);
Expand Down Expand Up @@ -1559,7 +1577,8 @@ TEST_F(AvmExecutionTests, ExecutorThrowsWithTooMuchGasAllocated)
auto bytecode = hex_to_bytes(bytecode_hex);
auto instructions = Deserialization::parse(bytecode);

EXPECT_THROW_WITH_MESSAGE(Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints()),
ExecutionHints execution_hints;
EXPECT_THROW_WITH_MESSAGE(gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints),
"Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for "
"execution of an enqueued call");
}
Expand All @@ -1578,7 +1597,8 @@ TEST_F(AvmExecutionTests, ExecutorThrowsWithIncorrectNumberOfPublicInputs)
auto bytecode = hex_to_bytes(bytecode_hex);
auto instructions = Deserialization::parse(bytecode);

EXPECT_THROW_WITH_MESSAGE(Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints()),
ExecutionHints execution_hints;
EXPECT_THROW_WITH_MESSAGE(gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints),
"Public inputs vector is not of PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH");
}

Expand Down Expand Up @@ -1621,7 +1641,8 @@ TEST_F(AvmExecutionTests, kernelOutputEmitOpcodes)

std::vector<FF> calldata = {};
std::vector<FF> returndata = {};
auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

// CHECK EMIT NOTE HASH
// Check output data + side effect counters have been set correctly
Expand Down Expand Up @@ -1656,7 +1677,7 @@ TEST_F(AvmExecutionTests, kernelOutputEmitOpcodes)
auto emit_log_row =
std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_emit_unencrypted_log == 1; });
// Trust me bro for now, this is the truncated sha output
FF expected_hash = FF(std::string("0x006db65fd59fd356f6729140571b5bcd6bb3b83492a16e1bf0a3884442fc3c8a"));
FF expected_hash = FF(std::string("0x003383cbb254941b33c0aaf8476c4b9b532d70a2fb105ee908dd332f7d942df6"));
EXPECT_EQ(emit_log_row->main_ia, expected_hash);
EXPECT_EQ(emit_log_row->main_side_effect_counter, 2);
// Value is 40 = 32 * log_length + 40 (and log_length is 0 in this case).
Expand Down Expand Up @@ -1722,7 +1743,7 @@ TEST_F(AvmExecutionTests, kernelOutputStorageLoadOpcodeSimple)
// side effect counter 0 = value 42
auto execution_hints = ExecutionHints().with_storage_value_hints({ { 0, 42 } });

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

// CHECK SLOAD
// Check output data + side effect counters have been set correctly
Expand Down Expand Up @@ -1776,7 +1797,8 @@ TEST_F(AvmExecutionTests, kernelOutputStorageStoreOpcodeSimple)

std::vector<FF> returndata;

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, ExecutionHints());
ExecutionHints execution_hints;
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
// CHECK SSTORE
auto sstore_row = std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_sstore == 1; });
EXPECT_EQ(sstore_row->main_ia, 42); // Read value
Expand Down Expand Up @@ -1839,7 +1861,7 @@ TEST_F(AvmExecutionTests, kernelOutputStorageOpcodes)
// side effect counter 0 = value 42
auto execution_hints = ExecutionHints().with_storage_value_hints({ { 0, 42 } });

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

// CHECK SLOAD
// Check output data + side effect counters have been set correctly
Expand Down Expand Up @@ -1922,7 +1944,7 @@ TEST_F(AvmExecutionTests, kernelOutputHashExistsOpcodes)
.with_storage_value_hints({ { 0, 1 }, { 1, 1 }, { 2, 1 } })
.with_note_hash_exists_hints({ { 0, 1 }, { 1, 1 }, { 2, 1 } });

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);

// CHECK NOTEHASHEXISTS
auto note_hash_row =
Expand Down Expand Up @@ -2056,10 +2078,10 @@ TEST_F(AvmExecutionTests, opCallOpcodes)
.l2_gas_used = 0,
.da_gas_used = 0,
.end_side_effect_counter = 0,
.bytecode = {},
.contract_address = 0,
} });

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
EXPECT_EQ(returndata, std::vector<FF>({ 9, 8, 1 })); // The 1 represents the success

validate_trace(std::move(trace), public_inputs, calldata, returndata);
Expand Down Expand Up @@ -2116,7 +2138,7 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes)
auto execution_hints =
ExecutionHints().with_contract_instance_hints({ { address, { address, 1, 2, 3, 4, 5, public_keys_hints } } });

auto trace = Execution::gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
EXPECT_EQ(returndata, std::vector<FF>({ 1, 2, 3, 4, 5, returned_point.x })); // The first one represents true

validate_trace(std::move(trace), public_inputs, calldata, returndata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace bb::avm_trace {
using poseidon2 = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>;
AvmBytecodeTraceBuilder::AvmBytecodeTraceBuilder(const std::vector<std::vector<uint8_t>>& all_contracts_bytecode)
AvmBytecodeTraceBuilder::AvmBytecodeTraceBuilder(const std::vector<AvmContractBytecode>& all_contracts_bytecode)
: all_contracts_bytecode(all_contracts_bytecode)
{}

Expand All @@ -31,7 +31,7 @@ void AvmBytecodeTraceBuilder::build_bytecode_columns()
// This is the main loop that will generate the bytecode trace
for (auto& contract_bytecode : all_contracts_bytecode) {
FF running_hash = FF::zero();
auto packed_bytecode = pack_bytecode(contract_bytecode);
auto packed_bytecode = pack_bytecode(contract_bytecode.bytecode);
// This size is already based on the number of fields
for (size_t i = 0; i < packed_bytecode.size(); ++i) {
bytecode_trace.push_back(BytecodeTraceEntry{
Expand Down
Loading

0 comments on commit 84205d8

Please sign in to comment.