Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions barretenberg/cpp/src/barretenberg/world_state/world_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,16 +587,6 @@ WorldStateStatusFull WorldState::sync_block(const StateReference& block_state_re
const std::vector<crypto::merkle_tree::PublicDataLeafValue>& public_writes)
{
validate_trees_are_equally_synched();
WorldStateStatusFull status;
if (is_same_state_reference(WorldStateRevision::uncommitted(), block_state_ref) &&
is_archive_tip(WorldStateRevision::uncommitted(), block_header_hash)) {
std::pair<bool, std::string> result = commit(status);
if (!result.first) {
throw std::runtime_error(result.second);
}
populate_status_summary(status);
return status;
}
rollback();

Fork::SharedPtr fork = retrieve_fork(CANONICAL_FORK_ID);
Expand Down Expand Up @@ -658,22 +648,32 @@ WorldStateStatusFull WorldState::sync_block(const StateReference& block_state_re

signal.wait_for_level();

if (!success) {
throw std::runtime_error("Failed to sync block: " + err_message);
}
// Check resulting state and commit if successful
WorldStateStatusFull status;
try {
if (!success) {
throw std::runtime_error("Failed to sync block: " + err_message);
}

if (!is_archive_tip(WorldStateRevision::uncommitted(), block_header_hash)) {
throw std::runtime_error("Can't synch block: block header hash is not the tip of the archive tree");
}
if (!is_archive_tip(WorldStateRevision::uncommitted(), block_header_hash)) {
throw std::runtime_error("Can't synch block: block header hash is not the tip of the archive tree");
}

if (!is_same_state_reference(WorldStateRevision::uncommitted(), block_state_ref)) {
throw std::runtime_error("Can't synch block: block state does not match world state");
}
if (!is_same_state_reference(WorldStateRevision::uncommitted(), block_state_ref)) {
throw std::runtime_error("Can't synch block: block state does not match world state");
}

std::pair<bool, std::string> result = commit(status);
if (!result.first) {
throw std::runtime_error(result.second);
std::pair<bool, std::string> result = commit(status);
if (!result.first) {
throw std::runtime_error(result.second);
}
} catch (const std::exception& e) {
// We failed, rollback any uncommitted state before leaving
rollback();
throw;
}

// Success return the status
populate_status_summary(status);
return status;
}
Expand Down Expand Up @@ -726,6 +726,9 @@ WorldStateStatusSummary WorldState::set_finalized_blocks(const block_number_t& t
}
WorldStateStatusFull WorldState::unwind_blocks(const block_number_t& toBlockNumber)
{
// Ensure no uncommitted state
rollback();

WorldStateRevision revision{ .forkId = CANONICAL_FORK_ID, .blockNumber = 0, .includeUncommitted = false };
std::array<TreeMeta, NUM_TREES> responses;
get_all_tree_info(revision, responses);
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AccountWalletWithSecretKey, AztecAddress, Fr } from '@aztec/aztec.js';
import { AccountWalletWithSecretKey, AztecAddress, Fr, type Logger } from '@aztec/aztec.js';
import { makeTuple } from '@aztec/foundation/array';
import { timesParallel } from '@aztec/foundation/collection';
import type { Tuple } from '@aztec/foundation/serialize';
Expand All @@ -20,17 +20,21 @@ describe('Logs', () => {
let account1Address: AztecAddress;
let account2Address: AztecAddress;

let log: Logger;
let teardown: () => Promise<void>;

beforeAll(async () => {
({
teardown,
wallets: [wallet1, wallet2],
accounts: [account1Address, account2Address],
logger: log,
} = await setup(2));

log.warn(`Setup complete, checking account contracts published`);
await ensureAccountContractsPublished(wallet1, [wallet1, wallet2]);

log.warn(`Deploying test contract`);
testLogContract = await TestLogContract.deploy(wallet1).send({ from: account1Address }).deployed();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Fr } from '@aztec/foundation/fields';
import { mockTx, mockTxForRollup } from '@aztec/stdlib/testing';
import { type AnyTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX, TX_ERROR_EXISTING_NULLIFIER } from '@aztec/stdlib/tx';

Expand Down Expand Up @@ -27,8 +28,8 @@ describe('DoubleSpendTxValidator', () => {
numberOfNonRevertiblePublicCallRequests: 1,
numberOfRevertiblePublicCallRequests: 0,
});
badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[1] =
badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[0];
const nullifiers = badTx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers;
nullifiers[1] = new Fr(nullifiers[0].toBigInt());
await expectInvalid(badTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX);
});

Expand All @@ -38,8 +39,8 @@ describe('DoubleSpendTxValidator', () => {
numberOfRevertiblePublicCallRequests: 1,
numberOfRevertibleNullifiers: 1,
});
badTx.data.forPublic!.revertibleAccumulatedData.nullifiers[1] =
badTx.data.forPublic!.revertibleAccumulatedData.nullifiers[0];
const nullifiers = badTx.data.forPublic!.revertibleAccumulatedData.nullifiers;
nullifiers[1] = new Fr(nullifiers[0].toBigInt());
await expectInvalid(badTx, TX_ERROR_DUPLICATE_NULLIFIER_IN_TX);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;

// Ditch this tx if it has repeated nullifiers
const uniqueNullifiers = new Set(nullifiers);
const uniqueNullifiers = new Set(nullifiers.map(n => n.toBigInt()));
if (uniqueNullifiers.size !== nullifiers.length) {
this.#log.verbose(`Rejecting tx ${'txHash' in tx ? tx.txHash : tx.hash} for emitting duplicate nullifiers`);
return { result: 'invalid', reason: [TX_ERROR_DUPLICATE_NULLIFIER_IN_TX] };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
StateReference,
Tx,
TxExecutionPhase,
type TxValidator,
makeProcessedTxFromPrivateOnlyTx,
makeProcessedTxFromTxWithPublicCalls,
} from '@aztec/stdlib/tx';
Expand Down Expand Up @@ -366,10 +365,7 @@ export class PublicProcessor implements Traceable {
return [processedTx, returnValues ?? []];
}

private async doTreeInsertionsForPrivateOnlyTx(
processedTx: ProcessedTx,
txValidator?: TxValidator<ProcessedTx>,
): Promise<void> {
private async doTreeInsertionsForPrivateOnlyTx(processedTx: ProcessedTx): Promise<void> {
const treeInsertionStart = process.hrtime.bigint();

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

const treeInsertionEnd = process.hrtime.bigint();
Expand Down
34 changes: 26 additions & 8 deletions yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
import { EthAddress } from '@aztec/foundation/eth-address';
import { sleep } from '@aztec/foundation/sleep';
import { L2Block, type L2BlockSourceEventEmitter, L2BlockSourceEvents } from '@aztec/stdlib/block';
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
import type {
BuildBlockResult,
IFullNodeBlockBuilder,
Expand All @@ -28,6 +29,10 @@ describe('EpochPruneWatcher', () => {
let txProvider: MockProxy<Pick<ITxProvider, 'getAvailableTxs'>>;
let blockBuilder: MockProxy<IFullNodeBlockBuilder>;
let fork: MockProxy<MerkleTreeWriteOperations>;

let ts: bigint;
let l1Constants: L1RollupConstants;

const validEpochPrunedPenalty = BigInt(1000000000000000000n);
const dataWithholdingPenalty = BigInt(2000000000000000000n);

Expand All @@ -41,6 +46,18 @@ describe('EpochPruneWatcher', () => {
fork = mock<MerkleTreeWriteOperations>();
blockBuilder.getFork.mockResolvedValue(fork);

ts = BigInt(Math.ceil(Date.now() / 1000));
l1Constants = {
l1StartBlock: 1n,
l1GenesisTime: ts,
slotDuration: 24,
epochDuration: 8,
ethereumSlotDuration: 12,
proofSubmissionEpochs: 1,
};

epochCache.getL1Constants.mockReturnValue(l1Constants);

watcher = new EpochPruneWatcher(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, {
slashPrunePenalty: validEpochPrunedPenalty,
slashDataWithholdingPenalty: dataWithholdingPenalty,
Expand All @@ -54,9 +71,10 @@ describe('EpochPruneWatcher', () => {

it('should emit WANT_TO_SLASH_EVENT when a validator is in a pruned epoch when data is unavailable', async () => {
const emitSpy = jest.spyOn(watcher, 'emit');
const epochNumber = 1n;

const block = await L2Block.random(
1, // block number
12, // block number
4, // txs per block
);
txProvider.getAvailableTxs.mockResolvedValue({ txs: [], missingTxs: [block.body.txEffects[0].txHash] });
Expand All @@ -68,11 +86,11 @@ describe('EpochPruneWatcher', () => {
epochCache.getCommitteeForEpoch.mockResolvedValue({
committee: committee.map(EthAddress.fromString),
seed: 0n,
epoch: 1n,
epoch: epochNumber,
});

l2BlockSource.emit(L2BlockSourceEvents.L2PruneDetected, {
epochNumber: 1n,
epochNumber,
blocks: [block],
type: L2BlockSourceEvents.L2PruneDetected,
});
Expand All @@ -85,13 +103,13 @@ describe('EpochPruneWatcher', () => {
validator: EthAddress.fromString(committee[0]),
amount: dataWithholdingPenalty,
offenseType: OffenseType.DATA_WITHHOLDING,
epochOrSlot: 1n,
epochOrSlot: epochNumber,
},
{
validator: EthAddress.fromString(committee[1]),
amount: dataWithholdingPenalty,
offenseType: OffenseType.DATA_WITHHOLDING,
epochOrSlot: 1n,
epochOrSlot: epochNumber,
},
] satisfies WantToSlashArgs[]);
});
Expand All @@ -100,7 +118,7 @@ describe('EpochPruneWatcher', () => {
const emitSpy = jest.spyOn(watcher, 'emit');

const block = await L2Block.random(
1, // block number
12, // block number
4, // txs per block
);
const tx = Tx.random();
Expand Down Expand Up @@ -152,11 +170,11 @@ describe('EpochPruneWatcher', () => {
const emitSpy = jest.spyOn(watcher, 'emit');

const blockFromL1 = await L2Block.random(
1, // block number
12, // block number
1, // txs per block
);
const blockFromBuilder = await L2Block.random(
2, // block number
13, // block number
1, // txs per block
);
const tx = Tx.random();
Expand Down
10 changes: 8 additions & 2 deletions yarn-project/slasher/src/watchers/epoch_prune_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type L2BlockSourceEventEmitter,
L2BlockSourceEvents,
} from '@aztec/stdlib/block';
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
import type {
IFullNodeBlockBuilder,
ITxProvider,
Expand Down Expand Up @@ -78,9 +79,14 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter

private handlePruneL2Blocks(event: L2BlockPruneEvent): void {
const { blocks, epochNumber } = event;
this.log.info(`Detected chain prune. Validating epoch ${epochNumber}`);
const l1Constants = this.epochCache.getL1Constants();
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber);
this.log.info(
`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
{ blocks: epochBlocks.map(b => b.toBlockInfo()) },
);

this.validateBlocks(blocks)
this.validateBlocks(epochBlocks)
.then(async () => {
this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
const validators = await this.getValidatorsForEpoch(epochNumber);
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/stdlib/src/p2p/consensus_payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export class ConsensusPayload implements Signable {
return serializeToBuffer([this.header, this.archive, this.stateReference]);
}

public equals(other: ConsensusPayload): boolean {
return (
this.header.equals(other.header) &&
this.archive.equals(other.archive) &&
this.stateReference.equals(other.stateReference)
);
}

static fromBuffer(buf: Buffer | BufferReader): ConsensusPayload {
const reader = BufferReader.asReader(buf);
const payload = new ConsensusPayload(
Expand Down Expand Up @@ -102,6 +110,14 @@ export class ConsensusPayload implements Signable {
return this.size;
}

toInspect() {
return {
header: this.header.toInspect(),
archive: this.archive.toString(),
stateReference: this.stateReference.toInspect(),
};
}

toString() {
return `header: ${this.header.toString()}, archive: ${this.archive.toString()}, stateReference: ${this.stateReference.l1ToL2MessageTree.root.toString()}`;
}
Expand Down
13 changes: 13 additions & 0 deletions yarn-project/stdlib/src/tx/proposed_block_header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export class ProposedBlockHeader {
);
}

equals(other: ProposedBlockHeader): boolean {
return (
this.lastArchiveRoot.equals(other.lastArchiveRoot) &&
this.contentCommitment.equals(other.contentCommitment) &&
this.slotNumber.equals(other.slotNumber) &&
this.timestamp === other.timestamp &&
this.coinbase.equals(other.coinbase) &&
this.feeRecipient.equals(other.feeRecipient) &&
this.gasFees.equals(other.gasFees) &&
this.totalManaUsed.equals(other.totalManaUsed)
);
}

toBuffer() {
// Note: The order here must match the order in the ProposedHeaderLib solidity library.
return serializeToBuffer([
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/stdlib/src/tx/state_reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,6 @@ export class StateReference {
}

public equals(other: this): boolean {
return this.l1ToL2MessageTree.root.equals(other.l1ToL2MessageTree.root) && this.partial.equals(other.partial);
return this.l1ToL2MessageTree.equals(other.l1ToL2MessageTree) && this.partial.equals(other.partial);
}
}
28 changes: 19 additions & 9 deletions yarn-project/telemetry-client/src/nodejs_metrics_monitor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Observable } from '@opentelemetry/api';
import { type EventLoopUtilization, type IntervalHistogram, monitorEventLoopDelay, performance } from 'node:perf_hooks';

import * as Attributes from './attributes.js';
Expand Down Expand Up @@ -142,17 +143,26 @@ export class NodejsMetricsMonitor {
// - https://youtu.be/WetXnEPraYM
obs.observe(this.eventLoopUilization, delta.utilization);

this.eventLoopTime.add(Math.floor(delta.idle), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'idle' });
this.eventLoopTime.add(Math.floor(delta.active), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'active' });
this.eventLoopTime.add(Math.trunc(delta.idle), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'idle' });
this.eventLoopTime.add(Math.trunc(delta.active), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'active' });

obs.observe(this.eventLoopDelayGauges.min, Math.floor(this.eventLoopDelay.min));
obs.observe(this.eventLoopDelayGauges.mean, Math.floor(this.eventLoopDelay.mean));
obs.observe(this.eventLoopDelayGauges.max, Math.floor(this.eventLoopDelay.max));
obs.observe(this.eventLoopDelayGauges.stddev, Math.floor(this.eventLoopDelay.stddev));
obs.observe(this.eventLoopDelayGauges.p50, Math.floor(this.eventLoopDelay.percentile(50)));
obs.observe(this.eventLoopDelayGauges.p90, Math.floor(this.eventLoopDelay.percentile(90)));
obs.observe(this.eventLoopDelayGauges.p99, Math.floor(this.eventLoopDelay.percentile(99)));
safeObserveInt(obs, this.eventLoopDelayGauges.min, this.eventLoopDelay.min);
safeObserveInt(obs, this.eventLoopDelayGauges.mean, this.eventLoopDelay.mean);
safeObserveInt(obs, this.eventLoopDelayGauges.max, this.eventLoopDelay.max);
safeObserveInt(obs, this.eventLoopDelayGauges.stddev, this.eventLoopDelay.stddev);
safeObserveInt(obs, this.eventLoopDelayGauges.p50, this.eventLoopDelay.percentile(50));
safeObserveInt(obs, this.eventLoopDelayGauges.p90, this.eventLoopDelay.percentile(90));
safeObserveInt(obs, this.eventLoopDelayGauges.p99, this.eventLoopDelay.percentile(99));

this.eventLoopDelay.reset();
};
}

function safeObserveInt(observer: BatchObservableResult, metric: Observable, value: number, attrs?: object) {
// discard NaN, Infinity, -Infinity
if (!Number.isFinite(value)) {
return;
}

observer.observe(metric, Math.trunc(value), attrs);
}
Loading