Skip to content

Commit

Permalink
feat(val): reex (#9768)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 authored Nov 22, 2024
1 parent ceaeda5 commit 2e58f0a
Show file tree
Hide file tree
Showing 24 changed files with 525 additions and 80 deletions.
1 change: 0 additions & 1 deletion scripts/ci/get_e2e_jobs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ done
# Add the input labels and expanded matches to allow_list
allow_list+=("${input_labels[@]}" "${expanded_allow_list[@]}")


# Generate full list of targets, excluding specific entries, on one line
test_list=$(echo "${full_list[@]}" | grep -v 'base' | grep -v 'bench' | grep -v "network" | grep -v 'devnet' | xargs echo)

Expand Down
2 changes: 2 additions & 0 deletions spartan/aztec-network/templates/validator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ spec:
value: "{{ .Values.validator.p2p.enabled }}"
- name: VALIDATOR_DISABLED
value: "{{ .Values.validator.validator.disabled }}"
- name: VALIDATOR_REEXECUTE
value: "{{ .Values.validator.validator.reexecute }}"
- name: SEQ_MAX_SECONDS_BETWEEN_BLOCKS
value: "{{ .Values.validator.sequencer.maxSecondsBetweenBlocks }}"
- name: SEQ_MIN_TX_PER_BLOCK
Expand Down
1 change: 1 addition & 0 deletions spartan/aztec-network/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ validator:
enforceTimeTable: true
validator:
disabled: false
reexecute: true
p2p:
enabled: "true"
startupProbe:
Expand Down
10 changes: 10 additions & 0 deletions yarn-project/circuits.js/src/structs/public_data_update_request.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { type AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { inspect } from 'util';

import { computePublicDataTreeLeafSlot } from '../hash/hash.js';
import { type ContractStorageUpdateRequest } from './contract_storage_update_request.js';

// TO BE REMOVED.
/**
* Write operations on the public data tree including the previous value.
Expand Down Expand Up @@ -75,6 +79,12 @@ export class PublicDataUpdateRequest {
return new PublicDataUpdateRequest(Fr.fromBuffer(reader), Fr.fromBuffer(reader), reader.readNumber());
}

static fromContractStorageUpdateRequest(contractAddress: AztecAddress, updateRequest: ContractStorageUpdateRequest) {
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, updateRequest.storageSlot);

return new PublicDataUpdateRequest(leafSlot, updateRequest.newValue, updateRequest.counter);
}

static empty() {
return new PublicDataUpdateRequest(Fr.ZERO, Fr.ZERO, 0);
}
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/scripts/e2e_test_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ tests:
test_path: 'e2e_p2p/rediscovery.test.ts'
e2e_p2p_reqresp:
test_path: 'e2e_p2p/reqresp.test.ts'
e2e_p2p_reex:
test_path: 'e2e_p2p/reex.test.ts'
flakey_e2e_tests:
test_path: './src/flakey'
ignore_failures: true
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ describe('e2e_p2p_network', () => {
throw new Error('Bootstrap node ENR is not available');
}

t.ctx.aztecNodeConfig.validatorReexecute = true;

// create our network of nodes and submit txs into each of them
// the number of txs per node and the number of txs per rollup
// should be set so that the only way for rollups to be built
Expand Down
52 changes: 50 additions & 2 deletions yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node';
import { EthCheatCodes } from '@aztec/aztec.js';
import { type AccountWalletWithSecretKey, EthCheatCodes } from '@aztec/aztec.js';
import { EthAddress } from '@aztec/circuits.js';
import { getL1ContractsConfigEnvVars } from '@aztec/ethereum';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RollupAbi } from '@aztec/l1-artifacts';
import { SpamContract } from '@aztec/noir-contracts.js';
import { type BootstrapNode } from '@aztec/p2p';
import { createBootstrapNodeFromPrivateKey } from '@aztec/p2p/mocks';

Expand All @@ -17,7 +19,12 @@ import {
generateNodePrivateKeys,
generatePeerIdPrivateKeys,
} from '../fixtures/setup_p2p_test.js';
import { type ISnapshotManager, type SubsystemsContext, createSnapshotManager } from '../fixtures/snapshot_manager.js';
import {
type ISnapshotManager,
type SubsystemsContext,
addAccounts,
createSnapshotManager,
} from '../fixtures/snapshot_manager.js';
import { getPrivateKeyFromIndex } from '../fixtures/utils.js';
import { getEndToEndTestTelemetryClient } from '../fixtures/with_telemetry_utils.js';

Expand All @@ -39,6 +46,10 @@ export class P2PNetworkTest {

public bootstrapNodeEnr: string = '';

// The re-execution test needs a wallet and a spam contract
public wallet?: AccountWalletWithSecretKey;
public spamContract?: SpamContract;

constructor(
testName: string,
public bootstrapNode: BootstrapNode,
Expand Down Expand Up @@ -108,12 +119,16 @@ export class P2PNetworkTest {
client: deployL1ContractsValues.walletClient,
});

this.logger.verbose(`Adding ${this.numberOfNodes} validators`);

const txHashes: `0x${string}`[] = [];
for (let i = 0; i < this.numberOfNodes; i++) {
const account = privateKeyToAccount(this.nodePrivateKeys[i]!);
this.logger.debug(`Adding ${account.address} as validator`);
const txHash = await rollup.write.addValidator([account.address]);
txHashes.push(txHash);

this.logger.debug(`Adding ${account.address} as validator`);
}

// Wait for all the transactions adding validators to be mined
Expand Down Expand Up @@ -148,6 +163,39 @@ export class P2PNetworkTest {
});
}

async setupAccount() {
await this.snapshotManager.snapshot(
'setup-account',
addAccounts(1, this.logger, false),
async ({ accountKeys }, ctx) => {
const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1));
await Promise.all(accountManagers.map(a => a.register()));
const wallets = await Promise.all(accountManagers.map(a => a.getWallet()));
this.wallet = wallets[0];
},
);
}

async deploySpamContract() {
await this.snapshotManager.snapshot(
'add-spam-contract',
async () => {
if (!this.wallet) {
throw new Error('Call snapshot t.setupAccount before deploying account contract');
}

const spamContract = await SpamContract.deploy(this.wallet).send().deployed();
return { contractAddress: spamContract.address };
},
async ({ contractAddress }) => {
if (!this.wallet) {
throw new Error('Call snapshot t.setupAccount before deploying account contract');
}
this.spamContract = await SpamContract.at(contractAddress, this.wallet);
},
);
}

async removeInitialNode() {
await this.snapshotManager.snapshot(
'remove-inital-validator',
Expand Down
130 changes: 130 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/reex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { type AztecNodeService } from '@aztec/aztec-node';
import { type SentTx, sleep } from '@aztec/aztec.js';

/* eslint-disable-next-line no-restricted-imports */
import { BlockProposal, SignatureDomainSeperator, getHashedSignaturePayload } from '@aztec/circuit-types';

import { beforeAll, describe, it, jest } from '@jest/globals';
import fs from 'fs';

import { createNodes } from '../fixtures/setup_p2p_test.js';
import { P2PNetworkTest } from './p2p_network.js';
import { submitComplexTxsTo } from './shared.js';

const NUM_NODES = 4;
const NUM_TXS_PER_NODE = 1;
const BOOT_NODE_UDP_PORT = 41000;

const DATA_DIR = './data/re-ex';

describe('e2e_p2p_reex', () => {
let t: P2PNetworkTest;
let nodes: AztecNodeService[];

beforeAll(async () => {
nodes = [];

t = await P2PNetworkTest.create({
testName: 'e2e_p2p_reex',
numberOfNodes: NUM_NODES,
basePort: BOOT_NODE_UDP_PORT,
});

t.logger.verbose('Setup account');
await t.setupAccount();

t.logger.verbose('Deploy spam contract');
await t.deploySpamContract();

t.logger.verbose('Apply base snapshots');
await t.applyBaseSnapshots();

t.logger.verbose('Setup nodes');
await t.setup();
});

afterAll(async () => {
// shutdown all nodes.
await t.stopNodes(nodes);
await t.teardown();
for (let i = 0; i < NUM_NODES; i++) {
fs.rmSync(`${DATA_DIR}-${i}`, { recursive: true, force: true });
}
});

it('validators should re-execute transactions before attesting', async () => {
// create the bootstrap node for the network
if (!t.bootstrapNodeEnr) {
throw new Error('Bootstrap node ENR is not available');
}

t.ctx.aztecNodeConfig.validatorReexecute = true;

nodes = await createNodes(
t.ctx.aztecNodeConfig,
t.peerIdPrivateKeys,
t.bootstrapNodeEnr,
NUM_NODES,
BOOT_NODE_UDP_PORT,
);

// Hook into the node and intercept re-execution logic, ensuring that it was infact called
const reExecutionSpies = [];
for (const node of nodes) {
// Make sure the nodes submit faulty proposals, in this case a faulty proposal is one where we remove one of the transactions
// Such that the calculated archive will be different!
jest.spyOn((node as any).p2pClient, 'broadcastProposal').mockImplementation(async (...args: unknown[]) => {
// We remove one of the transactions, therefore the block root will be different!
const proposal = args[0] as BlockProposal;
const { txHashes } = proposal.payload;

// We need to mutate the proposal, so we cast to any
(proposal.payload as any).txHashes = txHashes.slice(0, txHashes.length - 1);

// We sign over the proposal using the node's signing key
// Abusing javascript to access the nodes signing key
const signer = (node as any).sequencer.sequencer.validatorClient.validationService.keyStore;
const newProposal = new BlockProposal(
proposal.payload,
await signer.signMessage(getHashedSignaturePayload(proposal.payload, SignatureDomainSeperator.blockProposal)),
);

return (node as any).p2pClient.p2pService.propagate(newProposal);
});

// Store re-execution spys node -> sequencer Client -> seqeuncer -> validator
const spy = jest.spyOn((node as any).sequencer.sequencer.validatorClient, 'reExecuteTransactions');
reExecutionSpies.push(spy);
}

// wait a bit for peers to discover each other
await sleep(4000);

nodes.forEach(node => {
node.getSequencer()?.updateSequencerConfig({
minTxsPerBlock: NUM_TXS_PER_NODE,
maxTxsPerBlock: NUM_TXS_PER_NODE,
});
});
const txs = await submitComplexTxsTo(t.logger, t.spamContract!, NUM_TXS_PER_NODE);

// We ensure that the transactions are NOT mined
try {
await Promise.all(
txs.map(async (tx: SentTx, i: number) => {
t.logger.info(`Waiting for tx ${i}: ${await tx.getTxHash()} to be mined`);
return tx.wait();
}),
);
} catch (e) {
t.logger.info('Failed to mine all txs, as planned');
}

// Expect that all of the re-execution attempts failed with an invalid root
for (const spy of reExecutionSpies) {
for (const result of spy.mock.results) {
await expect(result.value).rejects.toThrow('Validator Error: Re-execution state mismatch');
}
}
});
});
27 changes: 26 additions & 1 deletion yarn-project/end-to-end/src/e2e_p2p/shared.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AztecNodeService } from '@aztec/aztec-node';
import { type DebugLogger } from '@aztec/aztec.js';
import { type DebugLogger, type SentTx } from '@aztec/aztec.js';
import { CompleteAddress, TxStatus } from '@aztec/aztec.js';
import { Fr, GrumpkinScalar } from '@aztec/foundation/fields';
import { type SpamContract } from '@aztec/noir-contracts.js';
import { type PXEService, createPXEService, getPXEServiceConfig as getRpcConfig } from '@aztec/pxe';

import { type NodeContext } from '../fixtures/setup_p2p_test.js';

// submits a set of transactions to the provided Private eXecution Environment (PXE)
export const submitComplexTxsTo = async (logger: DebugLogger, spamContract: SpamContract, numTxs: number) => {
const txs: SentTx[] = [];

const seed = 1234n;
const spamCount = 15;
for (let i = 0; i < numTxs; i++) {
const tx = spamContract.methods.spam(seed + BigInt(i * spamCount), spamCount, false).send();
const txHash = await tx.getTxHash();

logger.info(`Tx sent with hash ${txHash}`);
const receipt = await tx.getReceipt();
expect(receipt).toEqual(
expect.objectContaining({
status: TxStatus.PENDING,
error: '',
}),
);
logger.info(`Receipt received for ${txHash}`);
txs.push(tx);
}
return txs;
};

// creates an instance of the PXE and submit a given number of transactions to it.
export const createPXEServiceAndSubmitTransactions = async (
logger: DebugLogger,
Expand Down
1 change: 0 additions & 1 deletion yarn-project/end-to-end/src/e2e_synching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*
* To run the Setup run with the `AZTEC_GENERATE_TEST_DATA=1` flag. Without
* this flag, we will run in execution.
*
* There is functionality to store the `stats` of a sync, but currently we
* will simply be writing it to the log instead.
*
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/fixtures/snapshot_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ async function setupFromFresh(
opts: SetupOptions = {},
deployL1ContractsArgs: Partial<DeployL1ContractsArgs> = {
assumeProvenThrough: Number.MAX_SAFE_INTEGER,
initialValidators: [],
},
): Promise<SubsystemsContext> {
logger.verbose(`Initializing state...`);
Expand Down Expand Up @@ -390,7 +391,6 @@ async function setupFromFresh(
async function setupFromState(statePath: string, logger: Logger): Promise<SubsystemsContext> {
logger.verbose(`Initializing with saved state at ${statePath}...`);

// Load config.
// TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing.
const aztecNodeConfig: AztecNodeConfig & SetupOptions = JSON.parse(
readFileSync(`${statePath}/aztec_node_config.json`, 'utf-8'),
Expand Down
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export type EnvVar =
| 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS'
| 'VALIDATOR_DISABLED'
| 'VALIDATOR_PRIVATE_KEY'
| 'VALIDATOR_REEXECUTE'
| 'VERSION'
| 'WS_BLOCK_CHECK_INTERVAL_MS'
| 'WS_PROVEN_BLOCKS_ONLY'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export async function buildBaseRollupHints(
padArrayEnd(tx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
NULLIFIER_SUBTREE_HEIGHT,
);

if (nullifierWitnessLeaves === undefined) {
throw new Error(`Could not craft nullifier batch insertion proofs`);
}
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/sequencer-client/src/block_builder/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class LightweightBlockBuilder implements BlockBuilder {
constructor(private db: MerkleTreeWriteOperations, private telemetry: TelemetryClient) {}

async startNewBlock(numTxs: number, globalVariables: GlobalVariables, l1ToL2Messages: Fr[]): Promise<void> {
this.logger.verbose('Starting new block', { numTxs, globalVariables, l1ToL2Messages });
this.logger.verbose('Starting new block', { numTxs, globalVariables: globalVariables.toJSON(), l1ToL2Messages });
this.numTxs = numTxs;
this.globalVariables = globalVariables;
this.l1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
Expand Down
1 change: 1 addition & 0 deletions yarn-project/sequencer-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './publisher/index.js';
export * from './sequencer/index.js';

// Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
// ISSUE(#9832)
export * from './global_variable_builder/index.js';
Loading

0 comments on commit 2e58f0a

Please sign in to comment.