Skip to content
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
62 changes: 29 additions & 33 deletions docs/docs/protocol-specs/state/wonky-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ For example, using a balanced merkle tree to rollup 5 transactions requires padd
```mermaid
graph BT
R_c[Root]

M4_c[Merge]
M5_c[Merge]
M4_c --> R_c
Expand All @@ -27,12 +27,12 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M2_c[Merge]
M3_c[Merge*]
M2_c --> M5_c
M3_c --> M5_c

B4_c[Base]
B5_c[Base*]
B6_c[Base*]
Expand Down Expand Up @@ -62,7 +62,7 @@ Our wonky tree implementation instead gives the below structure for 5 transactio
```mermaid
graph BT
R_c[Root]

M4_c[Merge]
M4_c --> R_c

Expand All @@ -80,8 +80,8 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c


B4_c[Base]
B4_c --> R_c

Expand Down Expand Up @@ -115,7 +115,7 @@ graph
graph BT
M0_c[Merge 0]
M1_c[Merge 1]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -124,7 +124,7 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

B4_c[Base 4]
```

Expand All @@ -135,7 +135,7 @@ graph BT
M0_c[Merge 0]
M1_c[Merge 1]
M2_c[Merge 2]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -144,10 +144,10 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M0_c --> M2_c
M1_c --> M2_c

B4_c[Base 4]
```

Expand All @@ -156,11 +156,11 @@ Once paired, the base layer has length 4, the next merge layer has 2, and the fi
```mermaid
graph BT
R_c[Root]

M0_c[Merge 0]
M1_c[Merge 1]
M2_c[Merge 2]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -169,18 +169,17 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M0_c --> M2_c
M1_c --> M2_c

B4_c[Base 4]
M2_c --> R_c
B4_c --> R_c
```

Since we have processed all base circuits, this final pair will be input to a root circuit.

Filling from left to right means that we can easily reconstruct the tree only from the number of transactions `n`. The above method ensures that the final tree is a combination of _balanced_ subtrees of descending size. The widths of these subtrees are given by the decomposition of `n` into powers of 2. For example, 5 transactions:
Filling from left to right means that we can easily reconstruct the tree only from the number of transactions `n`. The above method ensures that the final tree is a combination of *balanced* subtrees of descending size. The widths of these subtrees are given by the decomposition of `n` into powers of 2. For example, 5 transactions:

```
Subtrees: [4, 1] ->
Expand All @@ -190,7 +189,6 @@ Subtrees: [4, 1] ->
```

For 31 transactions:

```
Subtrees: [16, 8, 4, 2, 1] ->
Merge D: left_subtree_root = balanced_tree(txs[0..16])
Expand All @@ -209,7 +207,6 @@ Subtrees: [16, 8, 4, 2, 1] ->
}
root = left_subtree_root | right_subtree_root
```

An unrolled recursive algorithm is not the easiest thing to read. This diagram represents the 31 transactions rolled up in our wonky structure, where each `Merge <num>` is a 'subroot' above:

```mermaid
Expand All @@ -218,37 +215,36 @@ graph BT
M3_c[Merge D
Subtree of 16 txs]
R_c[Root]


B4_c[Merge C
Subtree of 8 txs]
B5_c[Merge 1]

B4_c --> M2_c
B5_c --> M2_c

B6_c[Merge B
Subtree of 4 txs]
B7_c[Merge 0]

B6_c --> B5_c
B7_c --> B5_c

B8_c[Merge A
Subtree of 2 txs]
B9_c[Base 30]

B8_c --> B7_c
B9_c --> B7_c


M3_c --> R_c
M2_c --> R_c
```

The tree is reconstructed to check the `txs_effects_hash` (= the root of a wonky tree given by leaves of each tx's `tx_effects`) on L1. We also reconstruct it to provide a membership path against the stored `out_hash` (= the root of a wonky tree given by leaves of each tx's L2 to L1 message tree root) for consuming a L2 to L1 message.

