@aztec/pxe 0.0.1-commit.3100065 → 0.0.1-commit.321f6a9

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 (152) hide show
  1. package/dest/bin/check_oracle_version.js +3 -8
  2. package/dest/bin/index.d.ts +2 -2
  3. package/dest/bin/index.d.ts.map +1 -1
  4. package/dest/bin/index.js +1 -1
  5. package/dest/bin/oracle_version_helpers.d.ts +8 -25
  6. package/dest/bin/oracle_version_helpers.d.ts.map +1 -1
  7. package/dest/bin/oracle_version_helpers.js +11 -219
  8. package/dest/config/package_info.js +1 -1
  9. package/dest/contract/helpers.d.ts +1 -1
  10. package/dest/contract/helpers.d.ts.map +1 -1
  11. package/dest/contract/helpers.js +3 -3
  12. package/dest/contract/skip_sync_contracts.d.ts +11 -0
  13. package/dest/contract/skip_sync_contracts.d.ts.map +1 -0
  14. package/dest/contract/skip_sync_contracts.js +18 -0
  15. package/dest/contract_function_simulator/contract_function_simulator.d.ts +1 -1
  16. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  17. package/dest/contract_function_simulator/contract_function_simulator.js +5 -3
  18. package/dest/contract_function_simulator/index.d.ts +3 -3
  19. package/dest/contract_function_simulator/index.d.ts.map +1 -1
  20. package/dest/contract_function_simulator/index.js +1 -2
  21. package/dest/contract_function_simulator/noir-structs/resolved_tx.d.ts +7 -9
  22. package/dest/contract_function_simulator/noir-structs/resolved_tx.d.ts.map +1 -1
  23. package/dest/contract_function_simulator/noir-structs/resolved_tx.js +3 -44
  24. package/dest/contract_function_simulator/noir-structs/tx_effect_data.d.ts +5 -4
  25. package/dest/contract_function_simulator/noir-structs/tx_effect_data.d.ts.map +1 -1
  26. package/dest/contract_function_simulator/noir-structs/tx_effect_data.js +2 -2
  27. package/dest/contract_function_simulator/oracle/oracle_registry.d.ts +4 -3
  28. package/dest/contract_function_simulator/oracle/oracle_registry.d.ts.map +1 -1
  29. package/dest/contract_function_simulator/oracle/oracle_registry.js +15 -6
  30. package/dest/contract_function_simulator/oracle/oracle_type_mappings.d.ts +87 -35
  31. package/dest/contract_function_simulator/oracle/oracle_type_mappings.d.ts.map +1 -1
  32. package/dest/contract_function_simulator/oracle/oracle_type_mappings.js +286 -286
  33. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +1 -1
  34. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  35. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +5 -6
  36. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +6 -3
  37. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  38. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +41 -16
  39. package/dest/entrypoints/client/bundle/index.d.ts +2 -1
  40. package/dest/entrypoints/client/bundle/index.d.ts.map +1 -1
  41. package/dest/entrypoints/client/bundle/index.js +1 -0
  42. package/dest/entrypoints/client/bundle/utils.d.ts +1 -1
  43. package/dest/entrypoints/client/bundle/utils.d.ts.map +1 -1
  44. package/dest/entrypoints/client/bundle/utils.js +10 -4
  45. package/dest/entrypoints/client/lazy/index.d.ts +2 -1
  46. package/dest/entrypoints/client/lazy/index.d.ts.map +1 -1
  47. package/dest/entrypoints/client/lazy/index.js +1 -0
  48. package/dest/entrypoints/client/lazy/utils.d.ts +1 -1
  49. package/dest/entrypoints/client/lazy/utils.d.ts.map +1 -1
  50. package/dest/entrypoints/client/lazy/utils.js +10 -4
  51. package/dest/entrypoints/client/store.d.ts +14 -0
  52. package/dest/entrypoints/client/store.d.ts.map +1 -0
  53. package/dest/entrypoints/client/store.js +42 -0
  54. package/dest/entrypoints/server/index.d.ts +2 -1
  55. package/dest/entrypoints/server/index.d.ts.map +1 -1
  56. package/dest/entrypoints/server/index.js +1 -0
  57. package/dest/entrypoints/server/store.d.ts +18 -0
  58. package/dest/entrypoints/server/store.d.ts.map +1 -0
  59. package/dest/entrypoints/server/store.js +28 -0
  60. package/dest/entrypoints/server/utils.d.ts +1 -1
  61. package/dest/entrypoints/server/utils.d.ts.map +1 -1
  62. package/dest/entrypoints/server/utils.js +8 -2
  63. package/dest/logs/log_service.d.ts +3 -2
  64. package/dest/logs/log_service.d.ts.map +1 -1
  65. package/dest/logs/log_service.js +12 -3
  66. package/dest/messages/tx_resolver_service.d.ts +2 -2
  67. package/dest/messages/tx_resolver_service.d.ts.map +1 -1
  68. package/dest/messages/tx_resolver_service.js +7 -2
  69. package/dest/notes/note_service.d.ts +1 -1
  70. package/dest/notes/note_service.js +1 -1
  71. package/dest/oracle_version.d.ts +2 -2
  72. package/dest/oracle_version.js +3 -3
  73. package/dest/private_kernel/batch_planner.d.ts +1 -1
  74. package/dest/private_kernel/batch_planner.d.ts.map +1 -1
  75. package/dest/private_kernel/batch_planner.js +1 -1
  76. package/dest/private_kernel/private_kernel_execution_prover.d.ts +1 -1
  77. package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
  78. package/dest/private_kernel/private_kernel_execution_prover.js +45 -5
  79. package/dest/pxe.d.ts +1 -1
  80. package/dest/pxe.d.ts.map +1 -1
  81. package/dest/pxe.js +8 -5
  82. package/dest/storage/fact_store/fact_store.d.ts +1 -1
  83. package/dest/storage/fact_store/fact_store.js +1 -1
  84. package/dest/storage/fact_store/index.d.ts +2 -2
  85. package/dest/storage/fact_store/index.d.ts.map +1 -1
  86. package/dest/storage/fact_store/index.js +1 -1
  87. package/dest/storage/fact_store/origin_state.d.ts +3 -1
  88. package/dest/storage/fact_store/origin_state.d.ts.map +1 -1
  89. package/dest/storage/fact_store/origin_state.js +7 -0
  90. package/dest/storage/store_identity.d.ts +35 -0
  91. package/dest/storage/store_identity.d.ts.map +1 -0
  92. package/dest/storage/store_identity.js +44 -0
  93. package/dest/storage/tagging_store/sender_tagging_store.d.ts +19 -3
  94. package/dest/storage/tagging_store/sender_tagging_store.d.ts.map +1 -1
  95. package/dest/storage/tagging_store/sender_tagging_store.js +48 -11
  96. package/dest/tagging/constants.d.ts +2 -1
  97. package/dest/tagging/constants.d.ts.map +1 -1
  98. package/dest/tagging/constants.js +9 -0
  99. package/dest/tagging/index.d.ts +2 -2
  100. package/dest/tagging/index.d.ts.map +1 -1
  101. package/dest/tagging/index.js +1 -1
  102. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts +9 -4
  103. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts.map +1 -1
  104. package/dest/tagging/recipient_sync/sync_tagged_private_logs.js +60 -25
  105. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.js +1 -1
  106. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts +1 -1
  107. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
  108. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.js +6 -2
  109. package/dest/tagging/testing/tag_query_test_utils.d.ts +6 -0
  110. package/dest/tagging/testing/tag_query_test_utils.d.ts.map +1 -0
  111. package/dest/tagging/testing/tag_query_test_utils.js +10 -0
  112. package/package.json +17 -17
  113. package/src/bin/check_oracle_version.ts +3 -10
  114. package/src/bin/index.ts +1 -5
  115. package/src/bin/oracle_version_helpers.ts +11 -268
  116. package/src/config/package_info.ts +1 -1
  117. package/src/contract/helpers.ts +3 -3
  118. package/src/contract/skip_sync_contracts.ts +23 -0
  119. package/src/contract_function_simulator/contract_function_simulator.ts +5 -3
  120. package/src/contract_function_simulator/index.ts +23 -3
  121. package/src/contract_function_simulator/noir-structs/resolved_tx.ts +11 -45
  122. package/src/contract_function_simulator/noir-structs/tx_effect_data.ts +4 -3
  123. package/src/contract_function_simulator/oracle/oracle_registry.ts +12 -8
  124. package/src/contract_function_simulator/oracle/oracle_type_mappings.ts +285 -197
  125. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +12 -7
  126. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +55 -17
  127. package/src/entrypoints/client/bundle/index.ts +1 -0
  128. package/src/entrypoints/client/bundle/utils.ts +15 -3
  129. package/src/entrypoints/client/lazy/index.ts +1 -0
  130. package/src/entrypoints/client/lazy/utils.ts +15 -3
  131. package/src/entrypoints/client/store.ts +54 -0
  132. package/src/entrypoints/server/index.ts +1 -0
  133. package/src/entrypoints/server/store.ts +47 -0
  134. package/src/entrypoints/server/utils.ts +21 -7
  135. package/src/logs/log_service.ts +11 -2
  136. package/src/messages/tx_resolver_service.ts +8 -8
  137. package/src/notes/note_service.ts +1 -1
  138. package/src/oracle_version.ts +3 -3
  139. package/src/private_kernel/batch_planner.ts +0 -1
  140. package/src/private_kernel/private_kernel_execution_prover.ts +90 -4
  141. package/src/pxe.ts +8 -5
  142. package/src/storage/fact_store/fact_store.ts +1 -1
  143. package/src/storage/fact_store/index.ts +1 -0
  144. package/src/storage/fact_store/origin_state.ts +9 -0
  145. package/src/storage/store_identity.ts +72 -0
  146. package/src/storage/tagging_store/sender_tagging_store.ts +62 -15
  147. package/src/tagging/constants.ts +10 -0
  148. package/src/tagging/index.ts +1 -1
  149. package/src/tagging/recipient_sync/sync_tagged_private_logs.ts +103 -46
  150. package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +1 -1
  151. package/src/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts +6 -2
  152. package/src/tagging/testing/tag_query_test_utils.ts +11 -0
