@aztec/pxe 0.0.1-commit.cb6bed7c2 → 0.0.1-commit.cbf2c2d5d

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 (87) hide show
  1. package/dest/contract_function_simulator/contract_function_simulator.d.ts +9 -3
  2. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  3. package/dest/contract_function_simulator/contract_function_simulator.js +25 -3
  4. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts +5 -5
  5. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts.map +1 -1
  6. package/dest/contract_function_simulator/execution_tagging_index_cache.js +17 -9
  7. package/dest/contract_function_simulator/index.d.ts +2 -1
  8. package/dest/contract_function_simulator/index.d.ts.map +1 -1
  9. package/dest/contract_function_simulator/index.js +1 -0
  10. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts +2 -3
  11. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts.map +1 -1
  12. package/dest/contract_function_simulator/noir-structs/event_validation_request.js +5 -4
  13. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts +1 -1
  14. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts.map +1 -1
  15. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.js +1 -3
  16. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts +16 -0
  17. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts.map +1 -0
  18. package/dest/contract_function_simulator/noir-structs/message_tx_context.js +57 -0
  19. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts +2 -4
  20. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts.map +1 -1
  21. package/dest/contract_function_simulator/noir-structs/note_validation_request.js +3 -5
  22. package/dest/contract_function_simulator/oracle/interfaces.d.ts +8 -7
  23. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  24. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts +9 -0
  25. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts.map +1 -0
  26. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.js +42 -0
  27. package/dest/contract_function_simulator/oracle/oracle.d.ts +8 -7
  28. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  29. package/dest/contract_function_simulator/oracle/oracle.js +44 -18
  30. package/dest/contract_function_simulator/oracle/private_execution.js +4 -2
  31. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +8 -33
  32. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  33. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +7 -39
  34. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +19 -3
  35. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  36. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +60 -3
  37. package/dest/contract_sync/contract_sync_service.d.ts +5 -3
  38. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  39. package/dest/contract_sync/contract_sync_service.js +47 -30
  40. package/dest/messages/message_context_service.d.ts +17 -0
  41. package/dest/messages/message_context_service.d.ts.map +1 -0
  42. package/dest/messages/message_context_service.js +36 -0
  43. package/dest/oracle_version.d.ts +2 -2
  44. package/dest/oracle_version.js +2 -2
  45. package/dest/pxe.d.ts +6 -3
  46. package/dest/pxe.d.ts.map +1 -1
  47. package/dest/pxe.js +33 -19
  48. package/dest/storage/metadata.d.ts +1 -1
  49. package/dest/storage/metadata.js +1 -1
  50. package/dest/storage/tagging_store/sender_tagging_store.d.ts +26 -25
  51. package/dest/storage/tagging_store/sender_tagging_store.d.ts.map +1 -1
  52. package/dest/storage/tagging_store/sender_tagging_store.js +141 -115
  53. package/dest/tagging/index.d.ts +2 -2
  54. package/dest/tagging/index.d.ts.map +1 -1
  55. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts +1 -1
  56. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts.map +1 -1
  57. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.js +10 -1
  58. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts +4 -3
  59. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts.map +1 -1
  60. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.js +20 -10
  61. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts +2 -1
  62. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
  63. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.js +24 -11
  64. package/package.json +16 -16
  65. package/src/contract_function_simulator/contract_function_simulator.ts +36 -4
  66. package/src/contract_function_simulator/execution_tagging_index_cache.ts +16 -11
  67. package/src/contract_function_simulator/index.ts +1 -0
  68. package/src/contract_function_simulator/noir-structs/event_validation_request.ts +8 -5
  69. package/src/contract_function_simulator/noir-structs/log_retrieval_response.ts +1 -4
  70. package/src/contract_function_simulator/noir-structs/message_tx_context.ts +55 -0
  71. package/src/contract_function_simulator/noir-structs/note_validation_request.ts +3 -6
  72. package/src/contract_function_simulator/oracle/interfaces.ts +12 -15
  73. package/src/contract_function_simulator/oracle/legacy_oracle_mappings.ts +135 -0
  74. package/src/contract_function_simulator/oracle/oracle.ts +53 -43
  75. package/src/contract_function_simulator/oracle/private_execution.ts +3 -3
  76. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +9 -57
  77. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +93 -3
  78. package/src/contract_sync/contract_sync_service.ts +67 -38
  79. package/src/messages/message_context_service.ts +45 -0
  80. package/src/oracle_version.ts +2 -2
  81. package/src/pxe.ts +51 -18
  82. package/src/storage/metadata.ts +1 -1
  83. package/src/storage/tagging_store/sender_tagging_store.ts +182 -135
  84. package/src/tagging/index.ts +1 -1
  85. package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +19 -1
  86. package/src/tagging/sender_sync/utils/get_status_change_of_pending.ts +26 -11
  87. package/src/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts +19 -9
package/dest/pxe.js CHANGED
@@ -20,6 +20,7 @@ import { PXEDebugUtils } from './debug/pxe_debug_utils.js';
20
20
  import { enrichPublicSimulationError, enrichSimulationError } from './error_enriching.js';
21
21
  import { PrivateEventFilterValidator } from './events/private_event_filter_validator.js';
22
22
  import { JobCoordinator } from './job_coordinator/job_coordinator.js';
23
+ import { MessageContextService } from './messages/message_context_service.js';
23
24
  import { PrivateKernelExecutionProver } from './private_kernel/private_kernel_execution_prover.js';
24
25
  import { PrivateKernelOracle } from './private_kernel/private_kernel_oracle.js';
25
26
  import { AddressStore } from './storage/address_store/address_store.js';
@@ -49,6 +50,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
49
50
  addressStore;
50
51
  privateEventStore;
51
52
  contractSyncService;
53
+ messageContextService;
52
54
  simulator;
53
55
  proverEnabled;
54
56
  proofCreator;
@@ -57,7 +59,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
57
59
  jobQueue;
58
60
  jobCoordinator;
59
61
  debug;
