Skip to content

[BUG] PXE storePendingIndexes throws "Conflicting range" — execution-time and sync-time paths disagree on lowestIndex #23574

Closed

Description

@shiqicao

What are you trying to do?

Reading a private note from a PXE after submitting a transaction that delivers notes via ONCHAIN_CONSTRAINED. During note-discovery sync, the PXE consistently aborts with a Conflicting range error:

Error: Conflicting range for secret 0x096f3e58...:0x174f56ff... and txHash 0x196bd441...:
  existing [0, 5] vs new [1, 5]

Thrown from yarn-project/pxe/src/storage/tagging_store/sender_tagging_store.ts (storePendingIndexes).

Code Reference

A minimal standalone repro is hard to extract, so here is the source-level root cause instead. Two code paths write a pending range for the same (secret, txHash) pair but compute lowestIndex with different semantics:

1. Execution-time path — yarn-project/pxe/src/pxe.ts stores taggingIndexRangesUsedInTheTx after proving:

// pxe.ts (~L788)
const taggingIndexRangesUsedInTheTx = privateExecutionResult.entrypoint.taggingIndexRanges;
if (taggingIndexRangesUsedInTheTx.length > 0) {
  const txHash = (await txProvingResult.toTx()).txHash;
  await this.senderTaggingStore.storePendingIndexes(taggingIndexRangesUsedInTheTx, txHash, jobId);
}

The range here comes from contract_function_simulator/execution_tagging_index_cache.ts, where lowestIndex is initialized to the true first index the tx used:

// execution_tagging_index_cache.ts (~L23)
this.taggingIndexMap.set(secret.toString(), { lowestIndex: index, highestIndex: index });

For a tx that tags logs with indexes 0..5 on one directional secret, this stores [0, 5].

2. Sync-time path — tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts:

// load_and_store_new_tagging_indexes.ts (~L43)
const ranges = [{ extendedSecret, lowestIndex: Math.min(...indexes), highestIndex: Math.max(...indexes) }];
await taggingStore.storePendingIndexes(ranges, txHash, jobId);

Here indexes are only those found within the current scan window [start, end). The window start is anchored to finalizedIndex + 1 (enforced in storePendingIndexes: lowestIndex must be > finalizedIndex). Once index 0 has been finalized, the window starts at 1, the scan never observes index 0, and it recomputes [1, 5].

storePendingIndexes then finds the existing [0, 5] entry for that pair, sees a different range, and throws:

// sender_tagging_store.ts (~L192)
if (existingEntry.lowestIndex !== range.lowestIndex || existingEntry.highestIndex !== range.highestIndex) {
  throw new Error(
    `Conflicting range for secret ${secretStr} and txHash ${txHashStr}: ` +
      `existing [${existingEntry.lowestIndex}, ${existingEntry.highestIndex}] vs ` +
      `new [${range.lowestIndex}, ${range.highestIndex}]`,
  );
}

Why this appears to be a bug rather than a real inconsistency: lowestIndex is not used by note discovery. The rest of the store keys off highestIndex — see getTxHashesOfPendingIndexes ("we check based on the highest indexes only") and the pruning in finalizePendingIndexes which filters on item.highestIndex > currentFinalized. So the assertion aborts sync over a discrepancy in a field that does not affect correctness.

Triggering conditions (in lieu of a minimal repro): a transaction that emits multiple tagged logs under a single directional app tagging secret (so the tx occupies a multi-index range such as [0, 5]), where the lower indexes become finalized between the execution-time store and a later sync-time re-scan. In a local sandbox the block/finalization timing is deterministic, so this reproduces on every run.

Suggested fix: reconcile instead of throwing — when an entry for (secret, txHash) already exists, retain min(existing.lowestIndex, new.lowestIndex) and max(existing.highestIndex, new.highestIndex); or validate/key only on highestIndex, since that is the field discovery actually relies on.

Aztec Version

v4.2.0-aztecnr-rc.2

OS

Linux (6.8.0)

Node Version

v22.14.0

Additional Context

Line numbers above are from the v4.2.0-aztecnr-rc.2 tag and may differ on next. The error is fully reproducible in a local sandbox; it surfaced when reading a PrivateMutable note via a utility view after the note was delivered ONCHAIN_CONSTRAINED in a prior transaction.

Activity

  1. nventuro commented on May 27, 2026

    @nventuro
    Contributor

    This has all the signs of being a duplicate of #22949, which was fixed in #23044 and released as part of v4.3.0. You should be able to simply upgrade the js stack to this version (i.e. the wallet/pxe) and continue using contracts compiled and deployed with v4.2.0 with no problem whatsoever - PXE changes are backwards compatible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions