@aztec/simulator 0.62.0 → 0.63.1

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.
Files changed (216) hide show
  1. package/dest/acvm/acvm.d.ts +2 -16
  2. package/dest/acvm/acvm.d.ts.map +1 -1
  3. package/dest/acvm/acvm.js +2 -70
  4. package/dest/acvm/oracle/oracle.d.ts +4 -4
  5. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/oracle.js +10 -11
  7. package/dest/acvm/oracle/typed_oracle.d.ts +5 -5
  8. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  9. package/dest/acvm/oracle/typed_oracle.js +8 -8
  10. package/dest/avm/avm_gas.d.ts.map +1 -1
  11. package/dest/avm/avm_gas.js +2 -1
  12. package/dest/avm/avm_machine_state.d.ts +27 -8
  13. package/dest/avm/avm_machine_state.d.ts.map +1 -1
  14. package/dest/avm/avm_machine_state.js +6 -10
  15. package/dest/avm/avm_memory_types.d.ts +8 -0
  16. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  17. package/dest/avm/avm_memory_types.js +5 -1
  18. package/dest/avm/avm_simulator.d.ts +2 -19
  19. package/dest/avm/avm_simulator.d.ts.map +1 -1
  20. package/dest/avm/avm_simulator.js +12 -14
  21. package/dest/avm/avm_tree.d.ts +249 -0
  22. package/dest/avm/avm_tree.d.ts.map +1 -0
  23. package/dest/avm/avm_tree.js +637 -0
  24. package/dest/avm/errors.d.ts +4 -17
  25. package/dest/avm/errors.d.ts.map +1 -1
  26. package/dest/avm/errors.js +21 -50
  27. package/dest/avm/fixtures/index.d.ts +7 -2
  28. package/dest/avm/fixtures/index.d.ts.map +1 -1
  29. package/dest/avm/fixtures/index.js +12 -12
  30. package/dest/avm/index.d.ts +1 -0
  31. package/dest/avm/index.d.ts.map +1 -1
  32. package/dest/avm/index.js +2 -1
  33. package/dest/avm/journal/journal.d.ts +43 -24
  34. package/dest/avm/journal/journal.d.ts.map +1 -1
  35. package/dest/avm/journal/journal.js +172 -39
  36. package/dest/avm/journal/nullifiers.d.ts +5 -4
  37. package/dest/avm/journal/nullifiers.d.ts.map +1 -1
  38. package/dest/avm/journal/nullifiers.js +2 -3
  39. package/dest/avm/journal/public_storage.d.ts +6 -5
  40. package/dest/avm/journal/public_storage.d.ts.map +1 -1
  41. package/dest/avm/journal/public_storage.js +1 -1
  42. package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
  43. package/dest/avm/opcodes/accrued_substate.js +4 -10
  44. package/dest/avm/opcodes/arithmetic.d.ts +4 -1
  45. package/dest/avm/opcodes/arithmetic.d.ts.map +1 -1
  46. package/dest/avm/opcodes/arithmetic.js +18 -4
  47. package/dest/avm/opcodes/bitwise.d.ts.map +1 -1
  48. package/dest/avm/opcodes/bitwise.js +1 -3
  49. package/dest/avm/opcodes/comparators.d.ts.map +1 -1
  50. package/dest/avm/opcodes/comparators.js +1 -2
  51. package/dest/avm/opcodes/contract.d.ts.map +1 -1
  52. package/dest/avm/opcodes/contract.js +2 -3
  53. package/dest/avm/opcodes/control_flow.d.ts +4 -0
  54. package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
  55. package/dest/avm/opcodes/control_flow.js +21 -6
  56. package/dest/avm/opcodes/conversion.d.ts.map +1 -1
  57. package/dest/avm/opcodes/conversion.js +1 -2
  58. package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
  59. package/dest/avm/opcodes/ec_add.js +5 -11
  60. package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
  61. package/dest/avm/opcodes/environment_getters.js +1 -2
  62. package/dest/avm/opcodes/external_calls.d.ts +4 -2
  63. package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
  64. package/dest/avm/opcodes/external_calls.js +38 -22
  65. package/dest/avm/opcodes/hashing.d.ts.map +1 -1
  66. package/dest/avm/opcodes/hashing.js +1 -4
  67. package/dest/avm/opcodes/instruction.d.ts +4 -0
  68. package/dest/avm/opcodes/instruction.d.ts.map +1 -1
  69. package/dest/avm/opcodes/instruction.js +7 -1
  70. package/dest/avm/opcodes/memory.d.ts.map +1 -1
  71. package/dest/avm/opcodes/memory.js +1 -7
  72. package/dest/avm/opcodes/misc.js +3 -3
  73. package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
  74. package/dest/avm/opcodes/multi_scalar_mul.js +6 -5
  75. package/dest/avm/opcodes/storage.d.ts.map +1 -1
  76. package/dest/avm/opcodes/storage.js +2 -4
  77. package/dest/avm/serialization/bytecode_serialization.d.ts +1 -6
  78. package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  79. package/dest/avm/serialization/bytecode_serialization.js +24 -20
  80. package/dest/client/client_execution_context.d.ts +7 -11
  81. package/dest/client/client_execution_context.d.ts.map +1 -1
  82. package/dest/client/client_execution_context.js +18 -20
  83. package/dest/client/db_oracle.d.ts +17 -10
  84. package/dest/client/db_oracle.d.ts.map +1 -1
  85. package/dest/client/db_oracle.js +1 -1
  86. package/dest/client/private_execution.d.ts.map +1 -1
  87. package/dest/client/private_execution.js +5 -4
  88. package/dest/client/unconstrained_execution.d.ts.map +1 -1
  89. package/dest/client/unconstrained_execution.js +3 -2
  90. package/dest/client/view_data_oracle.d.ts +6 -12
  91. package/dest/client/view_data_oracle.d.ts.map +1 -1
  92. package/dest/client/view_data_oracle.js +10 -12
  93. package/dest/common/errors.d.ts +15 -2
  94. package/dest/common/errors.d.ts.map +1 -1
  95. package/dest/common/errors.js +85 -4
  96. package/dest/mocks/fixtures.d.ts +9 -28
  97. package/dest/mocks/fixtures.d.ts.map +1 -1
  98. package/dest/mocks/fixtures.js +12 -57
  99. package/dest/public/dual_side_effect_trace.d.ts +34 -26
  100. package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
  101. package/dest/public/dual_side_effect_trace.js +48 -36
  102. package/dest/public/enqueued_call_side_effect_trace.d.ts +96 -33
  103. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  104. package/dest/public/enqueued_call_side_effect_trace.js +212 -138
  105. package/dest/public/execution.d.ts +50 -17
  106. package/dest/public/execution.d.ts.map +1 -1
  107. package/dest/public/execution.js +1 -29
  108. package/dest/public/executor.d.ts +28 -11
  109. package/dest/public/executor.d.ts.map +1 -1
  110. package/dest/public/executor.js +33 -33
  111. package/dest/public/index.d.ts +4 -5
  112. package/dest/public/index.d.ts.map +1 -1
  113. package/dest/public/index.js +4 -5
  114. package/dest/public/public_db_sources.d.ts +1 -0
  115. package/dest/public/public_db_sources.d.ts.map +1 -1
  116. package/dest/public/public_db_sources.js +12 -5
  117. package/dest/public/public_processor.d.ts +7 -11
  118. package/dest/public/public_processor.d.ts.map +1 -1
  119. package/dest/public/public_processor.js +60 -42
  120. package/dest/public/public_processor_metrics.d.ts +3 -3
  121. package/dest/public/public_processor_metrics.d.ts.map +1 -1
  122. package/dest/public/public_processor_metrics.js +1 -1
  123. package/dest/public/public_tx_context.d.ts +130 -0
  124. package/dest/public/public_tx_context.d.ts.map +1 -0
  125. package/dest/public/public_tx_context.js +293 -0
  126. package/dest/public/public_tx_simulator.d.ts +36 -0
  127. package/dest/public/public_tx_simulator.d.ts.map +1 -0
  128. package/dest/public/public_tx_simulator.js +148 -0
  129. package/dest/public/side_effect_trace.d.ts +30 -15
  130. package/dest/public/side_effect_trace.d.ts.map +1 -1
  131. package/dest/public/side_effect_trace.js +70 -16
  132. package/dest/public/side_effect_trace_interface.d.ts +43 -12
  133. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  134. package/dest/public/transitional_adapters.d.ts +9 -0
  135. package/dest/public/transitional_adapters.d.ts.map +1 -0
  136. package/dest/public/transitional_adapters.js +127 -0
  137. package/dest/public/utils.d.ts +5 -0
  138. package/dest/public/utils.d.ts.map +1 -0
  139. package/dest/public/utils.js +30 -0
  140. package/package.json +12 -9
  141. package/src/acvm/acvm.ts +3 -94
  142. package/src/acvm/oracle/oracle.ts +9 -14
  143. package/src/acvm/oracle/typed_oracle.ts +8 -8
  144. package/src/avm/avm_gas.ts +1 -0
  145. package/src/avm/avm_machine_state.ts +28 -12
  146. package/src/avm/avm_memory_types.ts +5 -0
  147. package/src/avm/avm_simulator.ts +13 -16
  148. package/src/avm/avm_tree.ts +785 -0
  149. package/src/avm/errors.ts +25 -48
  150. package/src/avm/fixtures/index.ts +16 -12
  151. package/src/avm/index.ts +1 -0
  152. package/src/avm/journal/journal.ts +291 -52
  153. package/src/avm/journal/nullifiers.ts +7 -7
  154. package/src/avm/journal/public_storage.ts +5 -5
  155. package/src/avm/opcodes/accrued_substate.ts +3 -9
  156. package/src/avm/opcodes/arithmetic.ts +26 -4
  157. package/src/avm/opcodes/bitwise.ts +0 -2
  158. package/src/avm/opcodes/comparators.ts +0 -1
  159. package/src/avm/opcodes/contract.ts +1 -2
  160. package/src/avm/opcodes/control_flow.ts +24 -5
  161. package/src/avm/opcodes/conversion.ts +0 -1
  162. package/src/avm/opcodes/ec_add.ts +6 -9
  163. package/src/avm/opcodes/environment_getters.ts +0 -1
  164. package/src/avm/opcodes/external_calls.ts +39 -21
  165. package/src/avm/opcodes/hashing.ts +0 -3
  166. package/src/avm/opcodes/instruction.ts +7 -0
  167. package/src/avm/opcodes/memory.ts +0 -6
  168. package/src/avm/opcodes/misc.ts +2 -2
  169. package/src/avm/opcodes/multi_scalar_mul.ts +5 -4
  170. package/src/avm/opcodes/storage.ts +1 -3
  171. package/src/avm/serialization/bytecode_serialization.ts +31 -22
  172. package/src/client/client_execution_context.ts +22 -23
  173. package/src/client/db_oracle.ts +22 -11
  174. package/src/client/private_execution.ts +5 -4
  175. package/src/client/unconstrained_execution.ts +2 -1
  176. package/src/client/view_data_oracle.ts +14 -13
  177. package/src/common/errors.ts +119 -3
  178. package/src/mocks/fixtures.ts +15 -106
  179. package/src/public/dual_side_effect_trace.ts +138 -50
  180. package/src/public/enqueued_call_side_effect_trace.ts +352 -212
  181. package/src/public/execution.ts +58 -42
  182. package/src/public/executor.ts +52 -67
  183. package/src/public/index.ts +7 -5
  184. package/src/public/public_db_sources.ts +12 -4
  185. package/src/public/public_processor.ts +111 -73
  186. package/src/public/public_processor_metrics.ts +3 -3
  187. package/src/public/public_tx_context.ts +411 -0
  188. package/src/public/public_tx_simulator.ts +232 -0
  189. package/src/public/side_effect_trace.ts +154 -28
  190. package/src/public/side_effect_trace_interface.ts +92 -14
  191. package/src/public/transitional_adapters.ts +347 -0
  192. package/src/public/utils.ts +32 -0
  193. package/dest/public/enqueued_call_simulator.d.ts +0 -43
  194. package/dest/public/enqueued_call_simulator.d.ts.map +0 -1
  195. package/dest/public/enqueued_call_simulator.js +0 -156
  196. package/dest/public/enqueued_calls_processor.d.ts +0 -43
  197. package/dest/public/enqueued_calls_processor.d.ts.map +0 -1
  198. package/dest/public/enqueued_calls_processor.js +0 -209
  199. package/dest/public/hints_builder.d.ts +0 -29
  200. package/dest/public/hints_builder.d.ts.map +0 -1
  201. package/dest/public/hints_builder.js +0 -75
  202. package/dest/public/public_kernel.d.ts +0 -30
  203. package/dest/public/public_kernel.d.ts.map +0 -1
  204. package/dest/public/public_kernel.js +0 -67
  205. package/dest/public/public_kernel_circuit_simulator.d.ts +0 -25
  206. package/dest/public/public_kernel_circuit_simulator.d.ts.map +0 -1
  207. package/dest/public/public_kernel_circuit_simulator.js +0 -2
  208. package/dest/public/public_kernel_tail_simulator.d.ts +0 -15
  209. package/dest/public/public_kernel_tail_simulator.d.ts.map +0 -1
  210. package/dest/public/public_kernel_tail_simulator.js +0 -39
  211. package/src/public/enqueued_call_simulator.ts +0 -360
  212. package/src/public/enqueued_calls_processor.ts +0 -372
  213. package/src/public/hints_builder.ts +0 -168
  214. package/src/public/public_kernel.ts +0 -100
  215. package/src/public/public_kernel_circuit_simulator.ts +0 -32
  216. 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
+ }