60
- constructor(node, db, blockStateSynchronizer, keyStore, contractStore, noteStore, capsuleStore, anchorBlockStore, senderTaggingStore, senderAddressBookStore, recipientTaggingStore, addressStore, privateEventStore, contractSyncService, simulator, proverEnabled, proofCreator, protocolContractsProvider, log, jobQueue, jobCoordinator, debug){
62
+ constructor(node, db, blockStateSynchronizer, keyStore, contractStore, noteStore, capsuleStore, anchorBlockStore, senderTaggingStore, senderAddressBookStore, recipientTaggingStore, addressStore, privateEventStore, contractSyncService, messageContextService, simulator, proverEnabled, proofCreator, protocolContractsProvider, log, jobQueue, jobCoordinator, debug){
61
63
  this.node = node;
62
64
  this.db = db;
63
65
  this.blockStateSynchronizer = blockStateSynchronizer;
@@ -72,6 +74,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
72
74
  this.addressStore = addressStore;
73
75
  this.privateEventStore = privateEventStore;
74
76
  this.contractSyncService = contractSyncService;
77
+ this.messageContextService = messageContextService;
75
78
  this.simulator = simulator;
76
79
  this.proverEnabled = proverEnabled;
77
80
  this.proofCreator = proofCreator;
@@ -105,6 +108,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
105
108
  const keyStore = new KeyStore(store);
106
109
  const tipsStore = new L2TipsKVStore(store, 'pxe');
107
110
  const contractSyncService = new ContractSyncService(node, contractStore, noteStore, createLogger('pxe:contract_sync', bindings));
111
+ const messageContextService = new MessageContextService(node);
108
112
  const synchronizer = new BlockSynchronizer(node, store, anchorBlockStore, noteStore, privateEventStore, tipsStore, contractSyncService, config, bindings);
109
113
  const jobCoordinator = new JobCoordinator(store, bindings);
110
114
  jobCoordinator.registerStores([
@@ -117,7 +121,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
117
121
  ]);
118
122
  const debugUtils = new PXEDebugUtils(contractSyncService, noteStore, synchronizer, anchorBlockStore);
119
123
  const jobQueue = new SerialQueue();
120
- const pxe = new PXE(node, store, synchronizer, keyStore, contractStore, noteStore, capsuleStore, anchorBlockStore, senderTaggingStore, senderAddressBookStore, recipientTaggingStore, addressStore, privateEventStore, contractSyncService, simulator, proverEnabled, proofCreator, protocolContractsProvider, log, jobQueue, jobCoordinator, debugUtils);
124
+ const pxe = new PXE(node, store, synchronizer, keyStore, contractStore, noteStore, capsuleStore, anchorBlockStore, senderTaggingStore, senderAddressBookStore, recipientTaggingStore, addressStore, privateEventStore, contractSyncService, messageContextService, simulator, proverEnabled, proofCreator, protocolContractsProvider, log, jobQueue, jobCoordinator, debugUtils);
121
125
  debugUtils.setPXEHelpers(pxe.#putInJobQueue.bind(pxe), pxe.#getSimulatorForTx.bind(pxe), pxe.#executeUtility.bind(pxe));
122
126
  pxe.jobQueue.start();
123
127
  await pxe.#registerProtocolContracts();
@@ -139,7 +143,8 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
139
143
  capsuleStore: this.capsuleStore,
140
144
  privateEventStore: this.privateEventStore,
141
145
  simulator: this.simulator,
142
- contractSyncService: this.contractSyncService
146
+ contractSyncService: this.contractSyncService,
147
+ messageContextService: this.messageContextService
143
148
  });
144
149
  }
145
150
  #contextualizeError(err, ...context) {
@@ -225,7 +230,11 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
225
230
  */ async #executeUtility(contractFunctionSimulator, call, authWitnesses, scopes, jobId) {