Currently, this tree is built via the orchestrator given the number of transactions to rollup. Each 'node' is assigned a level (0 at the root) and index in that level. The below function finds the parent level:
Currently, this tree is built via the [orchestrator](../../../../yarn-project/prover-client/src/orchestrator/proving-state.ts#74) given the number of transactions to rollup (`this.totalNumTxs`). Each 'node' is assigned a level (0 at the root) and index in that level. The below function finds the parent level:

```
// Calculates the index and level of the parent rollup circuit
Expand Down Expand Up @@ -276,14 +272,14 @@ Currently, this tree is built via the orchestrator given the number of transacti
return [mergeLevel - 1n, thisIndex >> 1n, thisIndex & 1n];
}
```
For example, `Base 4` above starts with `level = 3` and `index = 4`. Since we have an odd number of transactions at this level, `thisLevelSize` is set to 4 with `shiftUp = true`.

For example, `Base 4` above starts with `level = 3` and `index = 4`. Since we have an odd number of transactions at this level, `thisLevelSize` is set to 4 with `shiftUp = true`.
The while loop triggers and shifts up our node to `level = 2` and `index = 2`. This level (containing `Merge 0` and `Merge 1`) is of even length, so the loop continues. The next iteration shifts up to `level = 1` and `index = 1` - we now have an odd level, so the loop stops. The actual position of `Base 4` is therefore at `level = 1` and `index = 1`. This function returns the parent level of the input node, so we return `level = 0`, `index = 0`, correctly indicating that the parent of `Base 4` is the root.

The while loop triggers and shifts up our node to `level = 2` and `index = 2`. This level (containing `Merge 0` and `Merge 1`) is of even length, so the loop continues. The next iteration shifts up to `level = 1` and `index = 1` - we now have an odd level, so the loop stops. The actual position of `Base 4` is therefore at `level = 1` and `index = 1`. This function returns the parent level of the input node, so we return `level = 0`, `index = 0`, correctly indicating that the parent of `Base 4` is the root.

### Flexible wonky trees

We can also encode the structure of _any_ binary merkle tree by tracking `number_of_branches` and `number_of_leaves` for each node in the tree. This encoding was originally designed for [logs](../logs/index.md) before they were included in the `txs_effects_hash`, so the below explanation references the leaves stored in relation to logs and transactions.
We can also encode the structure of *any* binary merkle tree by tracking `number_of_branches` and `number_of_leaves` for each node in the tree. This encoding was originally designed for [logs](../logs/index.md) before they were included in the `txs_effects_hash`, so the below explanation references the leaves stored in relation to logs and transactions.

The benefit of this method as opposed to the one above is allowing for any binary structure and therefore allowing for 'skipping' leaves with no information. However, the encoding grows as the tree grows, by at least 2 bytes per node. The above implementation only requires the number of leaves to be encoded, which will likely only require a single field to store.

Expand Down Expand Up @@ -423,4 +419,4 @@ function hash_tx_logs_data(logs_data) {
}
return res;
}
```
```
11 changes: 7 additions & 4 deletions yarn-project/bb-prover/src/prover/bb_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,19 +337,22 @@ export class BBNativeRollupProver implements ServerCircuitProver {
public async getBlockRootRollupProof(
input: BlockRootRollupInputs,
): Promise<PublicInputsAndRecursiveProof<BlockRootOrBlockMergePublicInputs>> {
const { circuitOutput, proof } = await this.createRecursiveProof(
// TODO(#7346): When batch rollups are integrated, we probably want the below to be this.createRecursiveProof
// since we will no longer be verifying it directly on L1
const { circuitOutput, proof } = await this.createProof(
input,
'BlockRootRollupArtifact',
NESTED_RECURSIVE_PROOF_LENGTH,
convertBlockRootRollupInputsToWitnessMap,
convertBlockRootRollupOutputsFromWitnessMap,
);

const recursiveProof = makeRecursiveProofFromBinary(proof, NESTED_RECURSIVE_PROOF_LENGTH);

const verificationKey = await this.getVerificationKeyDataForCircuit('BlockRootRollupArtifact');

await this.verifyProof('BlockRootRollupArtifact', proof.binaryProof);
await this.verifyProof('BlockRootRollupArtifact', proof);

return makePublicInputsAndRecursiveProof(circuitOutput, proof, verificationKey);
return makePublicInputsAndRecursiveProof(circuitOutput, recursiveProof, verificationKey);
}

/**
Expand Down
6 changes: 1 addition & 5 deletions yarn-project/circuit-types/src/interfaces/block-prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface BlockSimulator extends ProcessedTxHandler {
startNewBlock(numTxs: number, globalVariables: GlobalVariables, l1ToL2Messages: Fr[]): Promise<ProvingTicket>;

/** Cancels the block currently being processed. Processes already in progress built may continue but further proofs should not be started. */
cancel(): void;
cancelBlock(): void;

/** Performs the final archive tree insertion for this block and returns the L2Block. */
finaliseBlock(): Promise<SimulationBlockResult>;
Expand All @@ -72,7 +72,3 @@ export interface BlockProver extends BlockSimulator {
/** Performs the final archive tree insertion for this block and returns the L2Block. */
finaliseBlock(): Promise<ProvingBlockResult>;
}

export interface EpochProver extends BlockProver {
startNewEpoch(epochNumber: number, totalNumBlocks: number): ProvingTicket;
}
8 changes: 0 additions & 8 deletions yarn-project/circuits.js/src/structs/global_variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize';
import { type FieldsOf } from '@aztec/foundation/types';

import { inspect } from 'util';

import { GLOBAL_VARIABLES_LENGTH } from '../constants.gen.js';
import { GasFees } from './gas_fees.js';

Expand Down Expand Up @@ -152,10 +150,4 @@ export class GlobalVariables {
this.gasFees.isEmpty()
);
}

[inspect.custom]() {
return `GlobalVariables { chainId: ${this.chainId.toString()}, version: ${this.version.toString()}, blockNumber: ${this.blockNumber.toString()}, slotNumber: ${this.slotNumber.toString()}, timestamp: ${this.timestamp.toString()}, coinbase: ${this.coinbase.toString()}, feeRecipient: ${this.feeRecipient.toString()}, gasFees: ${inspect(
this.gasFees,
)} }`;
}
}
18 changes: 0 additions & 18 deletions yarn-project/circuits.js/src/structs/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize';
import { type FieldsOf } from '@aztec/foundation/types';

