@aztec/pxe 3.0.0-nightly.20251222 → 3.0.0-nightly.20251224

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 (131) hide show
  1. package/dest/contract_function_simulator/contract_function_simulator.d.ts +31 -6
  2. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  3. package/dest/contract_function_simulator/contract_function_simulator.js +35 -11
  4. package/dest/contract_function_simulator/noir-structs/log_retrieval_request.d.ts +4 -3
  5. package/dest/contract_function_simulator/noir-structs/log_retrieval_request.d.ts.map +1 -1
  6. package/dest/contract_function_simulator/noir-structs/log_retrieval_request.js +7 -6
  7. package/dest/contract_function_simulator/oracle/interfaces.d.ts +2 -3
  8. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  9. package/dest/contract_function_simulator/oracle/private_execution.d.ts +6 -8
  10. package/dest/contract_function_simulator/oracle/private_execution.d.ts.map +1 -1
  11. package/dest/contract_function_simulator/oracle/private_execution.js +10 -9
  12. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +14 -5
  13. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  14. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +19 -14
  15. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +44 -6
  16. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  17. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +135 -30
  18. package/dest/contract_function_simulator/proxied_contract_data_source.d.ts +2 -2
  19. package/dest/contract_function_simulator/proxied_contract_data_source.d.ts.map +1 -1
  20. package/dest/contract_function_simulator/proxied_contract_data_source.js +18 -0
  21. package/dest/debug/pxe_debug_utils.d.ts +3 -2
  22. package/dest/debug/pxe_debug_utils.d.ts.map +1 -1
  23. package/dest/entrypoints/client/bundle/index.d.ts +1 -2
  24. package/dest/entrypoints/client/bundle/index.d.ts.map +1 -1
  25. package/dest/entrypoints/client/bundle/index.js +0 -1
  26. package/dest/entrypoints/client/lazy/index.d.ts +1 -2
  27. package/dest/entrypoints/client/lazy/index.d.ts.map +1 -1
  28. package/dest/entrypoints/client/lazy/index.js +0 -1
  29. package/dest/entrypoints/server/index.d.ts +2 -2
  30. package/dest/entrypoints/server/index.d.ts.map +1 -1
  31. package/dest/entrypoints/server/index.js +1 -1
  32. package/dest/events/event_service.d.ts +15 -0
  33. package/dest/events/event_service.d.ts.map +1 -0
  34. package/dest/events/event_service.js +47 -0
  35. package/dest/events/private_event_filter_validator.d.ts +3 -2
  36. package/dest/events/private_event_filter_validator.d.ts.map +1 -1
  37. package/dest/logs/log_service.d.ts +43 -0
  38. package/dest/logs/log_service.d.ts.map +1 -0
  39. package/dest/logs/log_service.js +239 -0
  40. package/dest/notes/index.d.ts +2 -0
  41. package/dest/notes/index.d.ts.map +1 -0
  42. package/dest/notes/index.js +1 -0
  43. package/dest/notes/note_service.d.ts +48 -0
  44. package/dest/notes/note_service.d.ts.map +1 -0
  45. package/dest/notes/note_service.js +152 -0
  46. package/dest/private_kernel/private_kernel_oracle_impl.d.ts +2 -2
  47. package/dest/private_kernel/private_kernel_oracle_impl.d.ts.map +1 -1
  48. package/dest/public_storage/public_storage_service.d.ts +24 -0
  49. package/dest/public_storage/public_storage_service.d.ts.map +1 -0
  50. package/dest/public_storage/public_storage_service.js +26 -0
  51. package/dest/pxe.d.ts +1 -1
  52. package/dest/pxe.d.ts.map +1 -1
  53. package/dest/pxe.js +3 -5
  54. package/dest/storage/capsule_data_provider/capsule_data_provider.d.ts +33 -1
  55. package/dest/storage/capsule_data_provider/capsule_data_provider.d.ts.map +1 -1
  56. package/dest/storage/capsule_data_provider/capsule_data_provider.js +32 -4
  57. package/dest/storage/contract_data_provider/contract_data_provider.d.ts +2 -1
  58. package/dest/storage/contract_data_provider/contract_data_provider.d.ts.map +1 -1
  59. package/dest/storage/contract_data_provider/contract_data_provider.js +11 -0
  60. package/dest/storage/tagging_data_provider/sender_tagging_data_provider.js +3 -3
  61. package/dest/tagging/index.d.ts +2 -4
  62. package/dest/tagging/index.d.ts.map +1 -1
  63. package/dest/tagging/index.js +1 -3
  64. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +14 -0
  65. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +1 -0
  66. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.js +99 -0
  67. package/dest/tagging/recipient_sync/new_recipient_tagging_data_provider.d.ts +21 -0
  68. package/dest/tagging/recipient_sync/new_recipient_tagging_data_provider.d.ts.map +1 -0
  69. package/dest/tagging/recipient_sync/new_recipient_tagging_data_provider.js +42 -0
  70. package/dest/tagging/recipient_sync/utils/find_highest_indexes.d.ts +12 -0
  71. package/dest/tagging/recipient_sync/utils/find_highest_indexes.d.ts.map +1 -0
  72. package/dest/tagging/recipient_sync/utils/find_highest_indexes.js +20 -0
  73. package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts +14 -0
  74. package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts.map +1 -0
  75. package/dest/tagging/recipient_sync/utils/load_logs_for_range.js +29 -0
  76. package/dest/tagging/sync/sync_sender_tagging_indexes.d.ts +2 -2
  77. package/dest/tagging/sync/sync_sender_tagging_indexes.d.ts.map +1 -1
  78. package/dest/tagging/sync/sync_sender_tagging_indexes.js +3 -3
  79. package/dest/tagging/sync/utils/load_and_store_new_tagging_indexes.d.ts +1 -1
  80. package/dest/tagging/sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
  81. package/dest/tagging/sync/utils/load_and_store_new_tagging_indexes.js +3 -5
  82. package/dest/tree_membership/tree_membership_service.d.ts +52 -0
  83. package/dest/tree_membership/tree_membership_service.d.ts.map +1 -0
  84. package/dest/tree_membership/tree_membership_service.js +84 -0
  85. package/package.json +16 -16
  86. package/src/contract_function_simulator/contract_function_simulator.ts +59 -10
  87. package/src/contract_function_simulator/noir-structs/log_retrieval_request.ts +5 -4
  88. package/src/contract_function_simulator/oracle/interfaces.ts +1 -2
  89. package/src/contract_function_simulator/oracle/private_execution.ts +13 -10
  90. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +81 -21
  91. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +199 -38
  92. package/src/contract_function_simulator/proxied_contract_data_source.ts +18 -1
  93. package/src/debug/pxe_debug_utils.ts +2 -1
  94. package/src/entrypoints/client/bundle/index.ts +0 -1
  95. package/src/entrypoints/client/lazy/index.ts +0 -1
  96. package/src/entrypoints/server/index.ts +1 -1
  97. package/src/events/event_service.ts +77 -0
  98. package/src/events/private_event_filter_validator.ts +2 -1
  99. package/src/logs/log_service.ts +364 -0
  100. package/src/notes/index.ts +1 -0
  101. package/src/notes/note_service.ts +200 -0
  102. package/src/private_kernel/private_kernel_oracle_impl.ts +1 -1
  103. package/src/public_storage/public_storage_service.ts +33 -0
  104. package/src/pxe.ts +13 -11
  105. package/src/storage/capsule_data_provider/capsule_data_provider.ts +32 -0
  106. package/src/storage/contract_data_provider/contract_data_provider.ts +15 -0
  107. package/src/storage/tagging_data_provider/sender_tagging_data_provider.ts +3 -3
  108. package/src/tagging/index.ts +1 -3
  109. package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +129 -0
  110. package/src/tagging/recipient_sync/new_recipient_tagging_data_provider.ts +53 -0
  111. package/src/tagging/recipient_sync/utils/find_highest_indexes.ts +34 -0
  112. package/src/tagging/recipient_sync/utils/load_logs_for_range.ts +43 -0
  113. package/src/tagging/sync/sync_sender_tagging_indexes.ts +3 -3
  114. package/src/tagging/sync/utils/load_and_store_new_tagging_indexes.ts +3 -5
  115. package/src/tree_membership/tree_membership_service.ts +112 -0
  116. package/dest/contract_function_simulator/execution_data_provider.d.ts +0 -248
  117. package/dest/contract_function_simulator/execution_data_provider.d.ts.map +0 -1
  118. package/dest/contract_function_simulator/execution_data_provider.js +0 -14
  119. package/dest/contract_function_simulator/pxe_oracle_interface.d.ts +0 -113
  120. package/dest/contract_function_simulator/pxe_oracle_interface.d.ts.map +0 -1
  121. package/dest/contract_function_simulator/pxe_oracle_interface.js +0 -648
  122. package/dest/tagging/siloed_tag.d.ts +0 -14
  123. package/dest/tagging/siloed_tag.d.ts.map +0 -1
  124. package/dest/tagging/siloed_tag.js +0 -20
  125. package/dest/tagging/tag.d.ts +0 -12
  126. package/dest/tagging/tag.d.ts.map +0 -1
  127. package/dest/tagging/tag.js +0 -17
  128. package/src/contract_function_simulator/execution_data_provider.ts +0 -322
  129. package/src/contract_function_simulator/pxe_oracle_interface.ts +0 -967
  130. package/src/tagging/siloed_tag.ts +0 -22
  131. package/src/tagging/tag.ts +0 -16
