Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Dec 23, 2024
1 parent ab3f318 commit b598cf8
Show file tree
Hide file tree
Showing 28 changed files with 344 additions and 345 deletions.
4 changes: 1 addition & 3 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,10 +877,8 @@ export class AztecNodeService implements AztecNode, Traceable {

const txValidator = new AggregateTxValidator(...txValidators);

const [_, invalidTxs] = await txValidator.validateTxs([tx]);
if (invalidTxs.length > 0) {
if (!(await txValidator.validateTx(tx))) {
this.log.warn(`Rejecting tx ${tx.getTxHash()} because of validation errors`);

return false;
}

Expand Down
6 changes: 6 additions & 0 deletions yarn-project/circuit-types/src/interfaces/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export interface SequencerConfig {
maxTxsPerBlock?: number;
/** The minimum number of txs to include in a block. */
minTxsPerBlock?: number;
/** The maximum L2 block gas. */
maxL2BlockGas?: number;
/** The maximum DA block gas. */
maxDABlockGas?: number;
/** Recipient of block reward. */
coinbase?: EthAddress;
/** Address to receive fees. */
Expand Down Expand Up @@ -53,6 +57,8 @@ export const SequencerConfigSchema = z.object({
transactionPollingIntervalMS: z.number().optional(),
maxTxsPerBlock: z.number().optional(),
minTxsPerBlock: z.number().optional(),
maxL2BlockGas: z.number().optional(),
maxDABlockGas: z.number().optional(),
coinbase: schemas.EthAddress.optional(),
feeRecipient: schemas.AztecAddress.optional(),
acvmWorkingDirectory: z.string().optional(),
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/circuit-types/src/tx/tx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
ClientIvcProof,
Fr,
PrivateKernelTailCircuitPublicInputs,
PrivateLog,
type PrivateToPublicAccumulatedData,
type ScopedLogHash,
} from '@aztec/circuits.js';
Expand Down Expand Up @@ -230,6 +232,20 @@ export class Tx extends Gossipable {
);
}

/**
* Estimates the tx size based on its private effects. Note that the actual size of the tx
* after processing will probably be larger, as public execution would generate more data.
*/
getEstimatedPrivateTxEffectsSize() {
return (
this.unencryptedLogs.getSerializedLength() +
this.contractClassLogs.getSerializedLength() +
this.data.getNonEmptyNoteHashes().length * Fr.SIZE_IN_BYTES +
this.data.getNonEmptyNullifiers().length * Fr.SIZE_IN_BYTES +
this.data.getNonEmptyPrivateLogs().length * PrivateLog.SIZE_IN_BYTES
);
}

/**
* Convenience function to get a hash out of a tx or a tx-like.
* @param tx - Tx-like object.
Expand Down
10 changes: 3 additions & 7 deletions yarn-project/circuit-types/src/tx/validator/empty_validator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { type AnyTx, type TxValidator } from './tx_validator.js';
import { type AnyTx, type TxValidationResult, type TxValidator } from './tx_validator.js';

export class EmptyTxValidator<T extends AnyTx = AnyTx> implements TxValidator<T> {
public validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[], skippedTxs: T[]]> {
return Promise.resolve([txs, [], []]);
}

public validateTx(_tx: T): Promise<boolean> {
return Promise.resolve(true);
public validateTx(_tx: T): Promise<TxValidationResult> {
return Promise.resolve({ result: 'valid' });
}
}
8 changes: 6 additions & 2 deletions yarn-project/circuit-types/src/tx/validator/tx_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { type Tx } from '../tx.js';

export type AnyTx = Tx | ProcessedTx;

export type TxValidationResult =
| { result: 'valid' }
| { result: 'invalid'; reason: string[] }
| { result: 'skipped'; reason: string[] };

export interface TxValidator<T extends AnyTx = AnyTx> {
validateTx(tx: T): Promise<boolean>;
validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[], skippedTxs?: T[]]>;
validateTx(tx: T): Promise<TxValidationResult>;
}
5 changes: 5 additions & 0 deletions yarn-project/circuit-types/src/tx_effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ export class TxEffect {
]);
}

/** Returns the size of this tx effect in bytes as serialized onto DA. */
getDASize() {
return this.toBlobFields().length * Fr.SIZE_IN_BYTES;
}

/**
* Deserializes the TxEffect object from a Buffer.
* @param buffer - Buffer or BufferReader object to deserialize.
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/circuits.js/src/structs/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export class Gas {
return new Gas(Math.ceil(this.daGas * scalar), Math.ceil(this.l2Gas * scalar));
}

/** Returns true if any of this instance's dimensions is greater than the corresponding on the other. */
gtAny(other: Gas) {
return this.daGas > other.daGas || this.l2Gas > other.l2Gas;
}

computeFee(gasFees: GasFees) {
return GasDimensions.reduce(
(acc, dimension) => acc.add(gasFees.get(dimension).mul(new Fr(this.get(dimension)))),
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe('e2e_block_building', () => {
// This will leave the sequencer with just 2s to build the block, so it shouldn't be
// able to squeeze in more than 10 txs in each. This is sensitive to the time it takes
// to pick up and validate the txs, so we may need to bump it to work on CI.
sequencer.sequencer.timeTable[SequencerState.WAITING_FOR_TXS] = 2;
sequencer.sequencer.timeTable[SequencerState.INITIALIZING_PROPOSAL] = 2;
sequencer.sequencer.timeTable[SequencerState.CREATING_BLOCK] = 2;
sequencer.sequencer.processTxTime = 1;

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export type EnvVar =
| 'SEQ_MAX_BLOCK_SIZE_IN_BYTES'
| 'SEQ_MAX_TX_PER_BLOCK'
| 'SEQ_MIN_TX_PER_BLOCK'
| 'SEQ_MAX_DA_BLOCK_GAS'
| 'SEQ_MAX_L2_BLOCK_GAS'
| 'SEQ_PUBLISH_RETRY_INTERVAL_MS'
| 'SEQ_PUBLISHER_PRIVATE_KEY'
| 'SEQ_REQUIRED_CONFIRMATIONS'
Expand Down
20 changes: 20 additions & 0 deletions yarn-project/p2p/src/client/p2p_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ export type P2P<T extends P2PClientType = P2PClientType.Full> = P2PApi<T> & {
*/
getTxStatus(txHash: TxHash): 'pending' | 'mined' | undefined;

/** Returns an iterator over pending txs on the mempool. */
iteratePendingTxs(): Iterable<Tx>;

/** Returns the number of pending txs in the mempool. */
getPendingTxCount(): number;

/**
* Starts the p2p client.
* @returns A promise signalling the completion of the block sync.
Expand Down Expand Up @@ -460,6 +466,20 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
return Promise.resolve(this.getTxs('pending'));
}

public getPendingTxCount(): number {
return this.txPool.getPendingTxHashes().length;
}

public *iteratePendingTxs() {
const pendingTxHashes = this.txPool.getPendingTxHashes();
for (const txHash of pendingTxHashes) {
const tx = this.txPool.getTxByHash(txHash);
if (tx) {
yield tx;
}
}
}

/**
* Returns all transactions in the transaction pool.
* @returns An array of Txs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
import { type ProcessedTx, type Tx, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';

export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValidator<T> {
#validators: TxValidator<T>[];
Expand All @@ -10,27 +10,20 @@ export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValid
this.#validators = validators;
}

async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[], skippedTxs: T[]]> {
const invalidTxs: T[] = [];
const skippedTxs: T[] = [];
let txPool = txs;
async validateTx(tx: T): Promise<TxValidationResult> {
const aggregate: { result: string; reason: string[] } = { result: 'valid', reason: [] };
for (const validator of this.#validators) {
const [valid, invalid, skipped] = await validator.validateTxs(txPool);
invalidTxs.push(...invalid);
skippedTxs.push(...(skipped ?? []));
txPool = valid;
}

return [txPool, invalidTxs, skippedTxs];
}

async validateTx(tx: T): Promise<boolean> {
for (const validator of this.#validators) {
const valid = await validator.validateTx(tx);
if (!valid) {
return false;
const result = await validator.validateTx(tx);
if (result.result === 'invalid') {
aggregate.result = 'invalid';
aggregate.reason.push(...result.reason);
} else if (result.result === 'skipped') {
if (aggregate.result === 'valid') {
aggregate.result = 'skipped';
}
aggregate.reason.push(...result.reason);
}
}
return true;
return aggregate as TxValidationResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe('TxDataValidator', () => {
});

it('allows transactions with the correct data', async () => {
const txs = mockTxs(3);
await expect(validator.validateTxs(txs)).resolves.toEqual([txs, []]);
const [tx] = mockTxs(1);
await expect(validator.validateTx(tx)).resolves.toEqual({ result: 'valid' });
});

it('rejects txs with mismatch non revertible execution requests', async () => {
Expand Down
29 changes: 7 additions & 22 deletions yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import { Tx, type TxValidator } from '@aztec/circuit-types';
import { Tx, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';
import { createLogger } from '@aztec/foundation/log';

export class DataTxValidator implements TxValidator<Tx> {
#log = createLogger('p2p:tx_validator:tx_data');

validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
const validTxs: Tx[] = [];
const invalidTxs: Tx[] = [];
for (const tx of txs) {
if (!this.#hasCorrectExecutionRequests(tx)) {
invalidTxs.push(tx);
continue;
}

validTxs.push(tx);
}

return Promise.resolve([validTxs, invalidTxs]);
}

validateTx(tx: Tx): Promise<boolean> {
validateTx(tx: Tx): Promise<TxValidationResult> {
return Promise.resolve(this.#hasCorrectExecutionRequests(tx));
}

#hasCorrectExecutionRequests(tx: Tx): boolean {
#hasCorrectExecutionRequests(tx: Tx): TxValidationResult {
const callRequests = [
...tx.data.getRevertiblePublicCallRequests(),
...tx.data.getNonRevertiblePublicCallRequests(),
Expand All @@ -34,7 +19,7 @@ export class DataTxValidator implements TxValidator<Tx> {
callRequests.length
}. Got ${tx.enqueuedPublicFunctionCalls.length}.`,
);
return false;
return { result: 'invalid', reason: ['Wrong number of execution requests for public calls'] };
}

const invalidExecutionRequestIndex = tx.enqueuedPublicFunctionCalls.findIndex(
Expand All @@ -46,7 +31,7 @@ export class DataTxValidator implements TxValidator<Tx> {
tx,
)} because of incorrect execution requests for public call at index ${invalidExecutionRequestIndex}.`,
);
return false;
return { result: 'invalid', reason: ['Incorrect execution request for public call'] };
}

const teardownCallRequest = tx.data.getTeardownPublicCallRequest();
Expand All @@ -55,10 +40,10 @@ export class DataTxValidator implements TxValidator<Tx> {
(teardownCallRequest && !tx.publicTeardownFunctionCall.isForCallRequest(teardownCallRequest));
if (isInvalidTeardownExecutionRequest) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} because of incorrect teardown execution requests.`);
return false;
return { result: 'invalid', reason: ['Incorrect teardown execution request'] };
}

return true;
return { result: 'valid' };
}

// TODO: Check logs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,35 @@
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';
import { createLogger } from '@aztec/foundation/log';

export interface NullifierSource {
getNullifierIndices: (nullifiers: Buffer[]) => Promise<(bigint | undefined)[]>;
nullifierExists: (nullifier: Buffer) => Promise<boolean>;
}

export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
#log = createLogger('p2p:tx_validator:tx_double_spend');
#nullifierSource: NullifierSource;

constructor(nullifierSource: NullifierSource, private readonly isValidatingBlock: boolean = true) {
constructor(nullifierSource: NullifierSource) {
this.#nullifierSource = nullifierSource;
}

async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
const validTxs: T[] = [];
const invalidTxs: T[] = [];
const thisBlockNullifiers = new Set<bigint>();

for (const tx of txs) {
if (!(await this.#uniqueNullifiers(tx, thisBlockNullifiers))) {
invalidTxs.push(tx);
continue;
}

validTxs.push(tx);
}

return [validTxs, invalidTxs];
}

validateTx(tx: T): Promise<boolean> {
return this.#uniqueNullifiers(tx, new Set<bigint>());
}

async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
async validateTx(tx: T): Promise<TxValidationResult> {
const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;

// Ditch this tx if it has repeated nullifiers
const uniqueNullifiers = new Set(nullifiers);
if (uniqueNullifiers.size !== nullifiers.length) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
return false;
return { result: 'invalid', reason: ['Duplicate nullifier in tx'] };
}

if (this.isValidatingBlock) {
for (const nullifier of nullifiers) {
const nullifierBigInt = nullifier.toBigInt();
if (thisBlockNullifiers.has(nullifierBigInt)) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
return false;
}

thisBlockNullifiers.add(nullifierBigInt);
for (const nullifier of nullifiers) {
if (await this.#nullifierSource.nullifierExists(nullifier.toBuffer())) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier`);
return { result: 'invalid', reason: ['Repeated nullifier'] };
}
}

const nullifierIndexes = await this.#nullifierSource.getNullifierIndices(nullifiers.map(n => n.toBuffer()));

const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
if (hasDuplicates) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
return false;
}

return true;
return { result: 'valid' };
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';
import { type Fr } from '@aztec/circuits.js';
import { createLogger } from '@aztec/foundation/log';

Expand Down Expand Up @@ -27,8 +27,15 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
return Promise.resolve([validTxs, invalidTxs]);
}

validateTx(tx: T): Promise<boolean> {
return Promise.resolve(this.#hasCorrectChainId(tx) && this.#isValidForBlockNumber(tx));
validateTx(tx: T): Promise<TxValidationResult> {
const errors = [];
if (!this.#hasCorrectChainId(tx)) {
errors.push('Incorrect chain id');
}
if (!this.#isValidForBlockNumber(tx)) {
errors.push('Invalid block number');
}
return Promise.resolve(errors.length > 0 ? { result: 'invalid', reason: errors } : { result: 'valid' });
}

#hasCorrectChainId(tx: T): boolean {
Expand Down
Loading

0 comments on commit b598cf8

Please sign in to comment.