Skip to content

Commit 6d45a1b

Browse files
authored
fix: Rejecting txs with duplicate nullifiers (#17176)
Fixes a bug in the `DoubleSpendValidator` where txs that contained duplicate nullifiers were not being caught (`Set` of non-primitive types strikes again). Also updates the public-processor so it always throws when it stumbles upon a tx with duplicate nullifiers from private-land, and fixes the `e2e_event_logs` test that caused this due to an incorrect setup (it was calling `ensureAccountContractsPublished` with the same address twice, which resulted in duplicate requests in a `BatchCall`).
2 parents c5bdc74 + d959139 commit 6d45a1b

File tree

4 files changed

+15
-20
lines changed

4 files changed

+15
-20
lines changed

yarn-project/end-to-end/src/e2e_event_logs.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AztecAddress, type AztecNode, Fr, type Wallet, getDecodedPublicEvents } from '@aztec/aztec.js';
1+
import { AztecAddress, type AztecNode, Fr, type Logger, type Wallet, getDecodedPublicEvents } from '@aztec/aztec.js';
22
import { makeTuple } from '@aztec/foundation/array';
33
import { timesParallel } from '@aztec/foundation/collection';
44
import type { Tuple } from '@aztec/foundation/serialize';
@@ -20,6 +20,7 @@ describe('Logs', () => {
2020
let account1Address: AztecAddress;
2121
let account2Address: AztecAddress;
2222

23+
let log: Logger;
2324
let teardown: () => Promise<void>;
2425

2526
beforeAll(async () => {
@@ -28,10 +29,13 @@ describe('Logs', () => {
2829
wallet,
2930
accounts: [account1Address, account2Address],
3031
aztecNode,
32+
logger: log,
3133
} = await setup(2));
3234

33-
await ensureAccountContractsPublished(wallet, [account1Address, account1Address]);
35+
log.warn(`Setup complete, checking account contracts published`);
36+
await ensureAccountContractsPublished(wallet, [account1Address, account2Address]);
3437

38+
log.warn(`Deploying test contract`);
3539
testLogContract = await TestLogContract.deploy(wallet).send({ from: account1Address }).deployed();
3640
});
3741

yarn-project/p2p/src/msg_validators/tx_validator/double_spend_validator.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Fr } from '@aztec/foundation/fields';
12
import { mockTx, mockTxForRollup } from '@aztec/stdlib/testing';
23
import { type AnyTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX, TX_ERROR_EXISTING_NULLIFIER } from '@aztec/stdlib/tx';
34

@@ -27,8 +28,8 @@ describe('DoubleSpendTxValidator', () => {
2728
numberOfNonRevertiblePublicCallRequests: 1,
2829
numberOfRevertiblePublicCallRequests: 0,
2930
});
30-
badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[1] =
31-
badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[0];
31+
const nullifiers = badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers;
32+
nullifiers[1] = new Fr(nullifiers[0].toBigInt());
3233
await expectInvalid(badTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX);
3334
});
3435

@@ -38,8 +39,8 @@ describe('DoubleSpendTxValidator', () => {
3839
numberOfRevertiblePublicCallRequests: 1,
3940
numberOfRevertibleNullifiers: 1,
4041
});
41-
badTx.data.forPublic!.revertibleAccumulatedData.nullifiers[1] =
42-
badTx.data.forPublic!.revertibleAccumulatedData.nullifiers[0];
42+
const nullifiers = badTx.data.forPublic!.revertibleAccumulatedData.nullifiers;
43+
nullifiers[1] = new Fr(nullifiers[0].toBigInt());
4344
await expectInvalid(badTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX);
4445
});
4546

yarn-project/p2p/src/msg_validators/tx_validator/double_spend_validator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
2424
const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;
2525

2626
// Ditch this tx if it has repeated nullifiers
27-
const uniqueNullifiers = new Set(nullifiers);
27+
const uniqueNullifiers = new Set(nullifiers.map(n => n.toBigInt()));
2828
if (uniqueNullifiers.size !== nullifiers.length) {
2929
this.#log.verbose(`Rejecting tx ${'txHash' in tx ? tx.txHash : tx.hash} for emitting duplicate nullifiers`);
3030
return { result: 'invalid', reason: [TX_ERROR_DUPLICATE_NULLIFIER_IN_TX] };

yarn-project/simulator/src/public/public_processor/public_processor.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
StateReference,
2828
Tx,
2929
TxExecutionPhase,
30-
type TxValidator,
3130
makeProcessedTxFromPrivateOnlyTx,
3231
makeProcessedTxFromTxWithPublicCalls,
3332
} from '@aztec/stdlib/tx';
@@ -384,10 +383,7 @@ export class PublicProcessor implements Traceable {
384383
return [processedTx, returnValues ?? []];
385384
}
386385

387-
private async doTreeInsertionsForPrivateOnlyTx(
388-
processedTx: ProcessedTx,
389-
txValidator?: TxValidator<ProcessedTx>,
390-
): Promise<void> {
386+
private async doTreeInsertionsForPrivateOnlyTx(processedTx: ProcessedTx): Promise<void> {
391387
const treeInsertionStart = process.hrtime.bigint();
392388

393389
// Update the state so that the next tx in the loop has the correct .startState
@@ -406,14 +402,8 @@ export class PublicProcessor implements Traceable {
406402
padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
407403
NULLIFIER_SUBTREE_HEIGHT,
408404
);
409-
} catch {
410-
if (txValidator) {
411-
// Ideally the validator has already caught this above, but just in case:
412-
throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
413-
} else {
414-
// We have no validator and assume this call should blindly process txs with duplicates being caught later
415-
this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
416-
}
405+
} catch (cause) {
406+
throw new Error(`Transaction ${processedTx.hash} failed with duplicate nullifiers`, { cause });
417407
}
418408

419409
const treeInsertionEnd = process.hrtime.bigint();

0 commit comments

Comments
 (0)