import { inspect } from 'util';

import { GeneratorIndex, HEADER_LENGTH } from '../constants.gen.js';
import { ContentCommitment } from './content_commitment.js';
import { GlobalVariables } from './global_variables.js';
Expand Down Expand Up @@ -127,20 +125,4 @@ export class Header {
hash(): Fr {
return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.BLOCK_HASH);
}

[inspect.custom]() {
return `Header {
lastArchive: ${inspect(this.lastArchive)},
contentCommitment.numTx: ${this.contentCommitment.numTxs.toNumber()},
contentCommitment.txsEffectsHash: ${this.contentCommitment.txsEffectsHash.toString('hex')},
contentCommitment.inHash: ${this.contentCommitment.inHash.toString('hex')},
contentCommitment.outHash: ${this.contentCommitment.outHash.toString('hex')},
state.l1ToL2MessageTree: ${inspect(this.state.l1ToL2MessageTree)},
state.noteHashTree: ${inspect(this.state.partial.noteHashTree)},
state.nullifierTree: ${inspect(this.state.partial.nullifierTree)},
state.publicDataTree: ${inspect(this.state.partial.publicDataTree)},
globalVariables: ${inspect(this.globalVariables)},
totalFees: ${this.totalFees},
}`;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { inspect } from 'util';

import { STRING_ENCODING, type UInt32 } from '../shared.js';

/**
Expand Down Expand Up @@ -66,10 +64,4 @@ export class AppendOnlyTreeSnapshot {
isZero(): boolean {
return this.root.isZero() && this.nextAvailableLeafIndex === 0;
}

[inspect.custom]() {
return `AppendOnlyTreeSnapshot { root: ${this.root.toString()}, nextAvailableLeafIndex: ${
this.nextAvailableLeafIndex
} }`;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, type Tuple, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize';
import { type FieldsOf } from '@aztec/foundation/types';

import { GlobalVariables } from '../global_variables.js';
import { EthAddress } from '../index.js';
import { AppendOnlyTreeSnapshot } from './append_only_tree_snapshot.js';

/**
Expand Down Expand Up @@ -131,15 +131,4 @@ export class FeeRecipient {
toFields() {
return serializeToFields(...FeeRecipient.getFields(this));
}

isEmpty() {
return this.value.isZero() && this.recipient.isZero();
}

toFriendlyJSON() {
if (this.isEmpty()) {
return {};
}
return { recipient: this.recipient.toString(), value: this.value.toString() };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class BlockRootRollupInputs {
public newArchiveSiblingPath: Tuple<Fr, typeof ARCHIVE_HEIGHT>,
/**
* The hash of the block preceding this one.
* TODO(#7346): Integrate batch rollup circuits and inject below
*/
public previousBlockHash: Fr,
/**
Expand Down
11 changes: 0 additions & 11 deletions yarn-project/circuits.js/src/structs/state_reference.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { type Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { inspect } from 'util';

import { STATE_REFERENCE_LENGTH } from '../constants.gen.js';
import { PartialStateReference } from './partial_state_reference.js';
import { AppendOnlyTreeSnapshot } from './rollup/append_only_tree_snapshot.js';
Expand Down Expand Up @@ -58,13 +56,4 @@ export class StateReference {
isEmpty(): boolean {
return this.l1ToL2MessageTree.isZero() && this.partial.isEmpty();
}

[inspect.custom]() {
return `StateReference {
l1ToL2MessageTree: ${inspect(this.l1ToL2MessageTree)},
noteHashTree: ${inspect(this.partial.noteHashTree)},
nullifierTree: ${inspect(this.partial.nullifierTree)},
publicDataTree: ${inspect(this.partial.publicDataTree)},
}`;
}
}
4 changes: 2 additions & 2 deletions yarn-project/prover-client/src/mocks/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ export const makeGlobals = (blockNumber: number) => {
return new GlobalVariables(
Fr.ZERO,
Fr.ZERO,
new Fr(blockNumber) /** block number */,
new Fr(blockNumber),
new Fr(blockNumber) /** slot number */,
new Fr(blockNumber) /** timestamp */,
Fr.ZERO,
EthAddress.ZERO,
AztecAddress.ZERO,
GasFees.empty(),
Expand Down
Loading