@@ -19,10 +19,26 @@ export class CapsuleDataProvider {
19
19
  this.logger = createLogger('pxe:capsule-data-provider');
20
20
  }
21
21
 
22
+ /**
23
+ * Stores arbitrary information in a per-contract non-volatile database, which can later be retrieved with `loadCapsule`.
24
+ * * If data was already stored at this slot, it is overwritten.
25
+ * @param contractAddress - The contract address to scope the data under.
26
+ * @param slot - The slot in the database in which to store the value. Slots need not be contiguous.
27
+ * @param capsule - An array of field elements representing the capsule.
28
+ * @remarks A capsule is a "blob" of data that is passed to the contract through an oracle. It works similarly
29
+ * to public contract storage in that it's indexed by the contract address and storage slot but instead of the global
30
+ * network state it's backed by local PXE db.
31
+ */
22
32
  async storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise<void> {
23
33
  await this.#capsules.set(dbSlotToKey(contractAddress, slot), Buffer.concat(capsule.map(value => value.toBuffer())));
24
34
  }
25
35
 
36
+ /**
37
+ * Returns data previously stored via `storeCapsule` in the per-contract non-volatile database.
38
+ * @param contractAddress - The contract address under which the data is scoped.
39
+ * @param slot - The slot in the database to read.
40
+ * @returns The stored data or `null` if no data is stored under the slot.
41
+ */
26
42
  async loadCapsule(contractAddress: AztecAddress, slot: Fr): Promise<Fr[] | null> {
27
43
  const dataBuffer = await this.#capsules.getAsync(dbSlotToKey(contractAddress, slot));
28
44
  if (!dataBuffer) {
@@ -36,10 +52,26 @@ export class CapsuleDataProvider {
36
52
  return capsule;
37
53
  }
38
54
 
55
+ /**
56
+ * Deletes data in the per-contract non-volatile database. Does nothing if no data was present.
57
+ * @param contractAddress - The contract address under which the data is scoped.
58
+ * @param slot - The slot in the database to delete.
59
+ */
39
60
  async deleteCapsule(contractAddress: AztecAddress, slot: Fr): Promise<void> {
40
61
  await this.#capsules.delete(dbSlotToKey(contractAddress, slot));
41
62
  }
42
63
 
64
+ /**
65
+ * Copies a number of contiguous entries in the per-contract non-volatile database. This allows for efficient data
66
+ * structures by avoiding repeated calls to `loadCapsule` and `storeCapsule`.
67
+ * Supports overlapping source and destination regions (which will result in the overlapped source values being
68
+ * overwritten). All copied slots must exist in the database (i.e. have been stored and not deleted)
69
+ *
70
+ * @param contractAddress - The contract address under which the data is scoped.
71
+ * @param srcSlot - The first slot to copy from.
72
+ * @param dstSlot - The first slot to copy to.
73
+ * @param numEntries - The number of entries to copy.
74
+ */
43
75
  copyCapsule(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise<void> {
44
76
  return this.#store.transactionAsync(async () => {
45
77
  // In order to support overlapping source and destination regions, we need to check the relative positions of source
@@ -178,6 +178,21 @@ export class ContractDataProvider {
178
178
  return fnArtifact && { ...fnArtifact, contractName: artifact.name };
179
179
  }
180
180
 
181
+ public async getFunctionArtifactWithDebugMetadata(
182
+ contractAddress: AztecAddress,
183
+ selector: FunctionSelector,
184
+ ): Promise<FunctionArtifactWithContractName> {
185
+ const artifact = await this.getFunctionArtifact(contractAddress, selector);
186
+ if (!artifact) {
187
+ throw new Error(`Function artifact not found for contract ${contractAddress} and selector ${selector}.`);
188
+ }
189
+ const debug = await this.getFunctionDebugMetadata(contractAddress, selector);
190
+ return {
191
+ ...artifact,
192
+ debug,
193
+ };
194
+ }
195
+
181
196
  public async getPublicFunctionArtifact(
182
197
  contractAddress: AztecAddress,
183
198
  ): Promise<FunctionArtifactWithContractName | undefined> {
@@ -3,7 +3,7 @@ import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
3
3
  import type { DirectionalAppTaggingSecret, PreTag } from '@aztec/stdlib/logs';
4
4
  import { TxHash } from '@aztec/stdlib/tx';
5
5
 
6
- import { WINDOW_LEN as SENDER_TAGGING_INDEXES_SYNC_WINDOW_LEN } from '../../tagging/sync/sync_sender_tagging_indexes.js';
6
+ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/sync/sync_sender_tagging_indexes.js';
7
7
 
8
8
  /**
9
9
  * Data provider of tagging data used when syncing the sender tagging indexes. The recipient counterpart of this class
@@ -68,10 +68,10 @@ export class SenderTaggingDataProvider {
68
68
  // First we check that for any secret the highest used index in tx is not further than window length from
69
69
  // the highest finalized index.
70
70
  const finalizedIndex = (await this.getLastFinalizedIndex(secret)) ?? 0;
71
- if (index > finalizedIndex + SENDER_TAGGING_INDEXES_SYNC_WINDOW_LEN) {
71
+ if (index > finalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) {
72
72
  throw new Error(
73
73
  `Highest used index ${index} is further than window length from the highest finalized index ${finalizedIndex}.
74
- Tagging window length ${SENDER_TAGGING_INDEXES_SYNC_WINDOW_LEN} is configured too low. Contact the Aztec team
74
+ Tagging window length ${UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN} is configured too low. Contact the Aztec team
75
75
  to increase it!`,
76
76
  );
77
77
  }
@@ -1,6 +1,4 @@
1
- export * from './tag.js';
2
1
  export * from './constants.js';
3
- export * from './siloed_tag.js';
4
2
  export * from './utils.js';
5
- export { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs';
3
+ export { DirectionalAppTaggingSecret, Tag, SiloedTag } from '@aztec/stdlib/logs';
6
4
  export { type PreTag } from '@aztec/stdlib/logs';
@@ -0,0 +1,129 @@
1
+ import type { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
3
+ import type { AztecNode } from '@aztec/stdlib/interfaces/client';
4
+ import type { DirectionalAppTaggingSecret, TxScopedL2Log } from '@aztec/stdlib/logs';
5
+
6
+ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../sync/sync_sender_tagging_indexes.js';
7
+ import type { NewRecipientTaggingDataProvider } from './new_recipient_tagging_data_provider.js';
8
+ import { findHighestIndexes } from './utils/find_highest_indexes.js';
9
+ import { loadLogsForRange } from './utils/load_logs_for_range.js';
10
+
11
+ /**
12
+ * Loads private logs for `app` and sender-recipient pair defined by `secret` and updates the highest aged and
13
+ * finalized indexes in the db. At most load logs from blocks up to and including `anchorBlockNumber`.
14
+ *
15
+ * @dev This function can be safely executed "in parallel" for other sender-recipient pairs because the data in
16
+ * in the tagging data provider is indexed by the secret and hence completely disjoint.
17
+ */
18
+ export async function loadPrivateLogsForSenderRecipientPair(
19
+ secret: DirectionalAppTaggingSecret,
20
+ app: AztecAddress,
21
+ aztecNode: AztecNode,
22
+ taggingDataProvider: NewRecipientTaggingDataProvider,
23
+ anchorBlockNumber: BlockNumber,
24
+ ): Promise<TxScopedL2Log[]> {
25
+ // # Explanation of how the algorithm works
26
+ // When we perform the sync we will look at logs that correspond to the tagging index range
27
+ // (highestAgedIndex, highestFinalizedIndex + WINDOW_LEN]
28
+ //
29
+ // highestAgedIndex is the highest index that was used in a tx that is included in a block at least
30
+ // `MAX_INCLUDE_BY_TIMESTAMP_DURATION` seconds ago.
31
+ // highestFinalizedIndex is the highest index that was used in a tx that is included in a finalized block.
32
+ //
33
+ // "(" denotes an open end of the range - the index is not included in the range.
34
+ // "]" denotes a closed end of the range - the index is included in the range.
35
+ //
36
+ // ## Explanation of highestAgedIndex
37
+ //
38
+ // highestAgedIndex is chosen such that for all tagging indexes `i <= highestAgedIndex` we know that no new logs can
39
+ // ever appear.
40
+ //
41
+ // This relies on the "maximum inclusion timestamp" rule enforced by the kernel and rollup circuits:
42
+ // - a transaction's maximum inclusion timestamp is at most `MAX_INCLUDE_BY_TIMESTAMP_DURATION` seconds after
43
+ // the timestamp of its anchor block; and
44
+ // - a rollup only includes transactions whose inclusion timestamp is >= the L2 block's timestamp.
45
+ //
46
+ // Suppose some device used index `I` in a transaction anchored to block `B_N` at time `N`, and that block is now at
47
+ // least `MAX_INCLUDE_BY_TIMESTAMP_DURATION` seconds in the past. Then there is no possibility of any *other* device
48
+ // trying to use an index <= `I` while anchoring to a *newer* block than `B_N` because if we were anchoring to
49
+ // a newer block than `B_N` then we would already have seen the log with index `I` and hence the device would have
50
+ // chosen a larger index.
51
+ // If that *other* device would anchor to a block older than `B_N` then that tx could never be included in a block
52
+ // because it would already have been expired.
53
+ //
54
+ // Therefore, once we see that index `I` has been used in a block that is at least `MAX_INCLUDE_BY_TIMESTAMP_DURATION`
55
+ // seconds old, we can safely stop syncing logs for all indexes <= `I` and set highestAgedIndex = `I`.
56
+ //
57
+ // ## Explanation of the upper bound `highestFinalizedIndex + WINDOW_LEN`
58
+ //
59
+ // When a sender chooses a tagging index, they will select an index that is at most `WINDOW_LEN` greater than
60
+ // the highest finalized index. If that index was already used, they will throw an error. For this reason we
61
+ // don't have to look further than `highestFinalizedIndex + WINDOW_LEN`.
62
+
63
+ let finalizedBlockNumber: number, currentTimestamp: bigint;
64
+ {
65
+ const [l2Tips, latestBlockHeader] = await Promise.all([aztecNode.getL2Tips(), aztecNode.getBlockHeader('latest')]);
66
+
67
+ if (!latestBlockHeader) {
68
+ throw new Error('Node failed to return latest block header when syncing logs');
69
+ }
70
+
71
+ [finalizedBlockNumber, currentTimestamp] = [l2Tips.finalized.number, latestBlockHeader.globalVariables.timestamp];
72
+ }
73
+
74
+ let start: number, end: number;
75
+ {
76
+ const currentHighestAgedIndex = await taggingDataProvider.getHighestAgedIndex(secret);
77
+ const currentHighestFinalizedIndex = await taggingDataProvider.getHighestFinalizedIndex(secret);
78
+
79
+ // We don't want to include the highest aged index so we start from `currentHighestAgedIndex + 1` (or 0 if not set)
80
+ start = currentHighestAgedIndex === undefined ? 0 : currentHighestAgedIndex + 1;
81
+
82
+ // The highest index a sender can choose is "highest finalized index + window length" but given that
83
+ // `loadLogsForRange` expects an exclusive `end` we add 1.
84
+ end = (currentHighestFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
85
+ }
86
+
87
+ const logs: TxScopedL2Log[] = [];
88
+
89
+ while (true) {
90
+ // Get private logs with their block timestamps and corresponding tagging indexes
91
+ const privateLogsWithIndexes = await loadLogsForRange(secret, app, aztecNode, start, end, anchorBlockNumber);
92
+
93
+ if (privateLogsWithIndexes.length === 0) {
94
+ break;
95
+ }
96
+
97
+ logs.push(...privateLogsWithIndexes.map(({ log }) => log));
98
+
99
+ const { highestAgedIndex, highestFinalizedIndex } = findHighestIndexes(
100
+ privateLogsWithIndexes,
101
+ currentTimestamp,
102
+ finalizedBlockNumber,
103
+ );
104
+
105
+ // Store updates in data provider and update local variables
106
+ if (highestAgedIndex !== undefined) {
107
+ await taggingDataProvider.updateHighestAgedIndex(secret, highestAgedIndex);
108
+ }
109
+
110
+ if (highestFinalizedIndex === undefined) {
111
+ // We have not found a new highest finalized index, so there is no need to move the window forward.
112
+ break;
113
+ }
114
+
115
+ if (highestAgedIndex !== undefined && highestAgedIndex > highestFinalizedIndex) {
116
+ // This is just a sanity check as this should never happen.
117
+ throw new Error('Highest aged index lower than highest finalized index invariant violated');
118
+ }
119
+
120
+ await taggingDataProvider.updateHighestFinalizedIndex(secret, highestFinalizedIndex);
121
+
122
+ // For the next iteration we want to look only at indexes for which we have not attempted to load logs yet while
123
+ // ensuring that we do not look further than WINDOW_LEN ahead of the highest finalized index.
124
+ start = end;
125
+ end = highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1; // `end` is exclusive so we add 1.
126
+ }
127
+
128
+ return logs;
129
+ }
@@ -0,0 +1,53 @@
1
+ import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
2
+ import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs';
3
+
4
+ /**
5
+ * Data provider of tagging data used when syncing the logs as a recipient. The sender counterpart of this class
6
+ * is called SenderTaggingDataProvider. We have the providers separate for the sender and recipient because
7
+ * the algorithms are completely disjoint and there is not data reuse between the two.
8
+ *
9
+ * @dev Chain reorgs do not need to be handled here because both the finalized and aged indexes refer to finalized
10
+ * blocks, which by definition cannot be affected by reorgs.
11
+ *
12
+ * TODO(benesjan): Rename as to RecipientTaggingDataProvider and relocate once the old tagging sync is purged.
13
+ */
14
+ export class NewRecipientTaggingDataProvider {
15
+ #store: AztecAsyncKVStore;
16
+
17
+ #highestAgedIndex: AztecAsyncMap<string, number>;
18
+ #highestFinalizedIndex: AztecAsyncMap<string, number>;
19
+
20
+ constructor(store: AztecAsyncKVStore) {
21
+ this.#store = store;
22
+
23
+ this.#highestAgedIndex = this.#store.openMap('highest_aged_index');
24
+ this.#highestFinalizedIndex = this.#store.openMap('highest_finalized_index');
25
+ }
26
+
27
+ getHighestAgedIndex(secret: DirectionalAppTaggingSecret): Promise<number | undefined> {
28
+ return this.#highestAgedIndex.getAsync(secret.toString());
29
+ }
30
+
31
+ async updateHighestAgedIndex(secret: DirectionalAppTaggingSecret, index: number): Promise<void> {
32
+ const currentIndex = await this.#highestAgedIndex.getAsync(secret.toString());
33
+ if (currentIndex !== undefined && index <= currentIndex) {
34
+ // Log sync should never set a lower highest aged index.
35
+ throw new Error(`New highest aged index (${index}) must be higher than the current one (${currentIndex})`);
36
+ }
37
+ await this.#highestAgedIndex.set(secret.toString(), index);
38
+ }
39
+
40
+ getHighestFinalizedIndex(secret: DirectionalAppTaggingSecret): Promise<number | undefined> {
41
+ return this.#highestFinalizedIndex.getAsync(secret.toString());
42
+ }
43
+
44
+ async updateHighestFinalizedIndex(secret: DirectionalAppTaggingSecret, index: number): Promise<void> {
45
+ const currentIndex = await this.#highestFinalizedIndex.getAsync(secret.toString());
46
+ if (currentIndex !== undefined && index < currentIndex) {
47
+ // Log sync should never set a lower highest finalized index but it can happen that it would try to set the same
48
+ // one because we are loading logs from highest aged index + 1 and not from the highest finalized index.
49
+ throw new Error(`New highest finalized index (${index}) must be higher than the current one (${currentIndex})`);
50
+ }
51
+ await this.#highestFinalizedIndex.set(secret.toString(), index);
52
+ }
53
+ }
@@ -0,0 +1,34 @@
1
+ import { MAX_INCLUDE_BY_TIMESTAMP_DURATION } from '@aztec/constants';
2
+ import type { TxScopedL2Log } from '@aztec/stdlib/logs';
3
+
4
+ /**
5
+ * Finds the highest aged and the highest finalized tagging indexes.
6
+ */
7
+ export function findHighestIndexes(
8
+ privateLogsWithIndexes: Array<{ log: TxScopedL2Log; taggingIndex: number }>,
9
+ currentTimestamp: bigint,
10
+ finalizedBlockNumber: number,
11
+ ): { highestAgedIndex: number | undefined; highestFinalizedIndex: number | undefined } {
12
+ let highestAgedIndex = undefined;
13
+ let highestFinalizedIndex = undefined;
14
+
15
+ for (const { log, taggingIndex } of privateLogsWithIndexes) {
16
+ const ageInSeconds = currentTimestamp - log.blockTimestamp;
17
+
18
+ if (
19
+ ageInSeconds >= BigInt(MAX_INCLUDE_BY_TIMESTAMP_DURATION) &&
20
+ (highestAgedIndex === undefined || taggingIndex > highestAgedIndex)
21
+ ) {
22
+ highestAgedIndex = taggingIndex;
23
+ }
24
+
25
+ if (
26
+ log.blockNumber <= finalizedBlockNumber &&
27
+ (highestFinalizedIndex === undefined || taggingIndex > highestFinalizedIndex)
28
+ ) {
29
+ highestFinalizedIndex = taggingIndex;
30
+ }
31
+ }
32
+
33
+ return { highestAgedIndex, highestFinalizedIndex };
34
+ }
@@ -0,0 +1,43 @@
1
+ import type { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
3
+ import type { AztecNode } from '@aztec/stdlib/interfaces/client';
4
+ import type { DirectionalAppTaggingSecret, PreTag, TxScopedL2Log } from '@aztec/stdlib/logs';
5
+ import { SiloedTag, Tag } from '@aztec/stdlib/logs';
6
+
7
+ /**
8
+ * Gets private logs with their corresponding block timestamps and tagging indexes for the given index range, `app` and
9
+ * `secret`. At most load logs from blocks up to and including `anchorBlockNumber`. `start` is inclusive and `end` is
10
+ * exclusive.
11
+ */
12
+ export async function loadLogsForRange(
13
+ secret: DirectionalAppTaggingSecret,
14
+ app: AztecAddress,
15
+ aztecNode: AztecNode,
16
+ start: number,
17
+ end: number,
18
+ anchorBlockNumber: BlockNumber,
19
+ ): Promise<Array<{ log: TxScopedL2Log; taggingIndex: number }>> {
20
+ // Derive tags for the window
21
+ const preTags: PreTag[] = Array(end - start)
22
+ .fill(0)
23
+ .map((_, i) => ({ secret, index: start + i }));
24
+ const siloedTags = await Promise.all(preTags.map(preTag => Tag.compute(preTag))).then(tags =>
25
+ Promise.all(tags.map(tag => SiloedTag.compute(tag, app))),
26
+ );
27
+
28
+ const logs = await aztecNode.getPrivateLogsByTags(siloedTags);
29
+
30
+ // Pair logs with their corresponding tagging indexes
31
+ const logsWithIndexes: Array<{ log: TxScopedL2Log; taggingIndex: number }> = [];
32
+ for (let i = 0; i < logs.length; i++) {
33
+ const logsForTag = logs[i];
34
+ const taggingIndex = preTags[i].index;
35
+ for (const log of logsForTag) {
36
+ if (log.blockNumber <= anchorBlockNumber) {
37
+ logsWithIndexes.push({ log, taggingIndex });
38
+ }
39
+ }
40
+ }
41
+
42
+ return logsWithIndexes;
43
+ }
@@ -21,7 +21,7 @@ import { loadAndStoreNewTaggingIndexes } from './utils/load_and_store_new_taggin
21
21
  // - This value is below MAX_RPC_LEN (100) which is the limit for array parameters in the JSON RPC schema for
22
22
  // `getLogsByTags`. Any test that would perform sync over JSON RPC (not by having access to the Aztec node instance
23
23
  // directly) would error out if that maximum was hit (docs_examples.test.ts is an example of this).
24
- export const WINDOW_LEN = 95;
24
+ export const UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = 95;
25
25
 
26
26
  /**
27
27
  * Syncs tagging indexes. This function needs to be called whenever a private log is being sent.
@@ -64,7 +64,7 @@ export async function syncSenderTaggingIndexes(
64
64
  const finalizedIndex = await taggingDataProvider.getLastFinalizedIndex(secret);
65
65
 
66
66
  let start = finalizedIndex === undefined ? 0 : finalizedIndex + 1;
67
- let end = start + WINDOW_LEN;
67
+ let end = start + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
68
68
 
69
69
  let previousFinalizedIndex = finalizedIndex;
70
70
  let newFinalizedIndex = undefined;
@@ -101,7 +101,7 @@ export async function syncSenderTaggingIndexes(
101
101
 
102
102
  const previousEnd = end;
103
103
  // Add 1 because `end` is exclusive and the known finalized index is not included in the window.
104
- end = newFinalizedIndex! + WINDOW_LEN + 1;
104
+ end = newFinalizedIndex! + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
105
105
  start = previousEnd;
106
106
  previousFinalizedIndex = newFinalizedIndex;
107
107
  } else {
@@ -1,11 +1,10 @@
1
1
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
2
2
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
3
3
  import type { DirectionalAppTaggingSecret, PreTag } from '@aztec/stdlib/logs';
4
+ import { SiloedTag, Tag } from '@aztec/stdlib/logs';
4
5
  import { TxHash } from '@aztec/stdlib/tx';
5
6
 
6
7
  import type { SenderTaggingDataProvider } from '../../../storage/tagging_data_provider/sender_tagging_data_provider.js';
7
- import { SiloedTag } from '../../siloed_tag.js';
8
- import { Tag } from '../../tag.js';
9
8
 
10
9
  /**
11
10
  * Loads tagging indexes from the Aztec node and stores them in the tagging data provider.
@@ -48,9 +47,8 @@ export async function loadAndStoreNewTaggingIndexes(
48
47
  // Returns txs that used the given tags. A tag might have been used in multiple txs and for this reason we return
49
48
  // an array for each tag.
50
49
  async function getTxsContainingTags(tags: SiloedTag[], aztecNode: AztecNode): Promise<TxHash[][]> {
51
- const tagsAsFr = tags.map(tag => tag.value);
52
- const allLogs = await aztecNode.getLogsByTags(tagsAsFr);
53
- return allLogs.map(logs => logs.filter(log => !log.isFromPublic).map(log => log.txHash));
50
+ const allLogs = await aztecNode.getPrivateLogsByTags(tags);
51
+ return allLogs.map(logs => logs.map(log => log.txHash));
54
52
  }
55
53
 
56
54
  // Returns a map of txHash to the highest index for that txHash.
@@ -0,0 +1,112 @@
1
+ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants';
2
+ import type { Fr } from '@aztec/foundation/curves/bn254';
3
+ import type { SiblingPath } from '@aztec/foundation/trees';
4
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ import type { BlockParameter } from '@aztec/stdlib/block';
6
+ import type { AztecNode } from '@aztec/stdlib/interfaces/server';
7
+ import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
8
+ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
9
+
10
+ import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js';
11
+
12
+ export class TreeMembershipService {
13
+ constructor(
14
+ private readonly aztecNode: AztecNode,
15
+ private readonly anchorBlockDataProvider: AnchorBlockDataProvider,
16
+ ) {}
17
+
18
+ /**
19
+ * Gets the index of a nullifier in the nullifier tree.
20
+ * @returns - The index of the nullifier. Undefined if it does not exist in the tree.
21
+ */
22
+ public getNullifierIndex(nullifier: Fr) {
23
+ return this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier);
24
+ }
25
+
26
+ /**
27
+ * Fetches the index and sibling path of a leaf at a given block from a given tree.
28
+ * @param blockNumber - The block number at which to get the membership witness.
29
+ * @param treeId - Id of the tree to get the sibling path from.
30
+ * @param leafValue - The leaf value
31
+ * @returns The index and sibling path concatenated [index, sibling_path]
32
+ */
33
+ public async getMembershipWitness(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise<Fr[]> {
34
+ const witness = await this.#tryGetMembershipWitness(blockNumber, treeId, leafValue);
35
+ if (!witness) {
36
+ throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`);
37
+ }
38
+ return witness;
39
+ }
40
+
41
+ /**
42
+ * Returns a low nullifier membership witness for a given nullifier at a given block.
43
+ * @param blockNumber - The block number at which to get the index.
44
+ * @param nullifier - Nullifier we try to find the low nullifier witness for.
45
+ * @returns The low nullifier membership witness (if found).
46
+ * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
47
+ * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
48
+ * we are trying to prove non-inclusion for.
49
+ */
50
+ public async getLowNullifierMembershipWitness(
51
+ blockNumber: BlockParameter,
52
+ nullifier: Fr,
53
+ ): Promise<NullifierMembershipWitness | undefined> {
54
+ const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber();
55
+ if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) {
56
+ throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`);
57
+ }
58
+ return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier);
59
+ }
60
+
61
+ /**
62
+ * Returns a witness for a given slot of the public data tree at a given block.
63
+ * @param blockNumber - The block number at which to get the witness.
64
+ * @param leafSlot - The slot of the public data in the public data tree.
65
+ */
66
+ public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
67
+ const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber();
68
+ if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) {
69
+ throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`);
70
+ }
71
+ return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot);
72
+ }
73
+
74
+ /**
75
+ * Looks for the L1 to L2 membership witness of a message at the Aztec node, given its hash.
76
+ * @param contractAddress - Address of a contract by which the message was emitted.
77
+ * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages.
78
+ * The message nullifier is computed locally, so the secret is not sent to the node.
79
+ * @returns The l1 to l2 membership witness (index of message in the tree and sibling path).
80
+ */
81
+ public getL1ToL2MembershipWitness(
82
+ contractAddress: AztecAddress,
83
+ messageHash: Fr,
84
+ secret: Fr,
85
+ ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>]> {
86
+ return getNonNullifiedL1ToL2MessageWitness(this.aztecNode, contractAddress, messageHash, secret);
87
+ }
88
+
89
+ async #tryGetMembershipWitness(
90
+ blockNumber: BlockParameter,
91
+ treeId: MerkleTreeId,
92
+ value: Fr,
93
+ ): Promise<Fr[] | undefined> {
94
+ switch (treeId) {
95
+ case MerkleTreeId.NULLIFIER_TREE:
96
+ return (await this.aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields();
97
+ case MerkleTreeId.NOTE_HASH_TREE:
98
+ return (await this.aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields();
99
+ case MerkleTreeId.PUBLIC_DATA_TREE:
100
+ return (await this.aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields();
101
+ case MerkleTreeId.ARCHIVE:
102
+ return (await this.aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields();
103
+ default:
104
+ throw new Error('Not implemented');
105
+ }
106
+ }
107
+
108
+ async #findLeafIndex(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise<bigint | undefined> {
109
+ const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]);
110
+ return leafIndex?.data;
111
+ }
112
+ }