@@ -1,6 +1,7 @@
1
+ import { CircuitKind } from '@aztec/bb.js';
1
2
  import { MAX_APPS_PER_KERNEL } from '@aztec/constants';
2
3
  import { uniqueBy } from '@aztec/foundation/collection';
3
- import { vkAsFieldsMegaHonk } from '@aztec/foundation/crypto/keys';
4
+ import { vkAsFields } from '@aztec/foundation/crypto/keys';
4
5
  import { Fr } from '@aztec/foundation/curves/bn254';
5
6
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
6
7
  import { pushTestData } from '@aztec/foundation/testing';
@@ -20,9 +21,13 @@ import {
20
21
  type PrivateKernelExecutionProofOutput,
21
22
  PrivateKernelInit2CircuitPrivateInputs,
22
23
  PrivateKernelInit3CircuitPrivateInputs,
24
+ PrivateKernelInit4CircuitPrivateInputs,
25
+ PrivateKernelInit5CircuitPrivateInputs,
23
26
  PrivateKernelInitCircuitPrivateInputs,
24
27
  PrivateKernelInner2CircuitPrivateInputs,
25
28
  PrivateKernelInner3CircuitPrivateInputs,
29
+ PrivateKernelInner4CircuitPrivateInputs,
30
+ PrivateKernelInner5CircuitPrivateInputs,
26
31
  PrivateKernelInnerCircuitPrivateInputs,
27
32
  PrivateKernelResetTailCircuitPrivateInputs,
28
33
  type PrivateKernelSimulateOutput,
@@ -145,6 +150,7 @@ export class PrivateKernelExecutionProver {
145
150
  bytecode: output.bytecode,
146
151
  witness: output.outputWitness,
147
152
  vk: output.verificationKey.keyAsBytes,
153
+ kind: CircuitKind.Kernel,
148
154
  timings: {
149
155
  witgen: witgenTimer.ms(),
150
156
  },
@@ -230,6 +236,7 @@ export class PrivateKernelExecutionProver {
230
236
  bytecode: tailOutput.bytecode,
231
237
  witness: tailOutput.outputWitness,
232
238
  vk: tailOutput.verificationKey.keyAsBytes,
239
+ kind: CircuitKind.Kernel,
233
240
  timings: {
234
241
  witgen: witgenTimer.ms(),
235
242
  },
@@ -261,6 +268,7 @@ export class PrivateKernelExecutionProver {
261
268
  bytecode: hidingOutput.bytecode,
262
269
  witness: hidingOutput.outputWitness,
263
270
  vk: hidingOutput.verificationKey.keyAsBytes,
271
+ kind: CircuitKind.HidingKernel,
264
272
  timings: {
265
273
  witgen: witgenTimer.ms(),
266
274
  },
@@ -270,7 +278,11 @@ export class PrivateKernelExecutionProver {
270
278
  if (profileMode == 'gates' || profileMode == 'full') {
271
279
  for (const entry of executionSteps) {
272
280
  const gateCountTimer = new Timer();
273
- const gateCount = await this.proofCreator.computeGateCountForCircuit(entry.bytecode, entry.functionName);
281
+ const gateCount = await this.proofCreator.computeGateCountForCircuit(
282
+ entry.bytecode,
283
+ entry.functionName,
284
+ entry.kind,
285
+ );
274
286
  entry.gateCount = gateCount;
275
287
  entry.timings.gateCount = gateCountTimer.ms();
276
288
  }
@@ -366,6 +378,7 @@ export class PrivateKernelExecutionProver {
366
378
  bytecode: next.acir,
367
379
  witness: next.partialWitness,
368
380
  vk: next.vk,
381
+ kind: CircuitKind.App,
369
382
  timings: {
370
383
  witgen: next.profileResult?.timings.witgen ?? 0,
371
384
  oracles: next.profileResult?.timings.oracles,
@@ -399,8 +412,8 @@ export class PrivateKernelExecutionProver {
399
412
  ) {
400
413
  const { contractAddress, functionSelector } = publicInputs.callContext;
401
414
 
402
- const vkAsFields = await vkAsFieldsMegaHonk(vkAsBuffer);
403
- const vk = await VerificationKeyAsFields.fromKey(vkAsFields);
415
+ const vkFields = await vkAsFields(vkAsBuffer, CircuitKind.App);
416
+ const vk = await VerificationKeyAsFields.fromKey(vkFields);
404
417
 
405
418
  const { currentContractClassId, publicKeys, saltedInitializationHash } =
406
419
  await this.oracle.getContractAddressPreimage(contractAddress);
@@ -514,6 +527,47 @@ export class PrivateKernelExecutionProver {
514
527
  functionName = 'private_kernel_init_3';
515
528
  break;
516
529
  }
530
+ case 4: {
531
+ const proofInput = new PrivateKernelInit4CircuitPrivateInputs(
532
+ txRequest,
533
+ vkTreeRoot,
534
+ ProtocolContractsList,
535
+ apps[0],
536
+ apps[1],
537
+ apps[2],
538
+ apps[3],
539
+ isPrivateOnlyTx,
540
+ minRevertibleSideEffectCounter,
541
+ );
542
+ this.log.debug(`Calling private kernel init_4 with isPrivateOnly ${isPrivateOnlyTx}`);
543
+ pushTestData('private-kernel-inputs-init-4', proofInput);
544
+ output = generateWitnesses
545
+ ? await this.proofCreator.generateInit4Output(proofInput)
546
+ : await this.proofCreator.simulateInit4(proofInput);
547
+ functionName = 'private_kernel_init_4';
548
+ break;
549
+ }
550
+ case 5: {
551
+ const proofInput = new PrivateKernelInit5CircuitPrivateInputs(
552
+ txRequest,
553
+ vkTreeRoot,
554
+ ProtocolContractsList,
555
+ apps[0],
556
+ apps[1],
557
+ apps[2],
558
+ apps[3],
559
+ apps[4],
560
+ isPrivateOnlyTx,
561
+ minRevertibleSideEffectCounter,
562
+ );
563
+ this.log.debug(`Calling private kernel init_5 with isPrivateOnly ${isPrivateOnlyTx}`);
564
+ pushTestData('private-kernel-inputs-init-5', proofInput);
565
+ output = generateWitnesses
566
+ ? await this.proofCreator.generateInit5Output(proofInput)
567
+ : await this.proofCreator.simulateInit5(proofInput);
568
+ functionName = 'private_kernel_init_5';
569
+ break;
570
+ }
517
571
  default:
518
572
  throw new Error(`Unsupported init kernel batch size: ${apps.length}`);
519
573
  }
@@ -548,6 +602,37 @@ export class PrivateKernelExecutionProver {
548
602
  functionName = 'private_kernel_inner_3';
549
603
  break;
550
604
  }
605
+ case 4: {
606
+ const proofInput = new PrivateKernelInner4CircuitPrivateInputs(
607
+ previousKernelData,
608
+ apps[0],
609
+ apps[1],
610
+ apps[2],
611
+ apps[3],
612
+ );
613
+ pushTestData('private-kernel-inputs-inner-4', proofInput);
614
+ output = generateWitnesses
615
+ ? await this.proofCreator.generateInner4Output(proofInput)
616
+ : await this.proofCreator.simulateInner4(proofInput);
617
+ functionName = 'private_kernel_inner_4';
618
+ break;
619
+ }
620
+ case 5: {
621
+ const proofInput = new PrivateKernelInner5CircuitPrivateInputs(
622
+ previousKernelData,
623
+ apps[0],
624
+ apps[1],
625
+ apps[2],
626
+ apps[3],
627
+ apps[4],
628
+ );
629
+ pushTestData('private-kernel-inputs-inner-5', proofInput);
630
+ output = generateWitnesses
631
+ ? await this.proofCreator.generateInner5Output(proofInput)
632
+ : await this.proofCreator.simulateInner5(proofInput);
633
+ functionName = 'private_kernel_inner_5';
634
+ break;
635
+ }
551
636
  default:
552
637
  throw new Error(`Unsupported inner kernel batch size: ${apps.length}`);
553
638
  }
@@ -558,6 +643,7 @@ export class PrivateKernelExecutionProver {
558
643
  bytecode: output.bytecode,
559
644
  witness: output.outputWitness,
560
645
  vk: output.verificationKey.keyAsBytes,
646
+ kind: CircuitKind.Kernel,
561
647
  timings: {
562
648
  witgen: witgenTimer.ms(),
563
649
  },
package/src/pxe.ts CHANGED
@@ -37,7 +37,7 @@ import type {
37
37
  PrivateKernelTailCircuitPublicInputs,
38
38
  } from '@aztec/stdlib/kernel';
39
39
  import { computeAddressSecret } from '@aztec/stdlib/keys';
40
- import { deriveEcdhSharedSecretPoint } from '@aztec/stdlib/logs';
40
+ import { deriveEcdhSharedSecretPoint, protectFromForgery } from '@aztec/stdlib/logs';
41
41
  import {
42
42
  BlockHeader,
43
43
  type ContractOverrides,
@@ -890,9 +890,11 @@ export class PXE {
890
890
  }
891
891
 
892
892
  /**
893
- * Derives the shared secret of a handshake from its ephemeral public key: `S = addressSecret(recipient) * ephPk`,
894
- * the same derivation the recipient-side scan uses for handshakes discovered onchain. The ephemeral key is
895
- * reconstructed from its x-coordinate with a positive y, matching how the protocol generates it.
893
+ * Derives the shared secret of a handshake from its ephemeral public key: the raw ECDH secret
894
+ * `S = addressSecret(recipient) * ephPk` with the forgery protection applied,
895
+ * matching the protected secret the registry stores and the derivation the recipient-side scan uses for
896
+ * handshakes discovered onchain. The ephemeral key is reconstructed from its x-coordinate with a positive y,
897
+ * matching how the protocol generates it.
896
898
  *
897
899
  * @throws If `ephPk` is not the x-coordinate of a Grumpkin curve point, or if `recipient` is not an account of
898
900
  * this PXE, since the derivation needs its keys.
@@ -914,7 +916,8 @@ export class PXE {
914
916
 
915
917
  const ivskM = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
916
918
  const addressSecret = await computeAddressSecret(await completeAddress.getPreaddress(), ivskM);
917
- return deriveEcdhSharedSecretPoint(addressSecret, ephPkPoint);
919
+ const rawSecret = await deriveEcdhSharedSecretPoint(addressSecret, ephPkPoint);
920
+ return protectFromForgery(rawSecret, ephPkPoint, await recipient.toAddressPoint());
918
921
  }
919
922
 
920
923
  /** Registers a resolved shared secret scoped to a recipient. Returns whether it was newly added. */
@@ -82,7 +82,7 @@ export class FactStore implements StagedStore {
82
82
  }
83
83
 
84
84
  /**
85
- * Records a fact in a collection, visible under the given scope.
85
+ * Records a fact in a collection.
86
86
  *
87
87
  * The collection is created implicitly on the first fact recorded for its key: recording into an existing collection
88
88
  * just adds to it.
@@ -5,6 +5,7 @@ export type { Fact } from './stored_fact.js';
5
5
  export {
6
6
  OriginBlockState,
7
7
  classifyOriginBlockState,
8
+ originBlockStateFromNumber,
8
9
  anchoredTipBlockNumbers,
9
10
  toFactWithOriginState,
10
11
  type TipBlockNumbers,
@@ -21,6 +21,15 @@ export enum OriginBlockState {
21
21
  Finalized = 3,
22
22
  }
23
23
 
24
+ /** Parses a numeric origin-block-state discriminant, rejecting unknown values. */
25
+ export function originBlockStateFromNumber(value: number): OriginBlockState {
26
+ if (!(value in OriginBlockState)) {
27
+ const validNames = Object.keys(OriginBlockState).filter(k => isNaN(Number(k)));
28
+ throw new Error(`Invalid OriginBlockState value ${value}, expected one of ${validNames.join(', ')}`);
29
+ }
30
+ return value as OriginBlockState;
31
+ }
32
+
24
33
  /** The two chain-tip block numbers needed to classify an origin block (`finalized <= proven` always holds). */
25
34
  export type TipBlockNumbers = { provenBlockNumber: number; finalizedBlockNumber: number };
26
35
 
@@ -0,0 +1,72 @@
1
+ import type { EthAddress } from '@aztec/foundation/eth-address';
2
+ import type { AztecAsyncKVStore } from '@aztec/kv-store';
3
+ import { DatabaseVersion } from '@aztec/stdlib/database-version/version';
4
+
5
+ /** The triple that determine which physical store a logical store name maps to. */
6
+ export type StoreIdentity = {
7
+ /** Chain ID of the L1 the rollup is deployed to. */
8
+ l1ChainId: number;
9
+ /** Address of the rollup contract the store's data pertains to. */
10
+ rollupAddress: EthAddress;
11
+ /** Schema version of the data held in the store. */
12
+ schemaVersion: number;
13
+ };
14
+
15
+ /**
16
+ * Composes the store-name discriminator for a store identity. Two identities map to the same physical store iff
17
+ * their slugs are equal, so the format must stay stable: `<l1ChainId>-<rollupAddress>-v<schemaVersion>` — changing
18
+ * it orphans every existing store.
19
+ */
20
+ export function storeIdentitySlug({ l1ChainId, rollupAddress, schemaVersion }: StoreIdentity): string {
21
+ return `${l1ChainId}-${rollupAddress.toString()}-v${schemaVersion}`;
22
+ }
23
+
24
+ /** Composes the physical store name for a logical store name and identity. */
25
+ export function effectiveStoreName(name: string, identity: StoreIdentity): string {
26
+ return `${name}_${storeIdentitySlug(identity)}`;
27
+ }
28
+
29
+ /**
30
+ * Invariant check for stores whose physical name carries their identity: the recorded version can
31
+ * only disagree with the expected one if there is a store-naming bug.
32
+ */
33
+ export async function assertStoreIdentity(
34
+ store: AztecAsyncKVStore,
35
+ storeName: string,
36
+ identity: StoreIdentity,
37
+ ): Promise<void> {
38
+ const expected = new DatabaseVersion(identity.schemaVersion, identity.rollupAddress);
39
+ const singleton = store.openSingleton<string>('dbVersion');
40
+ const stored = await singleton.getAsync();
41
+ if (stored === undefined) {
42
+ await singleton.set(expected.toBuffer().toString('utf-8'));
43
+ return;
44
+ }
45
+ let storedVersion: DatabaseVersion;
46
+ try {
47
+ storedVersion = DatabaseVersion.fromBuffer(Buffer.from(stored, 'utf-8'));
48
+ } catch {
49
+ throw new StoreIdentityMismatchError(storeName, expected.toString(), stored);
50
+ }
51
+ if (!storedVersion.equals(expected)) {
52
+ throw new StoreIdentityMismatchError(storeName, expected.toString(), storedVersion.toString());
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Thrown when a store's recorded identity does not match the identity it was opened under. Since the identity is
58
+ * part of the physical store name, this can only indicate a store-naming bug; the store is left untouched.
59
+ */
60
+ export class StoreIdentityMismatchError extends Error {
61
+ constructor(
62
+ public readonly storeName: string,
63
+ public readonly expected: string,
64
+ public readonly actual: string,
65
+ ) {
66
+ super(
67
+ `Store '${storeName}' records identity ${actual} but was opened as ${expected}. ` +
68
+ `Refusing to open; data was NOT modified.`,
69
+ );
70
+ this.name = 'StoreIdentityMismatchError';
71
+ }
72
+ }
@@ -128,10 +128,11 @@ export class SenderTaggingStore implements StagedStore {
128
128
  }
129
129
 
130
130
  /**
131
- * Stores pending index ranges.
131
+ * Stores pending index ranges, rejecting any range that disagrees with an already-stored one.
132
132
  * @remarks If the same (secret, txHash) pair already exists in the db with an equal range, it's a no-op. This is
133
133
  * expected to happen because whenever we start sync we start from the last finalized index and we can have pending
134
- * ranges already stored from previous syncs. If the ranges differ, it throws an error as that indicates a bug.
134
+ * ranges already stored from previous syncs. If the ranges differ, it throws an error as that indicates a bug in
135
+ * callers that record indexes at prove time. Discovery from onchain logs must use `mergePendingIndexes` instead.
135
136
  * @param ranges - The tagging index ranges containing the directional app tagging secrets and the index ranges that are
136
137
  * to be stored in the db.
137
138
  * @param txHash - The tx in which the tagging indexes were used in private logs.
@@ -141,6 +142,33 @@ export class SenderTaggingStore implements StagedStore {
141
142
  * @throws If a different range already exists for the same (secret, txHash) pair.
142
143
  */
143
144
  storePendingIndexes(ranges: TaggingIndexRange[], txHash: TxHash, jobId: string): Promise<void> {
145
+ return this.#storePendingIndexes(ranges, txHash, jobId, false);
146
+ }
147
+
148
+ /**
149
+ * Stores pending index ranges, widening an existing entry for the same (secret, txHash) pair to the union of the
150
+ * stored and incoming ranges instead of throwing on a mismatch. Discovery from onchain logs needs this: it may see
151
+ * only the surviving (non-revertible phase) sub-range of a partially reverted tx recorded at prove time (the
152
+ * finalized receipt step of the sync resolves that difference), or indexes beyond a partially discovered entry
153
+ * when a tx from another PXE straddles a sync window boundary. Callers that record indexes at prove time must use
154
+ * `storePendingIndexes` instead, so that a range disagreement surfaces as a bug rather than being absorbed.
155
+ * @param ranges - The tagging index ranges containing the directional app tagging secrets and the index ranges that are
156
+ * to be stored in the db.
157
+ * @param txHash - The tx in which the tagging indexes were used in private logs.
158
+ * @param jobId - job context for staged writes to this store. See `JobCoordinator` for more details.
159
+ * @throws If the highestIndex is further than window length from the highest finalized index for the same secret.
160
+ * @throws If the lowestIndex is lower than or equal to the last finalized index for the same secret.
161
+ */
162
+ mergePendingIndexes(ranges: TaggingIndexRange[], txHash: TxHash, jobId: string): Promise<void> {
163
+ return this.#storePendingIndexes(ranges, txHash, jobId, true);
164
+ }
165
+
166
+ #storePendingIndexes(
167
+ ranges: TaggingIndexRange[],
168
+ txHash: TxHash,
169
+ jobId: string,
170
+ mergeExisting: boolean,
171
+ ): Promise<void> {
144
172
  if (ranges.length === 0) {
145
173
  return Promise.resolve();
146
174
  }
@@ -187,21 +215,40 @@ export class SenderTaggingStore implements StagedStore {
187
215
  // Check if an entry with the same txHash already exists
188
216
  const existingEntry = pendingData.find(entry => entry.txHash === txHashStr);
189
217
 
190
- if (existingEntry) {
191
- // Assert that the ranges are equal — different ranges for the same (secret, txHash) indicates a bug
192
- if (existingEntry.lowestIndex !== range.lowestIndex || existingEntry.highestIndex !== range.highestIndex) {
193
- throw new Error(
194
- `Conflicting range for secret ${secretStr} and txHash ${txHashStr}: ` +
195
- `existing [${existingEntry.lowestIndex}, ${existingEntry.highestIndex}] vs ` +
196
- `new [${range.lowestIndex}, ${range.highestIndex}]`,
197
- );
198
- }
199
- // Exact duplicate — skip
200
- } else {
201
- this.#writePendingIndexes(jobId, secretStr, [
218
+ let updatedPending: PendingIndexesEntry[] | undefined;
219
+ if (!existingEntry) {
220
+ updatedPending = [
202
221
  ...pendingData,
203
222
  { lowestIndex: range.lowestIndex, highestIndex: range.highestIndex, txHash: txHashStr },
204
- ]);
223
+ ];
224
+ } else if (mergeExisting) {
225
+ // Widen the entry to the union of both ranges, never shrink it: replacing would drop prove-time
226
+ // indexes the chain doesn't show (partially reverted tx), and skipping would drop onchain indexes
227
+ // discovered in a later sync window (tx straddling the window boundary).
228
+ const lowestIndex = Math.min(existingEntry.lowestIndex, range.lowestIndex);
229
+ const highestIndex = Math.max(existingEntry.highestIndex, range.highestIndex);
230
+ if (lowestIndex !== existingEntry.lowestIndex || highestIndex !== existingEntry.highestIndex) {
231
+ updatedPending = pendingData.map(entry =>
232
+ entry === existingEntry ? { lowestIndex, highestIndex, txHash: entry.txHash } : entry,
233
+ );
234
+ }
235
+ } else if (
236
+ existingEntry.lowestIndex !== range.lowestIndex ||
237
+ existingEntry.highestIndex !== range.highestIndex
238
+ ) {
239
+ // Different ranges for the same (secret, txHash) indicate a bug in callers that record indexes at prove
240
+ // time.
241
+ throw new Error(
242
+ `Conflicting range for secret ${secretStr} and txHash ${txHashStr}: ` +
243
+ `existing [${existingEntry.lowestIndex}, ${existingEntry.highestIndex}] vs ` +
244
+ `new [${range.lowestIndex}, ${range.highestIndex}]`,
245
+ );
246
+ }
247
+ // Remaining cases (a merge whose union equals the stored range, or an identical range without
248
+ // mergeExisting): duplicate evidence, nothing to write.
249
+
250
+ if (updatedPending) {
251
+ this.#writePendingIndexes(jobId, secretStr, updatedPending);
205
252
  }
206
253
  }
207
254
  });
@@ -18,3 +18,13 @@ import { MAX_PRIVATE_LOGS_PER_TX } from '@aztec/constants';
18
18
  // reserved at log emission time, before squashing is decided, and the kernel's reset/squash loop is not bounded by
19
19
  // MAX_PRIVATE_LOGS_PER_TX. No fixed window value closes that gap.
20
20
  export const UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = MAX_PRIVATE_LOGS_PER_TX + 20;
21
+
22
+ // The number of tags probed per constrained secret in the first round.
23
+ //
24
+ // The probe doubles each round (2, 4, 8, ..., capped at UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) while every probed
25
+ // index is a hit, stopping at the first missing tag. Constrained delivery is gapless, so a single missing tag proves
26
+ // the stream has ended: at steady state this turns a full WINDOW_LEN probe into two tags. A secret K logs behind
27
+ // catches up in ~log2(K) round-trips while the probe is still doubling (2, 4, 8, 16, 32), but once it saturates the cap
28
+ // and advances WINDOW_LEN tags per round, deeper catch-up is linear at ~K/WINDOW_LEN rounds. Either way it beats both
29
+ // the full window every round and one round per log.
30
+ export const INITIAL_CONSTRAINED_PROBE_LEN = 2;
@@ -12,7 +12,7 @@
12
12
  export { syncTaggedPrivateLogs } from './recipient_sync/sync_tagged_private_logs.js';
13
13
  export { syncSenderTaggingIndexes } from './sender_sync/sync_sender_tagging_indexes.js';
14
14
  export { persistSenderTaggingIndexRangesForTx } from './persist_sender_tagging_index_ranges.js';
15
- export { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from './constants.js';
15
+ export { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN, INITIAL_CONSTRAINED_PROBE_LEN } from './constants.js';
16
16
  export { getAllPrivateLogsByTags, getAllPublicLogsByTagsFromContract } from './get_all_logs_by_tags.js';
17
17
 
18
18
  // Re-export tagging-related types from stdlib