From b2ea9c2d517243b494fad7f70d5573bb150f3355 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 20 Apr 2023 18:43:38 -0300 Subject: [PATCH 1/3] Update CombinedAccumulatedData to use new PublicDataWrite instead of StateTransition and StateRead --- .../abis/combined_accumulated_data.hpp | 27 +----- .../circuits/abis/public_data_write.hpp | 91 +++++++++++++++++++ .../__snapshots__/private_kernel.test.ts.snap | 72 +++------------ .../kernel/combined_accumulated_data.ts | 59 ++++++++++-- .../src/structs/kernel/public_inputs.ts | 12 +++ .../src/structs/kernel/public_kernel.ts | 28 +----- .../structs/public_circuit_public_inputs.ts | 4 +- .../__snapshots__/base_rollup.test.ts.snap | 48 ++-------- .../src/structs/rollup/base_rollup.ts | 6 +- .../circuits.js/src/tests/factories.ts | 15 ++- 10 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 circuits/cpp/src/aztec3/circuits/abis/public_data_write.hpp diff --git a/circuits/cpp/src/aztec3/circuits/abis/combined_accumulated_data.hpp b/circuits/cpp/src/aztec3/circuits/abis/combined_accumulated_data.hpp index 0493445bebd3..e9332773318d 100644 --- a/circuits/cpp/src/aztec3/circuits/abis/combined_accumulated_data.hpp +++ b/circuits/cpp/src/aztec3/circuits/abis/combined_accumulated_data.hpp @@ -1,8 +1,7 @@ #pragma once #include "optionally_revealed_data.hpp" #include "new_contract_data.hpp" -#include "state_transition.hpp" -#include "state_read.hpp" +#include "public_data_write.hpp" #include "aztec3/constants.hpp" #include #include @@ -42,8 +41,7 @@ template struct CombinedAccumulatedData { std::array, KERNEL_OPTIONALLY_REVEALED_DATA_LENGTH> optionally_revealed_data{}; - std::array, STATE_TRANSITIONS_LENGTH> state_transitions{}; - std::array, STATE_READS_LENGTH> state_reads{}; + std::array, STATE_TRANSITIONS_LENGTH> state_transitions{}; boolean operator==(CombinedAccumulatedData const& other) const { @@ -52,7 +50,7 @@ template struct CombinedAccumulatedData { new_nullifiers == other.new_nullifiers && private_call_stack == other.private_call_stack && public_call_stack == other.public_call_stack && l1_msg_stack == other.l1_msg_stack && new_contracts == other.new_contracts && optionally_revealed_data == other.optionally_revealed_data && - state_transitions == other.state_transitions && state_reads == other.state_reads; + state_transitions == other.state_transitions; }; template @@ -87,7 +85,6 @@ template struct CombinedAccumulatedData { map(new_contracts, to_circuit_type), map(optionally_revealed_data, to_circuit_type), map(state_transitions, to_circuit_type), - map(state_reads, to_circuit_type), }; return acc_data; @@ -121,7 +118,6 @@ template struct CombinedAccumulatedData { map(new_contracts, to_native_type), map(optionally_revealed_data, to_native_type), map(state_transitions, to_native_type), - map(state_reads, to_native_type), }; return acc_data; @@ -146,7 +142,6 @@ template struct CombinedAccumulatedData { set_array_public(new_contracts); set_array_public(optionally_revealed_data); set_array_public(state_transitions); - set_array_public(state_reads); } template void set_array_public(std::array& arr) @@ -173,15 +168,7 @@ template struct CombinedAccumulatedData { } } - template void set_array_public(std::array, SIZE>& arr) - { - static_assert(!(std::is_same::value)); - for (auto& e : arr) { - e.set_public(); - } - } - - template void set_array_public(std::array, SIZE>& arr) + template void set_array_public(std::array, SIZE>& arr) { static_assert(!(std::is_same::value)); for (auto& e : arr) { @@ -205,7 +192,6 @@ template void read(uint8_t const*& it, CombinedAccumulatedData void write(std::vector& buf, CombinedAccumulatedData const& accum_data) @@ -223,7 +209,6 @@ template void write(std::vector& buf, CombinedAccumulate write(buf, accum_data.new_contracts); write(buf, accum_data.optionally_revealed_data); write(buf, accum_data.state_transitions); - write(buf, accum_data.state_reads); }; template std::ostream& operator<<(std::ostream& os, CombinedAccumulatedData const& accum_data) @@ -247,9 +232,7 @@ template std::ostream& operator<<(std::ostream& os, CombinedAccum << "optionally_revealed_data:\n" << accum_data.optionally_revealed_data << "\n" << "state_transitions:\n" - << accum_data.state_transitions << "\n" - << "state_reads:\n" - << accum_data.state_reads << "\n"; + << accum_data.state_transitions << "\n"; } } // namespace aztec3::circuits::abis \ No newline at end of file diff --git a/circuits/cpp/src/aztec3/circuits/abis/public_data_write.hpp b/circuits/cpp/src/aztec3/circuits/abis/public_data_write.hpp new file mode 100644 index 000000000000..5e5d93f56148 --- /dev/null +++ b/circuits/cpp/src/aztec3/circuits/abis/public_data_write.hpp @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include + +namespace aztec3::circuits::abis { + +using aztec3::utils::types::CircuitTypes; +using aztec3::utils::types::NativeTypes; +using plonk::stdlib::witness_t; + +template struct PublicDataWrite { + typedef typename NCT::fr fr; + + fr leaf_index = 0; + fr new_value = 0; + + bool operator==(PublicDataWrite const&) const = default; + + template PublicDataWrite> to_circuit_type(Composer& composer) const + { + static_assert((std::is_same::value)); + + // Capture the composer: + auto to_ct = [&](auto& e) { return aztec3::utils::types::to_ct(composer, e); }; + + PublicDataWrite> state_transition = { + to_ct(leaf_index), + to_ct(new_value), + }; + + return state_transition; + }; + + template PublicDataWrite to_native_type() const + { + static_assert((std::is_same, NCT>::value)); + + auto to_nt = [&](auto& e) { return aztec3::utils::types::to_nt(e); }; + + PublicDataWrite state_transition = { + to_nt(leaf_index), + to_nt(new_value), + }; + + return state_transition; + }; + + fr hash() const + { + std::vector inputs = { + leaf_index, + new_value, + }; + + return NCT::compress(inputs, GeneratorIndex::STATE_TRANSITION); + } + + void set_public() + { + static_assert(!(std::is_same::value)); + + leaf_index.set_public(); + new_value.set_public(); + } +}; + +template void read(uint8_t const*& it, PublicDataWrite& state_transition) +{ + using serialize::read; + + read(it, state_transition.leaf_index); + read(it, state_transition.new_value); +}; + +template void write(std::vector& buf, PublicDataWrite const& state_transition) +{ + using serialize::write; + + write(buf, state_transition.leaf_index); + write(buf, state_transition.new_value); +}; + +template std::ostream& operator<<(std::ostream& os, PublicDataWrite const& state_transition) +{ + return os << "leaf_index: " << state_transition.leaf_index << "\n" + << "new_value: " << state_transition.new_value << "\n"; +} + +} // namespace aztec3::circuits::abis \ No newline at end of file diff --git a/yarn-project/circuits.js/src/structs/kernel/__snapshots__/private_kernel.test.ts.snap b/yarn-project/circuits.js/src/structs/kernel/__snapshots__/private_kernel.test.ts.snap index 3e4857fad659..a31e753bfdcc 100644 --- a/yarn-project/circuits.js/src/structs/kernel/__snapshots__/private_kernel.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/kernel/__snapshots__/private_kernel.test.ts.snap @@ -90,28 +90,14 @@ called_from_l1: 1 called_from_public_l2: 0 ] state_transitions: -[ storage_slot: 0x801 -old_value: 0x802 +[ leaf_index: 0x801 +new_value: 0x802 + leaf_index: 0x802 new_value: 0x803 - storage_slot: 0x802 -old_value: 0x803 + leaf_index: 0x803 new_value: 0x804 - storage_slot: 0x803 -old_value: 0x804 + leaf_index: 0x804 new_value: 0x805 - storage_slot: 0x804 -old_value: 0x805 -new_value: 0x806 - ] -state_reads: -[ storage_slot: 0x901 -current_value: 0x902 - storage_slot: 0x902 -current_value: 0x903 - storage_slot: 0x903 -current_value: 0x904 - storage_slot: 0x904 -current_value: 0x905 ] constants: @@ -264,28 +250,14 @@ called_from_l1: 1 called_from_public_l2: 0 ] state_transitions: -[ storage_slot: 0x1801 -old_value: 0x1802 +[ leaf_index: 0x1801 +new_value: 0x1802 + leaf_index: 0x1802 new_value: 0x1803 - storage_slot: 0x1802 -old_value: 0x1803 + leaf_index: 0x1803 new_value: 0x1804 - storage_slot: 0x1803 -old_value: 0x1804 + leaf_index: 0x1804 new_value: 0x1805 - storage_slot: 0x1804 -old_value: 0x1805 -new_value: 0x1806 - ] -state_reads: -[ storage_slot: 0x1901 -current_value: 0x1902 - storage_slot: 0x1902 -current_value: 0x1903 - storage_slot: 0x1903 -current_value: 0x1904 - storage_slot: 0x1904 -current_value: 0x1905 ] constants: @@ -608,28 +580,14 @@ called_from_l1: 1 called_from_public_l2: 0 ] state_transitions: -[ storage_slot: 0x801 -old_value: 0x802 +[ leaf_index: 0x801 +new_value: 0x802 + leaf_index: 0x802 new_value: 0x803 - storage_slot: 0x802 -old_value: 0x803 + leaf_index: 0x803 new_value: 0x804 - storage_slot: 0x803 -old_value: 0x804 + leaf_index: 0x804 new_value: 0x805 - storage_slot: 0x804 -old_value: 0x805 -new_value: 0x806 - ] -state_reads: -[ storage_slot: 0x901 -current_value: 0x902 - storage_slot: 0x902 -current_value: 0x903 - storage_slot: 0x903 -current_value: 0x904 - storage_slot: 0x904 -current_value: 0x905 ] constants: diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index 946c1d771c23..f40fa50d6313 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -108,6 +108,54 @@ export class OptionallyRevealedData { } } +/** + * Read operations from the public state tree. + */ +export class PublicDataRead { + constructor(public readonly leafIndex: Fr, public readonly value: Fr) {} + + static from(args: { storageSlot: Fr; value: Fr }) { + return new PublicDataRead(args.storageSlot, args.value); + } + + toBuffer() { + return serializeToBuffer(this.leafIndex, this.value); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataRead(reader.readFr(), reader.readFr()); + } + + static empty() { + return new PublicDataRead(Fr.ZERO, Fr.ZERO); + } +} + +/** + * Write operations on the public state tree. + */ +export class PublicDataWrite { + constructor(public readonly leafIndex: Fr, public readonly newValue: Fr) {} + + static from(args: { storageSlot: Fr; oldValue: Fr; newValue: Fr }) { + return new PublicDataWrite(args.storageSlot, args.newValue); + } + + toBuffer() { + return serializeToBuffer(this.leafIndex, this.newValue); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataWrite(reader.readFr(), reader.readFr()); + } + + static empty() { + return new PublicDataWrite(Fr.ZERO, Fr.ZERO); + } +} + export class CombinedAccumulatedData { constructor( public aggregationObject: AggregationObject, @@ -126,8 +174,7 @@ export class CombinedAccumulatedData { public optionallyRevealedData: OptionallyRevealedData[], - public stateTransitions: StateTransition[], - public stateReads: StateRead[], + public stateTransitions: PublicDataWrite[], ) { assertLength(this, 'newCommitments', KERNEL_NEW_COMMITMENTS_LENGTH); assertLength(this, 'newNullifiers', KERNEL_NEW_NULLIFIERS_LENGTH); @@ -137,7 +184,6 @@ export class CombinedAccumulatedData { assertLength(this, 'newContracts', KERNEL_NEW_CONTRACTS_LENGTH); assertLength(this, 'optionallyRevealedData', KERNEL_OPTIONALLY_REVEALED_DATA_LENGTH); assertLength(this, 'stateTransitions', STATE_TRANSITIONS_LENGTH); - assertLength(this, 'stateReads', STATE_READS_LENGTH); } toBuffer() { @@ -153,7 +199,6 @@ export class CombinedAccumulatedData { this.newContracts, this.optionallyRevealedData, this.stateTransitions, - this.stateReads, ); } @@ -174,8 +219,7 @@ export class CombinedAccumulatedData { reader.readArray(KERNEL_L1_MSG_STACK_LENGTH, Fr), reader.readArray(KERNEL_NEW_CONTRACTS_LENGTH, NewContractData), reader.readArray(KERNEL_OPTIONALLY_REVEALED_DATA_LENGTH, OptionallyRevealedData), - reader.readArray(STATE_TRANSITIONS_LENGTH, StateTransition), - reader.readArray(STATE_READS_LENGTH, StateRead), + reader.readArray(STATE_TRANSITIONS_LENGTH, PublicDataWrite), ); } @@ -191,8 +235,7 @@ export class CombinedAccumulatedData { times(KERNEL_L1_MSG_STACK_LENGTH, Fr.zero), times(KERNEL_NEW_CONTRACTS_LENGTH, NewContractData.empty), times(KERNEL_OPTIONALLY_REVEALED_DATA_LENGTH, OptionallyRevealedData.empty), - times(STATE_TRANSITIONS_LENGTH, StateTransition.empty), - times(STATE_READS_LENGTH, StateRead.empty), + times(STATE_TRANSITIONS_LENGTH, PublicDataWrite.empty), ); } } diff --git a/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts index 20f32f52e21c..597ba25e6ff1 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts @@ -36,3 +36,15 @@ export class KernelCircuitPublicInputs { return new KernelCircuitPublicInputs(CombinedAccumulatedData.empty(), CombinedConstantData.empty(), true); } } + +export class PublicKernelPublicInputs extends KernelCircuitPublicInputs { + constructor(end: CombinedAccumulatedData, constants: CombinedConstantData) { + super(end, constants, false); + } +} + +export class PrivateKernelPublicInputs extends KernelCircuitPublicInputs { + constructor(end: CombinedAccumulatedData, constants: CombinedConstantData) { + super(end, constants, true); + } +} \ No newline at end of file diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel.ts index 3a6969519489..7e7fd0612aec 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_kernel.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel.ts @@ -7,13 +7,10 @@ import { STATE_READS_LENGTH, STATE_TRANSITIONS_LENGTH, } from '../constants.js'; -import { PreviousKernelData as PreviousPrivateKernelData } from './previous_kernel_data.js'; -import { CombinedConstantData } from './combined_constant_data.js'; -import { CombinedAccumulatedData } from './combined_accumulated_data.js'; -import { UInt8Vector } from '../shared.js'; import { MembershipWitness } from '../membership_witness.js'; +import { UInt8Vector } from '../shared.js'; import { SignedTxRequest } from '../tx_request.js'; -import { VerificationKey } from '../verification_key.js'; +import { PreviousKernelData } from './previous_kernel_data.js'; export type PublicKernelInputs = | PublicKernelInputsNonFirstIteration @@ -24,7 +21,7 @@ export class PublicKernelInputsNonFirstIteration { public kind = 'NonFirstIteration' as const; constructor( - public readonly previousKernel: PreviousPublicKernelData, + public readonly previousKernel: PreviousKernelData, public readonly witnessedPublicCall: WitnessedPublicCallData, ) {} } @@ -33,7 +30,7 @@ export class PublicKernelInputsPrivateKernelInput { public kind = 'PrivateKernelInput' as const; constructor( - public readonly previousKernel: PreviousPrivateKernelData, + public readonly previousKernel: PreviousKernelData, public readonly witnessedPublicCall: WitnessedPublicCallData, ) {} } @@ -71,20 +68,3 @@ export class PublicCallData { assertLength(this, 'publicCallStackPreimages', PUBLIC_CALL_STACK_LENGTH); } } - -export class PreviousPublicKernelData { - constructor( - public readonly publicInputs: PublicKernelPublicInputs, - public readonly proof: UInt8Vector, - public readonly vk: VerificationKey, - ) {} -} - -export class PublicKernelPublicInputs { - public readonly isPrivateKernel = false; - constructor(public readonly end: CombinedAccumulatedData, public readonly constants: CombinedConstantData) {} - - static empty() { - return new PublicKernelPublicInputs(CombinedAccumulatedData.empty(), CombinedConstantData.empty()); - } -} diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index e227de5dea6b..46da4d2bd438 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -14,7 +14,7 @@ import { import { serializeToBuffer } from '../utils/serialize.js'; /** - * Read operations from the public state tree. + * Public state read operation on a specific contract. */ export class StateRead { constructor(public readonly storageSlot: Fr, public readonly value: Fr) {} @@ -38,7 +38,7 @@ export class StateRead { } /** - * Write operations on the public state tree. + * Public state transition for a slot on a specific contract. */ export class StateTransition { constructor(public readonly storageSlot: Fr, public readonly oldValue: Fr, public readonly newValue: Fr) {} diff --git a/yarn-project/circuits.js/src/structs/rollup/__snapshots__/base_rollup.test.ts.snap b/yarn-project/circuits.js/src/structs/rollup/__snapshots__/base_rollup.test.ts.snap index 21e12d43f56f..c82bd1150af4 100644 --- a/yarn-project/circuits.js/src/structs/rollup/__snapshots__/base_rollup.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/rollup/__snapshots__/base_rollup.test.ts.snap @@ -91,28 +91,14 @@ called_from_l1: 1 called_from_public_l2: 0 ] state_transitions: -[ storage_slot: 0x900 -old_value: 0x901 +[ leaf_index: 0x900 +new_value: 0x901 + leaf_index: 0x901 new_value: 0x902 - storage_slot: 0x901 -old_value: 0x902 + leaf_index: 0x902 new_value: 0x903 - storage_slot: 0x902 -old_value: 0x903 + leaf_index: 0x903 new_value: 0x904 - storage_slot: 0x903 -old_value: 0x904 -new_value: 0x905 - ] -state_reads: -[ storage_slot: 0xa00 -current_value: 0xa01 - storage_slot: 0xa01 -current_value: 0xa02 - storage_slot: 0xa02 -current_value: 0xa03 - storage_slot: 0xa03 -current_value: 0xa04 ] constants: @@ -237,28 +223,14 @@ called_from_l1: 1 called_from_public_l2: 0 ] state_transitions: -[ storage_slot: 0xa00 -old_value: 0xa01 +[ leaf_index: 0xa00 +new_value: 0xa01 + leaf_index: 0xa01 new_value: 0xa02 - storage_slot: 0xa01 -old_value: 0xa02 + leaf_index: 0xa02 new_value: 0xa03 - storage_slot: 0xa02 -old_value: 0xa03 + leaf_index: 0xa03 new_value: 0xa04 - storage_slot: 0xa03 -old_value: 0xa04 -new_value: 0xa05 - ] -state_reads: -[ storage_slot: 0xb00 -current_value: 0xb01 - storage_slot: 0xb01 -current_value: 0xb02 - storage_slot: 0xb02 -current_value: 0xb03 - storage_slot: 0xb03 -current_value: 0xb04 ] constants: diff --git a/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts index eda5f3d32148..96044fb44e45 100644 --- a/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts @@ -100,7 +100,7 @@ export class BaseRollupInputs { public newCommitmentsSubtreeSiblingPath: Fr[], public newNullifiersSubtreeSiblingPath: Fr[], public newContractsSubtreeSiblingPath: Fr[], - public newStateTransitionsSiblingPath: MembershipWitness[], + public newStateTransitionsSiblingPaths: MembershipWitness[], public historicPrivateDataTreeRootMembershipWitnesses: [ MembershipWitness, @@ -130,7 +130,7 @@ export class BaseRollupInputs { 'newContractsSubtreeSiblingPath', CONTRACT_TREE_HEIGHT - BaseRollupInputs.CONTRACT_SUBTREE_HEIGHT, ); - assertLength(this, 'newStateTransitionsSiblingPath', 2 * STATE_TRANSITIONS_LENGTH); + assertLength(this, 'newStateTransitionsSiblingPaths', 2 * STATE_TRANSITIONS_LENGTH); } static from(fields: FieldsOf): BaseRollupInputs { @@ -149,7 +149,7 @@ export class BaseRollupInputs { fields.newCommitmentsSubtreeSiblingPath, fields.newNullifiersSubtreeSiblingPath, fields.newContractsSubtreeSiblingPath, - fields.newStateTransitionsSiblingPath, + fields.newStateTransitionsSiblingPaths, fields.historicPrivateDataTreeRootMembershipWitnesses, fields.historicContractsTreeRootMembershipWitnesses, fields.constants, diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index a1840d61ca71..d125ecde364d 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -22,6 +22,8 @@ import { RootRollupPublicInputs, StateRead, StateTransition, + PublicDataWrite, + PublicDataRead, } from '../index.js'; import { AggregationObject } from '../structs/aggregation_object.js'; import { PrivateCallStackItem } from '../structs/call_stack_item.js'; @@ -86,6 +88,14 @@ export function makeSelector(seed: number) { return buffer; } +export function makePublicDataWrite(seed = 1) { + return new PublicDataWrite(fr(seed), fr(seed + 1)); +} + +export function makePublicDataRead(seed = 1) { + return new PublicDataRead(fr(seed), fr(seed + 1)); +} + export function makeStateTransition(seed = 1) { return new StateTransition(fr(seed), fr(seed + 1), fr(seed + 2)); } @@ -106,8 +116,7 @@ export function makeAccumulatedData(seed = 1): CombinedAccumulatedData { range(KERNEL_L1_MSG_STACK_LENGTH, seed + 0x500).map(fr), range(KERNEL_NEW_CONTRACTS_LENGTH, seed + 0x600).map(makeNewContractData), range(KERNEL_OPTIONALLY_REVEALED_DATA_LENGTH, seed + 0x700).map(makeOptionallyRevealedData), - range(STATE_TRANSITIONS_LENGTH, seed + 0x800).map(makeStateTransition), - range(STATE_READS_LENGTH, seed + 0x900).map(makeStateRead), + range(STATE_TRANSITIONS_LENGTH, seed + 0x800).map(makePublicDataWrite), ); } @@ -399,7 +408,7 @@ export function makeBaseRollupInputs(seed = 0) { newCommitmentsSubtreeSiblingPath, newNullifiersSubtreeSiblingPath, newContractsSubtreeSiblingPath, - newStateTransitionsSiblingPath, + newStateTransitionsSiblingPaths: newStateTransitionsSiblingPath, historicPrivateDataTreeRootMembershipWitnesses, historicContractsTreeRootMembershipWitnesses, constants, From 41335198790bd591516106c6dfc85fb689580bfe Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 20 Apr 2023 19:13:48 -0300 Subject: [PATCH 2/3] Get processed public txs into the base rollup circuit --- yarn-project/foundation/src/fields/fields.ts | 2 +- .../circuit_block_builder.test.ts | 39 ++++++------ .../block_builder/circuit_block_builder.ts | 54 +++++++++-------- .../src/block_builder/index.ts | 5 +- .../block_builder/standalone_block_builder.ts | 7 ++- .../src/sequencer/processed_tx.ts | 59 +++++++++++++++++++ .../src/sequencer/public_processor.ts | 54 ++++++++++------- .../src/sequencer/sequencer.test.ts | 25 +++++--- .../src/sequencer/sequencer.ts | 20 +++---- yarn-project/types/src/tx.ts | 25 ++++---- 10 files changed, 190 insertions(+), 100 deletions(-) create mode 100644 yarn-project/sequencer-client/src/sequencer/processed_tx.ts diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 25fc74c6e648..fdabe76fb256 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -58,7 +58,7 @@ export class Fr { */ static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new this(toBigIntBE(reader.readBytes(this.SIZE_IN_BYTES))); + return new Fr(toBigIntBE(reader.readBytes(Fr.SIZE_IN_BYTES))); } /** diff --git a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts index 10e3d8e0e76f..8d44120429c5 100644 --- a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts @@ -1,34 +1,35 @@ import { AppendOnlyTreeSnapshot, - BaseRollupInputs, BaseOrMergeRollupPublicInputs, + BaseRollupInputs, CircuitsWasm, Fr, RootRollupPublicInputs, UInt8Vector, } from '@aztec/circuits.js'; +import { computeContractLeaf } from '@aztec/circuits.js/abis'; import { makeBaseRollupPublicInputs, - makeNewContractData, makeKernelPublicInputs, + makeNewContractData, makeRootRollupPublicInputs, } from '@aztec/circuits.js/factories'; -import { PrivateTx, Tx } from '@aztec/types'; +import { toBufferBE } from '@aztec/foundation'; +import { Tx } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations, MerkleTrees } from '@aztec/world-state'; import { MockProxy, mock } from 'jest-mock-extended'; import { default as levelup } from 'levelup'; import flatMap from 'lodash.flatmap'; +import times from 'lodash.times'; import { default as memdown, type MemDown } from 'memdown'; -import { makeEmptyPrivateTx, makeEmptyUnverifiedData } from '../mocks/tx.js'; +import { makeEmptyUnverifiedData } from '../mocks/tx.js'; import { VerificationKeys, getVerificationKeys } from '../mocks/verification_keys.js'; import { EmptyRollupProver } from '../prover/empty.js'; import { RollupProver } from '../prover/index.js'; +import { ProcessedTx, makeEmptyProcessedTx, makeProcessedTx } from '../sequencer/processed_tx.js'; import { RollupSimulator } from '../simulator/index.js'; import { WasmCircuitSimulator } from '../simulator/wasm.js'; import { CircuitBlockBuilder } from './circuit_block_builder.js'; -import { computeContractLeaf } from '@aztec/circuits.js/abis'; -import { toBufferBE } from '@aztec/foundation'; -import times from 'lodash.times'; export const createMemDown = () => (memdown as any)() as MemDown; @@ -92,7 +93,7 @@ describe('sequencer/circuit_block_builder', () => { }; // Updates the expectedDb trees based on the new commitments, contracts, and nullifiers from these txs - const updateExpectedTreesFromTxs = async (txs: PrivateTx[]) => { + const updateExpectedTreesFromTxs = async (txs: ProcessedTx[]) => { const newContracts = flatMap(txs, tx => tx.data.end.newContracts.map(n => computeContractLeaf(wasm, n))); for (const [tree, leaves] of [ [MerkleTreeId.PRIVATE_DATA_TREE, flatMap(txs, tx => tx.data.end.newCommitments.map(l => l.toBuffer()))], @@ -108,7 +109,7 @@ describe('sequencer/circuit_block_builder', () => { return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); }; - const setTxHistoricTreeRoots = async (tx: PrivateTx) => { + const setTxHistoricTreeRoots = async (tx: ProcessedTx) => { for (const [name, id] of [ ['privateDataTreeRoot', MerkleTreeId.PRIVATE_DATA_TREE], ['contractTreeRoot', MerkleTreeId.CONTRACT_TREE], @@ -127,9 +128,11 @@ describe('sequencer/circuit_block_builder', () => { await builder.updateRootTrees(); // Assemble a fake transaction, we'll tweak some fields below - const tx = Tx.createPrivate(makeKernelPublicInputs(), emptyProof, makeEmptyUnverifiedData()); - const txsLeft = [tx, makeEmptyPrivateTx()]; - const txsRight = [makeEmptyPrivateTx(), makeEmptyPrivateTx()]; + const tx = await makeProcessedTx( + Tx.createPrivate(makeKernelPublicInputs(), emptyProof, makeEmptyUnverifiedData()), + ); + const txsLeft = [tx, await makeEmptyProcessedTx()]; + const txsRight = [await makeEmptyProcessedTx(), await makeEmptyProcessedTx()]; // Set tree roots to proper values in the tx await setTxHistoricTreeRoots(tx); @@ -159,7 +162,7 @@ describe('sequencer/circuit_block_builder', () => { ); // Actually build a block! - const txs = [tx, makeEmptyPrivateTx(), makeEmptyPrivateTx(), makeEmptyPrivateTx()]; + const txs = [tx, await makeEmptyProcessedTx(), await makeEmptyProcessedTx(), await makeEmptyProcessedTx()]; const [l2Block, proof] = await builder.buildL2Block(blockNumber, txs); expect(l2Block.number).toEqual(blockNumber); @@ -196,7 +199,7 @@ describe('sequencer/circuit_block_builder', () => { }); const makeContractDeployTx = async (seed = 0x1) => { - const tx = makeEmptyPrivateTx(); + const tx = await makeEmptyProcessedTx(); await setTxHistoricTreeRoots(tx); tx.data.end.newContracts = [makeNewContractData(seed + 0x1000)]; return tx; @@ -215,7 +218,7 @@ describe('sequencer/circuit_block_builder', () => { const txs = [ ...(await Promise.all(times(deployCount, makeContractDeployTx))), - ...times(totalCount - deployCount, makeEmptyPrivateTx), + ...(await Promise.all(times(totalCount - deployCount, makeEmptyProcessedTx))), ]; const [l2Block] = await builder.buildL2Block(blockNumber, txs); @@ -252,14 +255,14 @@ describe('sequencer/circuit_block_builder', () => { ); // new added values - const tx = makeEmptyPrivateTx(); + const tx = await makeEmptyProcessedTx(); tx.data.end.newNullifiers[0] = new Fr( 10336601644835972678500657502133589897705389664587188571002640950065546264856n, ); tx.data.end.newNullifiers[1] = new Fr( 17490072961923661940560522096125238013953043065748521735636170028491723851741n, ); - const txs = [tx, makeEmptyPrivateTx(), makeEmptyPrivateTx(), makeEmptyPrivateTx()]; + const txs = [tx, await makeEmptyProcessedTx(), await makeEmptyProcessedTx(), await makeEmptyProcessedTx()]; const [l2Block] = await builder.buildL2Block(blockNumber, txs); expect(l2Block.number).toEqual(blockNumber); @@ -269,7 +272,7 @@ describe('sequencer/circuit_block_builder', () => { // Test subject class that exposes internal functions for testing class TestSubject extends CircuitBlockBuilder { - public buildBaseRollupInput(tx1: PrivateTx, tx2: PrivateTx): Promise { + public buildBaseRollupInput(tx1: ProcessedTx, tx2: ProcessedTx): Promise { return super.buildBaseRollupInput(tx1, tx2); } diff --git a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts index ec3b448e7227..5feef5ceb170 100644 --- a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts @@ -17,7 +17,6 @@ import { RollupTypes, RootRollupInputs, RootRollupPublicInputs, - STATE_TRANSITIONS_LENGTH, UInt8Vector, VK_TREE_HEIGHT, VerificationKey, @@ -25,7 +24,7 @@ import { import { computeContractLeaf } from '@aztec/circuits.js/abis'; import { Fr, createDebugLogger, toBigIntBE, toBufferBE } from '@aztec/foundation'; import { LeafData, SiblingPath } from '@aztec/merkle-tree'; -import { ContractData, L2Block, PrivateTx, Tx, isPrivateTx } from '@aztec/types'; +import { ContractData, L2Block } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations } from '@aztec/world-state'; import chunk from 'lodash.chunk'; import flatMap from 'lodash.flatmap'; @@ -34,6 +33,7 @@ import { VerificationKeys } from '../mocks/verification_keys.js'; import { Proof, RollupProver } from '../prover/index.js'; import { RollupSimulator } from '../simulator/index.js'; +import { ProcessedTx } from '../sequencer/processed_tx.js'; import { BlockBuilder } from './index.js'; const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer()); @@ -82,9 +82,7 @@ export class CircuitBlockBuilder implements BlockBuilder { protected debug = createDebugLogger('aztec:sequencer'), ) {} - public async buildL2Block(blockNumber: number, allTxs: Tx[]): Promise<[L2Block, UInt8Vector]> { - const txs: PrivateTx[] = allTxs.filter(isPrivateTx); - + public async buildL2Block(blockNumber: number, txs: ProcessedTx[]): Promise<[L2Block, UInt8Vector]> { const [ startPrivateDataTreeSnapshot, startNullifierTreeSnapshot, @@ -147,7 +145,7 @@ export class CircuitBlockBuilder implements BlockBuilder { return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); } - protected async runCircuits(txs: PrivateTx[]): Promise<[RootRollupPublicInputs, Proof]> { + protected async runCircuits(txs: ProcessedTx[]): Promise<[RootRollupPublicInputs, Proof]> { // Check that the length of the array of txs is a power of two // See https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 if (txs.length < 4 || (txs.length & (txs.length - 1)) !== 0) { @@ -178,8 +176,11 @@ export class CircuitBlockBuilder implements BlockBuilder { return this.rootRollupCircuit(mergeOutputLeft, mergeOutputRight); } - protected async baseRollupCircuit(tx1: PrivateTx, tx2: PrivateTx): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { - this.debug(`Running base rollup for ${await tx1.getTxHash()} ${await tx2.getTxHash()}`); + protected async baseRollupCircuit( + tx1: ProcessedTx, + tx2: ProcessedTx, + ): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + this.debug(`Running base rollup for ${tx1.hash} ${tx2.hash}`); const rollupInput = await this.buildBaseRollupInput(tx1, tx2); const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput); await this.validateTrees(rollupOutput); @@ -355,7 +356,7 @@ export class CircuitBlockBuilder implements BlockBuilder { ); } - protected getKernelDataFor(tx: PrivateTx) { + protected getKernelDataFor(tx: ProcessedTx) { return new PreviousKernelData( tx.data, tx.proof, @@ -391,7 +392,7 @@ export class CircuitBlockBuilder implements BlockBuilder { ); } - protected getContractMembershipWitnessFor(tx: PrivateTx) { + protected getContractMembershipWitnessFor(tx: ProcessedTx) { return this.getMembershipWitnessFor( tx.data.constants.historicTreeRoots.privateHistoricTreeRoots.contractTreeRoot, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, @@ -399,7 +400,7 @@ export class CircuitBlockBuilder implements BlockBuilder { ); } - protected getDataMembershipWitnessFor(tx: PrivateTx) { + protected getDataMembershipWitnessFor(tx: ProcessedTx) { return this.getMembershipWitnessFor( tx.data.constants.historicTreeRoots.privateHistoricTreeRoots.privateDataTreeRoot, MerkleTreeId.PRIVATE_DATA_TREE_ROOTS_TREE, @@ -686,20 +687,18 @@ export class CircuitBlockBuilder implements BlockBuilder { } // Builds the base rollup inputs, updating the contract, nullifier, and data trees in the process - protected async buildBaseRollupInput(tx1: PrivateTx, tx2: PrivateTx) { + protected async buildBaseRollupInput(tx1: ProcessedTx, tx2: ProcessedTx) { + const wasm = await CircuitsWasm.get(); + // Get trees info before any changes hit const constants = await this.getConstantBaseRollupData(); const startNullifierTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); const startContractTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); const startPrivateDataTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.PRIVATE_DATA_TREE); + const startPublicDataTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); - // TODO: Uncomment once the public data tree gets merged - // const startPublicDataTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); - const startPublicDataTreeSnapshot = AppendOnlyTreeSnapshot.empty(); - - // Update the contract and data trees with the new items being inserted to get the new roots + // Update the contract and private data trees with the new items being inserted to get the new roots // that will be used by the next iteration of the base rollup circuit, skipping the empty ones - const wasm = await CircuitsWasm.get(); const newContracts = flatMap([tx1, tx2], tx => tx.data.end.newContracts.map(cd => computeContractLeaf(wasm, cd))); const newCommitments = flatMap([tx1, tx2], tx => tx.data.end.newCommitments.map(x => x.toBuffer())); await this.db.appendLeaves( @@ -709,6 +708,17 @@ export class CircuitBlockBuilder implements BlockBuilder { await this.db.appendLeaves(MerkleTreeId.PRIVATE_DATA_TREE, newCommitments); + // Update the public data tree and get membership witnesses + const stateTransitions = [...tx1.data.end.stateTransitions, ...tx2.data.end.stateTransitions]; + const newStateTransitionsSiblingPaths: MembershipWitness<32>[] = []; + for (const stateTransition of stateTransitions) { + const index = stateTransition.leafIndex.value; + const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, index); + await this.db.updateLeaf(MerkleTreeId.PUBLIC_DATA_TREE, stateTransition.newValue.toBuffer(), index); + const witness = new MembershipWitness(PUBLIC_DATA_TREE_HEIGHT, Number(index), path.data.map(Fr.fromBuffer)); + newStateTransitionsSiblingPaths.push(witness); + } + // Update the nullifier tree, capturing the low nullifier info for each individual operation const newNullifiers = [...tx1.data.end.newNullifiers, ...tx2.data.end.newNullifiers]; @@ -716,6 +726,7 @@ export class CircuitBlockBuilder implements BlockBuilder { if (nullifierWitnesses === undefined) { throw new Error(`Could not craft nullifier batch insertion proofs`); } + // Extract witness objects from returned data const lowNullifierMembershipWitnesses = nullifierWitnesses.map(w => MembershipWitness.fromBufferArray(Number(w.index), w.siblingPath.data), @@ -735,11 +746,6 @@ export class CircuitBlockBuilder implements BlockBuilder { BaseRollupInputs.NULLIFIER_SUBTREE_HEIGHT, ); - // TODO: Implement based on public tx data - const newStateTransitionsSiblingPath = times(2 * STATE_TRANSITIONS_LENGTH, () => - MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0), - ); - return BaseRollupInputs.from({ constants, startNullifierTreeSnapshot, @@ -749,7 +755,7 @@ export class CircuitBlockBuilder implements BlockBuilder { newCommitmentsSubtreeSiblingPath, newContractsSubtreeSiblingPath, newNullifiersSubtreeSiblingPath, - newStateTransitionsSiblingPath, + newStateTransitionsSiblingPaths, lowNullifierLeafPreimages: nullifierWitnesses.map((w: LowNullifierWitnessData) => w.preimage), lowNullifierMembershipWitness: lowNullifierMembershipWitnesses, kernelData: [this.getKernelDataFor(tx1), this.getKernelDataFor(tx2)], diff --git a/yarn-project/sequencer-client/src/block_builder/index.ts b/yarn-project/sequencer-client/src/block_builder/index.ts index 5ef0efeece96..8859a405a7a2 100644 --- a/yarn-project/sequencer-client/src/block_builder/index.ts +++ b/yarn-project/sequencer-client/src/block_builder/index.ts @@ -1,6 +1,7 @@ -import { L2Block, Tx } from '@aztec/types'; +import { L2Block } from '@aztec/types'; import { Proof } from '../prover/index.js'; +import { ProcessedTx } from '../sequencer/processed_tx.js'; export interface BlockBuilder { - buildL2Block(blockNumber: number, txs: Tx[]): Promise<[L2Block, Proof]>; + buildL2Block(blockNumber: number, txs: ProcessedTx[]): Promise<[L2Block, Proof]>; } diff --git a/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts index 1fca04dd5403..39d805a764cb 100644 --- a/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts @@ -9,9 +9,10 @@ import { } from '@aztec/circuits.js'; import { computeContractLeaf } from '@aztec/circuits.js/abis'; import { AztecAddress, Fr, createDebugLogger } from '@aztec/foundation'; -import { ContractData, L2Block, PrivateTx } from '@aztec/types'; +import { ContractData, L2Block } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations } from '@aztec/world-state'; import { Proof } from '../prover/index.js'; +import { ProcessedTx } from '../sequencer/processed_tx.js'; import { BlockBuilder } from './index.js'; const mapContractData = (n: NewContractData) => { @@ -30,7 +31,7 @@ export class StandaloneBlockBuilder implements BlockBuilder { constructor(private db: MerkleTreeOperations, private log = createDebugLogger('aztec:block_builder')) {} - async buildL2Block(blockNumber: number, txs: PrivateTx[]): Promise<[L2Block, Proof]> { + async buildL2Block(blockNumber: number, txs: ProcessedTx[]): Promise<[L2Block, Proof]> { const startPrivateDataTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.PRIVATE_DATA_TREE); const startNullifierTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); const startContractTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); @@ -82,7 +83,7 @@ export class StandaloneBlockBuilder implements BlockBuilder { return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); } - private async updateTrees(tx: PrivateTx) { + private async updateTrees(tx: ProcessedTx) { const wasm = await CircuitsWasm.get(); const dataTreeLeaves = tx.data.end.newCommitments.map((x: Fr) => x.toBuffer()); const nullifierTreeLeaves = tx.data.end.newNullifiers.map((x: Fr) => x.toBuffer()); diff --git a/yarn-project/sequencer-client/src/sequencer/processed_tx.ts b/yarn-project/sequencer-client/src/sequencer/processed_tx.ts new file mode 100644 index 000000000000..1f49ade4acfd --- /dev/null +++ b/yarn-project/sequencer-client/src/sequencer/processed_tx.ts @@ -0,0 +1,59 @@ +import { KernelCircuitPublicInputs } from '@aztec/circuits.js'; +import { PrivateTx, PublicTx, Tx, TxHash } from '@aztec/types'; +import { makeEmptyPrivateTx } from '../index.js'; +import { Proof } from '../prover/index.js'; + +/** + * Represents a tx that has been processed by the sequencer public processor, + * so its kernel circuit public inputs are filled in. + */ +export type ProcessedTx = Pick & + Required> & { + /** + * Hash of the transaction. + */ + hash: TxHash; + }; + +/** + * Makes a processed tx out of a private only tx that has its proof already set. + * @param tx - source tx that doesn't need further processing + */ +export async function makeProcessedTx(tx: PrivateTx): Promise; + +/** + * Makes a processed tx out of a tx with a public component that needs processing. + * @param tx - source tx + * @param kernelOutput - output of the public kernel circuit simulation for this tx + * @param proof - proof of the public kernel circuit for this tx + */ +export async function makeProcessedTx( + tx: PublicTx, + kernelOutput: KernelCircuitPublicInputs, + proof: Proof, +): Promise; + +export async function makeProcessedTx( + tx: Tx, + kernelOutput?: KernelCircuitPublicInputs, + proof?: Proof, +): Promise { + return { + hash: await tx.getTxHash(), + data: kernelOutput ?? tx.data!, + proof: proof ?? tx.proof!, + unverifiedData: tx.unverifiedData, + txRequest: tx.txRequest, + }; +} + +/** + * Makes an empty tx from an empty kernel circuit public inputs. + * @returns A processed empty tx. + */ +export async function makeEmptyProcessedTx(): Promise { + const emptyTx = makeEmptyPrivateTx(); + const hash = await emptyTx.getTxHash(); + + return { hash, data: emptyTx.data, proof: emptyTx.proof }; +} diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index a40d819694fd..93645b85ddf1 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -15,16 +15,12 @@ import { WitnessedPublicCallData, } from '@aztec/circuits.js'; import { AztecAddress, createDebugLogger } from '@aztec/foundation'; -import { PublicTx } from '@aztec/types'; +import { PublicTx, Tx } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations, computePublicDataTreeLeafIndex } from '@aztec/world-state'; import times from 'lodash.times'; import { Proof, PublicProver } from '../prover/index.js'; import { PublicCircuitSimulator, PublicKernelCircuitSimulator } from '../simulator/index.js'; - -type ProcessedPublicTx = { - tx: PublicTx; - publicKernelOutput: PublicKernelPublicInputs; -}; +import { ProcessedTx, makeEmptyProcessedTx, makeProcessedTx } from './processed_tx.js'; export class PublicProcessor { constructor( @@ -37,33 +33,51 @@ export class PublicProcessor { ) {} /** - * Run each tx through the public circuit and the public kernel circuit. - * @param txs - public txs to process + * Run each tx through the public circuit and the public kernel circuit if needed. + * @param txs - txs to process * @returns the list of processed txs with their circuit simulation outputs. */ - public async process(txs: PublicTx[]): Promise<[ProcessedPublicTx[], PublicTx[]]> { - const result: ProcessedPublicTx[] = []; - const failed: PublicTx[] = []; + public async process(txs: Tx[]): Promise<[ProcessedTx[], Tx[]]> { + const result: ProcessedTx[] = []; + const failed: Tx[] = []; for (const tx of txs) { - this.log(`Processing public tx ${await tx.getTxHash()}`); + this.log(`Processing tx ${await tx.getTxHash()}`); try { - result.push({ tx, publicKernelOutput: await this.processTx(tx) }); + result.push(await this.processTx(tx)); } catch (err) { - this.log(`Error processing public tx ${await tx.getTxHash()}: ${err}`); + this.log(`Error processing tx ${await tx.getTxHash()}: ${err}`); failed.push(tx); } } return [result, failed]; } - protected async processTx(tx: PublicTx): Promise { + protected async processTx(tx: Tx): Promise { + if (tx.isPublic()) { + const [publicKernelOutput, publicKernelProof] = await this.processPublicTx(tx); + return makeProcessedTx(tx, publicKernelOutput, publicKernelProof); + } else if (tx.isPrivate()) { + return makeProcessedTx(tx); + } else { + return makeEmptyProcessedTx(); + } + } + + // TODO: This is just picking up the txRequest and executing one iteration of it. It disregards + // any existing private execution information, and any subsequent calls. + protected async processPublicTx(tx: PublicTx): Promise<[PublicKernelPublicInputs, Proof]> { const publicCircuitOutput = await this.publicCircuit.publicCircuit(tx.txRequest.txRequest); - const proof = await this.publicProver.getPublicCircuitProof(publicCircuitOutput); - const publicCallData = await this.processPublicCallData(tx.txRequest.txRequest, publicCircuitOutput, proof); + const publicCircuitProof = await this.publicProver.getPublicCircuitProof(publicCircuitOutput); + const publicCallData = await this.processPublicCallData( + tx.txRequest.txRequest, + publicCircuitOutput, + publicCircuitProof, + ); const publicKernelInput = new PublicKernelInputsNoKernelInput(tx.txRequest, publicCallData); const publicKernelOutput = await this.publicKernel.publicKernelCircuitNoInput(publicKernelInput); - return publicKernelOutput; + const publicKernelProof = await this.publicProver.getPublicKernelCircuitProof(publicKernelOutput); + return [publicKernelOutput, publicKernelProof]; } protected async processPublicCallData( @@ -135,7 +149,7 @@ export class PublicProcessor { } export class MockPublicProcessor extends PublicProcessor { - public process(_txs: PublicTx[]): Promise<[ProcessedPublicTx[], PublicTx[]]> { - return Promise.resolve([[], []]); + protected processPublicTx(_tx: PublicTx): Promise<[PublicKernelPublicInputs, Proof]> { + throw new Error('Public tx not supported by mock public processor'); } } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 1ac256cc8621..97c91917e8a6 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -1,12 +1,14 @@ import { makeEmptyProof } from '@aztec/circuits.js'; import { P2P, P2PClientState } from '@aztec/p2p'; -import { L2Block, UnverifiedData } from '@aztec/types'; +import { L2Block, PrivateTx, Tx, UnverifiedData } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations, WorldStateRunningState, WorldStateSynchroniser } from '@aztec/world-state'; import { MockProxy, mock } from 'jest-mock-extended'; +import times from 'lodash.times'; import { BlockBuilder } from '../block_builder/index.js'; import { L1Publisher, makeEmptyPrivateTx, makePrivateTx } from '../index.js'; -import { Sequencer } from './sequencer.js'; +import { makeProcessedTx } from './processed_tx.js'; import { PublicProcessor } from './public_processor.js'; +import { Sequencer } from './sequencer.js'; describe('sequencer', () => { let publisher: MockProxy; @@ -36,8 +38,9 @@ describe('sequencer', () => { status: () => Promise.resolve({ state: WorldStateRunningState.IDLE, syncedToL2Block: lastBlockNumber }), }); - publicProcessor = mock(); - publicProcessor.process.mockResolvedValue([[], []]); + publicProcessor = mock({ + process: async txs => [await Promise.all(txs.map(tx => makeProcessedTx(tx as PrivateTx))), []], + }); sequencer = new TestSubject(publisher, p2p, worldState, blockBuilder, publicProcessor); }); @@ -55,10 +58,13 @@ describe('sequencer', () => { await sequencer.initialSync(); await sequencer.work(); - const expectedTxs = [tx, makeEmptyPrivateTx(), makeEmptyPrivateTx(), makeEmptyPrivateTx()]; + const expectedTxHashes = await Tx.getHashes([tx, ...times(3, makeEmptyPrivateTx)]); const expectedUnverifiedData = tx.unverifiedData; - expect(blockBuilder.buildL2Block).toHaveBeenCalledWith(lastBlockNumber + 1, expectedTxs); + expect(blockBuilder.buildL2Block).toHaveBeenCalledWith( + lastBlockNumber + 1, + expectedTxHashes.map(hash => expect.objectContaining({ hash })), + ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(publisher.processUnverifiedData).toHaveBeenCalledWith(lastBlockNumber + 1, expectedUnverifiedData); }); @@ -85,13 +91,16 @@ describe('sequencer', () => { await sequencer.initialSync(); await sequencer.work(); - const expectedTxs = [txs[0], txs[2], makeEmptyPrivateTx(), makeEmptyPrivateTx()]; + const expectedTxHashes = await Tx.getHashes([txs[0], txs[2], makeEmptyPrivateTx(), makeEmptyPrivateTx()]); const expectedUnverifiedData = new UnverifiedData([ ...txs[0].unverifiedData.dataChunks, ...txs[2].unverifiedData.dataChunks, ]); - expect(blockBuilder.buildL2Block).toHaveBeenCalledWith(lastBlockNumber + 1, expectedTxs); + expect(blockBuilder.buildL2Block).toHaveBeenCalledWith( + lastBlockNumber + 1, + expectedTxHashes.map(hash => expect.objectContaining({ hash })), + ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(publisher.processUnverifiedData).toHaveBeenCalledWith(lastBlockNumber + 1, expectedUnverifiedData); expect(p2p.deleteTxs).toHaveBeenCalledWith([await doubleSpendTx.getTxHash()]); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 341edeeaac82..4a700e2fca6f 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,13 +1,13 @@ import { RunningPromise, createDebugLogger } from '@aztec/foundation'; import { P2P } from '@aztec/p2p'; -import { PrivateTx, PublicTx, Tx, UnverifiedData, isPrivateTx, isPublicTx } from '@aztec/types'; +import { PrivateTx, PublicTx, Tx, UnverifiedData, isPrivateTx } from '@aztec/types'; import { MerkleTreeId, WorldStateStatus, WorldStateSynchroniser } from '@aztec/world-state'; import times from 'lodash.times'; import { BlockBuilder } from '../block_builder/index.js'; -import { makeEmptyPrivateTx } from '../index.js'; import { L1Publisher } from '../publisher/l1-publisher.js'; import { ceilPowerOfTwo } from '../utils.js'; import { SequencerConfig } from './config.js'; +import { ProcessedTx, makeEmptyProcessedTx } from './processed_tx.js'; import { PublicProcessor } from './public_processor.js'; /** @@ -101,20 +101,19 @@ export class Sequencer { return; } - this.log(`Assembling block with txs ${(await Tx.getHashes(validTxs)).join(', ')}`); + this.log(`Processing txs ${(await Tx.getHashes(validTxs)).join(', ')}`); this.state = SequencerState.CREATING_BLOCK; // Process public txs and drop the ones that fail processing - const publicTxs = validTxs.filter(isPublicTx); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [processedTxs, failedTxs] = await this.publicProcessor.process(publicTxs); + const [processedTxs, failedTxs] = await this.publicProcessor.process(validTxs); if (failedTxs.length > 0) { + this.log(`Dropping failed txs ${(await Tx.getHashes(failedTxs)).join(', ')}`); await this.p2pClient.deleteTxs(await Tx.getHashes(failedTxs)); } // Build the new block by running the rollup circuits - // TODO: Get the public tx combined outputs in here! - const block = await this.buildBlock(validTxs); + this.log(`Assembling block with txs ${processedTxs.map(tx => tx.hash).join(', ')}`); + const block = await this.buildBlock(processedTxs); this.log(`Assembled block ${block.number}`); // Publishes new block to the network and awaits the tx to be mined @@ -189,11 +188,10 @@ export class Sequencer { ); } - protected async buildBlock(txs: Tx[]) { + protected async buildBlock(txs: ProcessedTx[]) { // Pad the txs array with empty txs to be a power of two, at least 4 const txsTargetSize = Math.max(ceilPowerOfTwo(txs.length), 4); - const allTxs = [...txs, ...times(txsTargetSize - txs.length, makeEmptyPrivateTx)]; - + const allTxs = [...txs, ...(await Promise.all(times(txsTargetSize - txs.length, makeEmptyProcessedTx)))]; const [block] = await this.blockBuilder.buildL2Block(this.lastBlockNumber + 1, allTxs); return block; } diff --git a/yarn-project/types/src/tx.ts b/yarn-project/types/src/tx.ts index f03f55075c55..987f199f327d 100644 --- a/yarn-project/types/src/tx.ts +++ b/yarn-project/types/src/tx.ts @@ -5,15 +5,18 @@ import { TxHash } from './tx_hash.js'; import { UnverifiedData } from './unverified_data.js'; import { keccak } from '@aztec/foundation'; -export type PrivateTx = Required> & Tx; -export type PublicTx = Required> & Tx; +type PrivateTxFields = 'data' | 'proof' | 'unverifiedData'; +type PublicTxFields = 'txRequest'; + +export type PrivateTx = Required> & Tx; +export type PublicTx = Required> & Tx; export function isPublicTx(tx: Tx): tx is PublicTx { - return tx.isPublic(); + return !!tx.txRequest; } export function isPrivateTx(tx: Tx): tx is PrivateTx { - return tx.isPrivate(); + return !!tx.data && !!tx.proof && !!tx.unverifiedData; } /** @@ -49,19 +52,15 @@ export class Tx { unverifiedData?: UnverifiedData, txRequest?: SignedTxRequest, ): Tx { - const tx = new this(data, proof, unverifiedData, txRequest); - if (!tx.isPrivate() && !tx.isPublic()) { - throw new Error(`Tx needs either public or private data`); - } - return tx; + return new this(data, proof, unverifiedData, txRequest); } public isPrivate(): this is PrivateTx { - return !!this.data && !!this.proof && !!this.unverifiedData; + return isPrivateTx(this); } public isPublic(): this is PublicTx { - return !!this.txRequest; + return isPublicTx(this); } /** @@ -116,7 +115,7 @@ export class Tx { // contract tree leaves, which then go into the L2 block, which are then used to regenerate // the tx hashes. This means we need the full circuits wasm, and cannot use the lighter primitives // wasm. Alternatively, we could stop using computeContractLeaf and manually use the same hash. - if (tx.isPrivate()) { + if (tx.data) { const wasm = await CircuitsWasm.get(); hashes.push( createTxHash({ @@ -128,7 +127,7 @@ export class Tx { // We hash the full signed tx request object (this is, the tx request along with the signature), // just like Ethereum does. - if (tx.isPublic()) { + if (tx.txRequest) { hashes.push(new TxHash(keccak(tx.txRequest.toBuffer()))); } From fcde126eb17e00af4459cfb79699b4f1fe6dfe72 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 20 Apr 2023 19:26:14 -0300 Subject: [PATCH 3/3] Format --- yarn-project/circuits.js/src/structs/kernel/public_inputs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts index 597ba25e6ff1..08db50e01baf 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_inputs.ts @@ -47,4 +47,4 @@ export class PrivateKernelPublicInputs extends KernelCircuitPublicInputs { constructor(end: CombinedAccumulatedData, constants: CombinedConstantData) { super(end, constants, true); } -} \ No newline at end of file +}