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: Light block builder #8662

Merged
merged 2 commits into from
Sep 23, 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 yarn-project/circuit-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"./jest": "./dest/jest/index.js",
"./interfaces": "./dest/interfaces/index.js",
"./log_id": "./dest/logs/log_id.js",
"./test": "./dest/test/index.js",
"./tx_hash": "./dest/tx/tx_hash.js"
},
"typedocOptions": {
Expand Down
47 changes: 3 additions & 44 deletions yarn-project/circuit-types/src/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import {
TxEffect,
UnencryptedL2BlockL2Logs,
} from '@aztec/circuit-types';
import { padArrayEnd } from '@aztec/foundation/collection';
import { sha256Trunc } from '@aztec/foundation/crypto';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
import { computeUnbalancedMerkleRoot } from '@aztec/foundation/trees';

import { inspect } from 'util';

Expand Down Expand Up @@ -52,49 +51,9 @@ export class Body {
* @returns The txs effects hash.
*/
getTxsEffectsHash() {
// Adapted from proving-state.ts -> findMergeLevel and unbalanced_tree.ts
// Calculates the tree upwards layer by layer until we reach the root
// The L1 calculation instead computes the tree from right to left (slightly cheaper gas)
// TODO: A more thorough investigation of which method is cheaper, then use that method everywhere
const computeRoot = (leaves: Buffer[]): Buffer => {
const depth = Math.ceil(Math.log2(leaves.length));
let [layerWidth, nodeToShift] =
leaves.length & 1 ? [leaves.length - 1, leaves[leaves.length - 1]] : [leaves.length, Buffer.alloc(0)];
// Allocate this layer's leaves and init the next layer up
let thisLayer = leaves.slice(0, layerWidth);
let nextLayer = [];
for (let i = 0; i < depth; i++) {
for (let j = 0; j < layerWidth; j += 2) {
// Store the hash of each pair one layer up
nextLayer[j / 2] = sha256Trunc(Buffer.concat([thisLayer[j], thisLayer[j + 1]]));
}
layerWidth /= 2;
if (layerWidth & 1) {
if (nodeToShift.length) {
// If the next layer has odd length, and we have a node that needs to be shifted up, add it here
nextLayer.push(nodeToShift);
layerWidth += 1;
nodeToShift = Buffer.alloc(0);
} else {
// If we don't have a node waiting to be shifted, store the next layer's final node to be shifted
layerWidth -= 1;
nodeToShift = nextLayer[layerWidth];
}
}
// reset the layers
thisLayer = nextLayer;
nextLayer = [];
}
// return the root
return thisLayer[0];
};

const emptyTxEffectHash = TxEffect.empty().hash();
let leaves: Buffer[] = this.txEffects.map(txEffect => txEffect.hash());
if (leaves.length < 2) {
leaves = padArrayEnd(leaves, emptyTxEffectHash, 2);
}
return computeRoot(leaves);
const leaves: Buffer[] = this.txEffects.map(txEffect => txEffect.hash());
return computeUnbalancedMerkleRoot(leaves, emptyTxEffectHash);
}

get noteEncryptedLogs(): EncryptedNoteL2BlockL2Logs {
Expand Down
19 changes: 19 additions & 0 deletions yarn-project/circuit-types/src/merkle_tree_id.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import {
ARCHIVE_HEIGHT,
ARCHIVE_TREE_ID,
L1_TO_L2_MESSAGE_TREE_ID,
L1_TO_L2_MSG_TREE_HEIGHT,
NOTE_HASH_TREE_HEIGHT,
NOTE_HASH_TREE_ID,
NULLIFIER_TREE_HEIGHT,
NULLIFIER_TREE_ID,
PUBLIC_DATA_TREE_HEIGHT,
PUBLIC_DATA_TREE_ID,
} from '@aztec/circuits.js';

Expand All @@ -21,3 +26,17 @@ export enum MerkleTreeId {
export const merkleTreeIds = () => {
return Object.values(MerkleTreeId).filter((v): v is MerkleTreeId => !isNaN(Number(v)));
};

const TREE_HEIGHTS = {
[MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
[MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
[MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
[MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
} as const;

export type TreeHeights = typeof TREE_HEIGHTS;

export function getTreeHeight<TID extends MerkleTreeId>(treeId: TID): TreeHeights[TID] {
return TREE_HEIGHTS[treeId];
}
63 changes: 63 additions & 0 deletions yarn-project/circuit-types/src/test/factories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { type MerkleTreeOperations, makeProcessedTx, mockTx } from '@aztec/circuit-types';
import {
Fr,
GasSettings,
type Header,
KernelCircuitPublicInputs,
LogHash,
MAX_L2_TO_L1_MSGS_PER_TX,
MAX_NOTE_HASHES_PER_TX,
MAX_NULLIFIERS_PER_TX,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
PublicDataUpdateRequest,
ScopedLogHash,
} from '@aztec/circuits.js';
import { makeScopedL2ToL1Message } from '@aztec/circuits.js/testing';
import { makeTuple } from '@aztec/foundation/array';

/** Makes a bloated processed tx for testing purposes. */
export function makeBloatedProcessedTx(
historicalHeaderOrDb: Header | MerkleTreeOperations,
vkRoot: Fr,
seed = 0x1,
overrides: { inclusionFee?: Fr } = {},
) {
seed *= MAX_NULLIFIERS_PER_TX; // Ensure no clashing given incremental seeds
const tx = mockTx(seed);
const kernelOutput = KernelCircuitPublicInputs.empty();
kernelOutput.constants.vkTreeRoot = vkRoot;
kernelOutput.constants.historicalHeader =
'getInitialHeader' in historicalHeaderOrDb ? historicalHeaderOrDb.getInitialHeader() : historicalHeaderOrDb;
kernelOutput.end.publicDataUpdateRequests = makeTuple(
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
i => new PublicDataUpdateRequest(new Fr(i), new Fr(i + 10), i + 20),
seed + 0x500,
);
kernelOutput.end.publicDataUpdateRequests = makeTuple(
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
i => new PublicDataUpdateRequest(new Fr(i), new Fr(i + 10), i + 20),
seed + 0x600,
);

kernelOutput.constants.txContext.gasSettings = GasSettings.default({ inclusionFee: overrides.inclusionFee });

const processedTx = makeProcessedTx(tx, kernelOutput, []);

processedTx.data.end.noteHashes = makeTuple(MAX_NOTE_HASHES_PER_TX, i => new Fr(i), seed + 0x100);
processedTx.data.end.nullifiers = makeTuple(MAX_NULLIFIERS_PER_TX, i => new Fr(i), seed + 0x100000);

processedTx.data.end.nullifiers[tx.data.forPublic!.end.nullifiers.length - 1] = Fr.zero();

processedTx.data.end.l2ToL1Msgs = makeTuple(MAX_L2_TO_L1_MSGS_PER_TX, makeScopedL2ToL1Message, seed + 0x300);
processedTx.noteEncryptedLogs.unrollLogs().forEach((log, i) => {
processedTx.data.end.noteEncryptedLogsHashes[i] = new LogHash(Fr.fromBuffer(log.hash()), 0, new Fr(log.length));
});
processedTx.encryptedLogs.unrollLogs().forEach((log, i) => {
processedTx.data.end.encryptedLogsHashes[i] = new ScopedLogHash(
new LogHash(Fr.fromBuffer(log.hash()), 0, new Fr(log.length)),
log.maskedContractAddress,
);
});

return processedTx;
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './factories.js';
53 changes: 29 additions & 24 deletions yarn-project/circuit-types/src/tx_effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,33 +146,10 @@ export class TxEffect {
*/
hash() {
const padBuffer = (buf: Buffer, length: number) => Buffer.concat([buf, Buffer.alloc(length - buf.length)]);
// Below follows computeTxOutHash in TxsDecoder.sol and new_sha in variable_merkle_tree.nr
// TODO(#7218): Revert to fixed height tree for outbox
const computeTxOutHash = (l2ToL1Msgs: Fr[]) => {
if (l2ToL1Msgs.length == 0) {
return Buffer.alloc(32);
}
const depth = l2ToL1Msgs.length == 1 ? 1 : Math.ceil(Math.log2(l2ToL1Msgs.length));
let thisLayer = padArrayEnd(
l2ToL1Msgs.map(msg => msg.toBuffer()),
Buffer.alloc(32),
2 ** depth,
);
let nextLayer = [];
for (let i = 0; i < depth; i++) {
for (let j = 0; j < thisLayer.length; j += 2) {
// Store the hash of each pair one layer up
nextLayer[j / 2] = sha256Trunc(Buffer.concat([thisLayer[j], thisLayer[j + 1]]));
}
thisLayer = nextLayer;
nextLayer = [];
}
return thisLayer[0];
};

const noteHashesBuffer = padBuffer(serializeToBuffer(this.noteHashes), Fr.SIZE_IN_BYTES * MAX_NOTE_HASHES_PER_TX);
const nullifiersBuffer = padBuffer(serializeToBuffer(this.nullifiers), Fr.SIZE_IN_BYTES * MAX_NULLIFIERS_PER_TX);
const outHashBuffer = computeTxOutHash(this.l2ToL1Msgs);
const outHashBuffer = this.txOutHash();
const publicDataWritesBuffer = padBuffer(
serializeToBuffer(this.publicDataWrites),
PublicDataWrite.SIZE_IN_BYTES * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
Expand Down Expand Up @@ -200,6 +177,34 @@ export class TxEffect {
return sha256Trunc(inputValue);
}

/**
* Computes txOutHash of this tx effect.
* TODO(#7218): Revert to fixed height tree for outbox
* @dev Follows computeTxOutHash in TxsDecoder.sol and new_sha in variable_merkle_tree.nr
*/
txOutHash() {
const { l2ToL1Msgs } = this;
if (l2ToL1Msgs.length == 0) {
return Buffer.alloc(32);
}
const depth = l2ToL1Msgs.length == 1 ? 1 : Math.ceil(Math.log2(l2ToL1Msgs.length));
let thisLayer = padArrayEnd(
l2ToL1Msgs.map(msg => msg.toBuffer()),
Buffer.alloc(32),
2 ** depth,
);
let nextLayer = [];
for (let i = 0; i < depth; i++) {
for (let j = 0; j < thisLayer.length; j += 2) {
// Store the hash of each pair one layer up
nextLayer[j / 2] = sha256Trunc(Buffer.concat([thisLayer[j], thisLayer[j + 1]]));
}
thisLayer = nextLayer;
nextLayer = [];
}
return thisLayer[0];
}

static random(
numPrivateCallsPerTx = 2,
numPublicCallsPerTx = 3,
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/circuits.js/src/merkle/merkle_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class MerkleTree {
}

/** Returns a nice string representation of the tree, useful for debugging purposes. */
public drawTree() {
public drawTree(elemSize = 8) {
const levels: string[][] = [];
const tree = this.nodes;
const maxRowSize = Math.ceil(tree.length / 2);
Expand All @@ -58,7 +58,7 @@ export class MerkleTree {
levels.push(
tree
.slice(rowOffset, rowOffset + rowSize)
.map(n => n.toString('hex').slice(0, 8) + ' '.repeat((paddingSize - 1) * 9)),
.map(n => n.toString('hex').slice(0, elemSize) + ' '.repeat((paddingSize - 1) * (elemSize + 1))),
);
rowOffset += rowSize;
paddingSize <<= 1;
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/circuits.js/src/structs/gas_settings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { compact } from '@aztec/foundation/collection';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize';
import { type FieldsOf } from '@aztec/foundation/types';
Expand Down Expand Up @@ -66,13 +67,13 @@ export class GasSettings {
}

/** Default gas settings to use when user has not provided them. */
static default(overrides?: Partial<FieldsOf<GasSettings>>) {
static default(overrides: Partial<FieldsOf<GasSettings>> = {}) {
return GasSettings.from({
gasLimits: { l2Gas: DEFAULT_GAS_LIMIT, daGas: DEFAULT_GAS_LIMIT },
teardownGasLimits: { l2Gas: DEFAULT_TEARDOWN_GAS_LIMIT, daGas: DEFAULT_TEARDOWN_GAS_LIMIT },
maxFeesPerGas: { feePerL2Gas: new Fr(DEFAULT_MAX_FEE_PER_GAS), feePerDaGas: new Fr(DEFAULT_MAX_FEE_PER_GAS) },
inclusionFee: new Fr(DEFAULT_INCLUSION_FEE),
...overrides,
...compact(overrides),
});
}

Expand Down
33 changes: 17 additions & 16 deletions yarn-project/circuits.js/src/tests/factories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type FieldsOf, makeHalfFullTuple, makeTuple } from '@aztec/foundation/array';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
import { compact } from '@aztec/foundation/collection';
import { EthAddress } from '@aztec/foundation/eth-address';
import { type Bufferable } from '@aztec/foundation/serialize';
import {
Expand Down Expand Up @@ -789,21 +790,18 @@ export function makePrivateCircuitPublicInputs(seed = 0): PrivateCircuitPublicIn
});
}

export function makeGlobalVariables(
seed = 1,
blockNumber: number | undefined = undefined,
slotNumber: number | undefined = undefined,
): GlobalVariables {
return new GlobalVariables(
new Fr(seed),
new Fr(seed + 1),
new Fr(blockNumber ?? seed + 2),
new Fr(slotNumber ?? seed + 3),
new Fr(seed + 4),
EthAddress.fromField(new Fr(seed + 5)),
AztecAddress.fromField(new Fr(seed + 6)),
new GasFees(new Fr(seed + 7), new Fr(seed + 8)),
);
export function makeGlobalVariables(seed = 1, overrides: Partial<FieldsOf<GlobalVariables>> = {}): GlobalVariables {
return GlobalVariables.from({
chainId: new Fr(seed),
version: new Fr(seed + 1),
blockNumber: new Fr(seed + 2),
slotNumber: new Fr(seed + 3),
timestamp: new Fr(seed + 4),
coinbase: EthAddress.fromField(new Fr(seed + 5)),
feeRecipient: AztecAddress.fromField(new Fr(seed + 6)),
gasFees: new GasFees(new Fr(seed + 7), new Fr(seed + 8)),
...compact(overrides),
});
}

export function makeGasFees(seed = 1) {
Expand Down Expand Up @@ -1076,7 +1074,10 @@ export function makeHeader(
makeAppendOnlyTreeSnapshot(seed + 0x100),
makeContentCommitment(seed + 0x200, txsEffectsHash),
makeStateReference(seed + 0x600),
makeGlobalVariables((seed += 0x700), blockNumber, slotNumber),
makeGlobalVariables((seed += 0x700), {
...(blockNumber ? { blockNumber: new Fr(blockNumber) } : {}),
...(slotNumber ? { slotNumber: new Fr(slotNumber) } : {}),
}),
fr(seed + 0x800),
);
}
Expand Down
32 changes: 30 additions & 2 deletions yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@aztec/aztec.js';
import { times } from '@aztec/foundation/collection';
import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto';
import { StatefulTestContractArtifact } from '@aztec/noir-contracts.js';
import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js';
import { TestContract } from '@aztec/noir-contracts.js/Test';
import { TokenContract } from '@aztec/noir-contracts.js/Token';

Expand Down Expand Up @@ -89,7 +89,35 @@ describe('e2e_block_building', () => {
expect(areDeployed).toEqual(times(TX_COUNT, () => true));
});

it.skip('can call public function from different tx in same block', async () => {
it('assembles a block with multiple txs with public fns', async () => {
// First deploy the contract
const ownerAddress = owner.getCompleteAddress().address;
const contract = await StatefulTestContract.deploy(owner, ownerAddress, ownerAddress, 1).send().deployed();

// Assemble N contract deployment txs
// We need to create them sequentially since we cannot have parallel calls to a circuit
const TX_COUNT = 8;
await aztecNode.setConfig({ minTxsPerBlock: TX_COUNT });

const methods = times(TX_COUNT, i => contract.methods.increment_public_value(ownerAddress, i));
for (let i = 0; i < TX_COUNT; i++) {
await methods[i].create({});
await methods[i].prove({});
}

// Send them simultaneously to be picked up by the sequencer
const txs = await Promise.all(methods.map(method => method.send()));
logger.info(`Txs sent with hashes: `);
for (const tx of txs) {
logger.info(` ${await tx.getTxHash()}`);
}

// Await txs to be mined and assert they are all mined on the same block
const receipts = await Promise.all(txs.map(tx => tx.wait()));
expect(receipts.map(r => r.blockNumber)).toEqual(times(TX_COUNT, () => receipts[0].blockNumber));
});

it.skip('can call public function from different tx in same block as deployed', async () => {
// Ensure both txs will land on the same block
await aztecNode.setConfig({ minTxsPerBlock: 2 });

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/foundation/src/serialize/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export function toFriendlyJSON(obj: object): string {
).toFriendlyJSON
) {
return value.toFriendlyJSON();
} else if (value && value.type && ['Fr', 'Fq', 'AztecAddress'].includes(value.type)) {
} else if (value && value.type && ['Fr', 'Fq', 'AztecAddress', 'EthAddress'].includes(value.type)) {
return value.value;
} else {
return value;
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/foundation/src/trees/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './unbalanced_merkle_root.js';

/**
* A leaf of an indexed merkle tree.
*/
Expand Down
Loading
Loading