226
231
  try {
227
232
  const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
228
- return contractFunctionSimulator.runUtility(call, authWitnesses ?? [], anchorBlockHeader, scopes, jobId);
233
+ const { result, offchainEffects } = await contractFunctionSimulator.runUtility(call, authWitnesses ?? [], anchorBlockHeader, scopes, jobId);
234
+ return {
235
+ result,
236
+ offchainEffects
237
+ };
229
238
  } catch (err) {
230
239
  if (err instanceof SimulationError) {
231
240
  await enrichSimulationError(err, this.contractStore, this.log);
@@ -337,6 +346,9 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
337
346
  const wasAdded = await this.senderAddressBookStore.addSender(sender);
338
347
  if (wasAdded) {
339
348
  this.log.info(`Added sender:\n ${sender.toString()}`);
349
+ // Wipe the entire sync cache: the new sender's tagged logs could contain notes/events for any contract, so
350
+ // all contracts must re-sync to discover them.
351
+ this.contractSyncService.wipe();
340
352
  } else {
341
353
  this.log.info(`Sender:\n "${sender.toString()}"\n already registered.`);
342
354
  }
@@ -500,16 +512,16 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
500
512
  // transaction before this one is included in a block from this PXE, and that transaction contains a log with
501
513
  // a tag derived from the same secret, we would reuse the tag and the transactions would be linked. Hence
502
514
  // storing the tags here prevents linkage of txs sent from the same PXE.
503
- const preTagsUsedInTheTx = privateExecutionResult.entrypoint.preTags;
504
- if (preTagsUsedInTheTx.length > 0) {
515
+ const taggingIndexRangesUsedInTheTx = privateExecutionResult.entrypoint.taggingIndexRanges;
516
+ if (taggingIndexRangesUsedInTheTx.length > 0) {
505
517
  // TODO(benesjan): The following is an expensive operation. Figure out a way to avoid it.
506
518
  const txHash = (await txProvingResult.toTx()).txHash;
507
- await this.senderTaggingStore.storePendingIndexes(preTagsUsedInTheTx, txHash, jobId);
508
- this.log.debug(`Stored used pre-tags as sender for the tx`, {
509
- preTagsUsedInTheTx
519
+ await this.senderTaggingStore.storePendingIndexes(taggingIndexRangesUsedInTheTx, txHash, jobId);
520
+ this.log.debug(`Stored used tagging index ranges as sender for the tx`, {
521
+ taggingIndexRangesUsedInTheTx
510
522
  });
511
523
  } else {
512
- this.log.debug(`No pre-tags used in the tx`);
524
+ this.log.debug(`No tagging index ranges used in the tx`);
513
525
  }
514
526
  return txProvingResult;
515
527
  } catch (err) {
@@ -591,7 +603,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
591
603
  * Also throws if simulatePublic is true and public simulation reverts.
592
604
  *
593
605
  * TODO(#7456) Prevent msgSender being defined here for the first call
594
- */ simulateTx(txRequest, { simulatePublic, skipTxValidation = false, skipFeeEnforcement = false, overrides, scopes }) {
606
+ */ simulateTx(txRequest, { simulatePublic, skipTxValidation = false, skipFeeEnforcement = false, skipKernels = true, overrides, scopes }) {
595
607
  // We disable concurrent simulations since those might execute oracles which read and write to the PXE stores (e.g.
596
608
  // to the capsules), and we need to prevent concurrent runs from interfering with one another (e.g. attempting to
597
609
  // delete the same read value, or reading values that another simulation is currently modifying).
@@ -610,16 +622,16 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
610
622
  const syncTimer = new Timer();
611
623
  await this.blockStateSynchronizer.sync();
612
624
  const syncTime = syncTimer.ms();
613
- const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
614
- // Temporary: in case there are overrides, we have to skip the kernels or validations
615
- // will fail. Consider handing control to the user/wallet on whether they want to run them
616
- // or not.
617
625
  const overriddenContracts = overrides?.contracts ? new Set(Object.keys(overrides.contracts)) : undefined;
618
626
  const hasOverriddenContracts = overriddenContracts !== undefined && overriddenContracts.size > 0;
619
- const skipKernels = hasOverriddenContracts;
620
- // Set overridden contracts on the sync service so it knows to skip syncing them
627
+ if (hasOverriddenContracts && !skipKernels) {
628
+ throw new Error('Simulating with overridden contracts is not compatible with kernel execution. Please set skipKernels to true when simulating with overridden contracts.');
629
+ }
630
+ const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
621
631
  if (hasOverriddenContracts) {
622
- this.contractSyncService.setOverriddenContracts(jobId, overriddenContracts);
632
+ // Overridden contracts don't have a sync function, so calling sync on them would fail.
633
+ // We exclude them so the sync service skips them entirely.
634
+ this.contractSyncService.setExcludedFromSync(jobId, overriddenContracts);
623
635
  }
624
636
  // Execution of private functions only; no proving, and no kernel logic.
625
637
  const privateExecutionResult = await this.#executePrivate(contractFunctionSimulator, txRequest, scopes, jobId);
@@ -713,7 +725,7 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
713
725
  const contractFunctionSimulator = this.#getSimulatorForTx();
714
726
  const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
715
727
  await this.contractSyncService.ensureContractSynced(call.to, call.selector, (privateSyncCall, execScopes)=>this.#executeUtility(contractFunctionSimulator, privateSyncCall, [], execScopes, jobId), anchorBlockHeader, jobId, scopes);
716
- const executionResult = await this.#executeUtility(contractFunctionSimulator, call, authwits ?? [], scopes, jobId);
728
+ const { result: executionResult, offchainEffects } = await this.#executeUtility(contractFunctionSimulator, call, authwits ?? [], scopes, jobId);
717
729
  const functionTime = functionTimer.ms();
718
730
  const totalTime = totalTimer.ms();
719
731
  const perFunction = [
@@ -731,6 +743,8 @@ import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store
731
743
  const simulationStats = contractFunctionSimulator.getStats();
732
744
  return {
733
745
  result: executionResult,
746
+ offchainEffects,
747
+ anchorBlockTimestamp: anchorBlockHeader.globalVariables.timestamp,
734
748
  stats: {
735
749
  timings,
736
750
  nodeRPCCalls: simulationStats.nodeRPCCalls
@@ -1,2 +1,2 @@
1
- export declare const PXE_DATA_SCHEMA_VERSION = 3;
1
+ export declare const PXE_DATA_SCHEMA_VERSION = 4;
2
2
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0YWRhdGEuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdG9yYWdlL21ldGFkYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGVBQU8sTUFBTSx1QkFBdUIsSUFBSSxDQUFDIn0=
@@ -1 +1 @@
1
- export const PXE_DATA_SCHEMA_VERSION = 3;
1
+ export const PXE_DATA_SCHEMA_VERSION = 4;
@@ -1,6 +1,6 @@
1
1
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
2
- import type { ExtendedDirectionalAppTaggingSecret, PreTag } from '@aztec/stdlib/logs';
3
- import { TxHash } from '@aztec/stdlib/tx';
2
+ import { ExtendedDirectionalAppTaggingSecret, type TaggingIndexRange } from '@aztec/stdlib/logs';
3
+ import { TxEffect, TxHash } from '@aztec/stdlib/tx';
4
4
  import type { StagedStore } from '../../job_coordinator/job_coordinator.js';
5
5
  /**
6
6
  * Data provider of tagging data used when syncing the sender tagging indexes. The recipient counterpart of this class
@@ -19,31 +19,23 @@ export declare class SenderTaggingStore implements StagedStore {
19
19
  commit(jobId: string): Promise<void>;
20
20
  discardStaged(jobId: string): Promise<void>;
21
21
  /**
22
- * Stores pending indexes.
23
- * @remarks Ignores the index if the same preTag + txHash combination already exists in the db with the same index.
24
- * This is expected to happen because whenever we start sync we start from the last finalized index and we can have
25
- * pending indexes already stored from previous syncs.
26
- * @param preTags - The pre-tags containing the directional app tagging secrets and the indexes that are to be
27
- * stored in the db.
28
- * @param txHash - The tx in which the pretags were used in private logs.
22
+ * Stores pending index ranges.
23
+ * @remarks If the same (secret, txHash) pair already exists in the db with an equal range, it's a no-op. This is
24
+ * expected to happen because whenever we start sync we start from the last finalized index and we can have pending
25
+ * ranges already stored from previous syncs. If the ranges differ, it throws an error as that indicates a bug.
26
+ * @param ranges - The tagging index ranges containing the directional app tagging secrets and the index ranges that are
27
+ * to be stored in the db.
28
+ * @param txHash - The tx in which the tagging indexes were used in private logs.
29
29
  * @param jobId - job context for staged writes to this store. See `JobCoordinator` for more details.
30
- * @throws If any two pre-tags contain the same directional app tagging secret. This is enforced because we care
31
- * only about the highest index for a given secret that was used in the tx. Hence this check is a good way to catch
32
- * bugs.
33
- * @throws If the newly stored pending index is further than window length from the highest finalized index for the
34
- * same secret. This is enforced in order to give a guarantee to a recipient that he doesn't need to look further than
35
- * window length ahead of the highest finalized index.
36
- * @throws If a secret + txHash pair already exists in the db with a different index value. It should never happen
37
- * that we would attempt to store a different index for a given secret-txHash pair because we always store just the
38
- * highest index for a given secret-txHash pair. Hence this is a good way to catch bugs.
39
- * @throws If the newly stored pending index is lower than or equal to the last finalized index for the same secret.
40
- * This is enforced because this should never happen if the syncing is done correctly as we look for logs from higher
41
- * indexes than finalized ones.
30
+ * @throws If the highestIndex is further than window length from the highest finalized index for the same secret.
31
+ * @throws If the lowestIndex is lower than or equal to the last finalized index for the same secret.
32
+ * @throws If a different range already exists for the same (secret, txHash) pair.
42
33
  */
43
- storePendingIndexes(preTags: PreTag[], txHash: TxHash, jobId: string): Promise<void>;
34
+ storePendingIndexes(ranges: TaggingIndexRange[], txHash: TxHash, jobId: string): Promise<void>;
44
35
  /**
45
- * Returns the transaction hashes of all pending transactions that contain indexes within a specified range
46
- * for a given directional app tagging secret.
36
+ * Returns the transaction hashes of all pending transactions that contain highest indexes within a specified range
37
+ * for a given directional app tagging secret. We check based on the highest indexes only as that is the relevant
38
+ * information for the caller of this function.
47
39
  * @param secret - The directional app tagging secret to query pending indexes for.
48
40
  * @param startIndex - The lower bound of the index range (inclusive).
49
41
  * @param endIndex - The upper bound of the index range (exclusive).
@@ -73,5 +65,14 @@ export declare class SenderTaggingStore implements StagedStore {
73
65
  * indexes.
74
66
  */
75
67
  finalizePendingIndexes(txHashes: TxHash[], jobId: string): Promise<void>;
68
+ /**
69
+ * Handles finalization of pending indexes for a transaction whose execution was partially reverted.
70
+ * Recomputes the siloed tags for each pending index of the given tx and checks which ones appear in the
71
+ * TxEffect's private logs (i.e., which ones made it onchain). Those that survived are finalized; those that
72
+ * didn't are dropped.
73
+ * @param txEffect - The tx effect of the partially reverted transaction.
74
+ * @param jobId - job context for staged writes to this store. See `JobCoordinator` for more details.
75
+ */
76
+ finalizePendingIndexesOfAPartiallyRevertedTx(txEffect: TxEffect, jobId: string): Promise<void>;
76
77
  }
77
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZGVyX3RhZ2dpbmdfc3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yYWdlL3RhZ2dpbmdfc3RvcmUvc2VuZGVyX3RhZ2dpbmdfc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQWlCLE1BQU0saUJBQWlCLENBQUM7QUFDeEUsT0FBTyxLQUFLLEVBQUUsbUNBQW1DLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDdEYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTFDLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBDQUEwQyxDQUFDO0FBRzVFOzs7O0dBSUc7QUFDSCxxQkFBYSxrQkFBbUIsWUFBVyxXQUFXOztJQUNwRCxRQUFRLENBQUMsU0FBUyxvQkFBb0I7SUE2QnRDLFlBQVksS0FBSyxFQUFFLGlCQUFpQixFQVFuQztJQTRDRDs7OztPQUlHO0lBQ0csTUFBTSxDQUFDLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQW9CekM7SUFFRCxhQUFhLENBQUMsS0FBSyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBSTFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXFCRztJQUNILG1CQUFtQixDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTBFbkY7SUFFRDs7Ozs7Ozs7T0FRRztJQUNILDJCQUEyQixDQUN6QixNQUFNLEVBQUUsbUNBQW1DLEVBQzNDLFVBQVUsRUFBRSxNQUFNLEVBQ2xCLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLEtBQUssRUFBRSxNQUFNLEdBQ1osT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBUW5CO0lBRUQ7Ozs7T0FJRztJQUNILHFCQUFxQixDQUFDLE1BQU0sRUFBRSxtQ0FBbUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLENBRTdHO0lBRUQ7Ozs7O09BS0c7SUFDSCxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUUsbUNBQW1DLEVBQUUsS0FBSyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQyxDQWtCeEc7SUFFRDs7T0FFRztJQUNILGtCQUFrQixDQUFDLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0EyQ25FO0lBRUQ7OztPQUdHO0lBQ0gsc0JBQXNCLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTJGdkU7Q0FDRiJ9
78
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZGVyX3RhZ2dpbmdfc3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yYWdlL3RhZ2dpbmdfc3RvcmUvc2VuZGVyX3RhZ2dpbmdfc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQWlCLE1BQU0saUJBQWlCLENBQUM7QUFDeEUsT0FBTyxFQUFFLG1DQUFtQyxFQUFhLEtBQUssaUJBQWlCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1RyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRXBELE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBDQUEwQyxDQUFDO0FBTTVFOzs7O0dBSUc7QUFDSCxxQkFBYSxrQkFBbUIsWUFBVyxXQUFXOztJQUNwRCxRQUFRLENBQUMsU0FBUyxvQkFBb0I7SUE0QnRDLFlBQVksS0FBSyxFQUFFLGlCQUFpQixFQVFuQztJQTRDRDs7OztPQUlHO0lBQ0csTUFBTSxDQUFDLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQW9CekM7SUFFRCxhQUFhLENBQUMsS0FBSyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBSTFDO0lBRUQ7Ozs7Ozs7Ozs7OztPQVlHO0lBQ0gsbUJBQW1CLENBQUMsTUFBTSxFQUFFLGlCQUFpQixFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FpRTdGO0lBRUQ7Ozs7Ozs7OztPQVNHO0lBQ0gsMkJBQTJCLENBQ3pCLE1BQU0sRUFBRSxtQ0FBbUMsRUFDM0MsVUFBVSxFQUFFLE1BQU0sRUFDbEIsUUFBUSxFQUFFLE1BQU0sRUFDaEIsS0FBSyxFQUFFLE1BQU0sR0FDWixPQUFPLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FRbkI7SUFFRDs7OztPQUlHO0lBQ0gscUJBQXFCLENBQUMsTUFBTSxFQUFFLG1DQUFtQyxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FFN0c7SUFFRDs7Ozs7T0FLRztJQUNILGdCQUFnQixDQUFDLE1BQU0sRUFBRSxtQ0FBbUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLENBaUJ4RztJQUVEOztPQUVHO0lBQ0gsa0JBQWtCLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTJDbkU7SUE0Q0Q7OztPQUdHO0lBQ0csc0JBQXNCLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQXNEN0U7SUFFRDs7Ozs7OztPQU9HO0lBQ0csNENBQTRDLENBQUMsUUFBUSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FnRG5HO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"sender_tagging_store.d.ts","sourceRoot":"","sources":["../../../src/storage/tagging_store/sender_tagging_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAG5E;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,WAAW;;IACpD,QAAQ,CAAC,SAAS,oBAAoB;IA6BtC,YAAY,KAAK,EAAE,iBAAiB,EAQnC;IA4CD;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzC;IAED,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1C;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EnF;IAED;;;;;;;;OAQG;IACH,2BAA2B,CACzB,MAAM,EAAE,mCAAmC,EAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnB;IAED;;;;OAIG;IACH,qBAAqB,CAAC,MAAM,EAAE,mCAAmC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7G;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,MAAM,EAAE,mCAAmC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkBxG;IAED;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CnE;IAED;;;OAGG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2FvE;CACF"}
1
+ {"version":3,"file":"sender_tagging_store.d.ts","sourceRoot":"","sources":["../../../src/storage/tagging_store/sender_tagging_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,mCAAmC,EAAa,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAM5E;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,WAAW;;IACpD,QAAQ,CAAC,SAAS,oBAAoB;IA4BtC,YAAY,KAAK,EAAE,iBAAiB,EAQnC;IA4CD;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzC;IAED,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1C;IAED;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiE7F;IAED;;;;;;;;;OASG;IACH,2BAA2B,CACzB,MAAM,EAAE,mCAAmC,EAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnB;IAED;;;;OAIG;IACH,qBAAqB,CAAC,MAAM,EAAE,mCAAmC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7G;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,MAAM,EAAE,mCAAmC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiBxG;IAED;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CnE;IA4CD;;;OAGG;IACG,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD7E;IAED;;;;;;;OAOG;IACG,4CAA4C,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDnG;CACF"}
@@ -1,3 +1,4 @@
1
+ import { ExtendedDirectionalAppTaggingSecret, SiloedTag } from '@aztec/stdlib/logs';
1
2
  import { TxHash } from '@aztec/stdlib/tx';
2
3
  import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.js';
3
4
  /**
@@ -7,18 +8,17 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
7
8
  */ export class SenderTaggingStore {
8
9
  storeName = 'sender_tagging';
9
10
  #store;
10
- // Stores the pending indexes for each directional app tagging secret. Pending here means that the tx that contained
11
- // the private logs with tags corresponding to these indexes has not been finalized yet.
11
+ // Stores the pending index ranges for each directional app tagging secret. Pending here means that the tx that
12
+ // contained the private logs with tags corresponding to these indexes has not been finalized yet.
12
13
  //
13
- // We don't store just the highest index because if their transaction is dropped we'd then need the information about
14
- // the lower pending indexes. For each secret-tx pair however we only store the largest index used in that tx, since
15
- // the smaller ones are irrelevant due to tx atomicity.
14
+ // We store the full range (lowestIndex, highestIndex) for each secret-tx pair because transactions can partially
15
+ // revert, in which case only some logs (from the non-revertible phase) survive onchain. By storing the range,
16
+ // we can expand it and check each individual siloed tag against the TxEffect to determine which indexes made it
17
+ // onchain.
16
18
  //
17
- // TODO(#17615): This assumes no logs are used in the non-revertible phase.
18
- //
19
- // directional app tagging secret => { pending index, txHash }[]
19
+ // directional app tagging secret => { lowestIndex, highestIndex, txHash }[]
20
20
  #pendingIndexes;
21
- // jobId => directional app tagging secret => { pending index, txHash }[]
21
+ // jobId => directional app tagging secret => { lowestIndex, highestIndex, txHash }[]
22
22
  #pendingIndexesForJob;
23
23
  // Stores the last (highest) finalized index for each directional app tagging secret. We care only about the last
24
24
  // index because unlike the pending indexes, it will never happen that a finalized index would be removed and hence
@@ -100,82 +100,62 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
100
100
  return Promise.resolve();
101
101
  }
102
102
  /**
103
- * Stores pending indexes.
104
- * @remarks Ignores the index if the same preTag + txHash combination already exists in the db with the same index.
105
- * This is expected to happen because whenever we start sync we start from the last finalized index and we can have
106
- * pending indexes already stored from previous syncs.
107
- * @param preTags - The pre-tags containing the directional app tagging secrets and the indexes that are to be
108
- * stored in the db.
109
- * @param txHash - The tx in which the pretags were used in private logs.
103
+ * Stores pending index ranges.
104
+ * @remarks If the same (secret, txHash) pair already exists in the db with an equal range, it's a no-op. This is
105
+ * expected to happen because whenever we start sync we start from the last finalized index and we can have pending
106
+ * ranges already stored from previous syncs. If the ranges differ, it throws an error as that indicates a bug.
107
+ * @param ranges - The tagging index ranges containing the directional app tagging secrets and the index ranges that are
108
+ * to be stored in the db.
109
+ * @param txHash - The tx in which the tagging indexes were used in private logs.
110
110
  * @param jobId - job context for staged writes to this store. See `JobCoordinator` for more details.
111
- * @throws If any two pre-tags contain the same directional app tagging secret. This is enforced because we care
112
- * only about the highest index for a given secret that was used in the tx. Hence this check is a good way to catch
113
- * bugs.
114
- * @throws If the newly stored pending index is further than window length from the highest finalized index for the
115
- * same secret. This is enforced in order to give a guarantee to a recipient that he doesn't need to look further than
116
- * window length ahead of the highest finalized index.
117
- * @throws If a secret + txHash pair already exists in the db with a different index value. It should never happen
118
- * that we would attempt to store a different index for a given secret-txHash pair because we always store just the
119
- * highest index for a given secret-txHash pair. Hence this is a good way to catch bugs.
120
- * @throws If the newly stored pending index is lower than or equal to the last finalized index for the same secret.
121
- * This is enforced because this should never happen if the syncing is done correctly as we look for logs from higher
122
- * indexes than finalized ones.
123
- */ storePendingIndexes(preTags, txHash, jobId) {
124
- if (preTags.length === 0) {
111
+ * @throws If the highestIndex is further than window length from the highest finalized index for the same secret.
112
+ * @throws If the lowestIndex is lower than or equal to the last finalized index for the same secret.
113
+ * @throws If a different range already exists for the same (secret, txHash) pair.
114
+ */ storePendingIndexes(ranges, txHash, jobId) {
115
+ if (ranges.length === 0) {
125
116
  return Promise.resolve();
126
117
  }
127
- // The secrets in pre-tags should be unique because we always store just the highest index per given secret-txHash
128
- // pair. Below we check that this is the case.
129
- const secretsSet = new Set(preTags.map((preTag)=>preTag.extendedSecret.toString()));
130
- if (secretsSet.size !== preTags.length) {
131
- return Promise.reject(new Error(`Duplicate secrets found when storing pending indexes`));
132
- }
133
118
  const txHashStr = txHash.toString();
134
119
  return this.#store.transactionAsync(async ()=>{
135
120
  // Prefetch all data, start reads during iteration to keep IndexedDB transaction alive
136
- const preTagReadPromises = preTags.map(({ extendedSecret, index })=>{
137
- const secretStr = extendedSecret.toString();
138
- return {
139
- extendedSecret,
140
- secretStr,
141
- index,
142
- pending: this.#readPendingIndexes(jobId, secretStr),
143
- finalized: this.#readLastFinalizedIndex(jobId, secretStr)
144
- };
145
- });
121
+ const rangeReadPromises = ranges.map((range)=>({
122
+ range,
123
+ secretStr: range.extendedSecret.toString(),
124
+ pending: this.#readPendingIndexes(jobId, range.extendedSecret.toString()),
125
+ finalized: this.#readLastFinalizedIndex(jobId, range.extendedSecret.toString())
126
+ }));
146
127
  // Await all reads together
147
- const preTagData = await Promise.all(preTagReadPromises.map(async (item)=>({
128
+ const rangeData = await Promise.all(rangeReadPromises.map(async (item)=>({
148
129
  ...item,
149
130
  pendingData: await item.pending,
150
131
  finalizedIndex: await item.finalized
151
132
  })));
152
133
  // Process in memory and validate
153
- for (const { secretStr, index, pendingData, finalizedIndex } of preTagData){
154
- // First we check that for any secret the highest used index in tx is not further than window length from
155
- // the highest finalized index.
156
- if (index > (finalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) {
157
- throw new Error(`Highest used index ${index} is further than window length from the highest finalized index ${finalizedIndex ?? 0}.
134
+ for (const { range, secretStr, pendingData, finalizedIndex } of rangeData){
135
+ // Check that the highest index is not further than window length from the highest finalized index.
136
+ if (range.highestIndex > (finalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) {
137
+ throw new Error(`Highest used index ${range.highestIndex} is further than window length from the highest finalized index ${finalizedIndex ?? 0}.
158
138
  Tagging window length ${UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN} is configured too low. Contact the Aztec team
159
139
  to increase it!`);
160
140
  }
161
- // Throw if the new pending index is lower than or equal to the last finalized index
162
- if (finalizedIndex !== undefined && index <= finalizedIndex) {
163
- throw new Error(`Cannot store pending index ${index} for secret ${secretStr}: ` + `it is lower than or equal to the last finalized index ${finalizedIndex}`);
141
+ // Throw if the lowest index is lower than or equal to the last finalized index
142
+ if (finalizedIndex !== undefined && range.lowestIndex <= finalizedIndex) {
143
+ throw new Error(`Cannot store pending index range [${range.lowestIndex}, ${range.highestIndex}] for secret ${secretStr}: ` + `lowestIndex is lower than or equal to the last finalized index ${finalizedIndex}`);
164
144
  }
165
- // Check if this secret + txHash combination already exists
166
- const existingForSecretAndTx = pendingData.find((entry)=>entry.txHash === txHashStr);
167
- if (existingForSecretAndTx) {
168
- // If it exists with a different index, throw an error
169
- if (existingForSecretAndTx.index !== index) {
170
- throw new Error(`Cannot store index ${index} for secret ${secretStr} and txHash ${txHashStr}: ` + `a different index ${existingForSecretAndTx.index} already exists for this secret-txHash pair`);
145
+ // Check if an entry with the same txHash already exists
146
+ const existingEntry = pendingData.find((entry)=>entry.txHash === txHashStr);
147
+ if (existingEntry) {
148
+ // Assert that the ranges are equal — different ranges for the same (secret, txHash) indicates a bug
149
+ if (existingEntry.lowestIndex !== range.lowestIndex || existingEntry.highestIndex !== range.highestIndex) {
150
+ throw new Error(`Conflicting range for secret ${secretStr} and txHash ${txHashStr}: ` + `existing [${existingEntry.lowestIndex}, ${existingEntry.highestIndex}] vs ` + `new [${range.lowestIndex}, ${range.highestIndex}]`);
171
151
  }
172
- // If it exists with the same index, ignore the update (no-op)
152
+ // Exact duplicate skip
173
153
  } else {
174
- // If it doesn't exist, add it
175
154
  this.#writePendingIndexes(jobId, secretStr, [
176
155
  ...pendingData,
177
156
  {
178
- index,
157
+ lowestIndex: range.lowestIndex,
158
+ highestIndex: range.highestIndex,
179
159
  txHash: txHashStr
180
160
  }
181
161
  ]);
@@ -184,8 +164,9 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
184
164
  });
185
165
  }
186
166
  /**
187
- * Returns the transaction hashes of all pending transactions that contain indexes within a specified range
188
- * for a given directional app tagging secret.
167
+ * Returns the transaction hashes of all pending transactions that contain highest indexes within a specified range
168
+ * for a given directional app tagging secret. We check based on the highest indexes only as that is the relevant
169
+ * information for the caller of this function.
189
170
  * @param secret - The directional app tagging secret to query pending indexes for.
190
171
  * @param startIndex - The lower bound of the index range (inclusive).
191
172
  * @param endIndex - The upper bound of the index range (exclusive).
@@ -194,7 +175,7 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
194
175
  */ getTxHashesOfPendingIndexes(secret, startIndex, endIndex, jobId) {
195
176
  return this.#store.transactionAsync(async ()=>{
196
177
  const existing = await this.#readPendingIndexes(jobId, secret.toString());
197
- const txHashes = existing.filter((entry)=>entry.index >= startIndex && entry.index < endIndex).map((entry)=>entry.txHash);
178
+ const txHashes = existing.filter((entry)=>entry.highestIndex >= startIndex && entry.highestIndex < endIndex).map((entry)=>entry.txHash);
198
179
  return Array.from(new Set(txHashes)).map(TxHash.fromString);
199
180
  });
200
181
  }
@@ -215,17 +196,16 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
215
196
  return this.#store.transactionAsync(async ()=>{
216
197
  const pendingPromise = this.#readPendingIndexes(jobId, secretStr);
217
198
  const finalizedPromise = this.#readLastFinalizedIndex(jobId, secretStr);
218
- const [pendingTxScopedIndexes, lastFinalized] = await Promise.all([
199
+ const [pendingEntries, lastFinalized] = await Promise.all([
219
200
  pendingPromise,
220
201
  finalizedPromise
221
202
  ]);
222
- const pendingIndexes = pendingTxScopedIndexes.map((entry)=>entry.index);
223
- if (pendingTxScopedIndexes.length === 0) {
203
+ if (pendingEntries.length === 0) {
224
204
  return lastFinalized;
225
205
  }
226
- // As the last used index we return the highest one from the pending indexes. Note that this value will be always
227
- // higher than the last finalized index because we prune lower pending indexes when a tx is finalized.
228
- return Math.max(...pendingIndexes);
206
+ // As the last used index we return the highest one from the pending index ranges. Note that this value will be
207
+ // always higher than the last finalized index because we prune lower pending indexes when a tx is finalized.
208
+ return Math.max(...pendingEntries.map((entry)=>entry.highestIndex));
229
209
  });
230
210
  }
231
211
  /**
@@ -269,14 +249,7 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
269
249
  }
270
250
  });
271
251
  }
272
- /**
273
- * Updates pending indexes corresponding to the given transaction hashes to be finalized and prunes any lower pending
274
- * indexes.
275
- */ finalizePendingIndexes(txHashes, jobId) {
276
- if (txHashes.length === 0) {
277
- return Promise.resolve();
278
- }
279
- const txHashStrings = new Set(txHashes.map((tx)=>tx.toString()));
252
+ /** Prefetches all pending and finalized index data for every secret (from both DB and staged writes). */ #getSecretsWithPendingData(jobId) {
280
253
  return this.#store.transactionAsync(async ()=>{
281
254
  // Prefetch all data, start reads during iteration to keep IndexedDB transaction alive
282
255
  const secretDataPromises = new Map();
@@ -304,45 +277,98 @@ import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.
304
277
  pendingData: await secretDataPromises.get(secret).pending,
305
278
  lastFinalized: await secretDataPromises.get(secret).finalized
306
279
  })));
307
- // Process all txHashes for each secret in memory
308
- for (const { secret, pendingData, lastFinalized } of dataResults){
309
- if (!pendingData || pendingData.length === 0) {
280
+ return dataResults.filter((r)=>r.pendingData.length > 0);
281
+ });
282
+ }
283
+ /**
284
+ * Updates pending indexes corresponding to the given transaction hashes to be finalized and prunes any lower pending
285
+ * indexes.
286
+ */ async finalizePendingIndexes(txHashes, jobId) {
287
+ if (txHashes.length === 0) {
288
+ return;
289
+ }
290
+ const txHashStrings = new Set(txHashes.map((tx)=>tx.toString()));
291
+ const secretsWithData = await this.#getSecretsWithPendingData(jobId);
292
+ for (const { secret, pendingData, lastFinalized } of secretsWithData){
293
+ let currentPending = pendingData;
294
+ let currentFinalized = lastFinalized;
295
+ // Process all txHashes for this secret
296
+ for (const txHashStr of txHashStrings){
297
+ const matchingEntries = currentPending.filter((item)=>item.txHash === txHashStr);
298
+ if (matchingEntries.length === 0) {
310
299
  continue;
311
300
  }
312
- let currentPending = pendingData;
313
- let currentFinalized = lastFinalized;
314
- // Process all txHashes for this secret
315
- for (const txHashStr of txHashStrings){
316
- const matchingIndexes = currentPending.filter((item)=>item.txHash === txHashStr).map((item)=>item.index);
317
- if (matchingIndexes.length === 0) {
318
- continue;
319
- }
320
- if (matchingIndexes.length > 1) {
321
- // We should always just store the highest pending index for a given tx hash and secret because the lower
322
- // values are irrelevant.
323
- throw new Error(`Multiple pending indexes found for tx hash ${txHashStr} and secret ${secret}`);
324
- }
325
- const newFinalized = matchingIndexes[0];
326
- if (newFinalized < (currentFinalized ?? 0)) {
327
- // This should never happen because when last finalized index was finalized we should have pruned the lower
328
- // pending indexes.
329
- throw new Error(`New finalized index ${newFinalized} is smaller than the current last finalized index ${currentFinalized}`);
330
- }
331
- currentFinalized = newFinalized;
332
- // When we add pending indexes, we ensure they are higher than the last finalized index. However, because we
333
- // cannot control the order in which transactions are finalized, there may be pending indexes that are now
334
- // obsolete because they are lower than the most recently finalized index. For this reason, we prune these
335
- // outdated pending indexes.
336
- currentPending = currentPending.filter((item)=>item.index > currentFinalized);
301
+ if (matchingEntries.length > 1) {
302
+ // We should always just store the highest pending index for a given tx hash and secret because the lower
303
+ // values are irrelevant.
304
+ throw new Error(`Multiple pending entries found for tx hash ${txHashStr} and secret ${secret}`);
337
305
  }
338
- // Write final state if changed
339
- if (currentFinalized !== lastFinalized) {
340
- this.#writeLastFinalizedIndex(jobId, secret, currentFinalized);
306
+ const newFinalized = matchingEntries[0].highestIndex;
307
+ if (newFinalized < (currentFinalized ?? 0)) {
308
+ // This should never happen because when last finalized index was finalized we should have pruned the lower
309
+ // pending indexes.
310
+ throw new Error(`New finalized index ${newFinalized} is smaller than the current last finalized index ${currentFinalized}`);
341
311
  }
342
- if (currentPending !== pendingData) {
343
- this.#writePendingIndexes(jobId, secret, currentPending);
312
+ currentFinalized = newFinalized;
313
+ // When we add pending indexes, we ensure they are higher than the last finalized index. However, because we
314
+ // cannot control the order in which transactions are finalized, there may be pending indexes that are now
315
+ // obsolete because they are lower than the most recently finalized index. For this reason, we prune these
316
+ // outdated pending indexes.
317
+ currentPending = currentPending.filter((item)=>item.highestIndex > currentFinalized);
318
+ }
319
+ // Write final state if changed
320
+ if (currentFinalized !== lastFinalized) {
321
+ this.#writeLastFinalizedIndex(jobId, secret, currentFinalized);
322
+ }
323
+ if (currentPending !== pendingData) {
324
+ this.#writePendingIndexes(jobId, secret, currentPending);
325
+ }
326
+ }
327
+ }
328
+ /**
329
+ * Handles finalization of pending indexes for a transaction whose execution was partially reverted.
330
+ * Recomputes the siloed tags for each pending index of the given tx and checks which ones appear in the
331
+ * TxEffect's private logs (i.e., which ones made it onchain). Those that survived are finalized; those that
332
+ * didn't are dropped.
333
+ * @param txEffect - The tx effect of the partially reverted transaction.
334
+ * @param jobId - job context for staged writes to this store. See `JobCoordinator` for more details.
335
+ */ async finalizePendingIndexesOfAPartiallyRevertedTx(txEffect, jobId) {
336
+ const txHashStr = txEffect.txHash.toString();
337
+ // Build a set of all siloed tag values that made it onchain (first field of each private log).
338
+ const onChainTags = new Set(txEffect.privateLogs.map((log)=>log.fields[0].toString()));
339
+ const secretsWithData = await this.#getSecretsWithPendingData(jobId);
340
+ for (const { secret, pendingData, lastFinalized } of secretsWithData){
341
+ const matchingEntries = pendingData.filter((item)=>item.txHash === txHashStr);
342
+ if (matchingEntries.length === 0) {
343
+ continue;
344
+ }
345
+ if (matchingEntries.length > 1) {
346
+ // We should always just store the highest pending index for a given tx hash and secret because the lower
347
+ // values are irrelevant.
348
+ throw new Error(`Multiple pending entries found for tx hash ${txHashStr} and secret ${secret}`);
349
+ }
350
+ const pendingEntry = matchingEntries[0];
351
+ // Expand each matching entry's range and recompute siloed tags for each index.
352
+ const extendedSecret = ExtendedDirectionalAppTaggingSecret.fromString(secret);
353
+ let highestSurvivingIndex;
354
+ for(let index = pendingEntry.lowestIndex; index <= pendingEntry.highestIndex; index++){
355
+ const siloedTag = await SiloedTag.compute({
356
+ extendedSecret,
357
+ index
358
+ });
359
+ if (onChainTags.has(siloedTag.value.toString())) {
360
+ highestSurvivingIndex = highestSurvivingIndex !== undefined ? Math.max(highestSurvivingIndex, index) : index;
344
361
  }
345
362
  }
346
- });
363
+ // Remove all entries for this txHash from pending (both surviving and non-surviving).
364
+ let currentPending = pendingData.filter((item)=>item.txHash !== txHashStr);
365
+ if (highestSurvivingIndex !== undefined) {
366
+ const newFinalized = Math.max(lastFinalized ?? 0, highestSurvivingIndex);
367
+ this.#writeLastFinalizedIndex(jobId, secret, newFinalized);
368
+ // Prune pending indexes that are now <= the finalized index.
369
+ currentPending = currentPending.filter((item)=>item.highestIndex > newFinalized);
370
+ }
371
+ this.#writePendingIndexes(jobId, secret, currentPending);
372
+ }
347
373
  }
348
374
  }
@@ -13,5 +13,5 @@ export { syncSenderTaggingIndexes } from './sender_sync/sync_sender_tagging_inde
13
13
  export { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from './constants.js';
14
14
  export { getAllPrivateLogsByTags, getAllPublicLogsByTagsFromContract } from './get_all_logs_by_tags.js';
15
15
  export { ExtendedDirectionalAppTaggingSecret, Tag, SiloedTag } from '@aztec/stdlib/logs';
16
- export { type PreTag } from '@aztec/stdlib/logs';
17
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90YWdnaW5nL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLGlFQUFpRSxDQUFDO0FBQ3hILE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDhDQUE4QyxDQUFDO0FBQ3hGLE9BQU8sRUFBRSxzQ0FBc0MsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxrQ0FBa0MsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBR3hHLE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDekYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUMifQ==
16
+ export { type PreTag, type TaggingIndexRange } from '@aztec/stdlib/logs';
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90YWdnaW5nL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLGlFQUFpRSxDQUFDO0FBQ3hILE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDhDQUE4QyxDQUFDO0FBQ3hGLE9BQU8sRUFBRSxzQ0FBc0MsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxrQ0FBa0MsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBR3hHLE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDekYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLEtBQUssaUJBQWlCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQyJ9