@aztec/sequencer-client 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +5 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +5 -34
- package/dest/index.d.ts +0 -7
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -8
- package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
- package/dest/publisher/viem-tx-sender.js +2 -3
- package/dest/sequencer/abstract_phase_manager.d.ts +1 -3
- package/dest/sequencer/abstract_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/abstract_phase_manager.js +13 -17
- package/dest/sequencer/app_logic_phase_manager.d.ts +1 -3
- package/dest/sequencer/app_logic_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/app_logic_phase_manager.js +3 -4
- package/dest/sequencer/phase_manager_factory.d.ts +2 -3
- package/dest/sequencer/phase_manager_factory.d.ts.map +1 -1
- package/dest/sequencer/phase_manager_factory.js +9 -9
- package/dest/sequencer/public_processor.d.ts +3 -7
- package/dest/sequencer/public_processor.d.ts.map +1 -1
- package/dest/sequencer/public_processor.js +6 -9
- package/dest/sequencer/sequencer.d.ts +4 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +13 -12
- package/dest/sequencer/setup_phase_manager.d.ts +1 -3
- package/dest/sequencer/setup_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/setup_phase_manager.js +3 -4
- package/dest/sequencer/tail_phase_manager.d.ts +1 -3
- package/dest/sequencer/tail_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/tail_phase_manager.js +3 -4
- package/dest/sequencer/teardown_phase_manager.d.ts +1 -3
- package/dest/sequencer/teardown_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/teardown_phase_manager.js +3 -4
- package/dest/sequencer/tx_validator.d.ts +1 -2
- package/dest/sequencer/tx_validator.d.ts.map +1 -1
- package/dest/sequencer/tx_validator.js +15 -2
- package/dest/simulator/index.d.ts +1 -37
- package/dest/simulator/index.d.ts.map +1 -1
- package/dest/simulator/index.js +2 -2
- package/dest/simulator/public_executor.d.ts +1 -1
- package/dest/simulator/public_executor.d.ts.map +1 -1
- package/dest/simulator/public_executor.js +22 -12
- package/dest/simulator/public_kernel.d.ts +1 -1
- package/dest/simulator/public_kernel.d.ts.map +1 -1
- package/dest/simulator/public_kernel.js +2 -2
- package/package.json +13 -13
- package/src/client/sequencer-client.ts +7 -42
- package/src/index.ts +0 -9
- package/src/publisher/viem-tx-sender.ts +1 -2
- package/src/sequencer/abstract_phase_manager.ts +14 -16
- package/src/sequencer/app_logic_phase_manager.ts +1 -3
- package/src/sequencer/phase_manager_factory.ts +0 -9
- package/src/sequencer/public_processor.ts +12 -17
- package/src/sequencer/sequencer.ts +12 -13
- package/src/sequencer/setup_phase_manager.ts +1 -3
- package/src/sequencer/tail_phase_manager.ts +1 -3
- package/src/sequencer/teardown_phase_manager.ts +1 -3
- package/src/sequencer/tx_validator.ts +17 -2
- package/src/simulator/index.ts +0 -45
- package/src/simulator/public_executor.ts +32 -11
- package/src/simulator/public_kernel.ts +2 -2
- package/dest/block_builder/index.d.ts +0 -19
- package/dest/block_builder/index.d.ts.map +0 -1
- package/dest/block_builder/index.js +0 -2
- package/dest/block_builder/solo_block_builder.d.ts +0 -78
- package/dest/block_builder/solo_block_builder.d.ts.map +0 -1
- package/dest/block_builder/solo_block_builder.js +0 -489
- package/dest/block_builder/types.d.ts +0 -10
- package/dest/block_builder/types.d.ts.map +0 -1
- package/dest/block_builder/types.js +0 -2
- package/dest/mocks/verification_keys.d.ts +0 -28
- package/dest/mocks/verification_keys.d.ts.map +0 -1
- package/dest/mocks/verification_keys.js +0 -14
- package/dest/prover/empty.d.ts +0 -53
- package/dest/prover/empty.d.ts.map +0 -1
- package/dest/prover/empty.js +0 -75
- package/dest/prover/index.d.ts +0 -52
- package/dest/prover/index.d.ts.map +0 -1
- package/dest/prover/index.js +0 -2
- package/dest/sequencer/processed_tx.d.ts +0 -81
- package/dest/sequencer/processed_tx.d.ts.map +0 -1
- package/dest/sequencer/processed_tx.js +0 -98
- package/dest/simulator/acvm_native.d.ts +0 -20
- package/dest/simulator/acvm_native.d.ts.map +0 -1
- package/dest/simulator/acvm_native.js +0 -96
- package/dest/simulator/acvm_wasm.d.ts +0 -7
- package/dest/simulator/acvm_wasm.d.ts.map +0 -1
- package/dest/simulator/acvm_wasm.js +0 -23
- package/dest/simulator/rollup.d.ts +0 -43
- package/dest/simulator/rollup.d.ts.map +0 -1
- package/dest/simulator/rollup.js +0 -78
- package/dest/simulator/simulation_provider.d.ts +0 -9
- package/dest/simulator/simulation_provider.d.ts.map +0 -1
- package/dest/simulator/simulation_provider.js +0 -2
- package/src/block_builder/index.ts +0 -20
- package/src/block_builder/solo_block_builder.ts +0 -812
- package/src/block_builder/types.ts +0 -8
- package/src/mocks/verification_keys.ts +0 -36
- package/src/prover/empty.ts +0 -97
- package/src/prover/index.ts +0 -70
- package/src/sequencer/processed_tx.ts +0 -210
- package/src/simulator/acvm_native.ts +0 -112
- package/src/simulator/acvm_wasm.ts +0 -31
- package/src/simulator/rollup.ts +0 -127
- package/src/simulator/simulation_provider.ts +0 -10
|
@@ -1,812 +0,0 @@
|
|
|
1
|
-
import { Body, L2Block, MerkleTreeId, TxEffect } from '@aztec/circuit-types';
|
|
2
|
-
import { CircuitSimulationStats } from '@aztec/circuit-types/stats';
|
|
3
|
-
import {
|
|
4
|
-
ARCHIVE_HEIGHT,
|
|
5
|
-
AppendOnlyTreeSnapshot,
|
|
6
|
-
BaseOrMergeRollupPublicInputs,
|
|
7
|
-
BaseParityInputs,
|
|
8
|
-
BaseRollupInputs,
|
|
9
|
-
ConstantRollupData,
|
|
10
|
-
GlobalVariables,
|
|
11
|
-
L1_TO_L2_MSG_SUBTREE_HEIGHT,
|
|
12
|
-
L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH,
|
|
13
|
-
MAX_NEW_NULLIFIERS_PER_TX,
|
|
14
|
-
MAX_PUBLIC_DATA_READS_PER_TX,
|
|
15
|
-
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
16
|
-
MembershipWitness,
|
|
17
|
-
MergeRollupInputs,
|
|
18
|
-
NOTE_HASH_SUBTREE_HEIGHT,
|
|
19
|
-
NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH,
|
|
20
|
-
NULLIFIER_SUBTREE_HEIGHT,
|
|
21
|
-
NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH,
|
|
22
|
-
NULLIFIER_TREE_HEIGHT,
|
|
23
|
-
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
|
|
24
|
-
NUM_BASE_PARITY_PER_ROOT_PARITY,
|
|
25
|
-
NullifierLeafPreimage,
|
|
26
|
-
PUBLIC_DATA_SUBTREE_HEIGHT,
|
|
27
|
-
PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH,
|
|
28
|
-
PUBLIC_DATA_TREE_HEIGHT,
|
|
29
|
-
PartialStateReference,
|
|
30
|
-
PreviousRollupData,
|
|
31
|
-
Proof,
|
|
32
|
-
PublicDataTreeLeaf,
|
|
33
|
-
PublicDataTreeLeafPreimage,
|
|
34
|
-
ROLLUP_VK_TREE_HEIGHT,
|
|
35
|
-
RollupKernelCircuitPublicInputs,
|
|
36
|
-
RollupKernelData,
|
|
37
|
-
RollupTypes,
|
|
38
|
-
RootParityInput,
|
|
39
|
-
RootParityInputs,
|
|
40
|
-
RootRollupInputs,
|
|
41
|
-
RootRollupPublicInputs,
|
|
42
|
-
StateDiffHints,
|
|
43
|
-
StateReference,
|
|
44
|
-
VK_TREE_HEIGHT,
|
|
45
|
-
VerificationKey,
|
|
46
|
-
} from '@aztec/circuits.js';
|
|
47
|
-
import { assertPermutation, makeTuple } from '@aztec/foundation/array';
|
|
48
|
-
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
|
|
49
|
-
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
50
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
51
|
-
import { createDebugLogger } from '@aztec/foundation/log';
|
|
52
|
-
import { Tuple, assertLength, toFriendlyJSON } from '@aztec/foundation/serialize';
|
|
53
|
-
import { elapsed } from '@aztec/foundation/timer';
|
|
54
|
-
import { MerkleTreeOperations } from '@aztec/world-state';
|
|
55
|
-
|
|
56
|
-
import chunk from 'lodash.chunk';
|
|
57
|
-
import { inspect } from 'util';
|
|
58
|
-
|
|
59
|
-
import { VerificationKeys } from '../mocks/verification_keys.js';
|
|
60
|
-
import { RollupProver } from '../prover/index.js';
|
|
61
|
-
import { ProcessedTx, toTxEffect } from '../sequencer/processed_tx.js';
|
|
62
|
-
import { RollupSimulator } from '../simulator/index.js';
|
|
63
|
-
import { BlockBuilder } from './index.js';
|
|
64
|
-
import { TreeNames } from './types.js';
|
|
65
|
-
|
|
66
|
-
const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer());
|
|
67
|
-
|
|
68
|
-
// Denotes fields that are not used now, but will be in the future
|
|
69
|
-
const FUTURE_FR = new Fr(0n);
|
|
70
|
-
const FUTURE_NUM = 0;
|
|
71
|
-
|
|
72
|
-
// Denotes fields that should be deleted
|
|
73
|
-
const DELETE_FR = new Fr(0n);
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Builds an L2 block out of a set of ProcessedTx's,
|
|
77
|
-
* using the base, merge, and root rollup circuits.
|
|
78
|
-
*/
|
|
79
|
-
export class SoloBlockBuilder implements BlockBuilder {
|
|
80
|
-
constructor(
|
|
81
|
-
protected db: MerkleTreeOperations,
|
|
82
|
-
protected vks: VerificationKeys,
|
|
83
|
-
protected simulator: RollupSimulator,
|
|
84
|
-
protected prover: RollupProver,
|
|
85
|
-
protected debug = createDebugLogger('aztec:sequencer:solo-block-builder'),
|
|
86
|
-
) {}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Builds an L2 block with the given number containing the given txs, updating state trees.
|
|
90
|
-
* @param globalVariables - Global variables to be used in the block.
|
|
91
|
-
* @param txs - Processed transactions to include in the block.
|
|
92
|
-
* @param l1ToL2Messages - L1 to L2 messages to be part of the block.
|
|
93
|
-
* @param timestamp - Timestamp of the block.
|
|
94
|
-
* @returns The new L2 block and a correctness proof as returned by the root rollup circuit.
|
|
95
|
-
*/
|
|
96
|
-
public async buildL2Block(
|
|
97
|
-
globalVariables: GlobalVariables,
|
|
98
|
-
txs: ProcessedTx[],
|
|
99
|
-
l1ToL2Messages: Fr[],
|
|
100
|
-
): Promise<[L2Block, Proof]> {
|
|
101
|
-
// Check txs are good for processing by checking if all the tree snapshots in header are non-empty
|
|
102
|
-
this.validateTxs(txs);
|
|
103
|
-
|
|
104
|
-
// We pad the messages as the circuits expect that.
|
|
105
|
-
const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
106
|
-
|
|
107
|
-
// We fill the tx batch with empty txs, we process only one tx at a time for now
|
|
108
|
-
const [circuitsOutput, proof] = await this.runCircuits(globalVariables, txs, l1ToL2MessagesPadded);
|
|
109
|
-
|
|
110
|
-
// Collect all new nullifiers, commitments, and contracts from all txs in this block
|
|
111
|
-
const txEffects: TxEffect[] = txs.map(tx => toTxEffect(tx));
|
|
112
|
-
|
|
113
|
-
const blockBody = new Body(l1ToL2MessagesPadded, txEffects);
|
|
114
|
-
|
|
115
|
-
const l2Block = L2Block.fromFields({
|
|
116
|
-
archive: circuitsOutput.archive,
|
|
117
|
-
header: circuitsOutput.header,
|
|
118
|
-
body: blockBody,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (!l2Block.body.getTxsEffectsHash().equals(circuitsOutput.header.contentCommitment.txsEffectsHash)) {
|
|
122
|
-
this.debug(inspect(blockBody));
|
|
123
|
-
throw new Error(
|
|
124
|
-
`Txs effects hash mismatch, ${l2Block.body
|
|
125
|
-
.getTxsEffectsHash()
|
|
126
|
-
.toString('hex')} == ${circuitsOutput.header.contentCommitment.txsEffectsHash.toString('hex')} `,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return [l2Block, proof];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
protected validateTxs(txs: ProcessedTx[]) {
|
|
134
|
-
for (const tx of txs) {
|
|
135
|
-
const txHeader = tx.data.constants.historicalHeader;
|
|
136
|
-
if (txHeader.state.l1ToL2MessageTree.isZero()) {
|
|
137
|
-
throw new Error(`Empty L1 to L2 messages tree in tx: ${toFriendlyJSON(tx)}`);
|
|
138
|
-
}
|
|
139
|
-
if (txHeader.state.partial.noteHashTree.isZero()) {
|
|
140
|
-
throw new Error(`Empty note hash tree in tx: ${toFriendlyJSON(tx)}`);
|
|
141
|
-
}
|
|
142
|
-
if (txHeader.state.partial.nullifierTree.isZero()) {
|
|
143
|
-
throw new Error(`Empty nullifier tree in tx: ${toFriendlyJSON(tx)}`);
|
|
144
|
-
}
|
|
145
|
-
if (txHeader.state.partial.publicDataTree.isZero()) {
|
|
146
|
-
throw new Error(`Empty public data tree in tx: ${toFriendlyJSON(tx)}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
protected async getTreeSnapshot(id: MerkleTreeId): Promise<AppendOnlyTreeSnapshot> {
|
|
152
|
-
const treeInfo = await this.db.getTreeInfo(id);
|
|
153
|
-
return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
protected async runCircuits(
|
|
157
|
-
globalVariables: GlobalVariables,
|
|
158
|
-
txs: ProcessedTx[],
|
|
159
|
-
l1ToL2Messages: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>,
|
|
160
|
-
): Promise<[RootRollupPublicInputs, Proof]> {
|
|
161
|
-
// Check that the length of the array of txs is a power of two
|
|
162
|
-
// See https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
|
|
163
|
-
if (txs.length < 2 || (txs.length & (txs.length - 1)) !== 0) {
|
|
164
|
-
throw new Error(`Length of txs for the block should be a power of two and at least two (got ${txs.length})`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// BASE PARITY CIRCUIT (run in parallel)
|
|
168
|
-
// Note: In the future we will want to cache the results of empty base and root parity circuits so that we don't
|
|
169
|
-
// have to run them. (It will most likely be quite common that some base parity circuits will be "empty")
|
|
170
|
-
let baseParityInputs: BaseParityInputs[] = [];
|
|
171
|
-
let elapsedBaseParityOutputsPromise: Promise<[number, RootParityInput[]]>;
|
|
172
|
-
{
|
|
173
|
-
baseParityInputs = Array.from({ length: NUM_BASE_PARITY_PER_ROOT_PARITY }, (_, i) =>
|
|
174
|
-
BaseParityInputs.fromSlice(l1ToL2Messages, i),
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
const baseParityOutputs: Promise<RootParityInput>[] = [];
|
|
178
|
-
for (const inputs of baseParityInputs) {
|
|
179
|
-
baseParityOutputs.push(this.baseParityCircuit(inputs));
|
|
180
|
-
}
|
|
181
|
-
elapsedBaseParityOutputsPromise = elapsed(() => Promise.all(baseParityOutputs));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// BASE ROLLUP CIRCUIT (run in parallel)
|
|
185
|
-
let elapsedBaseRollupOutputsPromise: Promise<[number, [BaseOrMergeRollupPublicInputs, Proof][]]>;
|
|
186
|
-
const baseRollupInputs: BaseRollupInputs[] = [];
|
|
187
|
-
{
|
|
188
|
-
// Perform all tree insertions and retrieve snapshots for all base rollups
|
|
189
|
-
const treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>[] = [];
|
|
190
|
-
for (const tx of txs) {
|
|
191
|
-
const input = await this.buildBaseRollupInput(tx, globalVariables);
|
|
192
|
-
baseRollupInputs.push(input);
|
|
193
|
-
const promises = [MerkleTreeId.NOTE_HASH_TREE, MerkleTreeId.NULLIFIER_TREE, MerkleTreeId.PUBLIC_DATA_TREE].map(
|
|
194
|
-
async (id: MerkleTreeId) => {
|
|
195
|
-
return { key: id, value: await this.getTreeSnapshot(id) };
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
const snapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot> = new Map(
|
|
199
|
-
(await Promise.all(promises)).map(obj => [obj.key, obj.value]),
|
|
200
|
-
);
|
|
201
|
-
treeSnapshots.push(snapshots);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Run the base rollup circuits for the txs in parallel
|
|
205
|
-
const baseRollupOutputs: Promise<[BaseOrMergeRollupPublicInputs, Proof]>[] = [];
|
|
206
|
-
for (let i = 0; i < txs.length; i++) {
|
|
207
|
-
baseRollupOutputs.push(this.baseRollupCircuit(txs[i], baseRollupInputs[i], treeSnapshots[i]));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
elapsedBaseRollupOutputsPromise = elapsed(() => Promise.all(baseRollupOutputs));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ROOT PARITY CIRCUIT
|
|
214
|
-
let elapsedRootParityOutputPromise: Promise<[number, RootParityInput]>;
|
|
215
|
-
let rootParityInputs: RootParityInputs;
|
|
216
|
-
{
|
|
217
|
-
// First we await the base parity outputs
|
|
218
|
-
const [duration, baseParityOutputs] = await elapsedBaseParityOutputsPromise;
|
|
219
|
-
|
|
220
|
-
// We emit stats for base parity circuits
|
|
221
|
-
for (let i = 0; i < baseParityOutputs.length; i++) {
|
|
222
|
-
this.debug(`Simulated base parity circuit`, {
|
|
223
|
-
eventName: 'circuit-simulation',
|
|
224
|
-
circuitName: 'base-parity',
|
|
225
|
-
duration: duration / baseParityOutputs.length,
|
|
226
|
-
inputSize: baseParityInputs[i].toBuffer().length,
|
|
227
|
-
outputSize: baseParityOutputs[i].toBuffer().length,
|
|
228
|
-
} satisfies CircuitSimulationStats);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
rootParityInputs = new RootParityInputs(
|
|
232
|
-
baseParityOutputs as Tuple<RootParityInput, typeof NUM_BASE_PARITY_PER_ROOT_PARITY>,
|
|
233
|
-
);
|
|
234
|
-
elapsedRootParityOutputPromise = elapsed(() => this.rootParityCircuit(rootParityInputs));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// MERGE ROLLUP CIRCUIT (each layer run in parallel)
|
|
238
|
-
let mergeOutputLeft: [BaseOrMergeRollupPublicInputs, Proof];
|
|
239
|
-
let mergeOutputRight: [BaseOrMergeRollupPublicInputs, Proof];
|
|
240
|
-
{
|
|
241
|
-
// Run merge rollups in layers until we have only two outputs
|
|
242
|
-
const [duration, mergeInputs] = await elapsedBaseRollupOutputsPromise;
|
|
243
|
-
|
|
244
|
-
// We emit stats for base rollup circuits
|
|
245
|
-
for (let i = 0; i < mergeInputs.length; i++) {
|
|
246
|
-
this.debug(`Simulated base rollup circuit`, {
|
|
247
|
-
eventName: 'circuit-simulation',
|
|
248
|
-
circuitName: 'base-rollup',
|
|
249
|
-
duration: duration / mergeInputs.length,
|
|
250
|
-
inputSize: baseRollupInputs[i].toBuffer().length,
|
|
251
|
-
outputSize: mergeInputs[i][0].toBuffer().length,
|
|
252
|
-
} satisfies CircuitSimulationStats);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let mergeRollupInputs: [BaseOrMergeRollupPublicInputs, Proof][] = mergeInputs;
|
|
256
|
-
while (mergeRollupInputs.length > 2) {
|
|
257
|
-
const mergeInputStructs: MergeRollupInputs[] = [];
|
|
258
|
-
for (const pair of chunk(mergeRollupInputs, 2)) {
|
|
259
|
-
const [r1, r2] = pair;
|
|
260
|
-
mergeInputStructs.push(this.createMergeRollupInputs(r1, r2));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const [duration, mergeOutputs] = await elapsed(() =>
|
|
264
|
-
Promise.all(mergeInputStructs.map(async input => await this.mergeRollupCircuit(input))),
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
// We emit stats for merge rollup circuits
|
|
268
|
-
for (let i = 0; i < mergeOutputs.length; i++) {
|
|
269
|
-
this.debug(`Simulated merge rollup circuit`, {
|
|
270
|
-
eventName: 'circuit-simulation',
|
|
271
|
-
circuitName: 'merge-rollup',
|
|
272
|
-
duration: duration / mergeOutputs.length,
|
|
273
|
-
inputSize: mergeInputStructs[i].toBuffer().length,
|
|
274
|
-
outputSize: mergeOutputs[i][0].toBuffer().length,
|
|
275
|
-
} satisfies CircuitSimulationStats);
|
|
276
|
-
}
|
|
277
|
-
mergeRollupInputs = mergeOutputs;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Run the root rollup with the last two merge rollups (or base, if no merge layers)
|
|
281
|
-
[mergeOutputLeft, mergeOutputRight] = mergeRollupInputs;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Finally, we emit stats for root parity circuit
|
|
285
|
-
const [duration, rootParityOutput] = await elapsedRootParityOutputPromise;
|
|
286
|
-
this.debug(`Simulated root parity circuit`, {
|
|
287
|
-
eventName: 'circuit-simulation',
|
|
288
|
-
circuitName: 'root-parity',
|
|
289
|
-
duration: duration,
|
|
290
|
-
inputSize: rootParityInputs.toBuffer().length,
|
|
291
|
-
outputSize: rootParityOutput.toBuffer().length,
|
|
292
|
-
} satisfies CircuitSimulationStats);
|
|
293
|
-
|
|
294
|
-
return this.rootRollupCircuit(mergeOutputLeft, mergeOutputRight, rootParityOutput, l1ToL2Messages);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
protected async baseParityCircuit(inputs: BaseParityInputs): Promise<RootParityInput> {
|
|
298
|
-
this.debug(`Running base parity circuit`);
|
|
299
|
-
const parityPublicInputs = await this.simulator.baseParityCircuit(inputs);
|
|
300
|
-
const proof = await this.prover.getBaseParityProof(inputs, parityPublicInputs);
|
|
301
|
-
return new RootParityInput(proof, parityPublicInputs);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
protected async rootParityCircuit(inputs: RootParityInputs): Promise<RootParityInput> {
|
|
305
|
-
this.debug(`Running root parity circuit`);
|
|
306
|
-
const parityPublicInputs = await this.simulator.rootParityCircuit(inputs);
|
|
307
|
-
const proof = await this.prover.getRootParityProof(inputs, parityPublicInputs);
|
|
308
|
-
return new RootParityInput(proof, parityPublicInputs);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
protected async baseRollupCircuit(
|
|
312
|
-
tx: ProcessedTx,
|
|
313
|
-
inputs: BaseRollupInputs,
|
|
314
|
-
treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>,
|
|
315
|
-
): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
|
|
316
|
-
this.debug(`Running base rollup for ${tx.hash}`);
|
|
317
|
-
const rollupOutput = await this.simulator.baseRollupCircuit(inputs);
|
|
318
|
-
this.validatePartialState(rollupOutput.end, treeSnapshots);
|
|
319
|
-
const proof = await this.prover.getBaseRollupProof(inputs, rollupOutput);
|
|
320
|
-
return [rollupOutput, proof];
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
protected createMergeRollupInputs(
|
|
324
|
-
left: [BaseOrMergeRollupPublicInputs, Proof],
|
|
325
|
-
right: [BaseOrMergeRollupPublicInputs, Proof],
|
|
326
|
-
) {
|
|
327
|
-
const vk = this.getVerificationKey(left[0].rollupType);
|
|
328
|
-
const mergeInputs = new MergeRollupInputs([
|
|
329
|
-
this.getPreviousRollupDataFromPublicInputs(left[0], left[1], vk),
|
|
330
|
-
this.getPreviousRollupDataFromPublicInputs(right[0], right[1], vk),
|
|
331
|
-
]);
|
|
332
|
-
return mergeInputs;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
protected async mergeRollupCircuit(mergeInputs: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
|
|
336
|
-
this.debug(`Running merge rollup circuit`);
|
|
337
|
-
const output = await this.simulator.mergeRollupCircuit(mergeInputs);
|
|
338
|
-
const proof = await this.prover.getMergeRollupProof(mergeInputs, output);
|
|
339
|
-
return [output, proof];
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
protected getVerificationKey(type: RollupTypes) {
|
|
343
|
-
switch (type) {
|
|
344
|
-
case RollupTypes.Base:
|
|
345
|
-
return this.vks.baseRollupCircuit;
|
|
346
|
-
case RollupTypes.Merge:
|
|
347
|
-
return this.vks.mergeRollupCircuit;
|
|
348
|
-
default:
|
|
349
|
-
throw new Error(`No verification key available for ${type}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
protected async rootRollupCircuit(
|
|
354
|
-
left: [BaseOrMergeRollupPublicInputs, Proof],
|
|
355
|
-
right: [BaseOrMergeRollupPublicInputs, Proof],
|
|
356
|
-
l1ToL2Roots: RootParityInput,
|
|
357
|
-
l1ToL2Messages: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>,
|
|
358
|
-
): Promise<[RootRollupPublicInputs, Proof]> {
|
|
359
|
-
this.debug(`Running root rollup circuit`);
|
|
360
|
-
const rootInput = await this.getRootRollupInput(...left, ...right, l1ToL2Roots, l1ToL2Messages);
|
|
361
|
-
|
|
362
|
-
// Update the local trees to include the l1 to l2 messages
|
|
363
|
-
await this.db.appendLeaves(
|
|
364
|
-
MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
|
|
365
|
-
l1ToL2Messages.map(m => m.toBuffer()),
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
// Simulate and get proof for the root circuit
|
|
369
|
-
const rootOutput = await this.simulator.rootRollupCircuit(rootInput);
|
|
370
|
-
|
|
371
|
-
const rootProof = await this.prover.getRootRollupProof(rootInput, rootOutput);
|
|
372
|
-
|
|
373
|
-
// Update the archive with the latest block header
|
|
374
|
-
this.debug(`Updating and validating root trees`);
|
|
375
|
-
await this.db.updateArchive(rootOutput.header);
|
|
376
|
-
|
|
377
|
-
await this.validateRootOutput(rootOutput);
|
|
378
|
-
|
|
379
|
-
return [rootOutput, rootProof];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
protected validatePartialState(
|
|
383
|
-
partialState: PartialStateReference,
|
|
384
|
-
treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>,
|
|
385
|
-
) {
|
|
386
|
-
this.validateSimulatedTree(
|
|
387
|
-
treeSnapshots.get(MerkleTreeId.NOTE_HASH_TREE)!,
|
|
388
|
-
partialState.noteHashTree,
|
|
389
|
-
'NoteHashTree',
|
|
390
|
-
);
|
|
391
|
-
this.validateSimulatedTree(
|
|
392
|
-
treeSnapshots.get(MerkleTreeId.NULLIFIER_TREE)!,
|
|
393
|
-
partialState.nullifierTree,
|
|
394
|
-
'NullifierTree',
|
|
395
|
-
);
|
|
396
|
-
this.validateSimulatedTree(
|
|
397
|
-
treeSnapshots.get(MerkleTreeId.PUBLIC_DATA_TREE)!,
|
|
398
|
-
partialState.publicDataTree,
|
|
399
|
-
'PublicDataTree',
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
protected async validateState(state: StateReference) {
|
|
404
|
-
const promises = [MerkleTreeId.NOTE_HASH_TREE, MerkleTreeId.NULLIFIER_TREE, MerkleTreeId.PUBLIC_DATA_TREE].map(
|
|
405
|
-
async (id: MerkleTreeId) => {
|
|
406
|
-
return { key: id, value: await this.getTreeSnapshot(id) };
|
|
407
|
-
},
|
|
408
|
-
);
|
|
409
|
-
const snapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot> = new Map(
|
|
410
|
-
(await Promise.all(promises)).map(obj => [obj.key, obj.value]),
|
|
411
|
-
);
|
|
412
|
-
this.validatePartialState(state.partial, snapshots);
|
|
413
|
-
this.validateSimulatedTree(
|
|
414
|
-
await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE),
|
|
415
|
-
state.l1ToL2MessageTree,
|
|
416
|
-
'L1ToL2MessageTree',
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Validate that the roots of all local trees match the output of the root circuit simulation
|
|
421
|
-
protected async validateRootOutput(rootOutput: RootRollupPublicInputs) {
|
|
422
|
-
await Promise.all([
|
|
423
|
-
this.validateState(rootOutput.header.state),
|
|
424
|
-
this.validateSimulatedTree(await this.getTreeSnapshot(MerkleTreeId.ARCHIVE), rootOutput.archive, 'Archive'),
|
|
425
|
-
]);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Helper for comparing two trees snapshots
|
|
429
|
-
protected validateSimulatedTree(
|
|
430
|
-
localTree: AppendOnlyTreeSnapshot,
|
|
431
|
-
simulatedTree: AppendOnlyTreeSnapshot,
|
|
432
|
-
name: TreeNames,
|
|
433
|
-
label?: string,
|
|
434
|
-
) {
|
|
435
|
-
if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) {
|
|
436
|
-
throw new Error(`${label ?? name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`);
|
|
437
|
-
}
|
|
438
|
-
if (simulatedTree.nextAvailableLeafIndex !== localTree.nextAvailableLeafIndex) {
|
|
439
|
-
throw new Error(
|
|
440
|
-
`${label ?? name} tree next available leaf index mismatch (local ${
|
|
441
|
-
localTree.nextAvailableLeafIndex
|
|
442
|
-
}, simulated ${simulatedTree.nextAvailableLeafIndex})`,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Builds the inputs for the root rollup circuit, without making any changes to trees
|
|
448
|
-
protected async getRootRollupInput(
|
|
449
|
-
rollupOutputLeft: BaseOrMergeRollupPublicInputs,
|
|
450
|
-
rollupProofLeft: Proof,
|
|
451
|
-
rollupOutputRight: BaseOrMergeRollupPublicInputs,
|
|
452
|
-
rollupProofRight: Proof,
|
|
453
|
-
l1ToL2Roots: RootParityInput,
|
|
454
|
-
newL1ToL2Messages: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>,
|
|
455
|
-
) {
|
|
456
|
-
const vk = this.getVerificationKey(rollupOutputLeft.rollupType);
|
|
457
|
-
const previousRollupData: RootRollupInputs['previousRollupData'] = [
|
|
458
|
-
this.getPreviousRollupDataFromPublicInputs(rollupOutputLeft, rollupProofLeft, vk),
|
|
459
|
-
this.getPreviousRollupDataFromPublicInputs(rollupOutputRight, rollupProofRight, vk),
|
|
460
|
-
];
|
|
461
|
-
|
|
462
|
-
const getRootTreeSiblingPath = async (treeId: MerkleTreeId) => {
|
|
463
|
-
const { size } = await this.db.getTreeInfo(treeId);
|
|
464
|
-
const path = await this.db.getSiblingPath(treeId, size);
|
|
465
|
-
return path.toFields();
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
const newL1ToL2MessageTreeRootSiblingPathArray = await this.getSubtreeSiblingPath(
|
|
469
|
-
MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
|
|
470
|
-
L1_TO_L2_MSG_SUBTREE_HEIGHT,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
const newL1ToL2MessageTreeRootSiblingPath = makeTuple(
|
|
474
|
-
L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH,
|
|
475
|
-
i =>
|
|
476
|
-
i < newL1ToL2MessageTreeRootSiblingPathArray.length ? newL1ToL2MessageTreeRootSiblingPathArray[i] : Fr.ZERO,
|
|
477
|
-
0,
|
|
478
|
-
);
|
|
479
|
-
|
|
480
|
-
// Get tree snapshots
|
|
481
|
-
const startL1ToL2MessageTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE);
|
|
482
|
-
|
|
483
|
-
// Get blocks tree
|
|
484
|
-
const startArchiveSnapshot = await this.getTreeSnapshot(MerkleTreeId.ARCHIVE);
|
|
485
|
-
const newArchiveSiblingPathArray = await getRootTreeSiblingPath(MerkleTreeId.ARCHIVE);
|
|
486
|
-
|
|
487
|
-
const newArchiveSiblingPath = makeTuple(
|
|
488
|
-
ARCHIVE_HEIGHT,
|
|
489
|
-
i => (i < newArchiveSiblingPathArray.length ? newArchiveSiblingPathArray[i] : Fr.ZERO),
|
|
490
|
-
0,
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
return RootRollupInputs.from({
|
|
494
|
-
previousRollupData,
|
|
495
|
-
l1ToL2Roots,
|
|
496
|
-
newL1ToL2Messages,
|
|
497
|
-
newL1ToL2MessageTreeRootSiblingPath,
|
|
498
|
-
startL1ToL2MessageTreeSnapshot,
|
|
499
|
-
startArchiveSnapshot,
|
|
500
|
-
newArchiveSiblingPath,
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
protected getPreviousRollupDataFromPublicInputs(
|
|
505
|
-
rollupOutput: BaseOrMergeRollupPublicInputs,
|
|
506
|
-
rollupProof: Proof,
|
|
507
|
-
vk: VerificationKey,
|
|
508
|
-
) {
|
|
509
|
-
return new PreviousRollupData(
|
|
510
|
-
rollupOutput,
|
|
511
|
-
rollupProof,
|
|
512
|
-
vk,
|
|
513
|
-
|
|
514
|
-
// MembershipWitness for a VK tree to be implemented in the future
|
|
515
|
-
FUTURE_NUM,
|
|
516
|
-
new MembershipWitness(
|
|
517
|
-
ROLLUP_VK_TREE_HEIGHT,
|
|
518
|
-
BigInt(FUTURE_NUM),
|
|
519
|
-
makeTuple(ROLLUP_VK_TREE_HEIGHT, () => FUTURE_FR),
|
|
520
|
-
),
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
protected getKernelDataFor(tx: ProcessedTx): RollupKernelData {
|
|
525
|
-
const inputs = new RollupKernelCircuitPublicInputs(
|
|
526
|
-
tx.data.aggregationObject,
|
|
527
|
-
tx.data.combinedData,
|
|
528
|
-
tx.data.constants,
|
|
529
|
-
);
|
|
530
|
-
return new RollupKernelData(
|
|
531
|
-
inputs,
|
|
532
|
-
tx.proof,
|
|
533
|
-
|
|
534
|
-
// VK for the kernel circuit
|
|
535
|
-
this.vks.privateKernelCircuit,
|
|
536
|
-
|
|
537
|
-
// MembershipWitness for a VK tree to be implemented in the future
|
|
538
|
-
FUTURE_NUM,
|
|
539
|
-
assertLength(Array(VK_TREE_HEIGHT).fill(FUTURE_FR), VK_TREE_HEIGHT),
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Scan a tree searching for a specific value and return a membership witness proof for it
|
|
544
|
-
protected async getMembershipWitnessFor<N extends number>(
|
|
545
|
-
value: Fr,
|
|
546
|
-
treeId: MerkleTreeId,
|
|
547
|
-
height: N,
|
|
548
|
-
): Promise<MembershipWitness<N>> {
|
|
549
|
-
// If this is an empty tx, then just return zeroes
|
|
550
|
-
if (value.isZero()) {
|
|
551
|
-
return this.makeEmptyMembershipWitness(height);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const index = await this.db.findLeafIndex(treeId, value.toBuffer());
|
|
555
|
-
if (index === undefined) {
|
|
556
|
-
throw new Error(`Leaf with value ${value} not found in tree ${MerkleTreeId[treeId]}`);
|
|
557
|
-
}
|
|
558
|
-
const path = await this.db.getSiblingPath(treeId, index);
|
|
559
|
-
return new MembershipWitness(height, index, assertLength(path.toFields(), height));
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
protected async getConstantRollupData(globalVariables: GlobalVariables): Promise<ConstantRollupData> {
|
|
563
|
-
return ConstantRollupData.from({
|
|
564
|
-
baseRollupVkHash: DELETE_FR,
|
|
565
|
-
mergeRollupVkHash: DELETE_FR,
|
|
566
|
-
privateKernelVkTreeRoot: FUTURE_FR,
|
|
567
|
-
publicKernelVkTreeRoot: FUTURE_FR,
|
|
568
|
-
lastArchive: await this.getTreeSnapshot(MerkleTreeId.ARCHIVE),
|
|
569
|
-
globalVariables,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
protected async getLowNullifierInfo(nullifier: Fr) {
|
|
574
|
-
// Return empty nullifier info for an empty tx
|
|
575
|
-
if (nullifier.value === 0n) {
|
|
576
|
-
return {
|
|
577
|
-
index: 0,
|
|
578
|
-
leafPreimage: NullifierLeafPreimage.empty(),
|
|
579
|
-
witness: this.makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT),
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const tree = MerkleTreeId.NULLIFIER_TREE;
|
|
584
|
-
const prevValueIndex = await this.db.getPreviousValueIndex(tree, frToBigInt(nullifier));
|
|
585
|
-
if (!prevValueIndex) {
|
|
586
|
-
throw new Error(`Nullifier tree should have one initial leaf`);
|
|
587
|
-
}
|
|
588
|
-
const prevValuePreimage = (await this.db.getLeafPreimage(tree, prevValueIndex.index))!;
|
|
589
|
-
|
|
590
|
-
const prevValueSiblingPath = await this.db.getSiblingPath(tree, BigInt(prevValueIndex.index));
|
|
591
|
-
|
|
592
|
-
return {
|
|
593
|
-
index: prevValueIndex,
|
|
594
|
-
leafPreimage: prevValuePreimage,
|
|
595
|
-
witness: new MembershipWitness(
|
|
596
|
-
NULLIFIER_TREE_HEIGHT,
|
|
597
|
-
BigInt(prevValueIndex.index),
|
|
598
|
-
assertLength(prevValueSiblingPath.toFields(), NULLIFIER_TREE_HEIGHT),
|
|
599
|
-
),
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
protected async getSubtreeSiblingPath(treeId: MerkleTreeId, subtreeHeight: number): Promise<Fr[]> {
|
|
604
|
-
const nextAvailableLeafIndex = await this.db.getTreeInfo(treeId).then(t => t.size);
|
|
605
|
-
const fullSiblingPath = await this.db.getSiblingPath(treeId, nextAvailableLeafIndex);
|
|
606
|
-
|
|
607
|
-
// Drop the first subtreeHeight items since we only care about the path to the subtree root
|
|
608
|
-
return fullSiblingPath.getSubtreeSiblingPath(subtreeHeight).toFields();
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
protected async processPublicDataUpdateRequests(tx: ProcessedTx) {
|
|
612
|
-
const combinedPublicDataUpdateRequests = tx.data.combinedData.publicDataUpdateRequests.map(updateRequest => {
|
|
613
|
-
return new PublicDataTreeLeaf(updateRequest.leafSlot, updateRequest.newValue);
|
|
614
|
-
});
|
|
615
|
-
const { lowLeavesWitnessData, newSubtreeSiblingPath, sortedNewLeaves, sortedNewLeavesIndexes } =
|
|
616
|
-
await this.db.batchInsert(
|
|
617
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
618
|
-
combinedPublicDataUpdateRequests.map(x => x.toBuffer()),
|
|
619
|
-
// TODO(#3675) remove oldValue from update requests
|
|
620
|
-
PUBLIC_DATA_SUBTREE_HEIGHT,
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
if (lowLeavesWitnessData === undefined) {
|
|
624
|
-
throw new Error(`Could not craft public data batch insertion proofs`);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const sortedPublicDataWrites = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
|
|
628
|
-
return PublicDataTreeLeaf.fromBuffer(sortedNewLeaves[i]);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
const sortedPublicDataWritesIndexes = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
|
|
632
|
-
return sortedNewLeavesIndexes[i];
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
const subtreeSiblingPathAsFields = newSubtreeSiblingPath.toFields();
|
|
636
|
-
const newPublicDataSubtreeSiblingPath = makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, i => {
|
|
637
|
-
return subtreeSiblingPathAsFields[i];
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
const lowPublicDataWritesMembershipWitnesses: Tuple<
|
|
641
|
-
MembershipWitness<typeof PUBLIC_DATA_TREE_HEIGHT>,
|
|
642
|
-
typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
|
|
643
|
-
> = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
|
|
644
|
-
const witness = lowLeavesWitnessData[i];
|
|
645
|
-
return MembershipWitness.fromBufferArray(
|
|
646
|
-
witness.index,
|
|
647
|
-
assertLength(witness.siblingPath.toBufferArray(), PUBLIC_DATA_TREE_HEIGHT),
|
|
648
|
-
);
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
const lowPublicDataWritesPreimages: Tuple<
|
|
652
|
-
PublicDataTreeLeafPreimage,
|
|
653
|
-
typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
|
|
654
|
-
> = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
|
|
655
|
-
return lowLeavesWitnessData[i].leafPreimage as PublicDataTreeLeafPreimage;
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
// validate that the sortedPublicDataWrites and sortedPublicDataWritesIndexes are in the correct order
|
|
659
|
-
// otherwise it will just fail in the circuit
|
|
660
|
-
assertPermutation(combinedPublicDataUpdateRequests, sortedPublicDataWrites, sortedPublicDataWritesIndexes, (a, b) =>
|
|
661
|
-
a.equals(b),
|
|
662
|
-
);
|
|
663
|
-
|
|
664
|
-
return {
|
|
665
|
-
lowPublicDataWritesPreimages,
|
|
666
|
-
lowPublicDataWritesMembershipWitnesses,
|
|
667
|
-
newPublicDataSubtreeSiblingPath,
|
|
668
|
-
sortedPublicDataWrites,
|
|
669
|
-
sortedPublicDataWritesIndexes,
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
protected async getPublicDataReadsInfo(tx: ProcessedTx) {
|
|
674
|
-
const newPublicDataReadsWitnesses: Tuple<
|
|
675
|
-
MembershipWitness<typeof PUBLIC_DATA_TREE_HEIGHT>,
|
|
676
|
-
typeof MAX_PUBLIC_DATA_READS_PER_TX
|
|
677
|
-
> = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0n));
|
|
678
|
-
|
|
679
|
-
const newPublicDataReadsPreimages: Tuple<PublicDataTreeLeafPreimage, typeof MAX_PUBLIC_DATA_READS_PER_TX> =
|
|
680
|
-
makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PublicDataTreeLeafPreimage.empty());
|
|
681
|
-
|
|
682
|
-
for (const i in tx.data.validationRequests.publicDataReads) {
|
|
683
|
-
const leafSlot = tx.data.validationRequests.publicDataReads[i].leafSlot.value;
|
|
684
|
-
const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
|
|
685
|
-
if (!lowLeafResult) {
|
|
686
|
-
throw new Error(`Public data tree should have one initial leaf`);
|
|
687
|
-
}
|
|
688
|
-
const preimage = await this.db.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
|
|
689
|
-
const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
|
|
690
|
-
newPublicDataReadsWitnesses[i] = new MembershipWitness(
|
|
691
|
-
PUBLIC_DATA_TREE_HEIGHT,
|
|
692
|
-
BigInt(lowLeafResult.index),
|
|
693
|
-
path.toTuple<typeof PUBLIC_DATA_TREE_HEIGHT>(),
|
|
694
|
-
);
|
|
695
|
-
newPublicDataReadsPreimages[i] = preimage! as PublicDataTreeLeafPreimage;
|
|
696
|
-
}
|
|
697
|
-
return {
|
|
698
|
-
newPublicDataReadsWitnesses,
|
|
699
|
-
newPublicDataReadsPreimages,
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Builds the base rollup inputs, updating the contract, nullifier, and data trees in the process
|
|
704
|
-
protected async buildBaseRollupInput(tx: ProcessedTx, globalVariables: GlobalVariables) {
|
|
705
|
-
// Get trees info before any changes hit
|
|
706
|
-
const constants = await this.getConstantRollupData(globalVariables);
|
|
707
|
-
const start = new PartialStateReference(
|
|
708
|
-
await this.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE),
|
|
709
|
-
await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE),
|
|
710
|
-
await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE),
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
// Get the subtree sibling paths for the circuit
|
|
714
|
-
const noteHashSubtreeSiblingPathArray = await this.getSubtreeSiblingPath(
|
|
715
|
-
MerkleTreeId.NOTE_HASH_TREE,
|
|
716
|
-
NOTE_HASH_SUBTREE_HEIGHT,
|
|
717
|
-
);
|
|
718
|
-
|
|
719
|
-
const noteHashSubtreeSiblingPath = makeTuple(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, i =>
|
|
720
|
-
i < noteHashSubtreeSiblingPathArray.length ? noteHashSubtreeSiblingPathArray[i] : Fr.ZERO,
|
|
721
|
-
);
|
|
722
|
-
|
|
723
|
-
// Update the note hash trees with the new items being inserted to get the new roots
|
|
724
|
-
// that will be used by the next iteration of the base rollup circuit, skipping the empty ones
|
|
725
|
-
const newNoteHashes = tx.data.combinedData.newNoteHashes.map(x => x.value.toBuffer());
|
|
726
|
-
await this.db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, newNoteHashes);
|
|
727
|
-
|
|
728
|
-
// The read witnesses for a given TX should be generated before the writes of the same TX are applied.
|
|
729
|
-
// All reads that refer to writes in the same tx are transient and can be simplified out.
|
|
730
|
-
const txPublicDataReadsInfo = await this.getPublicDataReadsInfo(tx);
|
|
731
|
-
const txPublicDataUpdateRequestInfo = await this.processPublicDataUpdateRequests(tx);
|
|
732
|
-
|
|
733
|
-
// Update the nullifier tree, capturing the low nullifier info for each individual operation
|
|
734
|
-
const {
|
|
735
|
-
lowLeavesWitnessData: nullifierWitnessLeaves,
|
|
736
|
-
newSubtreeSiblingPath: newNullifiersSubtreeSiblingPath,
|
|
737
|
-
sortedNewLeaves: sortedNewNullifiers,
|
|
738
|
-
sortedNewLeavesIndexes,
|
|
739
|
-
} = await this.db.batchInsert(
|
|
740
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
741
|
-
tx.data.combinedData.newNullifiers.map(sideEffectLinkedToNoteHash => sideEffectLinkedToNoteHash.value.toBuffer()),
|
|
742
|
-
NULLIFIER_SUBTREE_HEIGHT,
|
|
743
|
-
);
|
|
744
|
-
if (nullifierWitnessLeaves === undefined) {
|
|
745
|
-
throw new Error(`Could not craft nullifier batch insertion proofs`);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// Extract witness objects from returned data
|
|
749
|
-
const nullifierPredecessorMembershipWitnessesWithoutPadding: MembershipWitness<typeof NULLIFIER_TREE_HEIGHT>[] =
|
|
750
|
-
nullifierWitnessLeaves.map(l =>
|
|
751
|
-
MembershipWitness.fromBufferArray(l.index, assertLength(l.siblingPath.toBufferArray(), NULLIFIER_TREE_HEIGHT)),
|
|
752
|
-
);
|
|
753
|
-
|
|
754
|
-
const nullifierSubtreeSiblingPathArray = newNullifiersSubtreeSiblingPath.toFields();
|
|
755
|
-
|
|
756
|
-
const nullifierSubtreeSiblingPath = makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, i =>
|
|
757
|
-
i < nullifierSubtreeSiblingPathArray.length ? nullifierSubtreeSiblingPathArray[i] : Fr.ZERO,
|
|
758
|
-
);
|
|
759
|
-
|
|
760
|
-
const publicDataSiblingPath = txPublicDataUpdateRequestInfo.newPublicDataSubtreeSiblingPath;
|
|
761
|
-
|
|
762
|
-
const stateDiffHints = StateDiffHints.from({
|
|
763
|
-
nullifierPredecessorPreimages: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i =>
|
|
764
|
-
i < nullifierWitnessLeaves.length
|
|
765
|
-
? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage)
|
|
766
|
-
: NullifierLeafPreimage.empty(),
|
|
767
|
-
),
|
|
768
|
-
nullifierPredecessorMembershipWitnesses: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i =>
|
|
769
|
-
i < nullifierPredecessorMembershipWitnessesWithoutPadding.length
|
|
770
|
-
? nullifierPredecessorMembershipWitnessesWithoutPadding[i]
|
|
771
|
-
: this.makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT),
|
|
772
|
-
),
|
|
773
|
-
sortedNullifiers: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => Fr.fromBuffer(sortedNewNullifiers[i])),
|
|
774
|
-
sortedNullifierIndexes: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => sortedNewLeavesIndexes[i]),
|
|
775
|
-
noteHashSubtreeSiblingPath,
|
|
776
|
-
nullifierSubtreeSiblingPath,
|
|
777
|
-
publicDataSiblingPath,
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
const blockHash = tx.data.constants.historicalHeader.hash();
|
|
781
|
-
const archiveRootMembershipWitness = await this.getMembershipWitnessFor(
|
|
782
|
-
blockHash,
|
|
783
|
-
MerkleTreeId.ARCHIVE,
|
|
784
|
-
ARCHIVE_HEIGHT,
|
|
785
|
-
);
|
|
786
|
-
|
|
787
|
-
return BaseRollupInputs.from({
|
|
788
|
-
kernelData: this.getKernelDataFor(tx),
|
|
789
|
-
start,
|
|
790
|
-
stateDiffHints,
|
|
791
|
-
|
|
792
|
-
sortedPublicDataWrites: txPublicDataUpdateRequestInfo.sortedPublicDataWrites,
|
|
793
|
-
sortedPublicDataWritesIndexes: txPublicDataUpdateRequestInfo.sortedPublicDataWritesIndexes,
|
|
794
|
-
lowPublicDataWritesPreimages: txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages,
|
|
795
|
-
lowPublicDataWritesMembershipWitnesses: txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses,
|
|
796
|
-
publicDataReadsPreimages: txPublicDataReadsInfo.newPublicDataReadsPreimages,
|
|
797
|
-
publicDataReadsMembershipWitnesses: txPublicDataReadsInfo.newPublicDataReadsWitnesses,
|
|
798
|
-
|
|
799
|
-
archiveRootMembershipWitness,
|
|
800
|
-
|
|
801
|
-
constants,
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
protected makeEmptyMembershipWitness<N extends number>(height: N) {
|
|
806
|
-
return new MembershipWitness(
|
|
807
|
-
height,
|
|
808
|
-
0n,
|
|
809
|
-
makeTuple(height, () => Fr.ZERO),
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
}
|