@aztec/simulator 0.61.0 → 0.63.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/acvm/acvm.d.ts +2 -16
- package/dest/acvm/acvm.d.ts.map +1 -1
- package/dest/acvm/acvm.js +2 -70
- package/dest/acvm/oracle/oracle.d.ts +4 -3
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +11 -9
- package/dest/acvm/oracle/typed_oracle.d.ts +5 -4
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +9 -6
- package/dest/avm/avm_gas.d.ts.map +1 -1
- package/dest/avm/avm_gas.js +4 -3
- package/dest/avm/avm_machine_state.d.ts +27 -8
- package/dest/avm/avm_machine_state.d.ts.map +1 -1
- package/dest/avm/avm_machine_state.js +6 -10
- package/dest/avm/avm_memory_types.d.ts +8 -0
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +5 -1
- package/dest/avm/avm_simulator.d.ts +2 -19
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +12 -14
- package/dest/avm/avm_tree.d.ts +249 -0
- package/dest/avm/avm_tree.d.ts.map +1 -0
- package/dest/avm/avm_tree.js +637 -0
- package/dest/avm/errors.d.ts +4 -17
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +21 -50
- package/dest/avm/fixtures/index.d.ts +7 -2
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +12 -12
- package/dest/avm/index.d.ts +1 -0
- package/dest/avm/index.d.ts.map +1 -1
- package/dest/avm/index.js +2 -1
- package/dest/avm/journal/journal.d.ts +43 -24
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +172 -39
- package/dest/avm/journal/nullifiers.d.ts +5 -4
- package/dest/avm/journal/nullifiers.d.ts.map +1 -1
- package/dest/avm/journal/nullifiers.js +2 -3
- package/dest/avm/journal/public_storage.d.ts +6 -5
- package/dest/avm/journal/public_storage.d.ts.map +1 -1
- package/dest/avm/journal/public_storage.js +1 -1
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +4 -10
- package/dest/avm/opcodes/arithmetic.d.ts +4 -1
- package/dest/avm/opcodes/arithmetic.d.ts.map +1 -1
- package/dest/avm/opcodes/arithmetic.js +18 -4
- package/dest/avm/opcodes/bitwise.d.ts.map +1 -1
- package/dest/avm/opcodes/bitwise.js +1 -3
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +1 -2
- package/dest/avm/opcodes/contract.d.ts.map +1 -1
- package/dest/avm/opcodes/contract.js +2 -3
- package/dest/avm/opcodes/control_flow.d.ts +4 -0
- package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
- package/dest/avm/opcodes/control_flow.js +26 -11
- package/dest/avm/opcodes/conversion.d.ts.map +1 -1
- package/dest/avm/opcodes/conversion.js +1 -2
- package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
- package/dest/avm/opcodes/ec_add.js +5 -11
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +1 -2
- package/dest/avm/opcodes/external_calls.d.ts +4 -2
- package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +38 -22
- package/dest/avm/opcodes/hashing.d.ts.map +1 -1
- package/dest/avm/opcodes/hashing.js +1 -4
- package/dest/avm/opcodes/instruction.d.ts +4 -0
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +7 -1
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +1 -7
- package/dest/avm/opcodes/misc.js +3 -3
- package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
- package/dest/avm/opcodes/multi_scalar_mul.js +6 -5
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +2 -4
- package/dest/avm/serialization/bytecode_serialization.d.ts +1 -6
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +24 -20
- package/dest/avm/serialization/instruction_serialization.d.ts +2 -2
- package/dest/avm/serialization/instruction_serialization.js +2 -2
- package/dest/client/client_execution_context.d.ts +7 -10
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +19 -18
- package/dest/client/db_oracle.d.ts +22 -8
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/db_oracle.js +1 -1
- package/dest/client/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +5 -4
- package/dest/client/unconstrained_execution.d.ts.map +1 -1
- package/dest/client/unconstrained_execution.js +3 -2
- package/dest/client/view_data_oracle.d.ts +6 -12
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +10 -12
- package/dest/common/errors.d.ts +15 -2
- package/dest/common/errors.d.ts.map +1 -1
- package/dest/common/errors.js +85 -4
- package/dest/mocks/fixtures.d.ts +9 -28
- package/dest/mocks/fixtures.d.ts.map +1 -1
- package/dest/mocks/fixtures.js +12 -57
- package/dest/public/dual_side_effect_trace.d.ts +34 -26
- package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
- package/dest/public/dual_side_effect_trace.js +48 -36
- package/dest/public/enqueued_call_side_effect_trace.d.ts +96 -33
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +212 -138
- package/dest/public/execution.d.ts +50 -17
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +1 -29
- package/dest/public/executor.d.ts +28 -11
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +33 -33
- package/dest/public/index.d.ts +4 -5
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +4 -5
- package/dest/public/public_db_sources.d.ts +1 -0
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +21 -19
- package/dest/public/public_processor.d.ts +7 -11
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +60 -42
- package/dest/public/public_processor_metrics.d.ts +3 -3
- package/dest/public/public_processor_metrics.d.ts.map +1 -1
- package/dest/public/public_processor_metrics.js +1 -1
- package/dest/public/public_tx_context.d.ts +130 -0
- package/dest/public/public_tx_context.d.ts.map +1 -0
- package/dest/public/public_tx_context.js +293 -0
- package/dest/public/public_tx_simulator.d.ts +36 -0
- package/dest/public/public_tx_simulator.d.ts.map +1 -0
- package/dest/public/public_tx_simulator.js +148 -0
- package/dest/public/side_effect_trace.d.ts +30 -15
- package/dest/public/side_effect_trace.d.ts.map +1 -1
- package/dest/public/side_effect_trace.js +70 -16
- package/dest/public/side_effect_trace_interface.d.ts +43 -12
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts +9 -0
- package/dest/public/transitional_adapters.d.ts.map +1 -0
- package/dest/public/transitional_adapters.js +127 -0
- package/dest/public/utils.d.ts +5 -0
- package/dest/public/utils.d.ts.map +1 -0
- package/dest/public/utils.js +30 -0
- package/dest/test/utils.d.ts +2 -2
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +4 -4
- package/package.json +12 -9
- package/src/acvm/acvm.ts +3 -94
- package/src/acvm/oracle/oracle.ts +14 -12
- package/src/acvm/oracle/typed_oracle.ts +10 -6
- package/src/avm/avm_gas.ts +3 -2
- package/src/avm/avm_machine_state.ts +28 -12
- package/src/avm/avm_memory_types.ts +5 -0
- package/src/avm/avm_simulator.ts +13 -16
- package/src/avm/avm_tree.ts +785 -0
- package/src/avm/errors.ts +25 -48
- package/src/avm/fixtures/index.ts +16 -12
- package/src/avm/index.ts +1 -0
- package/src/avm/journal/journal.ts +291 -52
- package/src/avm/journal/nullifiers.ts +7 -7
- package/src/avm/journal/public_storage.ts +5 -5
- package/src/avm/opcodes/accrued_substate.ts +3 -9
- package/src/avm/opcodes/arithmetic.ts +26 -4
- package/src/avm/opcodes/bitwise.ts +0 -2
- package/src/avm/opcodes/comparators.ts +0 -1
- package/src/avm/opcodes/contract.ts +1 -2
- package/src/avm/opcodes/control_flow.ts +29 -10
- package/src/avm/opcodes/conversion.ts +0 -1
- package/src/avm/opcodes/ec_add.ts +6 -9
- package/src/avm/opcodes/environment_getters.ts +0 -1
- package/src/avm/opcodes/external_calls.ts +39 -21
- package/src/avm/opcodes/hashing.ts +0 -3
- package/src/avm/opcodes/instruction.ts +7 -0
- package/src/avm/opcodes/memory.ts +0 -6
- package/src/avm/opcodes/misc.ts +2 -2
- package/src/avm/opcodes/multi_scalar_mul.ts +5 -4
- package/src/avm/opcodes/storage.ts +1 -3
- package/src/avm/serialization/bytecode_serialization.ts +31 -22
- package/src/avm/serialization/instruction_serialization.ts +2 -2
- package/src/client/client_execution_context.ts +24 -21
- package/src/client/db_oracle.ts +31 -8
- package/src/client/private_execution.ts +5 -4
- package/src/client/unconstrained_execution.ts +2 -1
- package/src/client/view_data_oracle.ts +14 -13
- package/src/common/errors.ts +119 -3
- package/src/mocks/fixtures.ts +15 -106
- package/src/public/dual_side_effect_trace.ts +138 -50
- package/src/public/enqueued_call_side_effect_trace.ts +352 -212
- package/src/public/execution.ts +58 -42
- package/src/public/executor.ts +52 -67
- package/src/public/index.ts +7 -5
- package/src/public/public_db_sources.ts +22 -19
- package/src/public/public_processor.ts +111 -73
- package/src/public/public_processor_metrics.ts +3 -3
- package/src/public/public_tx_context.ts +411 -0
- package/src/public/public_tx_simulator.ts +232 -0
- package/src/public/side_effect_trace.ts +154 -28
- package/src/public/side_effect_trace_interface.ts +92 -14
- package/src/public/transitional_adapters.ts +347 -0
- package/src/public/utils.ts +32 -0
- package/src/test/utils.ts +9 -2
- package/dest/public/enqueued_call_simulator.d.ts +0 -43
- package/dest/public/enqueued_call_simulator.d.ts.map +0 -1
- package/dest/public/enqueued_call_simulator.js +0 -156
- package/dest/public/enqueued_calls_processor.d.ts +0 -43
- package/dest/public/enqueued_calls_processor.d.ts.map +0 -1
- package/dest/public/enqueued_calls_processor.js +0 -209
- package/dest/public/hints_builder.d.ts +0 -29
- package/dest/public/hints_builder.d.ts.map +0 -1
- package/dest/public/hints_builder.js +0 -75
- package/dest/public/public_kernel.d.ts +0 -30
- package/dest/public/public_kernel.d.ts.map +0 -1
- package/dest/public/public_kernel.js +0 -67
- package/dest/public/public_kernel_circuit_simulator.d.ts +0 -25
- package/dest/public/public_kernel_circuit_simulator.d.ts.map +0 -1
- package/dest/public/public_kernel_circuit_simulator.js +0 -2
- package/dest/public/public_kernel_tail_simulator.d.ts +0 -15
- package/dest/public/public_kernel_tail_simulator.d.ts.map +0 -1
- package/dest/public/public_kernel_tail_simulator.js +0 -39
- package/src/public/enqueued_call_simulator.ts +0 -360
- package/src/public/enqueued_calls_processor.ts +0 -372
- package/src/public/hints_builder.ts +0 -168
- package/src/public/public_kernel.ts +0 -100
- package/src/public/public_kernel_circuit_simulator.ts +0 -32
- package/src/public/public_kernel_tail_simulator.ts +0 -97
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import { type IndexedTreeId, MerkleTreeId, type MerkleTreeReadOperations, getTreeHeight } from '@aztec/circuit-types';
|
|
2
|
+
import { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/circuits.js';
|
|
3
|
+
import { poseidon2Hash } from '@aztec/foundation/crypto';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
5
|
+
import { type IndexedTreeLeafPreimage, type TreeLeafPreimage } from '@aztec/foundation/trees';
|
|
6
|
+
|
|
7
|
+
import cloneDeep from 'lodash.clonedeep';
|
|
8
|
+
|
|
9
|
+
/****************************************************/
|
|
10
|
+
/****** Structs Used by the AvmEphemeralForest ******/
|
|
11
|
+
/****************************************************/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The preimage and the leaf index of the Low Leaf (Low Nullifier or Low Public Data Leaf)
|
|
15
|
+
*/
|
|
16
|
+
type PreimageWitness<T extends IndexedTreeLeafPreimage> = {
|
|
17
|
+
preimage: T;
|
|
18
|
+
index: bigint;
|
|
19
|
+
update: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Contains the low sibling path and a boolean if the next index pointed to
|
|
24
|
+
* by the low leaf is an update or an insertion (i.e. exists or not).
|
|
25
|
+
*/
|
|
26
|
+
type LeafWitness<T extends IndexedTreeLeafPreimage> = PreimageWitness<T> & {
|
|
27
|
+
siblingPath: Fr[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The result of an indexed insertion in an indexed merkle tree.
|
|
32
|
+
* This will be used to hint the circuit
|
|
33
|
+
*/
|
|
34
|
+
export type IndexedInsertionResult<T extends IndexedTreeLeafPreimage> = {
|
|
35
|
+
leafIndex: bigint;
|
|
36
|
+
insertionPath: Fr[];
|
|
37
|
+
newOrElementToUpdate: { update: boolean; element: T };
|
|
38
|
+
lowWitness: LeafWitness<T>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/****************************************************/
|
|
42
|
+
/****** The AvmEphemeralForest Class ****************/
|
|
43
|
+
/****************************************************/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* This provides a forkable abstraction over the EphemeralAvmTree class
|
|
47
|
+
* It contains the logic to look up into a read-only MerkleTreeDb to discover
|
|
48
|
+
* the sibling paths and low witnesses that weren't inserted as part of this tx
|
|
49
|
+
*/
|
|
50
|
+
export class AvmEphemeralForest {
|
|
51
|
+
constructor(
|
|
52
|
+
public treeDb: MerkleTreeReadOperations,
|
|
53
|
+
public treeMap: Map<MerkleTreeId, EphemeralAvmTree>,
|
|
54
|
+
// This contains the preimage and the leaf index of leaf in the ephemeral tree that contains the lowest key (i.e. nullifier value or public data tree slot)
|
|
55
|
+
public indexedTreeMin: Map<IndexedTreeId, [IndexedTreeLeafPreimage, bigint]>,
|
|
56
|
+
// This contains the [leaf index,indexed leaf preimages] tuple that were updated or inserted in the ephemeral tree
|
|
57
|
+
// This is needed since we have a sparse collection of keys sorted leaves in the ephemeral tree
|
|
58
|
+
public indexedUpdates: Map<IndexedTreeId, Map<bigint, IndexedTreeLeafPreimage>>,
|
|
59
|
+
) {}
|
|
60
|
+
|
|
61
|
+
static async create(treeDb: MerkleTreeReadOperations): Promise<AvmEphemeralForest> {
|
|
62
|
+
const treeMap = new Map<MerkleTreeId, EphemeralAvmTree>();
|
|
63
|
+
for (const treeType of [MerkleTreeId.NULLIFIER_TREE, MerkleTreeId.NOTE_HASH_TREE, MerkleTreeId.PUBLIC_DATA_TREE]) {
|
|
64
|
+
const treeInfo = await treeDb.getTreeInfo(treeType);
|
|
65
|
+
const tree = await EphemeralAvmTree.create(treeInfo.size, treeInfo.depth, treeDb, treeType);
|
|
66
|
+
treeMap.set(treeType, tree);
|
|
67
|
+
}
|
|
68
|
+
return new AvmEphemeralForest(treeDb, treeMap, new Map(), new Map());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fork(): AvmEphemeralForest {
|
|
72
|
+
return new AvmEphemeralForest(
|
|
73
|
+
this.treeDb,
|
|
74
|
+
cloneDeep(this.treeMap),
|
|
75
|
+
cloneDeep(this.indexedTreeMin),
|
|
76
|
+
cloneDeep(this.indexedUpdates),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets sibling path for a leaf - if the sibling path is not found in the tree, it is fetched from the DB
|
|
82
|
+
* @param treeId - The tree to be queried for a sibling path.
|
|
83
|
+
* @param index - The index of the leaf for which a sibling path should be returned.
|
|
84
|
+
* @returns The sibling path of the leaf.
|
|
85
|
+
*/
|
|
86
|
+
async getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise<Fr[]> {
|
|
87
|
+
const tree = this.treeMap.get(treeId)!;
|
|
88
|
+
let path = tree.getSiblingPath(index);
|
|
89
|
+
if (path === undefined) {
|
|
90
|
+
// We dont have the sibling path in our tree - we have to get it from the DB
|
|
91
|
+
path = (await this.treeDb.getSiblingPath(treeId, index)).toFields();
|
|
92
|
+
// Since the sibling path could be outdated, we compare it with nodes in our tree
|
|
93
|
+
// if we encounter a mismatch, we replace it with the node we found in our tree.
|
|
94
|
+
for (let i = 0; i < path.length; i++) {
|
|
95
|
+
const siblingIndex = index ^ 1n;
|
|
96
|
+
const node = tree.getNode(siblingIndex, tree.depth - i);
|
|
97
|
+
if (node !== undefined) {
|
|
98
|
+
const nodeHash = tree.hashTree(node, i + 1);
|
|
99
|
+
if (!nodeHash.equals(path[i])) {
|
|
100
|
+
path[i] = nodeHash;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
index >>= 1n;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return path;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* This does the work of appending the new leaf and updating the low witness
|
|
111
|
+
* @param treeId - The tree to be queried for a sibling path.
|
|
112
|
+
* @param lowWitnessIndex - The index of the low leaf in the tree.
|
|
113
|
+
* @param lowWitness - The preimage of the low leaf.
|
|
114
|
+
* @param newLeafPreimage - The preimage of the new leaf to be inserted.
|
|
115
|
+
* @returns The sibling path of the new leaf (i.e. the insertion path)
|
|
116
|
+
*/
|
|
117
|
+
appendIndexedTree<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
118
|
+
treeId: ID,
|
|
119
|
+
lowLeafIndex: bigint,
|
|
120
|
+
lowLeafPreimage: T,
|
|
121
|
+
newLeafPreimage: T,
|
|
122
|
+
): Fr[] {
|
|
123
|
+
const tree = this.treeMap.get(treeId)!;
|
|
124
|
+
const newLeaf = this.hashPreimage(newLeafPreimage);
|
|
125
|
+
const insertIndex = tree.leafCount;
|
|
126
|
+
|
|
127
|
+
const lowLeaf = this.hashPreimage(lowLeafPreimage);
|
|
128
|
+
// Update the low nullifier hash
|
|
129
|
+
this.setIndexedUpdates(treeId, lowLeafIndex, lowLeafPreimage);
|
|
130
|
+
tree.updateLeaf(lowLeaf, lowLeafIndex);
|
|
131
|
+
// Append the new leaf
|
|
132
|
+
tree.appendLeaf(newLeaf);
|
|
133
|
+
this.setIndexedUpdates(treeId, insertIndex, newLeafPreimage);
|
|
134
|
+
|
|
135
|
+
return tree.getSiblingPath(insertIndex)!;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* This writes or updates a slot in the public data tree with a value
|
|
140
|
+
* @param slot - The slot to be written to.
|
|
141
|
+
* @param newValue - The value to be written or updated to
|
|
142
|
+
* @returns The insertion result which contains the insertion path, low leaf and the new leaf index
|
|
143
|
+
*/
|
|
144
|
+
async writePublicStorage(slot: Fr, newValue: Fr): Promise<IndexedInsertionResult<PublicDataTreeLeafPreimage>> {
|
|
145
|
+
// This only works for the public data tree
|
|
146
|
+
const treeId = MerkleTreeId.PUBLIC_DATA_TREE;
|
|
147
|
+
const tree = this.treeMap.get(treeId)!;
|
|
148
|
+
const { preimage, index, update }: PreimageWitness<PublicDataTreeLeafPreimage> = await this.getLeafOrLowLeafInfo(
|
|
149
|
+
treeId,
|
|
150
|
+
slot,
|
|
151
|
+
);
|
|
152
|
+
const siblingPath = await this.getSiblingPath(treeId, index);
|
|
153
|
+
if (update) {
|
|
154
|
+
const updatedPreimage = cloneDeep(preimage);
|
|
155
|
+
const existingPublicDataSiblingPath = siblingPath;
|
|
156
|
+
updatedPreimage.value = newValue;
|
|
157
|
+
|
|
158
|
+
// It is really unintuitive that by updating, we are also appending a Zero Leaf to the tree
|
|
159
|
+
// Additionally, this leaf preimage does not seem to factor into further appends
|
|
160
|
+
const emptyLeaf = new PublicDataTreeLeafPreimage(Fr.ZERO, Fr.ZERO, Fr.ZERO, 0n);
|
|
161
|
+
const insertionIndex = tree.leafCount;
|
|
162
|
+
tree.updateLeaf(this.hashPreimage(updatedPreimage), index);
|
|
163
|
+
tree.appendLeaf(Fr.ZERO);
|
|
164
|
+
this.setIndexedUpdates(treeId, index, updatedPreimage);
|
|
165
|
+
this.setIndexedUpdates(treeId, insertionIndex, emptyLeaf);
|
|
166
|
+
const insertionPath = tree.getSiblingPath(insertionIndex)!;
|
|
167
|
+
|
|
168
|
+
// Even though we append an empty leaf into the tree as a part of update - it doesnt seem to impact future inserts...
|
|
169
|
+
this._updateMinInfo(MerkleTreeId.PUBLIC_DATA_TREE, [updatedPreimage], [index]);
|
|
170
|
+
return {
|
|
171
|
+
leafIndex: insertionIndex,
|
|
172
|
+
insertionPath,
|
|
173
|
+
newOrElementToUpdate: { update: true, element: updatedPreimage },
|
|
174
|
+
lowWitness: {
|
|
175
|
+
preimage: preimage,
|
|
176
|
+
index: index,
|
|
177
|
+
update: true,
|
|
178
|
+
siblingPath: existingPublicDataSiblingPath,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// We are writing to a new slot, so our preimage is a lowNullifier
|
|
183
|
+
const insertionIndex = tree.leafCount;
|
|
184
|
+
const updatedLowLeaf = cloneDeep(preimage);
|
|
185
|
+
updatedLowLeaf.nextSlot = slot;
|
|
186
|
+
updatedLowLeaf.nextIndex = insertionIndex;
|
|
187
|
+
|
|
188
|
+
const newPublicDataLeaf = new PublicDataTreeLeafPreimage(
|
|
189
|
+
slot,
|
|
190
|
+
newValue,
|
|
191
|
+
new Fr(preimage.getNextKey()),
|
|
192
|
+
preimage.getNextIndex(),
|
|
193
|
+
);
|
|
194
|
+
const insertionPath = this.appendIndexedTree(treeId, index, updatedLowLeaf, newPublicDataLeaf);
|
|
195
|
+
|
|
196
|
+
// Since we are appending, we might have a new minimum public data leaf
|
|
197
|
+
this._updateMinInfo(MerkleTreeId.PUBLIC_DATA_TREE, [newPublicDataLeaf, updatedLowLeaf], [insertionIndex, index]);
|
|
198
|
+
return {
|
|
199
|
+
leafIndex: insertionIndex,
|
|
200
|
+
insertionPath: insertionPath,
|
|
201
|
+
newOrElementToUpdate: { update: false, element: newPublicDataLeaf },
|
|
202
|
+
lowWitness: {
|
|
203
|
+
preimage,
|
|
204
|
+
index: index,
|
|
205
|
+
update: false,
|
|
206
|
+
siblingPath,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* This is just a helper to compare the preimages and update the minimum public data leaf
|
|
213
|
+
* @param treeId - The tree to be queried for a sibling path.
|
|
214
|
+
* @param T - The type of the preimage (PublicData or Nullifier)
|
|
215
|
+
* @param preimages - The preimages to be compared
|
|
216
|
+
* @param indices - The indices of the preimages
|
|
217
|
+
*/
|
|
218
|
+
private _updateMinInfo<T extends IndexedTreeLeafPreimage>(
|
|
219
|
+
treeId: IndexedTreeId,
|
|
220
|
+
preimages: T[],
|
|
221
|
+
indices: bigint[],
|
|
222
|
+
): void {
|
|
223
|
+
let currentMin = this.getMinInfo(treeId);
|
|
224
|
+
if (currentMin === undefined) {
|
|
225
|
+
currentMin = { preimage: preimages[0], index: indices[0] };
|
|
226
|
+
}
|
|
227
|
+
for (let i = 0; i < preimages.length; i++) {
|
|
228
|
+
if (preimages[i].getKey() <= currentMin.preimage.getKey()) {
|
|
229
|
+
currentMin = { preimage: preimages[i], index: indices[i] };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.setMinInfo(treeId, currentMin.preimage, currentMin.index);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* This appends a nullifier to the nullifier tree, and throws if the nullifier already exists
|
|
237
|
+
* @param value - The nullifier to be appended
|
|
238
|
+
* @returns The insertion result which contains the insertion path, low leaf and the new leaf index
|
|
239
|
+
*/
|
|
240
|
+
async appendNullifier(nullifier: Fr): Promise<IndexedInsertionResult<NullifierLeafPreimage>> {
|
|
241
|
+
const treeId = MerkleTreeId.NULLIFIER_TREE;
|
|
242
|
+
const tree = this.treeMap.get(treeId)!;
|
|
243
|
+
const { preimage, index, update }: PreimageWitness<NullifierLeafPreimage> = await this.getLeafOrLowLeafInfo(
|
|
244
|
+
treeId,
|
|
245
|
+
nullifier,
|
|
246
|
+
);
|
|
247
|
+
const siblingPath = await this.getSiblingPath(treeId, index);
|
|
248
|
+
|
|
249
|
+
if (update) {
|
|
250
|
+
throw new Error('Not allowed to update a nullifier');
|
|
251
|
+
}
|
|
252
|
+
// We are writing a new entry
|
|
253
|
+
const insertionIndex = tree.leafCount;
|
|
254
|
+
const updatedLowNullifier = cloneDeep(preimage);
|
|
255
|
+
updatedLowNullifier.nextNullifier = nullifier;
|
|
256
|
+
updatedLowNullifier.nextIndex = insertionIndex;
|
|
257
|
+
|
|
258
|
+
const newNullifierLeaf = new NullifierLeafPreimage(nullifier, preimage.nextNullifier, preimage.nextIndex);
|
|
259
|
+
const insertionPath = this.appendIndexedTree(treeId, index, updatedLowNullifier, newNullifierLeaf);
|
|
260
|
+
|
|
261
|
+
// Since we are appending, we might have a new minimum nullifier leaf
|
|
262
|
+
this._updateMinInfo(MerkleTreeId.NULLIFIER_TREE, [newNullifierLeaf, updatedLowNullifier], [insertionIndex, index]);
|
|
263
|
+
return {
|
|
264
|
+
leafIndex: insertionIndex,
|
|
265
|
+
insertionPath: insertionPath,
|
|
266
|
+
newOrElementToUpdate: { update: false, element: newNullifierLeaf },
|
|
267
|
+
lowWitness: {
|
|
268
|
+
preimage,
|
|
269
|
+
index,
|
|
270
|
+
update,
|
|
271
|
+
siblingPath,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* This appends a note hash to the note hash tree
|
|
278
|
+
* @param value - The note hash to be appended
|
|
279
|
+
* @returns The insertion result which contains the insertion path
|
|
280
|
+
*/
|
|
281
|
+
appendNoteHash(noteHash: Fr): Fr[] {
|
|
282
|
+
const tree = this.treeMap.get(MerkleTreeId.NOTE_HASH_TREE)!;
|
|
283
|
+
tree.appendLeaf(noteHash);
|
|
284
|
+
// We use leafCount - 1 here because we would have just appended a leaf
|
|
285
|
+
const insertionPath = tree.getSiblingPath(tree.leafCount - 1n);
|
|
286
|
+
return insertionPath!;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* This is wrapper around treeId to get the correct minimum leaf preimage
|
|
291
|
+
*/
|
|
292
|
+
private getMinInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
293
|
+
treeId: ID,
|
|
294
|
+
): { preimage: T; index: bigint } | undefined {
|
|
295
|
+
const start = this.indexedTreeMin.get(treeId);
|
|
296
|
+
if (start === undefined) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
const [preimage, index] = start;
|
|
300
|
+
return { preimage: preimage as T, index };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* This is wrapper around treeId to set the correct minimum leaf preimage
|
|
305
|
+
*/
|
|
306
|
+
private setMinInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
307
|
+
treeId: ID,
|
|
308
|
+
preimage: T,
|
|
309
|
+
index: bigint,
|
|
310
|
+
): void {
|
|
311
|
+
this.indexedTreeMin.set(treeId, [preimage, index]);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* This is wrapper around treeId to set values in the indexedUpdates map
|
|
316
|
+
*/
|
|
317
|
+
private setIndexedUpdates<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
318
|
+
treeId: ID,
|
|
319
|
+
index: bigint,
|
|
320
|
+
preimage: T,
|
|
321
|
+
): void {
|
|
322
|
+
let updates = this.indexedUpdates.get(treeId);
|
|
323
|
+
if (updates === undefined) {
|
|
324
|
+
updates = new Map();
|
|
325
|
+
this.indexedUpdates.set(treeId, updates);
|
|
326
|
+
}
|
|
327
|
+
updates.set(index, preimage);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* This is wrapper around treeId to get values in the indexedUpdates map
|
|
332
|
+
*/
|
|
333
|
+
private getIndexedUpdates<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(treeId: ID, index: bigint): T {
|
|
334
|
+
const updates = this.indexedUpdates.get(treeId);
|
|
335
|
+
if (updates === undefined) {
|
|
336
|
+
throw new Error('No updates found');
|
|
337
|
+
}
|
|
338
|
+
const preimage = updates.get(index);
|
|
339
|
+
if (preimage === undefined) {
|
|
340
|
+
throw new Error('No updates found');
|
|
341
|
+
}
|
|
342
|
+
return preimage as T;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* This is wrapper around treeId to check membership (i.e. has()) of index in the indexedUpdates map
|
|
347
|
+
*/
|
|
348
|
+
private hasLocalUpdates<ID extends IndexedTreeId>(treeId: ID, index: bigint): boolean {
|
|
349
|
+
const updates = this.indexedUpdates.get(treeId);
|
|
350
|
+
if (updates === undefined) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return updates.has(index);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* This gets the low leaf preimage and the index of the low leaf in the indexed tree given a value (slot or nullifier value)
|
|
358
|
+
* If the value is not found in the tree, it does an external lookup to the merkleDB
|
|
359
|
+
* @param treeId - The tree we are looking up in
|
|
360
|
+
* @param key - The key for which we are look up the low leaf for.
|
|
361
|
+
* @param T - The type of the preimage (PublicData or Nullifier)
|
|
362
|
+
* @returns The low leaf preimage and the index of the low leaf in the indexed tree
|
|
363
|
+
*/
|
|
364
|
+
async getLeafOrLowLeafInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
365
|
+
treeId: ID,
|
|
366
|
+
key: Fr,
|
|
367
|
+
): Promise<PreimageWitness<T>> {
|
|
368
|
+
// This can probably be done better, we want to say if the minInfo is undefined (because this is our first operation) we do the external lookup
|
|
369
|
+
const minPreimage = this.getMinInfo(treeId);
|
|
370
|
+
const start = minPreimage?.preimage;
|
|
371
|
+
const bigIntKey = key.toBigInt();
|
|
372
|
+
// If the first element we have is already greater than the value, we need to do an external lookup
|
|
373
|
+
if (minPreimage === undefined || (start?.getKey() ?? 0n) >= key.toBigInt()) {
|
|
374
|
+
// The low public data witness is in the previous tree
|
|
375
|
+
const { index, alreadyPresent } = (await this.treeDb.getPreviousValueIndex(treeId, bigIntKey))!;
|
|
376
|
+
const preimage = await this.treeDb.getLeafPreimage(treeId, index);
|
|
377
|
+
|
|
378
|
+
// Since we have never seen this before - we should insert it into our tree
|
|
379
|
+
const siblingPath = (await this.treeDb.getSiblingPath(treeId, index)).toFields();
|
|
380
|
+
|
|
381
|
+
// Is it enough to just insert the sibling path without inserting the leaf? - right now probably since we will update this low nullifier index in append
|
|
382
|
+
this.treeMap.get(treeId)!.insertSiblingPath(index, siblingPath);
|
|
383
|
+
|
|
384
|
+
const lowPublicDataPreimage = preimage as T;
|
|
385
|
+
return { preimage: lowPublicDataPreimage, index: index, update: alreadyPresent };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// We look for the low element by bouncing between our local indexedUpdates map or the external DB
|
|
389
|
+
// The conditions we are looking for are:
|
|
390
|
+
// (1) Exact Match: curr.nextKey == key (this is only valid for public data tree)
|
|
391
|
+
// (2) Sandwich Match: curr.nextKey > key and curr.key < key
|
|
392
|
+
// (3) Max Condition: curr.next_index == 0 and curr.key < key
|
|
393
|
+
// Note the min condition does not need to be handled since indexed trees are prefilled with at least the 0 element
|
|
394
|
+
let found = false;
|
|
395
|
+
let curr = minPreimage.preimage as T;
|
|
396
|
+
let result: PreimageWitness<T> | undefined = undefined;
|
|
397
|
+
// Temp to avoid infinite loops - the limit is the number of leaves we may have to read
|
|
398
|
+
const LIMIT = 2n ** BigInt(getTreeHeight(treeId)) - 1n;
|
|
399
|
+
let counter = 0n;
|
|
400
|
+
let lowPublicDataIndex = minPreimage.index;
|
|
401
|
+
while (!found && counter < LIMIT) {
|
|
402
|
+
if (curr.getKey() === bigIntKey) {
|
|
403
|
+
// We found an exact match - therefore this is an update
|
|
404
|
+
found = true;
|
|
405
|
+
result = { preimage: curr, index: lowPublicDataIndex, update: true };
|
|
406
|
+
} else if (curr.getKey() < bigIntKey && (curr.getNextKey() === 0n || curr.getNextKey() > bigIntKey)) {
|
|
407
|
+
// We found it via sandwich or max condition, this is a low nullifier
|
|
408
|
+
found = true;
|
|
409
|
+
result = { preimage: curr, index: lowPublicDataIndex, update: false };
|
|
410
|
+
}
|
|
411
|
+
// Update the the values for the next iteration
|
|
412
|
+
else {
|
|
413
|
+
lowPublicDataIndex = curr.getNextIndex();
|
|
414
|
+
if (this.hasLocalUpdates(treeId, lowPublicDataIndex)) {
|
|
415
|
+
curr = this.getIndexedUpdates(treeId, lowPublicDataIndex)!;
|
|
416
|
+
} else {
|
|
417
|
+
const preimage: IndexedTreeLeafPreimage = (await this.treeDb.getLeafPreimage(treeId, lowPublicDataIndex))!;
|
|
418
|
+
curr = preimage as T;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
counter++;
|
|
422
|
+
}
|
|
423
|
+
// We did not find it - this is unexpected
|
|
424
|
+
if (result === undefined) {
|
|
425
|
+
throw new Error('No previous value found or ran out of iterations');
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* This hashes the preimage to a field element
|
|
432
|
+
*/
|
|
433
|
+
hashPreimage<T extends TreeLeafPreimage>(preimage: T): Fr {
|
|
434
|
+
// Watch for this edge-case, we are hashing the key=0 leaf to 0.
|
|
435
|
+
// This is for backward compatibility with the world state implementation
|
|
436
|
+
if (preimage.getKey() === 0n) {
|
|
437
|
+
return Fr.zero();
|
|
438
|
+
}
|
|
439
|
+
const input = preimage.toHashInputs().map(x => Fr.fromBuffer(x));
|
|
440
|
+
return poseidon2Hash(input);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/****************************************************/
|
|
445
|
+
/****** Some useful Structs and Enums **************/
|
|
446
|
+
/****************************************************/
|
|
447
|
+
enum TreeType {
|
|
448
|
+
LEAF,
|
|
449
|
+
NODE,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
type Leaf = {
|
|
453
|
+
tag: TreeType.LEAF;
|
|
454
|
+
value: Fr;
|
|
455
|
+
};
|
|
456
|
+
type Node = {
|
|
457
|
+
tag: TreeType.NODE;
|
|
458
|
+
leftTree: Tree;
|
|
459
|
+
rightTree: Tree;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
type Tree = Leaf | Node;
|
|
463
|
+
|
|
464
|
+
enum SiblingStatus {
|
|
465
|
+
MEMBER,
|
|
466
|
+
NONMEMBER,
|
|
467
|
+
ERROR,
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
type AccumulatedSiblingPath = {
|
|
471
|
+
path: Fr[];
|
|
472
|
+
status: SiblingStatus;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
/****************************************************/
|
|
476
|
+
/****** Some Helpful Constructors for Trees ********/
|
|
477
|
+
/****************************************************/
|
|
478
|
+
const Node = (left: Tree, right: Tree): Node => ({
|
|
479
|
+
tag: TreeType.NODE,
|
|
480
|
+
leftTree: left,
|
|
481
|
+
rightTree: right,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const Leaf = (value: Fr): Leaf => ({
|
|
485
|
+
tag: TreeType.LEAF,
|
|
486
|
+
value,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
/****************************************************/
|
|
490
|
+
/****** The EphemeralAvmTree Class *****************/
|
|
491
|
+
/****************************************************/
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* This class contains a recursively defined tree that has leaves at different heights
|
|
495
|
+
* It is seeded by an existing merkle treeDb for which it derives a frontier
|
|
496
|
+
* It is intended to be a lightweight tree that contains only the necessary information to suppport appends or updates
|
|
497
|
+
*/
|
|
498
|
+
export class EphemeralAvmTree {
|
|
499
|
+
private tree: Tree;
|
|
500
|
+
private readonly zeroHashes: Fr[];
|
|
501
|
+
public frontier: Fr[];
|
|
502
|
+
|
|
503
|
+
private constructor(public leafCount: bigint, public depth: number) {
|
|
504
|
+
let zeroHash = Fr.zero();
|
|
505
|
+
// Can probably cache this elsewhere
|
|
506
|
+
const zeroHashes = [];
|
|
507
|
+
for (let i = 0; i < this.depth; i++) {
|
|
508
|
+
zeroHashes.push(zeroHash);
|
|
509
|
+
zeroHash = poseidon2Hash([zeroHash, zeroHash]);
|
|
510
|
+
}
|
|
511
|
+
this.tree = Leaf(zeroHash);
|
|
512
|
+
this.zeroHashes = zeroHashes;
|
|
513
|
+
this.frontier = [];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
static async create(
|
|
517
|
+
forkedLeafCount: bigint,
|
|
518
|
+
depth: number,
|
|
519
|
+
treeDb: MerkleTreeReadOperations,
|
|
520
|
+
merkleId: MerkleTreeId,
|
|
521
|
+
): Promise<EphemeralAvmTree> {
|
|
522
|
+
const tree = new EphemeralAvmTree(forkedLeafCount, depth);
|
|
523
|
+
await tree.initializeFrontier(treeDb, merkleId);
|
|
524
|
+
return tree;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* This is a recursive function that inserts a leaf into the tree
|
|
529
|
+
* @param value - The value of the leaf to be inserted
|
|
530
|
+
*/
|
|
531
|
+
appendLeaf(value: Fr): void {
|
|
532
|
+
const insertPath = this._derivePathLE(this.leafCount);
|
|
533
|
+
this.tree = this._insertLeaf(value, insertPath, this.depth, this.tree);
|
|
534
|
+
this.leafCount++;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* This is a recursive function that upserts a leaf into the tree at a index and depth
|
|
539
|
+
* @param value - The value of the leaf to be inserted
|
|
540
|
+
* @param index - The index of the leaf to be inserted
|
|
541
|
+
* @param depth - The depth of the leaf to be inserted (defaults to the bottom of the tree)
|
|
542
|
+
*/
|
|
543
|
+
updateLeaf(value: Fr, index: bigint, depth = this.depth): void {
|
|
544
|
+
const insertPath = this._derivePathLE(index, depth);
|
|
545
|
+
this.tree = this._insertLeaf(value, insertPath, depth, this.tree);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get the sibling path of a leaf in the tree
|
|
550
|
+
* @param index - The index of the leaf for which a sibling path should be returned.
|
|
551
|
+
* @returns The sibling path of the leaf, can fail if the path is not found
|
|
552
|
+
*/
|
|
553
|
+
getSiblingPath(index: bigint): Fr[] | undefined {
|
|
554
|
+
const searchPath = this._derivePathLE(index);
|
|
555
|
+
// Handle cases where we error out
|
|
556
|
+
const { path, status } = this._getSiblingPath(searchPath, this.tree, []);
|
|
557
|
+
if (status === SiblingStatus.ERROR) {
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
return path;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* This upserts the nodes of the sibling path into the tree
|
|
565
|
+
* @param index - The index of the leaf that the sibling path is derived from
|
|
566
|
+
* @param siblingPath - The sibling path of the index
|
|
567
|
+
*/
|
|
568
|
+
insertSiblingPath(index: bigint, siblingPath: Fr[]): void {
|
|
569
|
+
for (let i = 0; i < siblingPath.length; i++) {
|
|
570
|
+
// Flip(XOR) the last bit because we are inserting siblings of the leaf
|
|
571
|
+
const sibIndex = index ^ 1n;
|
|
572
|
+
this.updateLeaf(siblingPath[i], sibIndex, this.depth - i);
|
|
573
|
+
index >>= 1n;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* This is a helper function that computes the index of the frontier nodes at each depth
|
|
579
|
+
* @param leafCount - The number of leaves in the tree
|
|
580
|
+
* @returns An array of frontier indices at each depth, sorted from leaf to root
|
|
581
|
+
*/
|
|
582
|
+
// Do we really need LeafCount to be a bigint - log2 is on numbers only
|
|
583
|
+
static computeFrontierLeafIndices(leafCount: number): number[] {
|
|
584
|
+
const numFrontierEntries = Math.floor(Math.log2(leafCount)) + 1;
|
|
585
|
+
const frontierIndices = [];
|
|
586
|
+
for (let i = 0; i < numFrontierEntries; i++) {
|
|
587
|
+
if (leafCount === 0) {
|
|
588
|
+
frontierIndices.push(0);
|
|
589
|
+
} else if (leafCount % 2 === 0) {
|
|
590
|
+
frontierIndices.push(leafCount - 2);
|
|
591
|
+
} else {
|
|
592
|
+
frontierIndices.push(leafCount - 1);
|
|
593
|
+
}
|
|
594
|
+
leafCount >>= 1;
|
|
595
|
+
}
|
|
596
|
+
return frontierIndices;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* This derives the frontier and inserts them into the tree
|
|
601
|
+
* @param treeDb - The treeDb to be queried for sibling paths
|
|
602
|
+
* @param merkleId - The treeId of the tree to be queried for sibling paths
|
|
603
|
+
*/
|
|
604
|
+
async initializeFrontier(treeDb: MerkleTreeReadOperations, merkleId: MerkleTreeId): Promise<void> {
|
|
605
|
+
// The frontier indices are sorted from the leaf to root
|
|
606
|
+
const frontierIndices = EphemeralAvmTree.computeFrontierLeafIndices(Number(this.leafCount));
|
|
607
|
+
// The frontier indices are level-based - i.e. index N at level L.
|
|
608
|
+
// Since we can only ask the DB for paths from the root to the leaf, we do the following complicated calculations
|
|
609
|
+
// 1) The goal is to insert the frontier node N at level L into the tree.
|
|
610
|
+
// 2) We get the path to a leaf that passes through the frontier node we want (there are multiple paths so we just pick one)
|
|
611
|
+
// 3) We can only get sibling paths from the root to the leaf, so we get the sibling path of the leaf from (2)
|
|
612
|
+
// NOTE: This is terribly inefficient and we should probably change the DB API to allow for getting paths to a node
|
|
613
|
+
|
|
614
|
+
const frontierValues = [];
|
|
615
|
+
// These are leaf indexes that pass through the frontier nodes
|
|
616
|
+
for (let i = 0; i < frontierIndices.length; i++) {
|
|
617
|
+
// Given the index to a frontier, we first xor it so we can get its sibling index at depth L
|
|
618
|
+
// We then extend the path to that sibling index by shifting left the requisite number of times (for simplicity we just go left down the tree - it doesnt matter)
|
|
619
|
+
// This provides us the leaf index such that if we ask for this leafIndex's sibling path, it will pass through the frontier node
|
|
620
|
+
const index = BigInt(frontierIndices[i] ^ 1) << BigInt(i);
|
|
621
|
+
// This path passes through our frontier node at depth - i
|
|
622
|
+
const path = await treeDb.getSiblingPath(merkleId, index);
|
|
623
|
+
|
|
624
|
+
// We derive the path that we can walk and truncate it so that it terminates exactly at the frontier node
|
|
625
|
+
const frontierPath = this._derivePathLE(BigInt(frontierIndices[i]), this.depth - i);
|
|
626
|
+
// The value of the frontier is the at the i-th index of the sibling path
|
|
627
|
+
const frontierValue = path.toFields()[i];
|
|
628
|
+
frontierValues.push(frontierValue);
|
|
629
|
+
// We insert it at depth - i (the truncated position)
|
|
630
|
+
// Note this is a leaf node that wont necessarily be at the bottom of the tree (besides the first frontier)
|
|
631
|
+
this.tree = this._insertLeaf(frontierValue, frontierPath, this.depth - i, this.tree);
|
|
632
|
+
}
|
|
633
|
+
this.frontier = frontierValues;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Computes the root of the tree
|
|
638
|
+
*/
|
|
639
|
+
public getRoot(): Fr {
|
|
640
|
+
return this.hashTree(this.tree, this.depth);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Recursively hashes the subtree
|
|
645
|
+
* @param tree - The tree to be hashed
|
|
646
|
+
* @param depth - The depth of the tree
|
|
647
|
+
*/
|
|
648
|
+
public hashTree(tree: Tree, depth: number): Fr {
|
|
649
|
+
switch (tree.tag) {
|
|
650
|
+
case TreeType.NODE: {
|
|
651
|
+
return poseidon2Hash([this.hashTree(tree.leftTree, depth - 1), this.hashTree(tree.rightTree, depth - 1)]);
|
|
652
|
+
}
|
|
653
|
+
case TreeType.LEAF: {
|
|
654
|
+
return tree.value;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Extracts the subtree from a given index and depth
|
|
661
|
+
* @param index - The index of the node to be extracted
|
|
662
|
+
* @param depth - The depth of the node to be extracted
|
|
663
|
+
* @returns The subtree rooted at the index and depth
|
|
664
|
+
*/
|
|
665
|
+
public getNode(index: bigint, depth: number): Tree | undefined {
|
|
666
|
+
const path = this._derivePathBE(index, depth);
|
|
667
|
+
const truncatedPath = path.slice(0, depth);
|
|
668
|
+
truncatedPath.reverse();
|
|
669
|
+
try {
|
|
670
|
+
return this._getNode(truncatedPath, this.tree);
|
|
671
|
+
} catch (e) {
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* This is the recursive helper for getNode
|
|
678
|
+
*/
|
|
679
|
+
private _getNode(nodePath: number[], tree: Tree): Tree {
|
|
680
|
+
if (nodePath.length === 0) {
|
|
681
|
+
return tree;
|
|
682
|
+
}
|
|
683
|
+
switch (tree.tag) {
|
|
684
|
+
case TreeType.NODE:
|
|
685
|
+
return nodePath.pop() === 0 ? this._getNode(nodePath, tree.leftTree) : this._getNode(nodePath, tree.rightTree);
|
|
686
|
+
|
|
687
|
+
case TreeType.LEAF:
|
|
688
|
+
throw new Error('Node not found');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** Our tree traversal uses an array of 1s and 0s to represent the path to a leaf and expects them to be in LE order
|
|
693
|
+
* This helps with deriving it given an index and (optionally a depth)
|
|
694
|
+
* @param index - The index to derive a path to within the tree, does not have to terminate at a leaf
|
|
695
|
+
* @param depth - The depth to traverse, if not provided it will traverse to the bottom of the tree
|
|
696
|
+
* @returns The path to the leaf in LE order
|
|
697
|
+
*/
|
|
698
|
+
private _derivePathLE(index: bigint, depth = this.depth): number[] {
|
|
699
|
+
return this._derivePathBE(index, depth).reverse();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** Sometimes we want it in BE order, to make truncating easier
|
|
703
|
+
* @param index - The index to derive a path to within the tree, does not have to terminate at a leaf
|
|
704
|
+
* @param depth - The depth to traverse, if not provided it will traverse to the bottom of the tree
|
|
705
|
+
* @returns The path to the leaf in LE order
|
|
706
|
+
*/
|
|
707
|
+
private _derivePathBE(index: bigint, depth = this.depth): number[] {
|
|
708
|
+
return index
|
|
709
|
+
.toString(2)
|
|
710
|
+
.padStart(depth, '0')
|
|
711
|
+
.split('')
|
|
712
|
+
.map(x => parseInt(x));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* This is a recursive function that upserts a leaf into the tree given a path
|
|
717
|
+
* @param value - The value of the leaf to be upserted
|
|
718
|
+
* @param insertPath - The path to the leaf, this should be ordered from leaf to root (i.e. LE encoded)
|
|
719
|
+
* @param depth - The depth of the tree
|
|
720
|
+
* @param tree - The current tree
|
|
721
|
+
* @param appendMode - If true we append the relevant zeroHashes to the tree as we traverse
|
|
722
|
+
*/
|
|
723
|
+
private _insertLeaf(value: Fr, insertPath: number[], depth: number, tree: Tree): Tree {
|
|
724
|
+
if (insertPath.length > this.depth || depth > this.depth) {
|
|
725
|
+
throw new Error('PATH EXCEEDS DEPTH');
|
|
726
|
+
}
|
|
727
|
+
if (depth === 0 || insertPath.length === 0) {
|
|
728
|
+
return Leaf(value);
|
|
729
|
+
}
|
|
730
|
+
switch (tree.tag) {
|
|
731
|
+
case TreeType.NODE: {
|
|
732
|
+
return insertPath.pop() === 0
|
|
733
|
+
? Node(this._insertLeaf(value, insertPath, depth - 1, tree.leftTree), tree.rightTree)
|
|
734
|
+
: Node(tree.leftTree, this._insertLeaf(value, insertPath, depth - 1, tree.rightTree));
|
|
735
|
+
}
|
|
736
|
+
case TreeType.LEAF: {
|
|
737
|
+
const zeroLeaf = Leaf(this.zeroHashes[depth - 1]);
|
|
738
|
+
return insertPath.pop() === 0
|
|
739
|
+
? Node(this._insertLeaf(value, insertPath, depth - 1, zeroLeaf), zeroLeaf)
|
|
740
|
+
: Node(zeroLeaf, this._insertLeaf(value, insertPath, depth - 1, zeroLeaf));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/* Recursive helper for getSiblingPath, this only looks inside the tree and does not resolve using
|
|
746
|
+
* a DB. If a path is not found, it returns an error status that is expected to be handled by the caller
|
|
747
|
+
* @param searchPath - The path to the leaf for which we would like the sibling pathin LE order
|
|
748
|
+
* @param tree - The current tree
|
|
749
|
+
* @param acc - The accumulated sibling path
|
|
750
|
+
*/
|
|
751
|
+
private _getSiblingPath(searchPath: number[], tree: Tree, acc: Fr[]): AccumulatedSiblingPath {
|
|
752
|
+
// If we have reached the end of the path, we should be at a leaf or empty node
|
|
753
|
+
// If it is a leaf, we check if the value is equal to the leaf value
|
|
754
|
+
// If it is empty we check if the value is equal to zero
|
|
755
|
+
if (searchPath.length === 0) {
|
|
756
|
+
switch (tree.tag) {
|
|
757
|
+
case TreeType.LEAF:
|
|
758
|
+
return { path: acc, status: SiblingStatus.MEMBER };
|
|
759
|
+
case TreeType.NODE:
|
|
760
|
+
return { path: [], status: SiblingStatus.ERROR };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Keep exploring here
|
|
764
|
+
switch (tree.tag) {
|
|
765
|
+
case TreeType.NODE: {
|
|
766
|
+
// Look at the next element of the path to decided if we go left or right, note this mutates!
|
|
767
|
+
return searchPath.pop() === 0
|
|
768
|
+
? this._getSiblingPath(
|
|
769
|
+
searchPath,
|
|
770
|
+
tree.leftTree,
|
|
771
|
+
[this.hashTree(tree.rightTree, searchPath.length)].concat(acc),
|
|
772
|
+
)
|
|
773
|
+
: this._getSiblingPath(
|
|
774
|
+
searchPath,
|
|
775
|
+
tree.rightTree,
|
|
776
|
+
[this.hashTree(tree.leftTree, searchPath.length)].concat(acc),
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
// In these two situations we are exploring a subtree we dont have information about
|
|
780
|
+
// We should return an error and look inside the DB
|
|
781
|
+
case TreeType.LEAF:
|
|
782
|
+
return { path: [], status: SiblingStatus.ERROR };
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|