From e2d78293d95cec0bc80216058df3712268d9af6f Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:32:12 +0000 Subject: [PATCH 01/29] feat: delete attestations older than a slot --- yarn-project/foundation/src/config/env_var.ts | 1 + .../p2p/src/client/p2p_client.test.ts | 1 + yarn-project/p2p/src/client/p2p_client.ts | 16 +++++++++- yarn-project/p2p/src/config.ts | 9 ++++++ .../attestation_pool/attestation_pool.ts | 10 ++++++ .../memory_attestation_pool.test.ts | 31 +++++++++++++++++++ .../memory_attestation_pool.ts | 21 +++++++++++++ .../reqresp/reqresp.integration.test.ts | 1 + 8 files changed, 89 insertions(+), 1 deletion(-) diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index ce7c17fb3ef..9dc10da7611 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -91,6 +91,7 @@ export type EnvVar = | 'P2P_TCP_LISTEN_ADDR' | 'P2P_TCP_ANNOUNCE_ADDR' | 'P2P_TX_POOL_KEEP_PROVEN_FOR' + | 'P2P_ATTESTATION_POOL_KEEP_FOR' | 'P2P_TX_PROTOCOL' | 'P2P_UDP_ANNOUNCE_ADDR' | 'P2P_UDP_LISTEN_ADDR' diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index b81929903a2..b277a21509a 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -58,6 +58,7 @@ describe('In-Memory P2P Client', () => { addAttestations: jest.fn(), deleteAttestations: jest.fn(), deleteAttestationsForSlot: jest.fn(), + deleteAttestationsOlderThan: jest.fn(), getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }; diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index dfc74254bd9..9fcb8dd1f69 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -202,6 +202,9 @@ export class P2PClient extends WithTracer implements P2P { private attestationPool: AttestationPool; private epochProofQuotePool: EpochProofQuotePool; + /** How many slots to keep attestations for. */ + private keepAttestationsInPoolFor: number; + private blockStream; /** @@ -224,7 +227,10 @@ export class P2PClient extends WithTracer implements P2P { ) { super(telemetry, 'P2PClient'); - const { blockCheckIntervalMS, blockRequestBatchSize } = getP2PConfigFromEnv(); + const { blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } = + getP2PConfigFromEnv(); + + this.keepAttestationsInPoolFor = keepAttestationsInPoolFor; this.blockStream = new L2BlockStream(l2BlockSource, this, this, { batchSize: blockRequestBatchSize, @@ -615,6 +621,7 @@ export class P2PClient extends WithTracer implements P2P { const firstBlockNum = blocks[0].number; const lastBlockNum = blocks[blocks.length - 1].number; + const lastBlockSlot = blocks[blocks.length - 1].header.globalVariables.slotNumber.toBigInt(); if (this.keepProvenTxsFor === 0) { await this.deleteTxsFromBlocks(blocks); @@ -626,12 +633,19 @@ export class P2PClient extends WithTracer implements P2P { await this.deleteTxsFromBlocks(blocksToDeleteTxsFrom); } + // We delete attestations older than the last block slot minus the number of slots we want to keep in the pool. + if (lastBlockSlot - BigInt(this.keepAttestationsInPoolFor) >= BigInt(INITIAL_L2_BLOCK_NUM)) { + await this.attestationPool.deleteAttestationsOlderThan(lastBlockSlot - BigInt(this.keepAttestationsInPoolFor)); + } + await this.synchedProvenBlockNumber.set(lastBlockNum); this.log.debug(`Synched to proven block ${lastBlockNum}`); const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); if (provenEpochNumber !== undefined) { this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); } + + await this.startServiceIfSynched(); } diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index 7cff1711b48..b16f0f46543 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -91,6 +91,10 @@ export interface P2PConfig extends P2PReqRespConfig { /** How many blocks have to pass after a block is proven before its txs are deleted (zero to delete immediately once proven) */ keepProvenTxsInPoolFor: number; + /** How many slots to keep attestations for. */ + keepAttestationsInPoolFor: number; + + /** * The interval of the gossipsub heartbeat to perform maintenance tasks. */ @@ -229,6 +233,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = { 'How many blocks have to pass after a block is proven before its txs are deleted (zero to delete immediately once proven)', ...numberConfigHelper(0), }, + keepAttestationsInPoolFor: { + env: 'P2P_ATTESTATION_POOL_KEEP_FOR', + description: 'How many slots to keep attestations for.', + ...numberConfigHelper(96), + }, gossipsubInterval: { env: 'P2P_GOSSIPSUB_INTERVAL_MS', description: 'The interval of the gossipsub heartbeat to perform maintenance tasks.', diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts index cdfe8729911..5c57e85b87b 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts @@ -21,6 +21,16 @@ export interface AttestationPool { */ deleteAttestations(attestations: BlockAttestation[]): Promise<void>; + /** + * Delete Attestations with a slot number smaller than the given slot + * + * Removes all attestations associated with a slot + * + * @param slot - The oldest slot to keep. + */ + deleteAttestationsOlderThan(slot: bigint): Promise<void>; + + /** * Delete Attestations for slot * diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts index b8bb71f30ce..0840f5e7d94 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts @@ -5,6 +5,7 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type MockProxy, mock } from 'jest-mock-extended'; +import { jest } from '@jest/globals'; import { type PoolInstrumentation } from '../instrumentation.js'; import { InMemoryAttestationPool } from './memory_attestation_pool.js'; import { mockAttestation } from './mocks.js'; @@ -30,6 +31,11 @@ describe('MemoryAttestationPool', () => { (ap as any).metrics = metricsMock; }); + const createAttestationsForSlot = (slotNumber: number) => { + const archive = Fr.random(); + return signers.map(signer => mockAttestation(signer, slotNumber, archive)); + }; + it('should add attestations to pool', async () => { const slotNumber = 420; const archive = Fr.random(); @@ -171,4 +177,29 @@ describe('MemoryAttestationPool', () => { const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId); expect(retreivedAttestationsAfterDelete.length).toBe(0); }); + + it('Should delete attestations older than a given slot', async () => { + const slotNumbers = [1, 2, 3, 69, 72, 74, 88, 420]; + const attestations = slotNumbers.map(slotNumber => createAttestationsForSlot(slotNumber)).flat(); + const proposalId = attestations[0].archive.toString(); + + await ap.addAttestations(attestations); + + const attestationsForSlot1 = await ap.getAttestationsForSlot(BigInt(1), proposalId); + expect(attestationsForSlot1.length).toBe(signers.length); + + const deleteAttestationsSpy = jest.spyOn(ap, 'deleteAttestationsForSlot'); + + await ap.deleteAttestationsOlderThan(BigInt(73)); + + const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlot(BigInt(1), proposalId); + expect(attestationsForSlot1AfterDelete.length).toBe(0); + + expect(deleteAttestationsSpy).toHaveBeenCalledTimes(5); + expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(1)); + expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(2)); + expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(3)); + expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(69)); + expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(72)); + }); }); diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts index 9364130c4f8..fa738340c9a 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts @@ -58,6 +58,27 @@ export class InMemoryAttestationPool implements AttestationPool { return total; } + public deleteAttestationsOlderThan(oldestSlot: bigint): Promise<void> { + const olderThan = []; + + // Entries are iterated in insertion order, so we can break as soon as we find a slot that is older than the oldestSlot. + // Note: this will only prune correctly if attestations are added in order of rising slot, it is important that we do not allow + // insertion of attestations that are old. #(https://github.com/AztecProtocol/aztec-packages/issues/10322) + const slots = this.attestations.keys(); + for (const slot of slots) { + if (slot < oldestSlot) { + olderThan.push(slot); + } else { + break; + } + } + + for (const oldSlot of olderThan) { + this.deleteAttestationsForSlot(oldSlot); + } + return Promise.resolve(); + } + public deleteAttestationsForSlot(slot: bigint): Promise<void> { // We count the number of attestations we are removing const numberOfAttestations = this.#getNumberOfAttestationsInSlot(slot); diff --git a/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts b/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts index 3e28c031a0d..57445e9d396 100644 --- a/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts @@ -64,6 +64,7 @@ const makeMockPools = () => { addAttestations: jest.fn(), deleteAttestations: jest.fn(), deleteAttestationsForSlot: jest.fn(), + deleteAttestationsOlderThan: jest.fn(), getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }, epochProofQuotePool: { From 67ea59c5c475fdd0b0dc4be894dac000a95b13e4 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:58:39 +0000 Subject: [PATCH 02/29] fmt --- yarn-project/p2p/src/client/p2p_client.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 9fcb8dd1f69..8e6e86f1761 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -623,6 +623,7 @@ export class P2PClient extends WithTracer implements P2P { const lastBlockNum = blocks[blocks.length - 1].number; const lastBlockSlot = blocks[blocks.length - 1].header.globalVariables.slotNumber.toBigInt(); + // If keepProvenTxsFor is 0, we delete all txs from all proven blocks. if (this.keepProvenTxsFor === 0) { await this.deleteTxsFromBlocks(blocks); } else if (lastBlockNum - this.keepProvenTxsFor >= INITIAL_L2_BLOCK_NUM) { @@ -634,8 +635,9 @@ export class P2PClient extends WithTracer implements P2P { } // We delete attestations older than the last block slot minus the number of slots we want to keep in the pool. - if (lastBlockSlot - BigInt(this.keepAttestationsInPoolFor) >= BigInt(INITIAL_L2_BLOCK_NUM)) { - await this.attestationPool.deleteAttestationsOlderThan(lastBlockSlot - BigInt(this.keepAttestationsInPoolFor)); + const lastBlockSlotMinusKeepAttestationsInPoolFor = lastBlockSlot - BigInt(this.keepAttestationsInPoolFor); + if (lastBlockSlotMinusKeepAttestationsInPoolFor >= BigInt(INITIAL_L2_BLOCK_NUM)) { + await this.attestationPool.deleteAttestationsOlderThan(lastBlockSlotMinusKeepAttestationsInPoolFor); } await this.synchedProvenBlockNumber.set(lastBlockNum); From b95bcef2b6207cf62a8fc1f06704013be3e95b54 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:19:40 +0000 Subject: [PATCH 03/29] test: attestation pool pruning --- yarn-project/p2p/src/client/p2p_client.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index b277a21509a..43b68442c1c 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -327,5 +327,20 @@ describe('In-Memory P2P Client', () => { }); }); - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7971): tests for attestation pool pruning + describe('Attestation pool pruning', () => { + it('deletes attestations older than the number of slots we want to keep in the pool', async () => { + const advanceToProvenBlockNumber = 20; + const keepAttestationsInPoolFor = 12; + + blockSource.setProvenBlockNumber(0); + (client as any).keepAttestationsInPoolFor = keepAttestationsInPoolFor; + await client.start(); + expect(attestationPool.deleteAttestationsOlderThan).not.toHaveBeenCalled(); + + await advanceToProvenBlock(advanceToProvenBlockNumber); + + expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledTimes(1); + expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledWith(BigInt(advanceToProvenBlockNumber - keepAttestationsInPoolFor)); + }); + }); }); From f6ac46631f4c05762d653cdcd45b4d34bda05a01 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:24:21 +0000 Subject: [PATCH 04/29] fmt --- yarn-project/p2p/src/client/p2p_client.test.ts | 4 +++- yarn-project/p2p/src/client/p2p_client.ts | 4 +--- yarn-project/p2p/src/config.ts | 1 - .../p2p/src/mem_pools/attestation_pool/attestation_pool.ts | 1 - .../attestation_pool/memory_attestation_pool.test.ts | 2 +- .../src/mem_pools/attestation_pool/memory_attestation_pool.ts | 4 ++-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 43b68442c1c..219d2caeded 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -340,7 +340,9 @@ describe('In-Memory P2P Client', () => { await advanceToProvenBlock(advanceToProvenBlockNumber); expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledTimes(1); - expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledWith(BigInt(advanceToProvenBlockNumber - keepAttestationsInPoolFor)); + expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledWith( + BigInt(advanceToProvenBlockNumber - keepAttestationsInPoolFor), + ); }); }); }); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 8e6e86f1761..58575d5e599 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -227,8 +227,7 @@ export class P2PClient extends WithTracer implements P2P { ) { super(telemetry, 'P2PClient'); - const { blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } = - getP2PConfigFromEnv(); + const { blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } = getP2PConfigFromEnv(); this.keepAttestationsInPoolFor = keepAttestationsInPoolFor; @@ -647,7 +646,6 @@ export class P2PClient extends WithTracer implements P2P { this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); } - await this.startServiceIfSynched(); } diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index b16f0f46543..3150722a21a 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -94,7 +94,6 @@ export interface P2PConfig extends P2PReqRespConfig { /** How many slots to keep attestations for. */ keepAttestationsInPoolFor: number; - /** * The interval of the gossipsub heartbeat to perform maintenance tasks. */ diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts index 5c57e85b87b..bb7ecb5b704 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts @@ -30,7 +30,6 @@ export interface AttestationPool { */ deleteAttestationsOlderThan(slot: bigint): Promise<void>; - /** * Delete Attestations for slot * diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts index 0840f5e7d94..ef80dad21ec 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.test.ts @@ -3,9 +3,9 @@ import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { jest } from '@jest/globals'; import { type PoolInstrumentation } from '../instrumentation.js'; import { InMemoryAttestationPool } from './memory_attestation_pool.js'; import { mockAttestation } from './mocks.js'; diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts index fa738340c9a..95f9af415cb 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/memory_attestation_pool.ts @@ -58,7 +58,7 @@ export class InMemoryAttestationPool implements AttestationPool { return total; } - public deleteAttestationsOlderThan(oldestSlot: bigint): Promise<void> { + public async deleteAttestationsOlderThan(oldestSlot: bigint): Promise<void> { const olderThan = []; // Entries are iterated in insertion order, so we can break as soon as we find a slot that is older than the oldestSlot. @@ -74,7 +74,7 @@ export class InMemoryAttestationPool implements AttestationPool { } for (const oldSlot of olderThan) { - this.deleteAttestationsForSlot(oldSlot); + await this.deleteAttestationsForSlot(oldSlot); } return Promise.resolve(); } From e67eaef3ac2598f2f799ecb2ea8ddf0edde7547a Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:58:38 +0000 Subject: [PATCH 05/29] feat: epoch cache, do not attest if not in committee or from current proposer --- l1-contracts/src/core/Leonidas.sol | 5 + yarn-project/accounts/package.json | 2 +- .../archiver/src/archiver/archiver.ts | 2 +- .../archiver/src/test/mock_l2_block_source.ts | 3 +- .../circuit-types/src/epoch-helpers/index.ts | 53 +++++++++++ yarn-project/circuit-types/src/index.ts | 1 + yarn-project/circuits.js/package.json | 2 +- yarn-project/epoch-cache/.eslintrc.cjs | 1 + yarn-project/epoch-cache/READMD.md | 3 + yarn-project/epoch-cache/package.json | 93 +++++++++++++++++++ yarn-project/epoch-cache/src/epoch_cache.ts | 85 +++++++++++++++++ yarn-project/epoch-cache/src/index.ts | 1 + yarn-project/epoch-cache/tsconfig.json | 17 ++++ yarn-project/ethereum/src/contracts/rollup.ts | 4 + yarn-project/package.json | 1 + .../validator-client/src/validator.ts | 21 ++++- yarn-project/yarn.lock | 27 +++++- 17 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 yarn-project/circuit-types/src/epoch-helpers/index.ts create mode 100644 yarn-project/epoch-cache/.eslintrc.cjs create mode 100644 yarn-project/epoch-cache/READMD.md create mode 100644 yarn-project/epoch-cache/package.json create mode 100644 yarn-project/epoch-cache/src/epoch_cache.ts create mode 100644 yarn-project/epoch-cache/src/index.ts create mode 100644 yarn-project/epoch-cache/tsconfig.json diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 6602a0085e6..5f03179e55d 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -329,6 +329,11 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { validatorSet.add(_validator); } + // Public view function for get committee at + function getCommitteeAt(Timestamp _ts) external view returns (address[] memory) { + return _getCommitteeAt(_ts); + } + function _getCommitteeAt(Timestamp _ts) internal view returns (address[] memory) { Epoch epochNumber = getEpochAt(_ts); EpochData storage epoch = epochs[epochNumber]; diff --git a/yarn-project/accounts/package.json b/yarn-project/accounts/package.json index d73f4c9ebf3..6af84aee1c9 100644 --- a/yarn-project/accounts/package.json +++ b/yarn-project/accounts/package.json @@ -102,4 +102,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index ff6b9624902..b5f35d534c0 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -67,7 +67,7 @@ import { getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampRangeForEpoch, -} from './epoch_helpers.js'; +} from '@aztec/circuit-types'; import { ArchiverInstrumentation } from './instrumentation.js'; import { type DataRetrieval } from './structs/data_retrieval.js'; import { type L1Published } from './structs/published.js'; diff --git a/yarn-project/archiver/src/test/mock_l2_block_source.ts b/yarn-project/archiver/src/test/mock_l2_block_source.ts index cbd2e3363d3..d819ae3e81b 100644 --- a/yarn-project/archiver/src/test/mock_l2_block_source.ts +++ b/yarn-project/archiver/src/test/mock_l2_block_source.ts @@ -10,8 +10,7 @@ import { import { EthAddress, type Header } from '@aztec/circuits.js'; import { DefaultL1ContractsConfig } from '@aztec/ethereum'; import { createDebugLogger } from '@aztec/foundation/log'; - -import { getSlotRangeForEpoch } from '../archiver/epoch_helpers.js'; +import { getSlotRangeForEpoch } from '@aztec/circuit-types'; /** * A mocked implementation of L2BlockSource to be used in tests. diff --git a/yarn-project/circuit-types/src/epoch-helpers/index.ts b/yarn-project/circuit-types/src/epoch-helpers/index.ts new file mode 100644 index 00000000000..7d78d882027 --- /dev/null +++ b/yarn-project/circuit-types/src/epoch-helpers/index.ts @@ -0,0 +1,53 @@ + +export type EpochConstants = { + l1GenesisBlock: bigint; + l1GenesisTime: bigint; + epochDuration: number; + slotDuration: number; +}; + +/** Returns the slot number for a given timestamp. */ +export function getSlotAtTimestamp(ts: bigint, constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration'>) { + return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(constants.slotDuration); +} + +/** Returns the epoch number for a given timestamp. */ +export function getEpochNumberAtTimestamp( + ts: bigint, + constants: Pick<EpochConstants, 'epochDuration' | 'slotDuration' | 'l1GenesisTime'>, +) { + return getSlotAtTimestamp(ts, constants) / BigInt(constants.epochDuration); +} + +/** Returns the range of L2 slots (inclusive) for a given epoch number. */ +export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<EpochConstants, 'epochDuration'>) { + const startSlot = epochNumber * BigInt(constants.epochDuration); + return [startSlot, startSlot + BigInt(constants.epochDuration) - 1n]; +} + +/** Returns the range of L1 timestamps (inclusive) for a given epoch number. */ +export function getTimestampRangeForEpoch( + epochNumber: bigint, + constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>, +) { + const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, constants); + return [ + constants.l1GenesisTime + startSlot * BigInt(constants.slotDuration), + constants.l1GenesisTime + endSlot * BigInt(constants.slotDuration), + ]; +} + +/** + * Returns the range of L1 blocks (inclusive) for a given epoch number. + * @remarks This assumes no time warp has happened. + */ +export function getL1BlockRangeForEpoch( + epochNumber: bigint, + constants: Pick<EpochConstants, 'l1GenesisBlock' | 'epochDuration' | 'slotDuration'>, +) { + const epochDurationInL1Blocks = BigInt(constants.epochDuration) * BigInt(constants.slotDuration); + return [ + epochNumber * epochDurationInL1Blocks + constants.l1GenesisBlock, + (epochNumber + 1n) * epochDurationInL1Blocks + constants.l1GenesisBlock - 1n, + ]; +} diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index 829e43cf8e3..87db2e5e2dd 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -25,3 +25,4 @@ export * from './tx_execution_request.js'; export * from './in_block.js'; export * from './nullifier_with_block_source.js'; export * from './proving_error.js'; +export * from './epoch-helpers/index.js'; diff --git a/yarn-project/circuits.js/package.json b/yarn-project/circuits.js/package.json index 9406e50c6ee..009150b8807 100644 --- a/yarn-project/circuits.js/package.json +++ b/yarn-project/circuits.js/package.json @@ -99,4 +99,4 @@ ] ] } -} \ No newline at end of file +} diff --git a/yarn-project/epoch-cache/.eslintrc.cjs b/yarn-project/epoch-cache/.eslintrc.cjs new file mode 100644 index 00000000000..e659927475c --- /dev/null +++ b/yarn-project/epoch-cache/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/epoch-cache/READMD.md b/yarn-project/epoch-cache/READMD.md new file mode 100644 index 00000000000..c5a9f339f13 --- /dev/null +++ b/yarn-project/epoch-cache/READMD.md @@ -0,0 +1,3 @@ +## Epoch Cache + +Stores the current validator set. \ No newline at end of file diff --git a/yarn-project/epoch-cache/package.json b/yarn-project/epoch-cache/package.json new file mode 100644 index 00000000000..8f5107f93aa --- /dev/null +++ b/yarn-project/epoch-cache/package.json @@ -0,0 +1,93 @@ +{ + "name": "@aztec/epoch-cache", + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./dest/index.js", + "./test": "./dest/test/index.js", + "./contracts": "./dest/contracts/index.js" + }, + "typedocOptions": { + "entryPoints": [ + "./src/index.ts" + ], + "name": "Epoch Cache", + "tsconfig": "./tsconfig.json" + }, + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint ./src", + "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", + "start:dev": "tsc-watch -p tsconfig.json --onSuccess 'yarn start'", + "start": "node ./dest/index.js", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests" + }, + "inherits": [ + "../package.common.json" + ], + "dependencies": { + "@aztec/circuit-types": "workspace:*", + "@aztec/ethereum": "workspace:*", + "@aztec/foundation": "workspace:^", + "@aztec/l1-artifacts": "workspace:^", + "@viem/anvil": "^0.0.10", + "dotenv": "^16.0.3", + "get-port": "^7.1.0", + "tslib": "^2.4.0", + "viem": "^2.7.15", + "zod": "^3.23.8" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.14.6", + "jest": "^29.5.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "types": "./dest/index.d.ts", + "jest": { + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src", + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest", + { + "jsc": { + "parser": { + "syntax": "typescript", + "decorators": true + }, + "transform": { + "decoratorVersion": "2022-03" + } + } + } + ] + }, + "extensionsToTreatAsEsm": [ + ".ts" + ], + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ] + }, + "engines": { + "node": ">=18" + } +} diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts new file mode 100644 index 00000000000..677c4b1b577 --- /dev/null +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -0,0 +1,85 @@ + +import { getEpochNumberAtTimestamp, getSlotNumberAtTimestamp } from "@aztec/circuit-types"; +import { EthAddress } from "@aztec/foundation/eth-address"; +import { HttpTransport, PublicClient, Chain } from "viem"; +import { RollupContract } from "@aztec/ethereum"; + +type EpochAndSlot = { + epoch: bigint; + slot: bigint; + ts: bigint; +} + +export class EpochCache { + private validators: Map<EthAddress, boolean>; + + private currentEpoch: bigint; + + private rollup: RollupContract; + + constructor( + private publicClient: PublicClient<HttpTransport, Chain>, + private rollupAddress: EthAddress, + private l1GenesisTime: bigint, + private aztecEpochDuration: number, + private aztecSlotDuration: number, + ){ + this.validators = new Map<EthAddress, boolean>(); + + this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), { + l1GenesisTime: this.l1GenesisTime, + epochDuration: this.aztecEpochDuration, + slotDuration: this.aztecSlotDuration + }); + + this.rollup = new RollupContract(this.publicClient, this.rollupAddress.toString()); + } + + getEpochAndSlotNow(): EpochAndSlot { + const now = BigInt(Math.floor(Date.now() / 1000)); + return this.getEpochAndSlotAtTimestamp(now); + } + + getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot { + return { + epoch: getEpochNumberAtTimestamp(ts, { + l1GenesisTime: this.l1GenesisTime, + epochDuration: this.aztecEpochDuration, + slotDuration: this.aztecSlotDuration + }), + slot: getSlotNumberAtTimestamp(ts, { + l1GenesisTime: this.l1GenesisTime, + epochDuration: this.aztecEpochDuration, + slotDuration: this.aztecSlotDuration + }), + ts, + } + } + + async getValidatorSet(): Promise<EthAddress[]> { + // If the current epoch has changed, then we need to make a request to update the validator set + const { epoch: currentEpoch, ts } = this.getEpochAndSlotNow(); + + if (currentEpoch !== this.currentEpoch) { + this.currentEpoch = currentEpoch; + const validatorSet = await this.rollup.read.getCommitteeAt(ts); + this.validators = new Map(validatorSet.map((v: `0x${string}`) => [EthAddress.fromString(v), true])); + } + + return Array.from(this.validators.keys()); + } + + async getCurrentValidator(): Promise<EthAddress> { + // Validators are sorted by their index in the committee, and getValidatorSet will cache + // TODO: should we get the timestamp from the underlying l1 node? + const { slot: currentSlot } = this.getEpochAndSlotNow(); + const validators = await this.getValidatorSet(); + + return validators[Number(currentSlot) % validators.length]; + } + + async isInCommittee(validator: EthAddress): Promise<boolean> { + const validators = await this.getValidatorSet(); + return validators.includes(validator); + } +} diff --git a/yarn-project/epoch-cache/src/index.ts b/yarn-project/epoch-cache/src/index.ts new file mode 100644 index 00000000000..2cfec3fa2ae --- /dev/null +++ b/yarn-project/epoch-cache/src/index.ts @@ -0,0 +1 @@ +export * from './epoch_cache.js'; \ No newline at end of file diff --git a/yarn-project/epoch-cache/tsconfig.json b/yarn-project/epoch-cache/tsconfig.json new file mode 100644 index 00000000000..b12e902b946 --- /dev/null +++ b/yarn-project/epoch-cache/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "include": ["src"], + "references": [ + { + "path": "../foundation" + }, + { + "path": "../l1-artifacts" + } + ] +} diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 98e3ac29fe2..29713a08f38 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -44,6 +44,10 @@ export class RollupContract { return this.rollup.read.getCurrentSlot(); } + async getCommitteeAt(timestamp: bigint) { + return this.rollup.read.getCommitteeAt([timestamp]); + } + async getEpochNumber(blockNumber?: bigint) { blockNumber ??= await this.getBlockNumber(); return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]); diff --git a/yarn-project/package.json b/yarn-project/package.json index a1547a4ccda..7d31e95a5c8 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -38,6 +38,7 @@ "docs", "end-to-end", "entrypoints", + "epoch-cache", "ethereum", "foundation", "key-store", diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 7ced2639ab1..c6708e6540d 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -28,6 +28,7 @@ import { import { type ValidatorKeyStore } from './key_store/interface.js'; import { LocalKeyStore } from './key_store/local_key_store.js'; import { ValidatorMetrics } from './metrics.js'; +import { EpochCache } from '@aztec/epoch-cache'; /** * Callback function for building a block @@ -65,7 +66,8 @@ export class ValidatorClient extends WithTracer implements Validator { private blockBuilder?: BlockBuilderCallback = undefined; constructor( - keyStore: ValidatorKeyStore, + private keyStore: ValidatorKeyStore, + private epochCache: EpochCache, private p2pClient: P2P, private config: ValidatorClientConfig, telemetry: TelemetryClient = new NoopTelemetryClient(), @@ -80,7 +82,7 @@ export class ValidatorClient extends WithTracer implements Validator { this.log.verbose('Initialized validator'); } - static new(config: ValidatorClientConfig, p2pClient: P2P, telemetry: TelemetryClient = new NoopTelemetryClient()) { + static new(config: ValidatorClientConfig, epochCache: EpochCache, p2pClient: P2P, telemetry: TelemetryClient = new NoopTelemetryClient()) { if (!config.validatorPrivateKey) { throw new InvalidValidatorPrivateKeyError(); } @@ -88,7 +90,7 @@ export class ValidatorClient extends WithTracer implements Validator { const privateKey = validatePrivateKey(config.validatorPrivateKey); const localKeyStore = new LocalKeyStore(privateKey); - const validator = new ValidatorClient(localKeyStore, p2pClient, config, telemetry); + const validator = new ValidatorClient(localKeyStore, epochCache, p2pClient, config, telemetry); validator.registerBlockProposalHandler(); return validator; } @@ -118,6 +120,19 @@ export class ValidatorClient extends WithTracer implements Validator { } async attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined> { + // Check that I am in the committee + if (!(await this.epochCache.isInCommittee(this.keyStore.getAddress()))) { + this.log.debug(`Not in the committee, skipping attestation`); + return undefined; + } + + // Check that the proposal is from the current validator + const currentValidator = await this.epochCache.getCurrentValidator(); + if (proposal.getSender() !== currentValidator) { + this.log.debug(`Not the current validator, skipping attestation`); + return undefined; + } + // Check that all of the tranasctions in the proposal are available in the tx pool before attesting this.log.verbose(`request to attest`, { archive: proposal.payload.archive.toString(), diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index cea030422b8..b338be6d0b0 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -376,7 +376,7 @@ __metadata: languageName: unknown linkType: soft -"@aztec/circuit-types@workspace:^, @aztec/circuit-types@workspace:circuit-types": +"@aztec/circuit-types@workspace:*, @aztec/circuit-types@workspace:^, @aztec/circuit-types@workspace:circuit-types": version: 0.0.0-use.local resolution: "@aztec/circuit-types@workspace:circuit-types" dependencies: @@ -615,7 +615,30 @@ __metadata: languageName: unknown linkType: soft -"@aztec/ethereum@workspace:^, @aztec/ethereum@workspace:ethereum": +"@aztec/epoch-cache@workspace:epoch-cache": + version: 0.0.0-use.local + resolution: "@aztec/epoch-cache@workspace:epoch-cache" + dependencies: + "@aztec/circuit-types": "workspace:*" + "@aztec/ethereum": "workspace:*" + "@aztec/foundation": "workspace:^" + "@aztec/l1-artifacts": "workspace:^" + "@jest/globals": ^29.5.0 + "@types/jest": ^29.5.0 + "@types/node": ^18.14.6 + "@viem/anvil": ^0.0.10 + dotenv: ^16.0.3 + get-port: ^7.1.0 + jest: ^29.5.0 + ts-node: ^10.9.1 + tslib: ^2.4.0 + typescript: ^5.0.4 + viem: ^2.7.15 + zod: ^3.23.8 + languageName: unknown + linkType: soft + +"@aztec/ethereum@workspace:*, @aztec/ethereum@workspace:^, @aztec/ethereum@workspace:ethereum": version: 0.0.0-use.local resolution: "@aztec/ethereum@workspace:ethereum" dependencies: From 0265e478af34eb65fe517aa945f3a1f9fbc50470 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:35:42 +0000 Subject: [PATCH 06/29] cleanup --- .../archiver/src/archiver/archiver.ts | 18 +----- .../archiver/src/archiver/epoch_helpers.ts | 54 ---------------- .../aztec-node/src/aztec-node/server.ts | 2 +- .../circuit-types/src/epoch-helpers/index.ts | 15 +++++ yarn-project/epoch-cache/src/epoch_cache.ts | 63 +++++++++++-------- yarn-project/epoch-cache/tsconfig.json | 6 ++ yarn-project/ethereum/src/index.ts | 1 + yarn-project/ethereum/src/l1_reader.ts | 6 +- yarn-project/validator-client/package.json | 1 + yarn-project/validator-client/src/factory.ts | 9 ++- .../validator-client/src/validator.test.ts | 11 ++-- .../validator-client/src/validator.ts | 3 +- yarn-project/validator-client/tsconfig.json | 3 + yarn-project/yarn.lock | 3 +- 14 files changed, 87 insertions(+), 108 deletions(-) delete mode 100644 yarn-project/archiver/src/archiver/epoch_helpers.ts diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index b5f35d534c0..29fcf5a9540 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -67,6 +67,8 @@ import { getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampRangeForEpoch, + type L1RollupConstants, + EmptyL1RollupConstants, } from '@aztec/circuit-types'; import { ArchiverInstrumentation } from './instrumentation.js'; import { type DataRetrieval } from './structs/data_retrieval.js'; @@ -1061,19 +1063,3 @@ class ArchiverStoreHelper return this.store.estimateSize(); } } - -type L1RollupConstants = { - l1StartBlock: bigint; - l1GenesisTime: bigint; - slotDuration: number; - epochDuration: number; - ethereumSlotDuration: number; -}; - -const EmptyL1RollupConstants: L1RollupConstants = { - l1StartBlock: 0n, - l1GenesisTime: 0n, - epochDuration: 0, - slotDuration: 0, - ethereumSlotDuration: 0, -}; diff --git a/yarn-project/archiver/src/archiver/epoch_helpers.ts b/yarn-project/archiver/src/archiver/epoch_helpers.ts deleted file mode 100644 index 55fe28e2f0e..00000000000 --- a/yarn-project/archiver/src/archiver/epoch_helpers.ts +++ /dev/null @@ -1,54 +0,0 @@ -// REFACTOR: This file should go in a package lower in the dependency graph. - -export type EpochConstants = { - l1GenesisBlock: bigint; - l1GenesisTime: bigint; - epochDuration: number; - slotDuration: number; -}; - -/** Returns the slot number for a given timestamp. */ -export function getSlotAtTimestamp(ts: bigint, constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration'>) { - return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(constants.slotDuration); -} - -/** Returns the epoch number for a given timestamp. */ -export function getEpochNumberAtTimestamp( - ts: bigint, - constants: Pick<EpochConstants, 'epochDuration' | 'slotDuration' | 'l1GenesisTime'>, -) { - return getSlotAtTimestamp(ts, constants) / BigInt(constants.epochDuration); -} - -/** Returns the range of L2 slots (inclusive) for a given epoch number. */ -export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<EpochConstants, 'epochDuration'>) { - const startSlot = epochNumber * BigInt(constants.epochDuration); - return [startSlot, startSlot + BigInt(constants.epochDuration) - 1n]; -} - -/** Returns the range of L1 timestamps (inclusive) for a given epoch number. */ -export function getTimestampRangeForEpoch( - epochNumber: bigint, - constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>, -) { - const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, constants); - return [ - constants.l1GenesisTime + startSlot * BigInt(constants.slotDuration), - constants.l1GenesisTime + endSlot * BigInt(constants.slotDuration), - ]; -} - -/** - * Returns the range of L1 blocks (inclusive) for a given epoch number. - * @remarks This assumes no time warp has happened. - */ -export function getL1BlockRangeForEpoch( - epochNumber: bigint, - constants: Pick<EpochConstants, 'l1GenesisBlock' | 'epochDuration' | 'slotDuration'>, -) { - const epochDurationInL1Blocks = BigInt(constants.epochDuration) * BigInt(constants.slotDuration); - return [ - epochNumber * epochDurationInL1Blocks + constants.l1GenesisBlock, - (epochNumber + 1n) * epochDurationInL1Blocks + constants.l1GenesisBlock - 1n, - ]; -} diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 57f576c55d3..b3671cd8920 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -170,7 +170,7 @@ export class AztecNodeService implements AztecNode { // start both and wait for them to sync from the block source await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]); - const validatorClient = createValidatorClient(config, p2pClient, telemetry); + const validatorClient = await createValidatorClient(config, config.l1Contracts.rollupAddress, p2pClient, telemetry); // now create the sequencer const sequencer = config.disableValidator diff --git a/yarn-project/circuit-types/src/epoch-helpers/index.ts b/yarn-project/circuit-types/src/epoch-helpers/index.ts index 7d78d882027..833e796f3b5 100644 --- a/yarn-project/circuit-types/src/epoch-helpers/index.ts +++ b/yarn-project/circuit-types/src/epoch-helpers/index.ts @@ -1,3 +1,18 @@ +export type L1RollupConstants = { + l1StartBlock: bigint; + l1GenesisTime: bigint; + slotDuration: number; + epochDuration: number; + ethereumSlotDuration: number; +}; + +export const EmptyL1RollupConstants: L1RollupConstants = { + l1StartBlock: 0n, + l1GenesisTime: 0n, + epochDuration: 0, + slotDuration: 0, + ethereumSlotDuration: 0, +}; export type EpochConstants = { l1GenesisBlock: bigint; diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 677c4b1b577..dce2afa6e42 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -1,8 +1,8 @@ -import { getEpochNumberAtTimestamp, getSlotNumberAtTimestamp } from "@aztec/circuit-types"; +import { getEpochNumberAtTimestamp, getSlotAtTimestamp, type L1RollupConstants, EmptyL1RollupConstants } from "@aztec/circuit-types"; import { EthAddress } from "@aztec/foundation/eth-address"; -import { HttpTransport, PublicClient, Chain } from "viem"; -import { RollupContract } from "@aztec/ethereum"; +import { HttpTransport, PublicClient, Chain, http, createPublicClient } from "viem"; +import { createEthereumChain, getL1ContractsConfigEnvVars, getL1ReaderConfigFromEnv, RollupContract } from "@aztec/ethereum"; type EpochAndSlot = { epoch: bigint; @@ -12,27 +12,44 @@ type EpochAndSlot = { export class EpochCache { private validators: Map<EthAddress, boolean>; - private currentEpoch: bigint; - private rollup: RollupContract; - constructor( - private publicClient: PublicClient<HttpTransport, Chain>, - private rollupAddress: EthAddress, - private l1GenesisTime: bigint, - private aztecEpochDuration: number, - private aztecSlotDuration: number, + private rollup: RollupContract, + private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants, ){ this.validators = new Map<EthAddress, boolean>(); - this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), { - l1GenesisTime: this.l1GenesisTime, - epochDuration: this.aztecEpochDuration, - slotDuration: this.aztecSlotDuration + this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); + } + + // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver + static async create(rollupAddress: EthAddress) { + const l1ReaderConfig = getL1ReaderConfigFromEnv(); + const l1constants = getL1ContractsConfigEnvVars(); + + const chain = createEthereumChain(l1ReaderConfig.l1RpcUrl, l1ReaderConfig.l1ChainId); + const publicClient = createPublicClient({ + chain: chain.chainInfo, + transport: http(chain.rpcUrl), + pollingInterval: l1ReaderConfig.viemPollingIntervalMS, }); - this.rollup = new RollupContract(this.publicClient, this.rollupAddress.toString()); + const rollup = new RollupContract(publicClient, rollupAddress.toString()); + const [l1StartBlock, l1GenesisTime] = await Promise.all([ + rollup.getL1StartBlock(), + rollup.getL1GenesisTime(), + ] as const); + + const l1RollupConstants: L1RollupConstants = { + l1StartBlock, + l1GenesisTime, + slotDuration: l1constants.aztecSlotDuration, + epochDuration: l1constants.aztecEpochDuration, + ethereumSlotDuration: l1constants.ethereumSlotDuration, + } + + return new EpochCache(rollup, l1RollupConstants); } getEpochAndSlotNow(): EpochAndSlot { @@ -42,16 +59,8 @@ export class EpochCache { getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot { return { - epoch: getEpochNumberAtTimestamp(ts, { - l1GenesisTime: this.l1GenesisTime, - epochDuration: this.aztecEpochDuration, - slotDuration: this.aztecSlotDuration - }), - slot: getSlotNumberAtTimestamp(ts, { - l1GenesisTime: this.l1GenesisTime, - epochDuration: this.aztecEpochDuration, - slotDuration: this.aztecSlotDuration - }), + epoch: getEpochNumberAtTimestamp(ts, this.l1constants), + slot: getSlotAtTimestamp(ts, this.l1constants), ts, } } @@ -62,7 +71,7 @@ export class EpochCache { if (currentEpoch !== this.currentEpoch) { this.currentEpoch = currentEpoch; - const validatorSet = await this.rollup.read.getCommitteeAt(ts); + const validatorSet = await this.rollup.getCommitteeAt(ts); this.validators = new Map(validatorSet.map((v: `0x${string}`) => [EthAddress.fromString(v), true])); } diff --git a/yarn-project/epoch-cache/tsconfig.json b/yarn-project/epoch-cache/tsconfig.json index b12e902b946..249d08ef855 100644 --- a/yarn-project/epoch-cache/tsconfig.json +++ b/yarn-project/epoch-cache/tsconfig.json @@ -7,6 +7,12 @@ }, "include": ["src"], "references": [ + { + "path": "../circuit-types" + }, + { + "path": "../ethereum" + }, { "path": "../foundation" }, diff --git a/yarn-project/ethereum/src/index.ts b/yarn-project/ethereum/src/index.ts index 30a990db651..c9716d91bd7 100644 --- a/yarn-project/ethereum/src/index.ts +++ b/yarn-project/ethereum/src/index.ts @@ -6,3 +6,4 @@ export * from './ethereum_chain.js'; export * from './utils.js'; export * from './config.js'; export * from './types.js'; +export * from './contracts/index.js'; diff --git a/yarn-project/ethereum/src/l1_reader.ts b/yarn-project/ethereum/src/l1_reader.ts index f2f48196824..2c6340ba025 100644 --- a/yarn-project/ethereum/src/l1_reader.ts +++ b/yarn-project/ethereum/src/l1_reader.ts @@ -1,4 +1,4 @@ -import { type ConfigMappingsType, numberConfigHelper } from '@aztec/foundation/config'; +import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config'; import { type L1ContractAddresses, l1ContractAddressesMapping } from './l1_contract_addresses.js'; @@ -36,3 +36,7 @@ export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = { ...numberConfigHelper(1_000), }, }; + +export function getL1ReaderConfigFromEnv(): L1ReaderConfig { + return getConfigFromMappings<L1ReaderConfig>(l1ReaderConfigMappings); +} diff --git a/yarn-project/validator-client/package.json b/yarn-project/validator-client/package.json index 7fff0272c51..23cba4d9036 100644 --- a/yarn-project/validator-client/package.json +++ b/yarn-project/validator-client/package.json @@ -61,6 +61,7 @@ "dependencies": { "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", + "@aztec/epoch-cache": "workspace:^", "@aztec/ethereum": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/p2p": "workspace:^", diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 56b5306969b..1712a52cb9e 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -5,8 +5,10 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; +import { EpochCache } from '@aztec/epoch-cache'; +import { EthAddress } from '@aztec/foundation/eth-address'; -export function createValidatorClient(config: ValidatorClientConfig, p2pClient: P2P, telemetry: TelemetryClient) { +export async function createValidatorClient(config: ValidatorClientConfig, rollupAddress: EthAddress, p2pClient: P2P, telemetry: TelemetryClient) { if (config.disableValidator) { return undefined; } @@ -14,5 +16,8 @@ export function createValidatorClient(config: ValidatorClientConfig, p2pClient: config.validatorPrivateKey = generatePrivateKey(); } - return ValidatorClient.new(config, p2pClient, telemetry); + // Create the epoch cache + const epochCache = await EpochCache.create(rollupAddress); + + return ValidatorClient.new(config, epochCache, p2pClient, telemetry); } diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index d60937f2fcc..6b8ce496d90 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -22,12 +22,15 @@ import { TransactionsNotAvailableError, } from './errors/validator.error.js'; import { ValidatorClient } from './validator.js'; +import { EpochCache } from '@aztec/epoch-cache'; describe('ValidationService', () => { let config: ValidatorClientConfig; let validatorClient: ValidatorClient; let p2pClient: MockProxy<P2P>; + let epochCache: MockProxy<EpochCache>; let validatorAccount: PrivateKeyAccount; + let rollupAddress: EthAddress = EthAddress.fromString('0x1234567890123456789012345678901234567890'); beforeEach(() => { p2pClient = mock<P2P>(); @@ -43,12 +46,12 @@ describe('ValidationService', () => { disableValidator: false, validatorReexecute: false, }; - validatorClient = ValidatorClient.new(config, p2pClient, new NoopTelemetryClient()); + validatorClient = ValidatorClient.new(config, epochCache, p2pClient, new NoopTelemetryClient()); }); it('Should throw error if an invalid private key is provided', () => { config.validatorPrivateKey = '0x1234567890123456789'; - expect(() => ValidatorClient.new(config, p2pClient, new NoopTelemetryClient())).toThrow( + expect(() => ValidatorClient.new(config, epochCache, p2pClient, new NoopTelemetryClient())).toThrow( InvalidValidatorPrivateKeyError, ); }); @@ -56,7 +59,7 @@ describe('ValidationService', () => { it('Should throw an error if re-execution is enabled but no block builder is provided', async () => { config.validatorReexecute = true; p2pClient.getTxByHash.mockImplementation(() => Promise.resolve(mockTx())); - const val = ValidatorClient.new(config, p2pClient); + const val = ValidatorClient.new(config, epochCache, p2pClient); await expect(val.reExecuteTransactions(makeBlockProposal())).rejects.toThrow(BlockBuilderNotProvidedError); }); @@ -98,7 +101,7 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); - const val = ValidatorClient.new(config, p2pClient, new NoopTelemetryClient()); + const val = ValidatorClient.new(config, epochCache, p2pClient); val.registerBlockBuilder(() => { throw new Error('Failed to build block'); }); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index c6708e6540d..540ef118ec3 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -6,7 +6,7 @@ import { type Tx, type TxHash, } from '@aztec/circuit-types'; -import { type GlobalVariables, type Header } from '@aztec/circuits.js'; +import { EthAddress, type GlobalVariables, type Header } from '@aztec/circuits.js'; import { Buffer32 } from '@aztec/foundation/buffer'; import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -77,7 +77,6 @@ export class ValidatorClient extends WithTracer implements Validator { super(telemetry, 'Validator'); this.metrics = new ValidatorMetrics(telemetry); - //TODO: We need to setup and store all of the currently active validators https://github.com/AztecProtocol/aztec-packages/issues/7962 this.validationService = new ValidationService(keyStore); this.log.verbose('Initialized validator'); } diff --git a/yarn-project/validator-client/tsconfig.json b/yarn-project/validator-client/tsconfig.json index 17533523097..d2409d81fdb 100644 --- a/yarn-project/validator-client/tsconfig.json +++ b/yarn-project/validator-client/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../circuits.js" }, + { + "path": "../epoch-cache" + }, { "path": "../ethereum" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index b338be6d0b0..f3719ce1d7c 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -615,7 +615,7 @@ __metadata: languageName: unknown linkType: soft -"@aztec/epoch-cache@workspace:epoch-cache": +"@aztec/epoch-cache@workspace:^, @aztec/epoch-cache@workspace:epoch-cache": version: 0.0.0-use.local resolution: "@aztec/epoch-cache@workspace:epoch-cache" dependencies: @@ -1306,6 +1306,7 @@ __metadata: dependencies: "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" + "@aztec/epoch-cache": "workspace:^" "@aztec/ethereum": "workspace:^" "@aztec/foundation": "workspace:^" "@aztec/p2p": "workspace:^" From 6a795ea3b5dc0e21a5302528c55b5f1b71cfd6e4 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:41:43 +0000 Subject: [PATCH 07/29] test: validator client tests --- .../validator-client/src/validator.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 6b8ce496d90..e5d851baf82 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -35,6 +35,7 @@ describe('ValidationService', () => { beforeEach(() => { p2pClient = mock<P2P>(); p2pClient.getAttestationsForSlot.mockImplementation(() => Promise.resolve([])); + epochCache = mock<EpochCache>(); const validatorPrivateKey = generatePrivateKey(); validatorAccount = privateKeyToAccount(validatorPrivateKey); @@ -100,6 +101,8 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); + epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); const val = ValidatorClient.new(config, epochCache, p2pClient); val.registerBlockBuilder(() => { @@ -110,6 +113,28 @@ describe('ValidationService', () => { expect(attestation).toBeUndefined(); }); + it("Should not return an attestation if the validator is not in the committee", async () => { + const proposal = makeBlockProposal(); + + // Setup epoch cache mocks + epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false)); + + const attestation = await validatorClient.attestToProposal(proposal); + expect(attestation).toBeUndefined(); + }); + + it("Should not return an attestation if the proposer is not the current proposer", async () => { + const proposal = makeBlockProposal(); + + // Setup epoch cache mocks + epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(EthAddress.random())); + epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); + + const attestation = await validatorClient.attestToProposal(proposal); + expect(attestation).toBeUndefined(); + }); + it('Should collect attestations for a proposal', async () => { const signer = Secp256k1Signer.random(); const attestor1 = Secp256k1Signer.random(); From 50eaa4cb757f1f9ba61d7b21a70959e258bc5090 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:44:58 +0000 Subject: [PATCH 08/29] fmt --- .../archiver/src/archiver/archiver.ts | 14 +- .../archiver/src/test/mock_l2_block_source.ts | 2 +- .../epoch-cache/src/epoch_cache.test.ts | 10 + yarn-project/epoch-cache/src/epoch_cache.ts | 178 +++++++++--------- yarn-project/epoch-cache/src/index.ts | 2 +- yarn-project/validator-client/src/factory.ts | 2 +- .../validator-client/src/validator.test.ts | 3 +- .../validator-client/src/validator.ts | 4 +- 8 files changed, 116 insertions(+), 99 deletions(-) create mode 100644 yarn-project/epoch-cache/src/epoch_cache.test.ts diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 29fcf5a9540..bf699dcccce 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -1,7 +1,9 @@ import { + EmptyL1RollupConstants, type GetUnencryptedLogsResponse, type InBlock, type InboxLeaf, + type L1RollupConstants, type L1ToL2MessageSource, type L2Block, type L2BlockId, @@ -15,6 +17,10 @@ import { type TxReceipt, type TxScopedL2Log, type UnencryptedL2Log, + getEpochNumberAtTimestamp, + getSlotAtTimestamp, + getSlotRangeForEpoch, + getTimestampRangeForEpoch, } from '@aztec/circuit-types'; import { type ContractClassPublic, @@ -62,14 +68,6 @@ import { import { type ArchiverDataStore, type ArchiverL1SynchPoint } from './archiver_store.js'; import { type ArchiverConfig } from './config.js'; import { retrieveBlockFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js'; -import { - getEpochNumberAtTimestamp, - getSlotAtTimestamp, - getSlotRangeForEpoch, - getTimestampRangeForEpoch, - type L1RollupConstants, - EmptyL1RollupConstants, -} from '@aztec/circuit-types'; import { ArchiverInstrumentation } from './instrumentation.js'; import { type DataRetrieval } from './structs/data_retrieval.js'; import { type L1Published } from './structs/published.js'; diff --git a/yarn-project/archiver/src/test/mock_l2_block_source.ts b/yarn-project/archiver/src/test/mock_l2_block_source.ts index d819ae3e81b..86c120fcb2e 100644 --- a/yarn-project/archiver/src/test/mock_l2_block_source.ts +++ b/yarn-project/archiver/src/test/mock_l2_block_source.ts @@ -7,10 +7,10 @@ import { TxReceipt, TxStatus, } from '@aztec/circuit-types'; +import { getSlotRangeForEpoch } from '@aztec/circuit-types'; import { EthAddress, type Header } from '@aztec/circuits.js'; import { DefaultL1ContractsConfig } from '@aztec/ethereum'; import { createDebugLogger } from '@aztec/foundation/log'; -import { getSlotRangeForEpoch } from '@aztec/circuit-types'; /** * A mocked implementation of L2BlockSource to be used in tests. diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts new file mode 100644 index 00000000000..31e6ecb88b3 --- /dev/null +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -0,0 +1,10 @@ +// import { EpochCache } from "./epoch_cache.js"; + +describe('EpochCache', () => { + it('Should return the current validator', () => { + expect(true).toBe(true); + // const epochCache = await EpochCache.create(EthAddress.random()); + // const currentValidator = await epochCache.getCurrentValidator(); + // expect(currentValidator).toBeDefined(); + }); +}); diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index dce2afa6e42..1c05584a7b2 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -1,94 +1,104 @@ - -import { getEpochNumberAtTimestamp, getSlotAtTimestamp, type L1RollupConstants, EmptyL1RollupConstants } from "@aztec/circuit-types"; -import { EthAddress } from "@aztec/foundation/eth-address"; -import { HttpTransport, PublicClient, Chain, http, createPublicClient } from "viem"; -import { createEthereumChain, getL1ContractsConfigEnvVars, getL1ReaderConfigFromEnv, RollupContract } from "@aztec/ethereum"; +import { + EmptyL1RollupConstants, + type L1RollupConstants, + getEpochNumberAtTimestamp, + getSlotAtTimestamp, +} from '@aztec/circuit-types'; +import { + RollupContract, + createEthereumChain, + getL1ContractsConfigEnvVars, + getL1ReaderConfigFromEnv, +} from '@aztec/ethereum'; +import { EthAddress } from '@aztec/foundation/eth-address'; + +import { createPublicClient, http } from 'viem'; type EpochAndSlot = { - epoch: bigint; - slot: bigint; - ts: bigint; -} + epoch: bigint; + slot: bigint; + ts: bigint; +}; export class EpochCache { - private validators: Map<EthAddress, boolean>; - private currentEpoch: bigint; - - constructor( - private rollup: RollupContract, - private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants, - ){ - this.validators = new Map<EthAddress, boolean>(); - - this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); + private validators: Map<EthAddress, boolean>; + private currentEpoch: bigint; + + constructor( + private rollup: RollupContract, + private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants, + ) { + this.validators = new Map<EthAddress, boolean>(); + + this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); + } + + // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver + static async create(rollupAddress: EthAddress) { + const l1ReaderConfig = getL1ReaderConfigFromEnv(); + const l1constants = getL1ContractsConfigEnvVars(); + + const chain = createEthereumChain(l1ReaderConfig.l1RpcUrl, l1ReaderConfig.l1ChainId); + const publicClient = createPublicClient({ + chain: chain.chainInfo, + transport: http(chain.rpcUrl), + pollingInterval: l1ReaderConfig.viemPollingIntervalMS, + }); + + const rollup = new RollupContract(publicClient, rollupAddress.toString()); + const [l1StartBlock, l1GenesisTime] = await Promise.all([ + rollup.getL1StartBlock(), + rollup.getL1GenesisTime(), + ] as const); + + const l1RollupConstants: L1RollupConstants = { + l1StartBlock, + l1GenesisTime, + slotDuration: l1constants.aztecSlotDuration, + epochDuration: l1constants.aztecEpochDuration, + ethereumSlotDuration: l1constants.ethereumSlotDuration, + }; + + return new EpochCache(rollup, l1RollupConstants); + } + + getEpochAndSlotNow(): EpochAndSlot { + const now = BigInt(Math.floor(Date.now() / 1000)); + return this.getEpochAndSlotAtTimestamp(now); + } + + getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot { + return { + epoch: getEpochNumberAtTimestamp(ts, this.l1constants), + slot: getSlotAtTimestamp(ts, this.l1constants), + ts, + }; + } + + async getValidatorSet(): Promise<EthAddress[]> { + // If the current epoch has changed, then we need to make a request to update the validator set + const { epoch: currentEpoch, ts } = this.getEpochAndSlotNow(); + + if (currentEpoch !== this.currentEpoch) { + this.currentEpoch = currentEpoch; + const validatorSet = await this.rollup.getCommitteeAt(ts); + this.validators = new Map(validatorSet.map((v: `0x${string}`) => [EthAddress.fromString(v), true])); } - // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver - static async create(rollupAddress: EthAddress) { - const l1ReaderConfig = getL1ReaderConfigFromEnv(); - const l1constants = getL1ContractsConfigEnvVars(); - - const chain = createEthereumChain(l1ReaderConfig.l1RpcUrl, l1ReaderConfig.l1ChainId); - const publicClient = createPublicClient({ - chain: chain.chainInfo, - transport: http(chain.rpcUrl), - pollingInterval: l1ReaderConfig.viemPollingIntervalMS, - }); - - const rollup = new RollupContract(publicClient, rollupAddress.toString()); - const [l1StartBlock, l1GenesisTime] = await Promise.all([ - rollup.getL1StartBlock(), - rollup.getL1GenesisTime(), - ] as const); - - const l1RollupConstants: L1RollupConstants = { - l1StartBlock, - l1GenesisTime, - slotDuration: l1constants.aztecSlotDuration, - epochDuration: l1constants.aztecEpochDuration, - ethereumSlotDuration: l1constants.ethereumSlotDuration, - } - - return new EpochCache(rollup, l1RollupConstants); - } - - getEpochAndSlotNow(): EpochAndSlot { - const now = BigInt(Math.floor(Date.now() / 1000)); - return this.getEpochAndSlotAtTimestamp(now); - } + return Array.from(this.validators.keys()); + } - getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot { - return { - epoch: getEpochNumberAtTimestamp(ts, this.l1constants), - slot: getSlotAtTimestamp(ts, this.l1constants), - ts, - } - } + async getCurrentValidator(): Promise<EthAddress> { + // Validators are sorted by their index in the committee, and getValidatorSet will cache + // TODO: should we get the timestamp from the underlying l1 node? + const { slot: currentSlot } = this.getEpochAndSlotNow(); + const validators = await this.getValidatorSet(); - async getValidatorSet(): Promise<EthAddress[]> { - // If the current epoch has changed, then we need to make a request to update the validator set - const { epoch: currentEpoch, ts } = this.getEpochAndSlotNow(); + return validators[Number(currentSlot) % validators.length]; + } - if (currentEpoch !== this.currentEpoch) { - this.currentEpoch = currentEpoch; - const validatorSet = await this.rollup.getCommitteeAt(ts); - this.validators = new Map(validatorSet.map((v: `0x${string}`) => [EthAddress.fromString(v), true])); - } - - return Array.from(this.validators.keys()); - } - - async getCurrentValidator(): Promise<EthAddress> { - // Validators are sorted by their index in the committee, and getValidatorSet will cache - // TODO: should we get the timestamp from the underlying l1 node? - const { slot: currentSlot } = this.getEpochAndSlotNow(); - const validators = await this.getValidatorSet(); - - return validators[Number(currentSlot) % validators.length]; - } - - async isInCommittee(validator: EthAddress): Promise<boolean> { - const validators = await this.getValidatorSet(); - return validators.includes(validator); - } + async isInCommittee(validator: EthAddress): Promise<boolean> { + const validators = await this.getValidatorSet(); + return validators.includes(validator); + } } diff --git a/yarn-project/epoch-cache/src/index.ts b/yarn-project/epoch-cache/src/index.ts index 2cfec3fa2ae..9a016b1d67b 100644 --- a/yarn-project/epoch-cache/src/index.ts +++ b/yarn-project/epoch-cache/src/index.ts @@ -1 +1 @@ -export * from './epoch_cache.js'; \ No newline at end of file +export * from './epoch_cache.js'; diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 1712a52cb9e..3a88f192126 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -6,7 +6,7 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; import { EpochCache } from '@aztec/epoch-cache'; -import { EthAddress } from '@aztec/foundation/eth-address'; +import { type EthAddress } from '@aztec/foundation/eth-address'; export async function createValidatorClient(config: ValidatorClientConfig, rollupAddress: EthAddress, p2pClient: P2P, telemetry: TelemetryClient) { if (config.disableValidator) { diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index e5d851baf82..f9e0d4f2151 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -22,7 +22,7 @@ import { TransactionsNotAvailableError, } from './errors/validator.error.js'; import { ValidatorClient } from './validator.js'; -import { EpochCache } from '@aztec/epoch-cache'; +import { type EpochCache } from '@aztec/epoch-cache'; describe('ValidationService', () => { let config: ValidatorClientConfig; @@ -30,7 +30,6 @@ describe('ValidationService', () => { let p2pClient: MockProxy<P2P>; let epochCache: MockProxy<EpochCache>; let validatorAccount: PrivateKeyAccount; - let rollupAddress: EthAddress = EthAddress.fromString('0x1234567890123456789012345678901234567890'); beforeEach(() => { p2pClient = mock<P2P>(); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 540ef118ec3..5fed2f83e2c 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -6,7 +6,7 @@ import { type Tx, type TxHash, } from '@aztec/circuit-types'; -import { EthAddress, type GlobalVariables, type Header } from '@aztec/circuits.js'; +import { type GlobalVariables, type Header } from '@aztec/circuits.js'; import { Buffer32 } from '@aztec/foundation/buffer'; import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -28,7 +28,7 @@ import { import { type ValidatorKeyStore } from './key_store/interface.js'; import { LocalKeyStore } from './key_store/local_key_store.js'; import { ValidatorMetrics } from './metrics.js'; -import { EpochCache } from '@aztec/epoch-cache'; +import { type EpochCache } from '@aztec/epoch-cache'; /** * Callback function for building a block From 98b118a41bedee84bf88f01bce6557bc6eb5bb9f Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:09:32 +0000 Subject: [PATCH 09/29] fmt + test --- .../circuit-types/src/epoch-helpers/index.ts | 6 +- yarn-project/epoch-cache/package.json | 1 + .../epoch-cache/src/epoch_cache.test.ts | 99 +++++++++++++++++-- yarn-project/epoch-cache/src/epoch_cache.ts | 28 +++--- yarn-project/ethereum/src/contracts/rollup.ts | 4 + .../validator-client/src/validator.ts | 4 + yarn-project/yarn.lock | 1 + 7 files changed, 123 insertions(+), 20 deletions(-) diff --git a/yarn-project/circuit-types/src/epoch-helpers/index.ts b/yarn-project/circuit-types/src/epoch-helpers/index.ts index 833e796f3b5..50c698e1160 100644 --- a/yarn-project/circuit-types/src/epoch-helpers/index.ts +++ b/yarn-project/circuit-types/src/epoch-helpers/index.ts @@ -9,9 +9,9 @@ export type L1RollupConstants = { export const EmptyL1RollupConstants: L1RollupConstants = { l1StartBlock: 0n, l1GenesisTime: 0n, - epochDuration: 0, - slotDuration: 0, - ethereumSlotDuration: 0, + epochDuration: 1, // Not 0 to pervent division by zero + slotDuration: 1, + ethereumSlotDuration: 1, }; export type EpochConstants = { diff --git a/yarn-project/epoch-cache/package.json b/yarn-project/epoch-cache/package.json index 8f5107f93aa..ed86b9c1b59 100644 --- a/yarn-project/epoch-cache/package.json +++ b/yarn-project/epoch-cache/package.json @@ -35,6 +35,7 @@ "@viem/anvil": "^0.0.10", "dotenv": "^16.0.3", "get-port": "^7.1.0", + "jest-mock-extended": "^3.0.7", "tslib": "^2.4.0", "viem": "^2.7.15", "zod": "^3.23.8" diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 31e6ecb88b3..00366a79c23 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -1,10 +1,97 @@ -// import { EpochCache } from "./epoch_cache.js"; +import { type RollupContract } from '@aztec/ethereum'; +import { EthAddress } from '@aztec/foundation/eth-address'; + +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { EpochCache } from './epoch_cache.js'; describe('EpochCache', () => { - it('Should return the current validator', () => { - expect(true).toBe(true); - // const epochCache = await EpochCache.create(EthAddress.random()); - // const currentValidator = await epochCache.getCurrentValidator(); - // expect(currentValidator).toBeDefined(); + let rollupContract: MockProxy<RollupContract>; + let epochCache: EpochCache; + + // Test constants + const SLOT_DURATION = 12; + const EPOCH_DURATION = 32; // 384 seconds + const L1_GENESIS_TIME = 1000n; + + const testValidators = [ + EthAddress.fromString('0x0000000000000000000000000000000000000001'), + EthAddress.fromString('0x0000000000000000000000000000000000000002'), + EthAddress.fromString('0x0000000000000000000000000000000000000003'), + ]; + + const extraTestValidator = EthAddress.fromString('0x0000000000000000000000000000000000000004'); + + beforeEach(() => { + rollupContract = mock<RollupContract>(); + + // Mock the getCommitteeAt method + rollupContract.getCommitteeAt.mockResolvedValue(testValidators.map(v => v.toString())); + + // Setup fake timers + jest.useFakeTimers(); + + // Initialize with test constants + const testConstants = { + l1StartBlock: 0n, + l1GenesisTime: L1_GENESIS_TIME, + slotDuration: SLOT_DURATION, + ethereumSlotDuration: SLOT_DURATION, + epochDuration: EPOCH_DURATION, + }; + + epochCache = new EpochCache(rollupContract, testValidators, testConstants); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should cache the validator set for the length of an epoch', async () => { + // Initial call to get validators + const initialValidators = await epochCache.getValidatorSet(); + expect(initialValidators).toEqual(testValidators); + // Not called as we should cache with the initial validator set + expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(0); + + // Move time forward within the same epoch (less than EPOCH_DURATION) (x 1000 for milliseconds) + jest.setSystemTime(Date.now() + (Number(EPOCH_DURATION * SLOT_DURATION) / 2) * 1000); + + // Add another validator to the set + rollupContract.getCommitteeAt.mockResolvedValue([...testValidators, extraTestValidator].map(v => v.toString())); + + // Should use cached validators + const midEpochValidators = await epochCache.getValidatorSet(); + expect(midEpochValidators).toEqual(testValidators); + expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(0); // Still cached + + // Move time forward to next epoch (x 1000 for milliseconds) + jest.setSystemTime(Date.now() + Number(EPOCH_DURATION * SLOT_DURATION) * 1000); + + // Should fetch new validators + const nextEpochValidators = await epochCache.getValidatorSet(); + expect(nextEpochValidators).toEqual([...testValidators, extraTestValidator]); + expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(1); // Called again for new epoch + }); + + it('should correctly get current validator based on slot number', async () => { + // Set initial time to a known slot + const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds + jest.setSystemTime(initialTime); + + // Get validator for slot 0 + let currentValidator = await epochCache.getCurrentValidator(); + expect(currentValidator).toEqual(testValidators[0]); // First validator for slot 0 + + // Move to next slot + jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); + currentValidator = await epochCache.getCurrentValidator(); + expect(currentValidator).toEqual(testValidators[1]); // Second validator for slot 1 + + // Move to slot that wraps around validator set + jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); + currentValidator = await epochCache.getCurrentValidator(); + expect(currentValidator).toEqual(testValidators[0]); // Back to first validator for slot 3 }); }); diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 1c05584a7b2..64c22de3b45 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -21,16 +21,17 @@ type EpochAndSlot = { }; export class EpochCache { - private validators: Map<EthAddress, boolean>; - private currentEpoch: bigint; + private validators: EthAddress[]; + private cachedEpoch: bigint; constructor( private rollup: RollupContract, + initialValidators: EthAddress[] = [], private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants, ) { - this.validators = new Map<EthAddress, boolean>(); + this.validators = initialValidators; - this.currentEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); + this.cachedEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); } // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver @@ -46,9 +47,10 @@ export class EpochCache { }); const rollup = new RollupContract(publicClient, rollupAddress.toString()); - const [l1StartBlock, l1GenesisTime] = await Promise.all([ + const [l1StartBlock, l1GenesisTime, initialValidators] = await Promise.all([ rollup.getL1StartBlock(), rollup.getL1GenesisTime(), + rollup.getCurrentEpochCommittee(), ] as const); const l1RollupConstants: L1RollupConstants = { @@ -59,7 +61,11 @@ export class EpochCache { ethereumSlotDuration: l1constants.ethereumSlotDuration, }; - return new EpochCache(rollup, l1RollupConstants); + return new EpochCache( + rollup, + initialValidators.map(v => EthAddress.fromString(v)), + l1RollupConstants, + ); } getEpochAndSlotNow(): EpochAndSlot { @@ -77,15 +83,15 @@ export class EpochCache { async getValidatorSet(): Promise<EthAddress[]> { // If the current epoch has changed, then we need to make a request to update the validator set - const { epoch: currentEpoch, ts } = this.getEpochAndSlotNow(); + const { epoch: calculatedEpoch, ts } = this.getEpochAndSlotNow(); - if (currentEpoch !== this.currentEpoch) { - this.currentEpoch = currentEpoch; + if (calculatedEpoch !== this.cachedEpoch) { + this.cachedEpoch = calculatedEpoch; const validatorSet = await this.rollup.getCommitteeAt(ts); - this.validators = new Map(validatorSet.map((v: `0x${string}`) => [EthAddress.fromString(v), true])); + this.validators = validatorSet.map((v: `0x${string}`) => EthAddress.fromString(v)); } - return Array.from(this.validators.keys()); + return this.validators; } async getCurrentValidator(): Promise<EthAddress> { diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 29713a08f38..f2aa2369759 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -48,6 +48,10 @@ export class RollupContract { return this.rollup.read.getCommitteeAt([timestamp]); } + async getCurrentEpochCommittee() { + return this.rollup.read.getCurrentEpochCommittee(); + } + async getEpochNumber(blockNumber?: bigint) { blockNumber ??= await this.getBlockNumber(); return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 5fed2f83e2c..06b4f1df69a 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -145,14 +145,18 @@ export class ValidatorClient extends WithTracer implements Validator { await this.reExecuteTransactions(proposal); } } catch (error: any) { + // If the transactions are not available, then we should not attempt to attest if (error instanceof TransactionsNotAvailableError) { this.log.error(`Transactions not available, skipping attestation ${error.message}`); } else { + // This branch most commonly be hit if the transactions are available, but the re-execution fails // Catch all error handler this.log.error(`Failed to attest to proposal: ${error.message}`); } return undefined; } + + // Provided all of the above checks pass, we can attest to the proposal this.log.verbose( `Transactions available, attesting to proposal with ${proposal.payload.txHashes.length} transactions`, ); diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index f3719ce1d7c..b915cc3c335 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -630,6 +630,7 @@ __metadata: dotenv: ^16.0.3 get-port: ^7.1.0 jest: ^29.5.0 + jest-mock-extended: ^3.0.7 ts-node: ^10.9.1 tslib: ^2.4.0 typescript: ^5.0.4 From 6507c026e1d1acbfec9f7c3720f8ed3ea166dee7 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:19:28 +0000 Subject: [PATCH 10/29] tmp --- l1-contracts/src/core/Leonidas.sol | 70 +++++++++++ .../archiver/src/archiver/archiver.ts | 3 + yarn-project/end-to-end/package.json | 1 + .../src/e2e_p2p/gossip_network.test.ts | 13 +- .../end-to-end/src/e2e_p2p/p2p_network.ts | 30 ++++- .../src/fixtures/snapshot_manager.ts | 16 +++ yarn-project/end-to-end/src/fixtures/utils.ts | 1 + yarn-project/epoch-cache/src/config.ts | 7 ++ .../epoch-cache/src/epoch_cache.test.ts | 35 +++--- yarn-project/epoch-cache/src/epoch_cache.ts | 115 ++++++++++++++---- yarn-project/epoch-cache/src/index.ts | 1 + .../epoch-cache/src/timestamp_provider.ts | 1 + yarn-project/ethereum/src/contracts/rollup.ts | 24 ++++ .../ethereum/src/deploy_l1_contracts.ts | 1 + .../p2p/src/service/libp2p_service.ts | 3 + yarn-project/validator-client/src/factory.ts | 12 +- .../validator-client/src/validator.test.ts | 6 +- .../validator-client/src/validator.ts | 14 ++- yarn-project/yarn.lock | 12 +- 19 files changed, 304 insertions(+), 61 deletions(-) create mode 100644 yarn-project/epoch-cache/src/config.ts create mode 100644 yarn-project/epoch-cache/src/timestamp_provider.ts diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 5f03179e55d..9ae67eed754 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -288,6 +288,65 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return committee[_computeProposerIndex(epochNumber, slot, sampleSeed, committee.length)]; } + // DEBUG + function getInternalSampleEncodingAt(Timestamp _ts) public view returns (bytes memory, uint256, uint256, uint256) { + Epoch epochNumber = getEpochAt(_ts); + Slot slot = getSlotAt(_ts); + + EpochData storage epoch = epochs[epochNumber]; + + // If the epoch is setup, we can just return the proposer. Otherwise we have to emulate sampling + if (epoch.sampleSeed != 0) { + uint256 committeeSize = epoch.committee.length; + if (committeeSize == 0) { + return (abi.encode(epochNumber, slot, 0), epochNumber.unwrap(), slot.unwrap(), 0); + } + + return (abi.encode(epochNumber, slot, epoch.sampleSeed), epochNumber.unwrap(), slot.unwrap(), epoch.sampleSeed); + } + + // Allow anyone if there is no validator set + if (validatorSet.length() == 0) { + return (abi.encode(epochNumber, slot, 0), epochNumber.unwrap(), slot.unwrap(), 0); + } + + // Emulate a sampling of the validators + uint256 sampleSeed = _getSampleSeed(epochNumber); + return (abi.encode(epochNumber, slot, sampleSeed), epochNumber.unwrap(), slot.unwrap(), sampleSeed); + } + + function getInternalProposerIndexAt(Timestamp _ts) public view returns (uint256) { + Epoch epochNumber = getEpochAt(_ts); + Slot slot = getSlotAt(_ts); + + EpochData storage epoch = epochs[epochNumber]; + + // If the epoch is setup, we can just return the proposer. Otherwise we have to emulate sampling + if (epoch.sampleSeed != 0) { + uint256 committeeSize = epoch.committee.length; + if (committeeSize == 0) { + return 0; + } + + return + _computeProposerIndex(epochNumber, slot, epoch.sampleSeed, committeeSize); + } + + // Allow anyone if there is no validator set + if (validatorSet.length() == 0) { + return 0; + } + + // Emulate a sampling of the validators + uint256 sampleSeed = _getSampleSeed(epochNumber); + address[] memory committee = _sampleValidators(sampleSeed); + return _computeProposerIndex(epochNumber, slot, sampleSeed, committee.length); + } + + function getTimestamp() public view returns (uint256) { + return block.timestamp; + } + /** * @notice Computes the epoch at a specific time * @@ -334,6 +393,15 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return _getCommitteeAt(_ts); } + // TODO: make sure this cannot modify state + function getSampleSeedAt(Timestamp _ts) external view returns (uint256) { + return _getSampleSeed(getEpochAt(_ts)); + } + + function getCurrentSampleSeed() external view returns (uint256) { + return _getSampleSeed(getCurrentEpoch()); + } + function _getCommitteeAt(Timestamp _ts) internal view returns (address[] memory) { Epoch epochNumber = getEpochAt(_ts); EpochData storage epoch = epochs[epochNumber]; @@ -501,6 +569,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return lastSeed; } + /** * @notice Computes the index of the committee member that acts as proposer for a given slot * @@ -518,4 +587,5 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { { return uint256(keccak256(abi.encode(_epoch, _slot, _seed))) % _size; } + } diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index bf699dcccce..574929e247c 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -167,6 +167,9 @@ export class Archiver implements ArchiveSource { rollup.read.GENESIS_TIME(), ] as const); + console.log('l1GenesisTime', l1GenesisTime); + console.log('l1StartBlock', l1StartBlock); + const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config; const archiver = new Archiver( diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index d9514c42dfb..754aa58b48e 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -99,6 +99,7 @@ "devDependencies": { "0x": "^5.7.0", "@jest/globals": "^29.5.0", + "@sinonjs/fake-timers": "^13.0.5", "@types/jest": "^29.5.0", "@types/js-yaml": "^4.0.9", "@types/lodash.chunk": "^4.2.9", diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts index bd80824e18b..8b140042890 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts @@ -33,13 +33,14 @@ describe('e2e_p2p_network', () => { let nodes: AztecNodeService[]; beforeEach(async () => { + t = await P2PNetworkTest.create({ testName: 'e2e_p2p_network', numberOfNodes: NUM_NODES, basePort: BOOT_NODE_UDP_PORT, - // To collect metrics - run in aztec-packages `docker compose --profile metrics up` and set COLLECT_METRICS=true metricsPort: shouldCollectMetrics(), }); + await t.applyBaseSnapshots(); await t.setup(); }); @@ -65,6 +66,9 @@ describe('e2e_p2p_network', () => { throw new Error('Bootstrap node ENR is not available'); } + const t0 = Date.now(); + console.log('current time', t0); + t.ctx.aztecNodeConfig.validatorReexecute = true; // create our network of nodes and submit txs into each of them @@ -84,6 +88,13 @@ describe('e2e_p2p_network', () => { shouldCollectMetrics(), ); + const t1 = Date.now(); + console.log('time after creating nodes', t1); + console.log('time diff', t1 - t0); + + // When using fake timers, we need to keep the system and anvil clocks in sync. + await t.syncMockSystemTime(); + // wait a bit for peers to discover each other await sleep(4000); diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index 95d263156e6..f7feeeca67c 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -27,14 +27,16 @@ import { } from '../fixtures/snapshot_manager.js'; import { getPrivateKeyFromIndex } from '../fixtures/utils.js'; import { getEndToEndTestTelemetryClient } from '../fixtures/with_telemetry_utils.js'; +import { InstalledClock } from '@sinonjs/fake-timers'; // Use a fixed bootstrap node private key so that we can re-use the same snapshot and the nodes can find each other const BOOTSTRAP_NODE_PRIVATE_KEY = '080212208f988fc0899e4a73a5aee4d271a5f20670603a756ad8d84f2c94263a6427c591'; const l1ContractsConfig = getL1ContractsConfigEnvVars(); export const WAIT_FOR_TX_TIMEOUT = l1ContractsConfig.aztecSlotDuration * 3; + export class P2PNetworkTest { - private snapshotManager: ISnapshotManager; + public snapshotManager: ISnapshotManager; private baseAccount; public logger: DebugLogger; @@ -81,6 +83,15 @@ export class P2PNetworkTest { }); } + /** + * When using fake timers, we need to keep the system and anvil clocks in sync. + */ + public async syncMockSystemTime() { + const { timer, deployL1ContractsValues } = this.ctx!; + const timestamp = await deployL1ContractsValues.publicClient.getBlock({blockTag: 'latest'}); + timer.setSystemTime(Number(timestamp.timestamp) * 1000); + } + static async create({ testName, numberOfNodes, @@ -112,7 +123,7 @@ export class P2PNetworkTest { } async applyBaseSnapshots() { - await this.snapshotManager.snapshot('add-validators', async ({ deployL1ContractsValues, aztecNodeConfig }) => { + await this.snapshotManager.snapshot('add-validators', async ({ deployL1ContractsValues, aztecNodeConfig, timer }) => { const rollup = getContract({ address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), abi: RollupAbi, @@ -160,6 +171,11 @@ export class P2PNetworkTest { account: this.baseAccount, }), }); + + // Set the system time in the node, only after we have warped the time and waited for a block + // Time is only set in the NEXT block + timer.setSystemTime(Number(timestamp) * 1000); + console.log("getting system time after jump", Date.now()); }); } @@ -199,7 +215,7 @@ export class P2PNetworkTest { async removeInitialNode() { await this.snapshotManager.snapshot( 'remove-inital-validator', - async ({ deployL1ContractsValues, aztecNodeConfig }) => { + async ({ deployL1ContractsValues, aztecNodeConfig, aztecNode, timer }) => { const rollup = getContract({ address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), abi: RollupAbi, @@ -227,15 +243,19 @@ export class P2PNetworkTest { } // Send and await a tx to make sure we mine a block for the warp to correctly progress. - await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + const receipt = await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: await deployL1ContractsValues.walletClient.sendTransaction({ to: this.baseAccount.address, value: 1n, account: this.baseAccount, }), }); + const block = await deployL1ContractsValues.publicClient.getBlock({ + blockNumber: receipt.blockNumber, + }); + timer.setSystemTime(Number(block.timestamp) * 1000); - await this.ctx.aztecNode.stop(); + await aztecNode.stop(); }, ); } diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index 488e7291bda..3cbebb0e967 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -38,6 +38,9 @@ import { setupL1Contracts } from './setup_l1_contracts.js'; import { type SetupOptions, createAndSyncProverNode, getPrivateKeyFromIndex } from './utils.js'; import { getEndToEndTestTelemetryClient } from './with_telemetry_utils.js'; +import { jest } from '@jest/globals'; +import FakeTimers, { InstalledClock } from '@sinonjs/fake-timers'; + export type SubsystemsContext = { anvil: Anvil; acvmConfig: any; @@ -49,6 +52,7 @@ export type SubsystemsContext = { proverNode?: ProverNode; watcher: AnvilTestWatcher; cheatCodes: CheatCodes; + timer: InstalledClock; }; type SnapshotEntry = { @@ -98,6 +102,7 @@ class MockSnapshotManager implements ISnapshotManager { this.logger.warn(`No data path given, will not persist any snapshots.`); } + public async snapshot<T>( name: string, apply: (context: SubsystemsContext) => Promise<T>, @@ -146,6 +151,7 @@ class SnapshotManager implements ISnapshotManager { this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`); } + public async snapshot<T>( name: string, apply: (context: SubsystemsContext) => Promise<T>, @@ -247,6 +253,7 @@ async function teardown(context: SubsystemsContext | undefined) { await context.acvmConfig?.cleanup(); await context.anvil.stop(); await context.watcher.stop(); + await context.timer?.uninstall(); } /** @@ -265,6 +272,9 @@ async function setupFromFresh( ): Promise<SubsystemsContext> { logger.verbose(`Initializing state...`); + // Use sinonjs fake timers + const timer = FakeTimers.install({shouldAdvanceTime: true, advanceTimeDelta: 200, toFake: ['Date']}); + // Fetch the AztecNode config. // TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing. const aztecNodeConfig: AztecNodeConfig & SetupOptions = { ...getConfigEnvVars(), ...opts }; @@ -326,6 +336,7 @@ async function setupFromFresh( logger.info(`Funding rewardDistributor in ${rewardDistributorMintTxHash}`); } + const watcher = new AnvilTestWatcher( new EthCheatCodes(aztecNodeConfig.l1RpcUrl), deployL1ContractsValues.l1ContractAddresses.rollupAddress, @@ -382,6 +393,7 @@ async function setupFromFresh( proverNode, watcher, cheatCodes, + timer, }; } @@ -391,6 +403,9 @@ async function setupFromFresh( async function setupFromState(statePath: string, logger: Logger): Promise<SubsystemsContext> { logger.verbose(`Initializing with saved state at ${statePath}...`); + // TODO: make one function + const timer = FakeTimers.install({shouldAdvanceTime: true, advanceTimeDelta: 20}); + // 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'), @@ -463,6 +478,7 @@ async function setupFromState(statePath: string, logger: Logger): Promise<Subsys }, watcher, cheatCodes, + timer, }; } diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 2ff469f815f..cf1bce28a26 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -68,6 +68,7 @@ import { MNEMONIC } from './fixtures.js'; import { getACVMConfig } from './get_acvm_config.js'; import { getBBConfig } from './get_bb_config.js'; import { isMetricsLoggingRequested, setupMetricsLogger } from './logging.js'; +import { InstalledClock } from '@sinonjs/fake-timers'; export { deployAndInitializeTokenAndBridgeContracts } from '../shared/cross_chain_test_harness.js'; export { startAnvil }; diff --git a/yarn-project/epoch-cache/src/config.ts b/yarn-project/epoch-cache/src/config.ts new file mode 100644 index 00000000000..4abe7d7913a --- /dev/null +++ b/yarn-project/epoch-cache/src/config.ts @@ -0,0 +1,7 @@ +import { type L1ReaderConfig, type L1ContractsConfig, getL1ContractsConfigEnvVars, getL1ReaderConfigFromEnv } from '@aztec/ethereum'; + +export type EpochCacheConfig = L1ReaderConfig & L1ContractsConfig; + +export function getEpochCacheConfigEnvVars(): EpochCacheConfig { + return { ...getL1ReaderConfigFromEnv(), ...getL1ContractsConfigEnvVars() }; +} diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 00366a79c23..6e68669e223 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -15,7 +15,7 @@ describe('EpochCache', () => { const EPOCH_DURATION = 32; // 384 seconds const L1_GENESIS_TIME = 1000n; - const testValidators = [ + const testCommittee = [ EthAddress.fromString('0x0000000000000000000000000000000000000001'), EthAddress.fromString('0x0000000000000000000000000000000000000002'), EthAddress.fromString('0x0000000000000000000000000000000000000003'), @@ -27,7 +27,8 @@ describe('EpochCache', () => { rollupContract = mock<RollupContract>(); // Mock the getCommitteeAt method - rollupContract.getCommitteeAt.mockResolvedValue(testValidators.map(v => v.toString())); + rollupContract.getCommitteeAt.mockResolvedValue(testCommittee.map(v => v.toString())); + rollupContract.getSampleSeedAt.mockResolvedValue(0n); // Setup fake timers jest.useFakeTimers(); @@ -41,7 +42,7 @@ describe('EpochCache', () => { epochDuration: EPOCH_DURATION, }; - epochCache = new EpochCache(rollupContract, testValidators, testConstants); + epochCache = new EpochCache(rollupContract, testCommittee, 0n, testConstants); }); afterEach(() => { @@ -50,8 +51,8 @@ describe('EpochCache', () => { it('should cache the validator set for the length of an epoch', async () => { // Initial call to get validators - const initialValidators = await epochCache.getValidatorSet(); - expect(initialValidators).toEqual(testValidators); + const initialCommittee = await epochCache.getCommittee(); + expect(initialCommittee).toEqual(testCommittee); // Not called as we should cache with the initial validator set expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(0); @@ -59,19 +60,19 @@ describe('EpochCache', () => { jest.setSystemTime(Date.now() + (Number(EPOCH_DURATION * SLOT_DURATION) / 2) * 1000); // Add another validator to the set - rollupContract.getCommitteeAt.mockResolvedValue([...testValidators, extraTestValidator].map(v => v.toString())); + rollupContract.getCommitteeAt.mockResolvedValue([...testCommittee, extraTestValidator].map(v => v.toString())); // Should use cached validators - const midEpochValidators = await epochCache.getValidatorSet(); - expect(midEpochValidators).toEqual(testValidators); + const midEpochCommittee = await epochCache.getCommittee(); + expect(midEpochCommittee).toEqual(testCommittee); expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(0); // Still cached // Move time forward to next epoch (x 1000 for milliseconds) jest.setSystemTime(Date.now() + Number(EPOCH_DURATION * SLOT_DURATION) * 1000); - // Should fetch new validators - const nextEpochValidators = await epochCache.getValidatorSet(); - expect(nextEpochValidators).toEqual([...testValidators, extraTestValidator]); + // Should fetch new validator + const nextEpochCommittee = await epochCache.getCommittee(); + expect(nextEpochCommittee).toEqual([...testCommittee, extraTestValidator]); expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(1); // Called again for new epoch }); @@ -81,17 +82,17 @@ describe('EpochCache', () => { jest.setSystemTime(initialTime); // Get validator for slot 0 - let currentValidator = await epochCache.getCurrentValidator(); - expect(currentValidator).toEqual(testValidators[0]); // First validator for slot 0 + let currentValidator = await epochCache.getCurrentProposer(); + expect(currentValidator).toEqual(testCommittee[0]); // First validator for slot 0 // Move to next slot jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); - currentValidator = await epochCache.getCurrentValidator(); - expect(currentValidator).toEqual(testValidators[1]); // Second validator for slot 1 + currentValidator = await epochCache.getCurrentProposer(); + expect(currentValidator).toEqual(testCommittee[1]); // Second validator for slot 1 // Move to slot that wraps around validator set jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); - currentValidator = await epochCache.getCurrentValidator(); - expect(currentValidator).toEqual(testValidators[0]); // Back to first validator for slot 3 + currentValidator = await epochCache.getCurrentProposer(); + expect(currentValidator).toEqual(testCommittee[0]); // Back to first validator for slot 3 }); }); diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 64c22de3b45..a8d71cf6b1b 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -7,12 +7,13 @@ import { import { RollupContract, createEthereumChain, - getL1ContractsConfigEnvVars, - getL1ReaderConfigFromEnv, } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; +import { getEpochCacheConfigEnvVars, type EpochCacheConfig } from './config.js'; -import { createPublicClient, http } from 'viem'; +import { createPublicClient, encodeAbiParameters, keccak256, http, PublicClient } from 'viem'; +import { Logger } from '@aztec/foundation/log'; +import { createDebugLogger } from '@aztec/foundation/log'; type EpochAndSlot = { epoch: bigint; @@ -20,55 +21,66 @@ type EpochAndSlot = { ts: bigint; }; +// TODO: adjust log levels in file export class EpochCache { - private validators: EthAddress[]; + private committee: EthAddress[]; private cachedEpoch: bigint; + private cachedSampleSeed: bigint; + private readonly log: Logger = createDebugLogger('aztec:EpochCache'); constructor( private rollup: RollupContract, initialValidators: EthAddress[] = [], + initialSampleSeed: bigint = 0n, private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants, ) { - this.validators = initialValidators; + this.committee = initialValidators; + this.cachedSampleSeed = initialSampleSeed; + + this.log.verbose(`Initialized EpochCache with constants and validators`, { l1constants, initialValidators }); this.cachedEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); } + // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver - static async create(rollupAddress: EthAddress) { - const l1ReaderConfig = getL1ReaderConfigFromEnv(); - const l1constants = getL1ContractsConfigEnvVars(); + // TODO: be aware that the timestamp source may be lagging behind since it is from the archiver??? - double check that there are no issues here + // how will this behave at the epoch boundary? + static async create(rollupAddress: EthAddress, config?: EpochCacheConfig) { + config = config ?? getEpochCacheConfigEnvVars(); - const chain = createEthereumChain(l1ReaderConfig.l1RpcUrl, l1ReaderConfig.l1ChainId); + const chain = createEthereumChain(config.l1RpcUrl, config.l1ChainId); const publicClient = createPublicClient({ chain: chain.chainInfo, transport: http(chain.rpcUrl), - pollingInterval: l1ReaderConfig.viemPollingIntervalMS, + pollingInterval: config.viemPollingIntervalMS, }); const rollup = new RollupContract(publicClient, rollupAddress.toString()); - const [l1StartBlock, l1GenesisTime, initialValidators] = await Promise.all([ + const [l1StartBlock, l1GenesisTime, initialValidators, sampleSeed] = await Promise.all([ rollup.getL1StartBlock(), rollup.getL1GenesisTime(), rollup.getCurrentEpochCommittee(), + rollup.getCurrentSampleSeed(), ] as const); const l1RollupConstants: L1RollupConstants = { l1StartBlock, l1GenesisTime, - slotDuration: l1constants.aztecSlotDuration, - epochDuration: l1constants.aztecEpochDuration, - ethereumSlotDuration: l1constants.ethereumSlotDuration, + slotDuration: config.aztecSlotDuration, + epochDuration: config.aztecEpochDuration, + ethereumSlotDuration: config.ethereumSlotDuration, }; return new EpochCache( rollup, initialValidators.map(v => EthAddress.fromString(v)), + sampleSeed, l1RollupConstants, ); } - getEpochAndSlotNow(): EpochAndSlot { + async getEpochAndSlotNow(): Promise<EpochAndSlot> { const now = BigInt(Math.floor(Date.now() / 1000)); return this.getEpochAndSlotAtTimestamp(now); } @@ -81,30 +93,81 @@ export class EpochCache { }; } - async getValidatorSet(): Promise<EthAddress[]> { + // TODO: when the validator is asking about this, it may be in a slot that is across the epoch boundary + // we need to make sure that whenever we receive a request we are certain that the timing is correct + // - my first attempt used node timers, but this did not work well in tests that we jump into the future + // . - now i am reading the timestamp from the chain, but i need to make sure that we are only proposing for + // - the next slot, which means i sort of still need access to the archiver, to know what the last slot we + // - saw was, and that we do not get requested to attest to slots that are in the past + + + async getCommittee(): Promise<EthAddress[]> { // If the current epoch has changed, then we need to make a request to update the validator set - const { epoch: calculatedEpoch, ts } = this.getEpochAndSlotNow(); + const { epoch: calculatedEpoch, ts } = await this.getEpochAndSlotNow(); if (calculatedEpoch !== this.cachedEpoch) { + this.log.verbose(`Epoch changed, updating validator set`, { calculatedEpoch, cachedEpoch: this.cachedEpoch }); this.cachedEpoch = calculatedEpoch; - const validatorSet = await this.rollup.getCommitteeAt(ts); - this.validators = validatorSet.map((v: `0x${string}`) => EthAddress.fromString(v)); + const [committeeAtTs, sampleSeedAtTs] = await Promise.all([ + this.rollup.getCommitteeAt(ts), + this.rollup.getSampleSeedAt(ts), + ]); + this.committee = committeeAtTs.map((v: `0x${string}`) => EthAddress.fromString(v)); + this.cachedSampleSeed = sampleSeedAtTs; + console.log('this.committee updated to ', this.committee); } - return this.validators; + return this.committee; + } + + getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}` { + return encodeAbiParameters([ + { type: 'uint256', name: 'epoch' }, { type: 'uint256', name: 'slot' }, { type: 'uint256', name: 'seed' }], + [epoch, slot, seed]); + } + + async computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): Promise<bigint> { + return BigInt(keccak256(this.getProposerIndexEncoding(epoch, slot, seed))) % size; } - async getCurrentValidator(): Promise<EthAddress> { + // TODO: just use another timing library + async getCurrentProposer(slot: bigint): Promise<EthAddress> { + console.log('\n\n\n\n\ngetting current proposer\n\n\n\n\n'); // Validators are sorted by their index in the committee, and getValidatorSet will cache // TODO: should we get the timestamp from the underlying l1 node? - const { slot: currentSlot } = this.getEpochAndSlotNow(); - const validators = await this.getValidatorSet(); + const committee = await this.getCommittee(); + // const { slot: currentSlot, ts } = await this.getEpochAndSlotNow(); + // console.log('currentSlot', currentSlot); + const [proposerFromL1] = await Promise.all([this.rollup.getCurrentProposer()]); + + console.log('timestampFromL1', await this.rollup.getTimestamp()); + const { slot: currentSlot, ts } = await this.getEpochAndSlotNow(); + console.log('local ts', ts); + + // TODO: could also cache this + const proposerIndex = await this.computeProposerIndex(slot, this.cachedEpoch, this.cachedSampleSeed, BigInt(committee.length)); + const proposerIndexFromL1 = await this.rollup.getInternalProposerIndexAt(ts); + console.log('locally calculated proposerIndex', proposerIndex); + console.log('proposerIndexFromL1', proposerIndexFromL1); + + // return committee[Number(proposerIndex)]; + // TODO: fix this, we need to request the seed from l1 for this epoch, we can cache this along side the epoch information + const calculatedProposer = committee[Number(proposerIndex)]; + + + const [internalSampleEncoding, epoch] = await this.rollup.getInternalSampleEncodingAt(ts); + + console.log('self proposer encoding', this.getProposerIndexEncoding(this.cachedEpoch, slot, this.cachedSampleSeed)); + console.log('internalSampleEncoding', internalSampleEncoding); + + console.log('calculatedProposer', calculatedProposer); + console.log('proposerFromL1', proposerFromL1); - return validators[Number(currentSlot) % validators.length]; + return calculatedProposer; } async isInCommittee(validator: EthAddress): Promise<boolean> { - const validators = await this.getValidatorSet(); - return validators.includes(validator); + const committee = await this.getCommittee(); + return committee.some(v => v.equals(validator)); } } diff --git a/yarn-project/epoch-cache/src/index.ts b/yarn-project/epoch-cache/src/index.ts index 9a016b1d67b..8a126974a43 100644 --- a/yarn-project/epoch-cache/src/index.ts +++ b/yarn-project/epoch-cache/src/index.ts @@ -1 +1,2 @@ export * from './epoch_cache.js'; +export * from './config.js'; \ No newline at end of file diff --git a/yarn-project/epoch-cache/src/timestamp_provider.ts b/yarn-project/epoch-cache/src/timestamp_provider.ts new file mode 100644 index 00000000000..0519ecba6ea --- /dev/null +++ b/yarn-project/epoch-cache/src/timestamp_provider.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index f2aa2369759..0b71842d439 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -48,10 +48,34 @@ export class RollupContract { return this.rollup.read.getCommitteeAt([timestamp]); } + async getSampleSeedAt(timestamp: bigint) { + return this.rollup.read.getSampleSeedAt([timestamp]); + } + + async getCurrentSampleSeed() { + return this.rollup.read.getCurrentSampleSeed(); + } + async getCurrentEpochCommittee() { return this.rollup.read.getCurrentEpochCommittee(); } + async getCurrentProposer() { + return this.rollup.read.getCurrentProposer(); + } + + async getTimestamp() { + return this.rollup.read.getTimestamp(); + } + + async getInternalSampleEncodingAt(timestamp: bigint) { + return this.rollup.read.getInternalSampleEncodingAt([timestamp]); + } + + async getInternalProposerIndexAt(timestamp: bigint) { + return this.rollup.read.getInternalProposerIndexAt([timestamp]); + } + async getEpochNumber(blockNumber?: bigint) { blockNumber ??= await this.getBlockNumber(); return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index e9d8522f636..7108a1a5ef3 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -340,6 +340,7 @@ export const deployL1Contracts = async ( ]); logger.info(`Deployed RewardDistributor at ${rewardDistributorAddress}`); + logger.verbose(`Waiting for governance contracts to be deployed`); await govDeployer.waitForDeployments(); logger.info(`All governance contracts deployed`); diff --git a/yarn-project/p2p/src/service/libp2p_service.ts b/yarn-project/p2p/src/service/libp2p_service.ts index dd1a800e9a8..24a6f893f80 100644 --- a/yarn-project/p2p/src/service/libp2p_service.ts +++ b/yarn-project/p2p/src/service/libp2p_service.ts @@ -521,6 +521,9 @@ export class LibP2PService extends WithTracer implements P2PService { } private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> { + + this.node.services.pubsub.topicValidators.set + const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1; // basic data validation const dataValidator = new DataTxValidator(); diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 3a88f192126..8c11c34da6c 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -5,10 +5,16 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; -import { EpochCache } from '@aztec/epoch-cache'; +import { EpochCache, type EpochCacheConfig } from '@aztec/epoch-cache'; import { type EthAddress } from '@aztec/foundation/eth-address'; +// import { type L1TimestampSource } from '@aztec/circuit-types'; -export async function createValidatorClient(config: ValidatorClientConfig, rollupAddress: EthAddress, p2pClient: P2P, telemetry: TelemetryClient) { +export async function createValidatorClient( + config: ValidatorClientConfig & EpochCacheConfig, + rollupAddress: EthAddress, + p2pClient: P2P, + telemetry: TelemetryClient +) { if (config.disableValidator) { return undefined; } @@ -17,7 +23,7 @@ export async function createValidatorClient(config: ValidatorClientConfig, rollu } // Create the epoch cache - const epochCache = await EpochCache.create(rollupAddress); + const epochCache = await EpochCache.create(rollupAddress, /*l1TimestampSource,*/ config); return ValidatorClient.new(config, epochCache, p2pClient, telemetry); } diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index f9e0d4f2151..603ebf3eba9 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -100,7 +100,7 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); - epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(proposal.getSender()) ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); const val = ValidatorClient.new(config, epochCache, p2pClient); @@ -116,7 +116,7 @@ describe('ValidationService', () => { const proposal = makeBlockProposal(); // Setup epoch cache mocks - epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(proposal.getSender()) ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false)); const attestation = await validatorClient.attestToProposal(proposal); @@ -127,7 +127,7 @@ describe('ValidationService', () => { const proposal = makeBlockProposal(); // Setup epoch cache mocks - epochCache.getCurrentValidator.mockImplementation(() => Promise.resolve(EthAddress.random())); + epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(EthAddress.random())); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); const attestation = await validatorClient.attestToProposal(proposal); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 06b4f1df69a..8e39fde3410 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -120,15 +120,19 @@ export class ValidatorClient extends WithTracer implements Validator { async attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined> { // Check that I am in the committee + console.log('request to attest', proposal.payload.header.globalVariables.slotNumber.toString()); if (!(await this.epochCache.isInCommittee(this.keyStore.getAddress()))) { - this.log.debug(`Not in the committee, skipping attestation`); + this.log.verbose(`Not in the committee, skipping attestation`); return undefined; } - // Check that the proposal is from the current validator - const currentValidator = await this.epochCache.getCurrentValidator(); - if (proposal.getSender() !== currentValidator) { - this.log.debug(`Not the current validator, skipping attestation`); + // Check that the proposal is from the current proposer + // TODO: this must be updated to request the epoch seed from the l1 contract too + const currentProposer = await this.epochCache.getCurrentProposer(proposal.payload.header.globalVariables.slotNumber); + console.log('currentProposer', currentProposer); + console.log('proposal.getSender()', proposal.getSender()); + if (!proposal.getSender().equals(currentProposer)) { + this.log.verbose(`Not the current proposer, skipping attestation`); return undefined; } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index b915cc3c335..e3378b408af 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -548,6 +548,7 @@ __metadata: "@aztec/world-state": "workspace:^" "@jest/globals": ^29.5.0 "@noble/curves": ^1.0.0 + "@sinonjs/fake-timers": ^13.0.5 "@swc/core": ^1.4.11 "@swc/jest": ^0.2.36 "@types/fs-extra": ^11.0.2 @@ -4163,7 +4164,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.0": +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" dependencies: @@ -4181,6 +4182,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^13.0.5": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": ^3.0.1 + checksum: b1c6ba87fadb7666d3aa126c9e8b4ac32b2d9e84c9e5fd074aa24cab3c8342fd655459de014b08e603be1e6c24c9f9716d76d6d2a36c50f59bb0091be61601dd + languageName: node + linkType: hard + "@swc/core-darwin-arm64@npm:1.5.5": version: 1.5.5 resolution: "@swc/core-darwin-arm64@npm:1.5.5" From 97f35c3a805880aae3f7b3f0c3270e414d0b15cd Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:37:14 +0000 Subject: [PATCH 11/29] fix: next and current slot --- l1-contracts/src/core/Leonidas.sol | 59 -------- .../archiver/src/archiver/archiver.ts | 3 - .../src/e2e_p2p/gossip_network.test.ts | 8 - .../end-to-end/src/e2e_p2p/p2p_network.ts | 136 ++++++++++------- .../src/fixtures/snapshot_manager.ts | 13 +- yarn-project/end-to-end/src/fixtures/utils.ts | 1 - yarn-project/epoch-cache/src/config.ts | 7 +- .../epoch-cache/src/epoch_cache.test.ts | 28 +++- yarn-project/epoch-cache/src/epoch_cache.ts | 139 ++++++++++-------- yarn-project/epoch-cache/src/index.ts | 2 +- .../epoch-cache/src/timestamp_provider.ts | 1 - yarn-project/ethereum/src/contracts/rollup.ts | 22 +-- .../ethereum/src/deploy_l1_contracts.ts | 1 + .../p2p/src/service/libp2p_service.ts | 3 +- .../src/publisher/l1-publisher.ts | 4 + .../src/sequencer/sequencer.ts | 4 +- yarn-project/validator-client/src/factory.ts | 7 +- .../validator-client/src/validator.test.ts | 18 ++- .../validator-client/src/validator.ts | 21 +-- 19 files changed, 233 insertions(+), 244 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 9ae67eed754..985d79410c9 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -288,65 +288,6 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return committee[_computeProposerIndex(epochNumber, slot, sampleSeed, committee.length)]; } - // DEBUG - function getInternalSampleEncodingAt(Timestamp _ts) public view returns (bytes memory, uint256, uint256, uint256) { - Epoch epochNumber = getEpochAt(_ts); - Slot slot = getSlotAt(_ts); - - EpochData storage epoch = epochs[epochNumber]; - - // If the epoch is setup, we can just return the proposer. Otherwise we have to emulate sampling - if (epoch.sampleSeed != 0) { - uint256 committeeSize = epoch.committee.length; - if (committeeSize == 0) { - return (abi.encode(epochNumber, slot, 0), epochNumber.unwrap(), slot.unwrap(), 0); - } - - return (abi.encode(epochNumber, slot, epoch.sampleSeed), epochNumber.unwrap(), slot.unwrap(), epoch.sampleSeed); - } - - // Allow anyone if there is no validator set - if (validatorSet.length() == 0) { - return (abi.encode(epochNumber, slot, 0), epochNumber.unwrap(), slot.unwrap(), 0); - } - - // Emulate a sampling of the validators - uint256 sampleSeed = _getSampleSeed(epochNumber); - return (abi.encode(epochNumber, slot, sampleSeed), epochNumber.unwrap(), slot.unwrap(), sampleSeed); - } - - function getInternalProposerIndexAt(Timestamp _ts) public view returns (uint256) { - Epoch epochNumber = getEpochAt(_ts); - Slot slot = getSlotAt(_ts); - - EpochData storage epoch = epochs[epochNumber]; - - // If the epoch is setup, we can just return the proposer. Otherwise we have to emulate sampling - if (epoch.sampleSeed != 0) { - uint256 committeeSize = epoch.committee.length; - if (committeeSize == 0) { - return 0; - } - - return - _computeProposerIndex(epochNumber, slot, epoch.sampleSeed, committeeSize); - } - - // Allow anyone if there is no validator set - if (validatorSet.length() == 0) { - return 0; - } - - // Emulate a sampling of the validators - uint256 sampleSeed = _getSampleSeed(epochNumber); - address[] memory committee = _sampleValidators(sampleSeed); - return _computeProposerIndex(epochNumber, slot, sampleSeed, committee.length); - } - - function getTimestamp() public view returns (uint256) { - return block.timestamp; - } - /** * @notice Computes the epoch at a specific time * diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 574929e247c..bf699dcccce 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -167,9 +167,6 @@ export class Archiver implements ArchiveSource { rollup.read.GENESIS_TIME(), ] as const); - console.log('l1GenesisTime', l1GenesisTime); - console.log('l1StartBlock', l1StartBlock); - const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config; const archiver = new Archiver( diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts index 8b140042890..9bb9af55c0a 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts @@ -33,7 +33,6 @@ describe('e2e_p2p_network', () => { let nodes: AztecNodeService[]; beforeEach(async () => { - t = await P2PNetworkTest.create({ testName: 'e2e_p2p_network', numberOfNodes: NUM_NODES, @@ -66,9 +65,6 @@ describe('e2e_p2p_network', () => { throw new Error('Bootstrap node ENR is not available'); } - const t0 = Date.now(); - console.log('current time', t0); - t.ctx.aztecNodeConfig.validatorReexecute = true; // create our network of nodes and submit txs into each of them @@ -88,10 +84,6 @@ describe('e2e_p2p_network', () => { shouldCollectMetrics(), ); - const t1 = Date.now(); - console.log('time after creating nodes', t1); - console.log('time diff', t1 - t0); - // When using fake timers, we need to keep the system and anvil clocks in sync. await t.syncMockSystemTime(); diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index f7feeeca67c..c8976c75c9b 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -27,14 +27,12 @@ import { } from '../fixtures/snapshot_manager.js'; import { getPrivateKeyFromIndex } from '../fixtures/utils.js'; import { getEndToEndTestTelemetryClient } from '../fixtures/with_telemetry_utils.js'; -import { InstalledClock } from '@sinonjs/fake-timers'; // Use a fixed bootstrap node private key so that we can re-use the same snapshot and the nodes can find each other const BOOTSTRAP_NODE_PRIVATE_KEY = '080212208f988fc0899e4a73a5aee4d271a5f20670603a756ad8d84f2c94263a6427c591'; const l1ContractsConfig = getL1ContractsConfigEnvVars(); export const WAIT_FOR_TX_TIMEOUT = l1ContractsConfig.aztecSlotDuration * 3; - export class P2PNetworkTest { public snapshotManager: ISnapshotManager; private baseAccount; @@ -52,6 +50,8 @@ export class P2PNetworkTest { public wallet?: AccountWalletWithSecretKey; public spamContract?: SpamContract; + private cleanupInterval: NodeJS.Timeout | undefined = undefined; + constructor( testName: string, public bootstrapNode: BootstrapNode, @@ -83,13 +83,31 @@ export class P2PNetworkTest { }); } + public startSyncMockSystemTimeInterval() { + this.cleanupInterval = setInterval(async () => { + await this.syncMockSystemTime(); + }, l1ContractsConfig.aztecSlotDuration * 1000); + } + /** * When using fake timers, we need to keep the system and anvil clocks in sync. */ + // TODO: can we just calculate time based on the epoch number observed in the smart contract vs the genesis time? public async syncMockSystemTime() { + this.logger.info('Syncing mock system time'); const { timer, deployL1ContractsValues } = this.ctx!; - const timestamp = await deployL1ContractsValues.publicClient.getBlock({blockTag: 'latest'}); + // Send a tx and only update the time after the tx is mined, as eth time is not continuous + const tx = await deployL1ContractsValues.walletClient.sendTransaction({ + to: this.baseAccount.address, + value: 1n, + account: this.baseAccount, + }); + const receipt = await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + hash: tx, + }); + const timestamp = await deployL1ContractsValues.publicClient.getBlock({ blockNumber: receipt.blockNumber }); timer.setSystemTime(Number(timestamp.timestamp) * 1000); + this.logger.info(`Synced mock system time to ${timestamp.timestamp * 1000n}`); } static async create({ @@ -123,60 +141,62 @@ export class P2PNetworkTest { } async applyBaseSnapshots() { - await this.snapshotManager.snapshot('add-validators', async ({ deployL1ContractsValues, aztecNodeConfig, timer }) => { - const rollup = getContract({ - address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), - abi: RollupAbi, - 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 - await Promise.all( - txHashes.map(txHash => - deployL1ContractsValues.publicClient.waitForTransactionReceipt({ - hash: txHash, + await this.snapshotManager.snapshot( + 'add-validators', + async ({ deployL1ContractsValues, aztecNodeConfig, timer }) => { + const rollup = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), + abi: RollupAbi, + 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 + await Promise.all( + txHashes.map(txHash => + deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + hash: txHash, + }), + ), + ); + + //@note Now we jump ahead to the next epoch such that the validator committee is picked + // INTERVAL MINING: If we are using anvil interval mining this will NOT progress the time! + // Which means that the validator set will still be empty! So anyone can propose. + const slotsInEpoch = await rollup.read.EPOCH_DURATION(); + const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]); + const cheatCodes = new EthCheatCodes(aztecNodeConfig.l1RpcUrl); + try { + await cheatCodes.warp(Number(timestamp)); + } catch (err) { + this.logger.debug('Warp failed, time already satisfied'); + } + + // Send and await a tx to make sure we mine a block for the warp to correctly progress. + await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + hash: await deployL1ContractsValues.walletClient.sendTransaction({ + to: this.baseAccount.address, + value: 1n, + account: this.baseAccount, }), - ), - ); - - //@note Now we jump ahead to the next epoch such that the validator committee is picked - // INTERVAL MINING: If we are using anvil interval mining this will NOT progress the time! - // Which means that the validator set will still be empty! So anyone can propose. - const slotsInEpoch = await rollup.read.EPOCH_DURATION(); - const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]); - const cheatCodes = new EthCheatCodes(aztecNodeConfig.l1RpcUrl); - try { - await cheatCodes.warp(Number(timestamp)); - } catch (err) { - this.logger.debug('Warp failed, time already satisfied'); - } - - // Send and await a tx to make sure we mine a block for the warp to correctly progress. - await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ - hash: await deployL1ContractsValues.walletClient.sendTransaction({ - to: this.baseAccount.address, - value: 1n, - account: this.baseAccount, - }), - }); - - // Set the system time in the node, only after we have warped the time and waited for a block - // Time is only set in the NEXT block - timer.setSystemTime(Number(timestamp) * 1000); - console.log("getting system time after jump", Date.now()); - }); + }); + + // Set the system time in the node, only after we have warped the time and waited for a block + // Time is only set in the NEXT block + timer.setSystemTime(Number(timestamp) * 1000); + }, + ); } async setupAccount() { @@ -262,6 +282,7 @@ export class P2PNetworkTest { async setup() { this.ctx = await this.snapshotManager.setup(); + this.startSyncMockSystemTimeInterval(); } async stopNodes(nodes: AztecNodeService[]) { @@ -281,5 +302,8 @@ export class P2PNetworkTest { async teardown() { await this.bootstrapNode.stop(); await this.snapshotManager.teardown(); + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + } } } diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index 3cbebb0e967..8cfa2e70ac4 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -24,6 +24,7 @@ import { type ProverNode } from '@aztec/prover-node'; import { type PXEService, createPXEService, getPXEServiceConfig } from '@aztec/pxe'; import { createAndStartTelemetryClient, getConfigEnvVars as getTelemetryConfig } from '@aztec/telemetry-client/start'; +import { type InstalledClock, install } from '@sinonjs/fake-timers'; import { type Anvil } from '@viem/anvil'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { copySync, removeSync } from 'fs-extra/esm'; @@ -38,9 +39,6 @@ import { setupL1Contracts } from './setup_l1_contracts.js'; import { type SetupOptions, createAndSyncProverNode, getPrivateKeyFromIndex } from './utils.js'; import { getEndToEndTestTelemetryClient } from './with_telemetry_utils.js'; -import { jest } from '@jest/globals'; -import FakeTimers, { InstalledClock } from '@sinonjs/fake-timers'; - export type SubsystemsContext = { anvil: Anvil; acvmConfig: any; @@ -102,7 +100,6 @@ class MockSnapshotManager implements ISnapshotManager { this.logger.warn(`No data path given, will not persist any snapshots.`); } - public async snapshot<T>( name: string, apply: (context: SubsystemsContext) => Promise<T>, @@ -151,7 +148,6 @@ class SnapshotManager implements ISnapshotManager { this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`); } - public async snapshot<T>( name: string, apply: (context: SubsystemsContext) => Promise<T>, @@ -253,7 +249,7 @@ async function teardown(context: SubsystemsContext | undefined) { await context.acvmConfig?.cleanup(); await context.anvil.stop(); await context.watcher.stop(); - await context.timer?.uninstall(); + context.timer?.uninstall(); } /** @@ -273,7 +269,7 @@ async function setupFromFresh( logger.verbose(`Initializing state...`); // Use sinonjs fake timers - const timer = FakeTimers.install({shouldAdvanceTime: true, advanceTimeDelta: 200, toFake: ['Date']}); + const timer = install({ shouldAdvanceTime: true, advanceTimeDelta: 20, toFake: ['Date'] }); // Fetch the AztecNode config. // TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing. @@ -336,7 +332,6 @@ async function setupFromFresh( logger.info(`Funding rewardDistributor in ${rewardDistributorMintTxHash}`); } - const watcher = new AnvilTestWatcher( new EthCheatCodes(aztecNodeConfig.l1RpcUrl), deployL1ContractsValues.l1ContractAddresses.rollupAddress, @@ -404,7 +399,7 @@ async function setupFromState(statePath: string, logger: Logger): Promise<Subsys logger.verbose(`Initializing with saved state at ${statePath}...`); // TODO: make one function - const timer = FakeTimers.install({shouldAdvanceTime: true, advanceTimeDelta: 20}); + const timer = install({ shouldAdvanceTime: true, advanceTimeDelta: 20, toFake: ['Date'] }); // TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing. const aztecNodeConfig: AztecNodeConfig & SetupOptions = JSON.parse( diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index cf1bce28a26..2ff469f815f 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -68,7 +68,6 @@ import { MNEMONIC } from './fixtures.js'; import { getACVMConfig } from './get_acvm_config.js'; import { getBBConfig } from './get_bb_config.js'; import { isMetricsLoggingRequested, setupMetricsLogger } from './logging.js'; -import { InstalledClock } from '@sinonjs/fake-timers'; export { deployAndInitializeTokenAndBridgeContracts } from '../shared/cross_chain_test_harness.js'; export { startAnvil }; diff --git a/yarn-project/epoch-cache/src/config.ts b/yarn-project/epoch-cache/src/config.ts index 4abe7d7913a..3da15946771 100644 --- a/yarn-project/epoch-cache/src/config.ts +++ b/yarn-project/epoch-cache/src/config.ts @@ -1,4 +1,9 @@ -import { type L1ReaderConfig, type L1ContractsConfig, getL1ContractsConfigEnvVars, getL1ReaderConfigFromEnv } from '@aztec/ethereum'; +import { + type L1ContractsConfig, + type L1ReaderConfig, + getL1ContractsConfigEnvVars, + getL1ReaderConfigFromEnv, +} from '@aztec/ethereum'; export type EpochCacheConfig = L1ReaderConfig & L1ContractsConfig; diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 6e68669e223..271d19037be 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -81,18 +81,34 @@ describe('EpochCache', () => { const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds jest.setSystemTime(initialTime); + // The valid proposer has been calculated in advance to be [1,1,0] for the slots chosen + // Hence the chosen values for testCommittee below + // Get validator for slot 0 - let currentValidator = await epochCache.getCurrentProposer(); - expect(currentValidator).toEqual(testCommittee[0]); // First validator for slot 0 + let [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); + expect(currentValidator).toEqual(testCommittee[1]); // Move to next slot jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); - currentValidator = await epochCache.getCurrentProposer(); - expect(currentValidator).toEqual(testCommittee[1]); // Second validator for slot 1 + [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); + expect(currentValidator).toEqual(testCommittee[1]); // Move to slot that wraps around validator set jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); - currentValidator = await epochCache.getCurrentProposer(); - expect(currentValidator).toEqual(testCommittee[0]); // Back to first validator for slot 3 + [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); + expect(currentValidator).toEqual(testCommittee[0]); + }); + + it('Should request to update the validato set when on the epoch boundary', async () => { + // Set initial time to a known slot + const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds + jest.setSystemTime(initialTime); + + // Move forward to slot before the epoch boundary + jest.setSystemTime(initialTime + Number(SLOT_DURATION) * (EPOCH_DURATION - 1) * 1000); + + // Should request to update the validator set + await epochCache.getProposerInCurrentOrNextSlot(); + expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(2); }); }); diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index a8d71cf6b1b..7ebee68ef14 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -4,16 +4,13 @@ import { getEpochNumberAtTimestamp, getSlotAtTimestamp, } from '@aztec/circuit-types'; -import { - RollupContract, - createEthereumChain, -} from '@aztec/ethereum'; +import { RollupContract, createEthereumChain } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { getEpochCacheConfigEnvVars, type EpochCacheConfig } from './config.js'; +import { type Logger, createDebugLogger } from '@aztec/foundation/log'; + +import { createPublicClient, encodeAbiParameters, http, keccak256 } from 'viem'; -import { createPublicClient, encodeAbiParameters, keccak256, http, PublicClient } from 'viem'; -import { Logger } from '@aztec/foundation/log'; -import { createDebugLogger } from '@aztec/foundation/log'; +import { type EpochCacheConfig, getEpochCacheConfigEnvVars } from './config.js'; type EpochAndSlot = { epoch: bigint; @@ -21,7 +18,16 @@ type EpochAndSlot = { ts: bigint; }; -// TODO: adjust log levels in file +/** + * Epoch cache + * + * This class is responsible for managing traffic to the l1 node, by caching the validator set. + * It also provides a method to get the current or next proposer, and to check who is in the current slot. + * + * If the epoch changes, then we update the stored validator set. + * + * Note: This class is very dependent on the system clock being in sync. + */ export class EpochCache { private committee: EthAddress[]; private cachedEpoch: bigint; @@ -37,15 +43,11 @@ export class EpochCache { this.committee = initialValidators; this.cachedSampleSeed = initialSampleSeed; - this.log.verbose(`Initialized EpochCache with constants and validators`, { l1constants, initialValidators }); + this.log.debug(`Initialized EpochCache with constants and validators`, { l1constants, initialValidators }); this.cachedEpoch = getEpochNumberAtTimestamp(BigInt(Math.floor(Date.now() / 1000)), this.l1constants); } - - // TODO: cleanup and merge rollup getters with l1 createAndSync in the archiver - // TODO: be aware that the timestamp source may be lagging behind since it is from the archiver??? - double check that there are no issues here - // how will this behave at the epoch boundary? static async create(rollupAddress: EthAddress, config?: EpochCacheConfig) { config = config ?? getEpochCacheConfigEnvVars(); @@ -80,11 +82,16 @@ export class EpochCache { ); } - async getEpochAndSlotNow(): Promise<EpochAndSlot> { + getEpochAndSlotNow(): EpochAndSlot { const now = BigInt(Math.floor(Date.now() / 1000)); return this.getEpochAndSlotAtTimestamp(now); } + getEpochAndSlotInNextSlot(): EpochAndSlot { + const nextSlotTs = BigInt(Math.floor(Date.now() / 1000) + this.l1constants.slotDuration); + return this.getEpochAndSlotAtTimestamp(nextSlotTs); + } + getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot { return { epoch: getEpochNumberAtTimestamp(ts, this.l1constants), @@ -93,20 +100,18 @@ export class EpochCache { }; } - // TODO: when the validator is asking about this, it may be in a slot that is across the epoch boundary - // we need to make sure that whenever we receive a request we are certain that the timing is correct - // - my first attempt used node timers, but this did not work well in tests that we jump into the future - // . - now i am reading the timestamp from the chain, but i need to make sure that we are only proposing for - // - the next slot, which means i sort of still need access to the archiver, to know what the last slot we - // - saw was, and that we do not get requested to attest to slots that are in the past - - - async getCommittee(): Promise<EthAddress[]> { + /** + * Get the current validator set + * + * @param nextSlot - If true, get the validator set for the next slot. + * @returns The current validator set. + */ + async getCommittee(nextSlot: boolean = false): Promise<EthAddress[]> { // If the current epoch has changed, then we need to make a request to update the validator set - const { epoch: calculatedEpoch, ts } = await this.getEpochAndSlotNow(); + const { epoch: calculatedEpoch, ts } = nextSlot ? this.getEpochAndSlotInNextSlot() : this.getEpochAndSlotNow(); if (calculatedEpoch !== this.cachedEpoch) { - this.log.verbose(`Epoch changed, updating validator set`, { calculatedEpoch, cachedEpoch: this.cachedEpoch }); + this.log.debug(`Epoch changed, updating validator set`, { calculatedEpoch, cachedEpoch: this.cachedEpoch }); this.cachedEpoch = calculatedEpoch; const [committeeAtTs, sampleSeedAtTs] = await Promise.all([ this.rollup.getCommitteeAt(ts), @@ -114,58 +119,72 @@ export class EpochCache { ]); this.committee = committeeAtTs.map((v: `0x${string}`) => EthAddress.fromString(v)); this.cachedSampleSeed = sampleSeedAtTs; - console.log('this.committee updated to ', this.committee); } return this.committee; } + /** + * Get the ABI encoding of the proposer index - see Leonidas.sol _computeProposerIndex + */ getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}` { - return encodeAbiParameters([ - { type: 'uint256', name: 'epoch' }, { type: 'uint256', name: 'slot' }, { type: 'uint256', name: 'seed' }], - [epoch, slot, seed]); + return encodeAbiParameters( + [ + { type: 'uint256', name: 'epoch' }, + { type: 'uint256', name: 'slot' }, + { type: 'uint256', name: 'seed' }, + ], + [epoch, slot, seed], + ); } - async computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): Promise<bigint> { + computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint { return BigInt(keccak256(this.getProposerIndexEncoding(epoch, slot, seed))) % size; } - // TODO: just use another timing library - async getCurrentProposer(slot: bigint): Promise<EthAddress> { - console.log('\n\n\n\n\ngetting current proposer\n\n\n\n\n'); + /** + * Returns the current and next proposer + * + * We return the next proposer as the node will check if it is the proposer at the next ethereum block, which + * can be the next slot. If this is the case, then it will send proposals early. + * + * If we are at an epoch boundary, then we can update the cache for the next epoch, this is the last check + * we do in the validator client, so we can update the cache here. + */ + async getProposerInCurrentOrNextSlot(): Promise<[EthAddress, EthAddress]> { // Validators are sorted by their index in the committee, and getValidatorSet will cache - // TODO: should we get the timestamp from the underlying l1 node? const committee = await this.getCommittee(); - // const { slot: currentSlot, ts } = await this.getEpochAndSlotNow(); - // console.log('currentSlot', currentSlot); - const [proposerFromL1] = await Promise.all([this.rollup.getCurrentProposer()]); - - console.log('timestampFromL1', await this.rollup.getTimestamp()); - const { slot: currentSlot, ts } = await this.getEpochAndSlotNow(); - console.log('local ts', ts); - - // TODO: could also cache this - const proposerIndex = await this.computeProposerIndex(slot, this.cachedEpoch, this.cachedSampleSeed, BigInt(committee.length)); - const proposerIndexFromL1 = await this.rollup.getInternalProposerIndexAt(ts); - console.log('locally calculated proposerIndex', proposerIndex); - console.log('proposerIndexFromL1', proposerIndexFromL1); - - // return committee[Number(proposerIndex)]; - // TODO: fix this, we need to request the seed from l1 for this epoch, we can cache this along side the epoch information - const calculatedProposer = committee[Number(proposerIndex)]; - - - const [internalSampleEncoding, epoch] = await this.rollup.getInternalSampleEncodingAt(ts); + const { slot: currentSlot, epoch: currentEpoch } = this.getEpochAndSlotNow(); + const { slot: nextSlot, epoch: nextEpoch } = this.getEpochAndSlotInNextSlot(); + + // Compute the proposer in this and the next slot + const proposerIndex = this.computeProposerIndex( + currentSlot, + this.cachedEpoch, + this.cachedSampleSeed, + BigInt(committee.length), + ); - console.log('self proposer encoding', this.getProposerIndexEncoding(this.cachedEpoch, slot, this.cachedSampleSeed)); - console.log('internalSampleEncoding', internalSampleEncoding); + // Check if the next proposer is in the next epoch + if (nextEpoch !== currentEpoch) { + await this.getCommittee(/*next slot*/ true); + } + const nextProposerIndex = this.computeProposerIndex( + nextSlot, + this.cachedEpoch, + this.cachedSampleSeed, + BigInt(committee.length), + ); - console.log('calculatedProposer', calculatedProposer); - console.log('proposerFromL1', proposerFromL1); + const calculatedProposer = committee[Number(proposerIndex)]; + const nextCalculatedProposer = committee[Number(nextProposerIndex)]; - return calculatedProposer; + return [calculatedProposer, nextCalculatedProposer]; } + /** + * Check if a validator is in the current epoch's committee + */ async isInCommittee(validator: EthAddress): Promise<boolean> { const committee = await this.getCommittee(); return committee.some(v => v.equals(validator)); diff --git a/yarn-project/epoch-cache/src/index.ts b/yarn-project/epoch-cache/src/index.ts index 8a126974a43..f6a7dba8382 100644 --- a/yarn-project/epoch-cache/src/index.ts +++ b/yarn-project/epoch-cache/src/index.ts @@ -1,2 +1,2 @@ export * from './epoch_cache.js'; -export * from './config.js'; \ No newline at end of file +export * from './config.js'; diff --git a/yarn-project/epoch-cache/src/timestamp_provider.ts b/yarn-project/epoch-cache/src/timestamp_provider.ts index 0519ecba6ea..e69de29bb2d 100644 --- a/yarn-project/epoch-cache/src/timestamp_provider.ts +++ b/yarn-project/epoch-cache/src/timestamp_provider.ts @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 0b71842d439..69bc1065653 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -44,38 +44,26 @@ export class RollupContract { return this.rollup.read.getCurrentSlot(); } - async getCommitteeAt(timestamp: bigint) { + getCommitteeAt(timestamp: bigint) { return this.rollup.read.getCommitteeAt([timestamp]); } - async getSampleSeedAt(timestamp: bigint) { + getSampleSeedAt(timestamp: bigint) { return this.rollup.read.getSampleSeedAt([timestamp]); } - async getCurrentSampleSeed() { + getCurrentSampleSeed() { return this.rollup.read.getCurrentSampleSeed(); } - async getCurrentEpochCommittee() { + getCurrentEpochCommittee() { return this.rollup.read.getCurrentEpochCommittee(); } - async getCurrentProposer() { + getCurrentProposer() { return this.rollup.read.getCurrentProposer(); } - async getTimestamp() { - return this.rollup.read.getTimestamp(); - } - - async getInternalSampleEncodingAt(timestamp: bigint) { - return this.rollup.read.getInternalSampleEncodingAt([timestamp]); - } - - async getInternalProposerIndexAt(timestamp: bigint) { - return this.rollup.read.getInternalProposerIndexAt([timestamp]); - } - async getEpochNumber(blockNumber?: bigint) { blockNumber ??= await this.getBlockNumber(); return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 7108a1a5ef3..2e9ac35acd5 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -255,6 +255,7 @@ export function createL1Clients( const publicClient = createPublicClient({ chain, transport: http(rpcUrl), + pollingInterval: 100, }); return { walletClient, publicClient }; diff --git a/yarn-project/p2p/src/service/libp2p_service.ts b/yarn-project/p2p/src/service/libp2p_service.ts index 24a6f893f80..88a474ade5c 100644 --- a/yarn-project/p2p/src/service/libp2p_service.ts +++ b/yarn-project/p2p/src/service/libp2p_service.ts @@ -521,8 +521,7 @@ export class LibP2PService extends WithTracer implements P2PService { } private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> { - - this.node.services.pubsub.topicValidators.set + this.node.services.pubsub.topicValidators.set; const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1; // basic data validation diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index b1e0aa5a50c..3341970ce47 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -161,6 +161,10 @@ export class L1Publisher { public static PROPOSE_GAS_GUESS: bigint = 12_000_000n; public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = this.PROPOSE_GAS_GUESS + 100_000n; + get publisherAddress() { + return this.account.address; + } + constructor( config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>, client: TelemetryClient, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 3380025bb3c..920c9be50ce 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -363,7 +363,9 @@ export class Sequencer { throw new Error(msg); } - this.log.verbose(`Can propose block ${proposalBlockNumber} at slot ${slot}`); + this.log.verbose(`Can propose block ${proposalBlockNumber} at slot ${slot}`, { + publisherAddress: this.publisher.publisherAddress, + }); return slot; } catch (err) { const msg = prettyLogViemErrorMsg(err); diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 8c11c34da6c..75d9c38a3e3 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -1,3 +1,5 @@ +import { EpochCache, type EpochCacheConfig } from '@aztec/epoch-cache'; +import { type EthAddress } from '@aztec/foundation/eth-address'; import { type P2P } from '@aztec/p2p'; import { type TelemetryClient } from '@aztec/telemetry-client'; @@ -5,15 +7,14 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; -import { EpochCache, type EpochCacheConfig } from '@aztec/epoch-cache'; -import { type EthAddress } from '@aztec/foundation/eth-address'; + // import { type L1TimestampSource } from '@aztec/circuit-types'; export async function createValidatorClient( config: ValidatorClientConfig & EpochCacheConfig, rollupAddress: EthAddress, p2pClient: P2P, - telemetry: TelemetryClient + telemetry: TelemetryClient, ) { if (config.disableValidator) { return undefined; diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 603ebf3eba9..43e81e53ed0 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -3,6 +3,7 @@ */ import { TxHash, mockTx } from '@aztec/circuit-types'; import { makeHeader } from '@aztec/circuits.js/testing'; +import { type EpochCache } from '@aztec/epoch-cache'; import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -22,7 +23,6 @@ import { TransactionsNotAvailableError, } from './errors/validator.error.js'; import { ValidatorClient } from './validator.js'; -import { type EpochCache } from '@aztec/epoch-cache'; describe('ValidationService', () => { let config: ValidatorClientConfig; @@ -100,7 +100,9 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); - epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => + Promise.resolve([proposal.getSender(), proposal.getSender()]), + ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); const val = ValidatorClient.new(config, epochCache, p2pClient); @@ -112,22 +114,26 @@ describe('ValidationService', () => { expect(attestation).toBeUndefined(); }); - it("Should not return an attestation if the validator is not in the committee", async () => { + it('Should not return an attestation if the validator is not in the committee', async () => { const proposal = makeBlockProposal(); // Setup epoch cache mocks - epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(proposal.getSender()) ); + epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => + Promise.resolve([proposal.getSender(), proposal.getSender()]), + ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false)); const attestation = await validatorClient.attestToProposal(proposal); expect(attestation).toBeUndefined(); }); - it("Should not return an attestation if the proposer is not the current proposer", async () => { + it('Should not return an attestation if the proposer is not the current proposer', async () => { const proposal = makeBlockProposal(); // Setup epoch cache mocks - epochCache.getCurrentProposer.mockImplementation(() => Promise.resolve(EthAddress.random())); + epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => + Promise.resolve([proposal.getSender(), proposal.getSender()]), + ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); const attestation = await validatorClient.attestToProposal(proposal); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 8e39fde3410..8133a21b433 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -7,6 +7,7 @@ import { type TxHash, } from '@aztec/circuit-types'; import { type GlobalVariables, type Header } from '@aztec/circuits.js'; +import { type EpochCache } from '@aztec/epoch-cache'; import { Buffer32 } from '@aztec/foundation/buffer'; import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -28,7 +29,6 @@ import { import { type ValidatorKeyStore } from './key_store/interface.js'; import { LocalKeyStore } from './key_store/local_key_store.js'; import { ValidatorMetrics } from './metrics.js'; -import { type EpochCache } from '@aztec/epoch-cache'; /** * Callback function for building a block @@ -81,7 +81,12 @@ export class ValidatorClient extends WithTracer implements Validator { this.log.verbose('Initialized validator'); } - static new(config: ValidatorClientConfig, epochCache: EpochCache, p2pClient: P2P, telemetry: TelemetryClient = new NoopTelemetryClient()) { + static new( + config: ValidatorClientConfig, + epochCache: EpochCache, + p2pClient: P2P, + telemetry: TelemetryClient = new NoopTelemetryClient(), + ) { if (!config.validatorPrivateKey) { throw new InvalidValidatorPrivateKeyError(); } @@ -120,19 +125,15 @@ export class ValidatorClient extends WithTracer implements Validator { async attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined> { // Check that I am in the committee - console.log('request to attest', proposal.payload.header.globalVariables.slotNumber.toString()); if (!(await this.epochCache.isInCommittee(this.keyStore.getAddress()))) { this.log.verbose(`Not in the committee, skipping attestation`); return undefined; } - // Check that the proposal is from the current proposer - // TODO: this must be updated to request the epoch seed from the l1 contract too - const currentProposer = await this.epochCache.getCurrentProposer(proposal.payload.header.globalVariables.slotNumber); - console.log('currentProposer', currentProposer); - console.log('proposal.getSender()', proposal.getSender()); - if (!proposal.getSender().equals(currentProposer)) { - this.log.verbose(`Not the current proposer, skipping attestation`); + // Check that the proposal is from the current proposer, or the next proposer. + const [currentProposer, nextSlotProposer] = await this.epochCache.getProposerInCurrentOrNextSlot(); + if (!proposal.getSender().equals(currentProposer) && !proposal.getSender().equals(nextSlotProposer)) { + this.log.verbose(`Not the current or next proposer, skipping attestation`); return undefined; } From 7003114bb98158e8a08fdc2fc069e4cd0e713714 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:43:10 +0000 Subject: [PATCH 12/29] fmt --- l1-contracts/src/core/Leonidas.sol | 3 --- yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts | 3 --- yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts | 6 ++++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 985d79410c9..4e7864ec766 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -334,7 +334,6 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return _getCommitteeAt(_ts); } - // TODO: make sure this cannot modify state function getSampleSeedAt(Timestamp _ts) external view returns (uint256) { return _getSampleSeed(getEpochAt(_ts)); } @@ -510,7 +509,6 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return lastSeed; } - /** * @notice Computes the index of the committee member that acts as proposer for a given slot * @@ -528,5 +526,4 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { { return uint256(keccak256(abi.encode(_epoch, _slot, _seed))) % _size; } - } diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts index 9bb9af55c0a..5f5a3690cf6 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts @@ -84,9 +84,6 @@ describe('e2e_p2p_network', () => { shouldCollectMetrics(), ); - // When using fake timers, we need to keep the system and anvil clocks in sync. - await t.syncMockSystemTime(); - // wait a bit for peers to discover each other await sleep(4000); diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index c8976c75c9b..25341aa0b29 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -34,7 +34,7 @@ const l1ContractsConfig = getL1ContractsConfigEnvVars(); export const WAIT_FOR_TX_TIMEOUT = l1ContractsConfig.aztecSlotDuration * 3; export class P2PNetworkTest { - public snapshotManager: ISnapshotManager; + private snapshotManager: ISnapshotManager; private baseAccount; public logger: DebugLogger; @@ -83,6 +83,9 @@ export class P2PNetworkTest { }); } + /** + * Start a loop to sync the mock system time with the L1 block time + */ public startSyncMockSystemTimeInterval() { this.cleanupInterval = setInterval(async () => { await this.syncMockSystemTime(); @@ -92,7 +95,6 @@ export class P2PNetworkTest { /** * When using fake timers, we need to keep the system and anvil clocks in sync. */ - // TODO: can we just calculate time based on the epoch number observed in the smart contract vs the genesis time? public async syncMockSystemTime() { this.logger.info('Syncing mock system time'); const { timer, deployL1ContractsValues } = this.ctx!; From 8c8df0c8365d75a7e05b044ddbe8d042139f4c25 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:57:55 +0000 Subject: [PATCH 13/29] post merge fix --- l1-contracts/src/core/Leonidas.sol | 7 ++++++- l1-contracts/src/core/interfaces/ILeonidas.sol | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 1ae3b5c9def..ee9d79be6a2 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -286,7 +286,12 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { } // Public view function for get committee at - function getCommitteeAt(Timestamp _ts) external view override(ILeonidas) returns (address[] memory) { + function getCommitteeAt(Timestamp _ts) + external + view + override(ILeonidas) + returns (address[] memory) + { return store.getCommitteeAt(getEpochAt(_ts), TARGET_COMMITTEE_SIZE); } diff --git a/l1-contracts/src/core/interfaces/ILeonidas.sol b/l1-contracts/src/core/interfaces/ILeonidas.sol index 256abed990e..828cdbc452e 100644 --- a/l1-contracts/src/core/interfaces/ILeonidas.sol +++ b/l1-contracts/src/core/interfaces/ILeonidas.sol @@ -48,9 +48,13 @@ interface ILeonidas { // Likely removal of these to replace with a size and indiviual getter // Get the current epoch committee function getCurrentEpochCommittee() external view returns (address[] memory); + function getCommitteeAt(Timestamp _ts) external view returns (address[] memory); function getEpochCommittee(Epoch _epoch) external view returns (address[] memory); function getValidators() external view returns (address[] memory); + function getSampleSeedAt(Timestamp _ts) external view returns (uint256); + function getCurrentSampleSeed() external view returns (uint256); + function getEpochAt(Timestamp _ts) external view returns (Epoch); function getSlotAt(Timestamp _ts) external view returns (Slot); function getEpochAtSlot(Slot _slotNumber) external view returns (Epoch); From e2b43fb164f21fc003d00db2a8d52206903db045 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:11:07 +0000 Subject: [PATCH 14/29] fix: ordering --- l1-contracts/src/core/Leonidas.sol | 36 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index ee9d79be6a2..76a621bc74b 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -278,14 +278,12 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { } /** - * @notice Adds a validator to the set WITHOUT setting up the epoch - * @param _validator - The validator to add + * @notice Get the committee for a given timestamp + * + * @param _ts - The timestamp to get the committee for + * + * @return The committee for the given timestamp */ - function _addValidator(address _validator) internal { - store.validatorSet.add(_validator); - } - - // Public view function for get committee at function getCommitteeAt(Timestamp _ts) external view @@ -295,14 +293,34 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return store.getCommitteeAt(getEpochAt(_ts), TARGET_COMMITTEE_SIZE); } - function getSampleSeedAt(Timestamp _ts) external view returns (uint256) { + /** + * @notice Get the sample seed for a given timestamp + * + * @param _ts - The timestamp to get the sample seed for + * + * @return The sample seed for the given timestamp + */ + function getSampleSeedAt(Timestamp _ts) external view override(ILeonidas) returns (uint256) { return store.getSampleSeed(getEpochAt(_ts)); } - function getCurrentSampleSeed() external view returns (uint256) { + /** + * @notice Get the sample seed for the current epoch + * + * @return The sample seed for the current epoch + */ + function getCurrentSampleSeed() external view override(ILeonidas) returns (uint256) { return store.getSampleSeed(getCurrentEpoch()); } + /** + * @notice Adds a validator to the set WITHOUT setting up the epoch + * @param _validator - The validator to add + */ + function _addValidator(address _validator) internal { + store.validatorSet.add(_validator); + } + /** * @notice Propose a pending block from the point-of-view of sequencer selection. Will: * - Setup the epoch if needed (if epoch committee is empty skips the rest) From e0976bbe485c46cec4435e084e8f14511d240d25 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:12:55 +0000 Subject: [PATCH 15/29] fix: ordering --- l1-contracts/src/core/Leonidas.sol | 72 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 76a621bc74b..df58ce7ae72 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -121,6 +121,42 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return store.validatorSet.values(); } + /** + * @notice Get the committee for a given timestamp + * + * @param _ts - The timestamp to get the committee for + * + * @return The committee for the given timestamp + */ + function getCommitteeAt(Timestamp _ts) + external + view + override(ILeonidas) + returns (address[] memory) + { + return store.getCommitteeAt(getEpochAt(_ts), TARGET_COMMITTEE_SIZE); + } + + /** + * @notice Get the sample seed for a given timestamp + * + * @param _ts - The timestamp to get the sample seed for + * + * @return The sample seed for the given timestamp + */ + function getSampleSeedAt(Timestamp _ts) external view override(ILeonidas) returns (uint256) { + return store.getSampleSeed(getEpochAt(_ts)); + } + + /** + * @notice Get the sample seed for the current epoch + * + * @return The sample seed for the current epoch + */ + function getCurrentSampleSeed() external view override(ILeonidas) returns (uint256) { + return store.getSampleSeed(getCurrentEpoch()); + } + /** * @notice Performs a setup of an epoch if needed. The setup will * - Sample the validator set for the epoch @@ -277,42 +313,6 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { return Epoch.wrap(_slotNumber.unwrap() / EPOCH_DURATION); } - /** - * @notice Get the committee for a given timestamp - * - * @param _ts - The timestamp to get the committee for - * - * @return The committee for the given timestamp - */ - function getCommitteeAt(Timestamp _ts) - external - view - override(ILeonidas) - returns (address[] memory) - { - return store.getCommitteeAt(getEpochAt(_ts), TARGET_COMMITTEE_SIZE); - } - - /** - * @notice Get the sample seed for a given timestamp - * - * @param _ts - The timestamp to get the sample seed for - * - * @return The sample seed for the given timestamp - */ - function getSampleSeedAt(Timestamp _ts) external view override(ILeonidas) returns (uint256) { - return store.getSampleSeed(getEpochAt(_ts)); - } - - /** - * @notice Get the sample seed for the current epoch - * - * @return The sample seed for the current epoch - */ - function getCurrentSampleSeed() external view override(ILeonidas) returns (uint256) { - return store.getSampleSeed(getCurrentEpoch()); - } - /** * @notice Adds a validator to the set WITHOUT setting up the epoch * @param _validator - The validator to add From cda7e998dc088a7c19ad00229819992d42b8ba90 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:32:57 +0000 Subject: [PATCH 16/29] temp --- yarn-project/epoch-cache/src/epoch_cache.test.ts | 12 ++++++------ yarn-project/epoch-cache/src/epoch_cache.ts | 13 +++++++++---- yarn-project/validator-client/src/validator.ts | 12 ++++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 271d19037be..87c14ebc518 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -85,18 +85,18 @@ describe('EpochCache', () => { // Hence the chosen values for testCommittee below // Get validator for slot 0 - let [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[1]); + let { currentProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(currentProposer).toEqual(testCommittee[1]); // Move to next slot jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); - [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[1]); + let { currentProposer: nextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(nextProposer).toEqual(testCommittee[1]); // Move to slot that wraps around validator set jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); - [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[0]); + let { currentProposer: nextNextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(nextNextProposer).toEqual(testCommittee[0]); }); it('Should request to update the validato set when on the epoch boundary', async () => { diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 7ebee68ef14..fb02f053a2f 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -151,7 +151,12 @@ export class EpochCache { * If we are at an epoch boundary, then we can update the cache for the next epoch, this is the last check * we do in the validator client, so we can update the cache here. */ - async getProposerInCurrentOrNextSlot(): Promise<[EthAddress, EthAddress]> { + async getProposerInCurrentOrNextSlot(): Promise<{ + currentProposer: EthAddress; + nextProposer: EthAddress; + currentSlot: bigint; + nextSlot: bigint; + }> { // Validators are sorted by their index in the committee, and getValidatorSet will cache const committee = await this.getCommittee(); const { slot: currentSlot, epoch: currentEpoch } = this.getEpochAndSlotNow(); @@ -176,10 +181,10 @@ export class EpochCache { BigInt(committee.length), ); - const calculatedProposer = committee[Number(proposerIndex)]; - const nextCalculatedProposer = committee[Number(nextProposerIndex)]; + const currentProposer = committee[Number(proposerIndex)]; + const nextProposer = committee[Number(nextProposerIndex)]; - return [calculatedProposer, nextCalculatedProposer]; + return { currentProposer, nextProposer, currentSlot, nextSlot }; } /** diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 8133a21b433..f38c005bcb6 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -131,12 +131,20 @@ export class ValidatorClient extends WithTracer implements Validator { } // Check that the proposal is from the current proposer, or the next proposer. - const [currentProposer, nextSlotProposer] = await this.epochCache.getProposerInCurrentOrNextSlot(); - if (!proposal.getSender().equals(currentProposer) && !proposal.getSender().equals(nextSlotProposer)) { + const proposalSender = proposal.getSender(); + const { currentProposer, nextProposer, currentSlot, nextSlot } = await this.epochCache.getProposerInCurrentOrNextSlot(); + if (!proposalSender.equals(currentProposer) && !proposalSender.equals(nextProposer)) { this.log.verbose(`Not the current or next proposer, skipping attestation`); return undefined; } + // Check that the proposal is for the current or next slot + const { slotNumber } = proposal.payload.header.globalVariables; + if (slotNumber !== currentSlot && slotNumber !== nextSlot) { + this.log.verbose(`Not the current or next slot, skipping attestation`); + return undefined; + } + // Check that all of the tranasctions in the proposal are available in the tx pool before attesting this.log.verbose(`request to attest`, { archive: proposal.payload.archive.toString(), From da87b55ef8cee33a71bd5e7c042b94534425616c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:51:21 +0000 Subject: [PATCH 17/29] fix --- yarn-project/end-to-end/src/e2e_epochs.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn-project/end-to-end/src/e2e_epochs.test.ts b/yarn-project/end-to-end/src/e2e_epochs.test.ts index 3ac4e07afd8..d45173cb554 100644 --- a/yarn-project/end-to-end/src/e2e_epochs.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs.test.ts @@ -1,5 +1,6 @@ -import { type EpochConstants, getTimestampRangeForEpoch } from '@aztec/archiver/epoch'; +// eslint-disable-next-line no-restricted-imports import { type DebugLogger, retryUntil } from '@aztec/aztec.js'; +import { type EpochConstants, getTimestampRangeForEpoch } from '@aztec/circuit-types'; import { RollupContract } from '@aztec/ethereum/contracts'; import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/test'; From 5fc99004c4474ee8663ab5ad0d91b49770f33c28 Mon Sep 17 00:00:00 2001 From: Mitch <mitchell@aztecprotocol.com> Date: Thu, 5 Dec 2024 13:55:07 -0500 Subject: [PATCH 18/29] fix: linter and test --- yarn-project/aztec.js/src/index.ts | 8 +++++--- yarn-project/end-to-end/src/e2e_epochs.test.ts | 3 +-- yarn-project/p2p/src/service/libp2p_service.ts | 2 -- yarn-project/validator-client/src/factory.ts | 2 -- yarn-project/validator-client/src/validator.test.ts | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 3a67fd9a6fd..78a293b7b3a 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -32,9 +32,9 @@ export { type ContractNotes, type ContractStorageLayout, type DeployOptions, + type ProfileResult, type SendMethodOptions, type WaitOpts, - type ProfileResult, } from './contract/index.js'; export { ContractDeployer } from './deployment/index.js'; @@ -59,8 +59,8 @@ export { type FieldLike, type FunctionSelectorLike, type L2AmountClaim, - type L2Claim, type L2AmountClaimWithRecipient, + type L2Claim, type WrappedFieldLike, } from './utils/index.js'; @@ -138,10 +138,12 @@ export { UnencryptedL2Log, UniqueNote, createAztecNodeClient, + getTimestampRangeForEpoch, merkleTreeIds, mockEpochProofQuote, mockTx, type AztecNode, + type EpochConstants, type LogFilter, type PXE, type PartialAddress, @@ -164,7 +166,7 @@ export { elapsed } from '@aztec/foundation/timer'; export { type FieldsOf } from '@aztec/foundation/types'; export { fileURLToPath } from '@aztec/foundation/url'; -export { type DeployL1Contracts, EthCheatCodes, deployL1Contract, deployL1Contracts } from '@aztec/ethereum'; +export { EthCheatCodes, deployL1Contract, deployL1Contracts, type DeployL1Contracts } from '@aztec/ethereum'; // Start of section that exports public api via granular api. // Here you *can* do `export *` as the granular api defacto exports things explicitly. diff --git a/yarn-project/end-to-end/src/e2e_epochs.test.ts b/yarn-project/end-to-end/src/e2e_epochs.test.ts index d45173cb554..ee0dc3197ee 100644 --- a/yarn-project/end-to-end/src/e2e_epochs.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs.test.ts @@ -1,6 +1,5 @@ // eslint-disable-next-line no-restricted-imports -import { type DebugLogger, retryUntil } from '@aztec/aztec.js'; -import { type EpochConstants, getTimestampRangeForEpoch } from '@aztec/circuit-types'; +import { type DebugLogger, type EpochConstants, getTimestampRangeForEpoch, retryUntil } from '@aztec/aztec.js'; import { RollupContract } from '@aztec/ethereum/contracts'; import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/test'; diff --git a/yarn-project/p2p/src/service/libp2p_service.ts b/yarn-project/p2p/src/service/libp2p_service.ts index dcb225feb6a..18d2d180a4a 100644 --- a/yarn-project/p2p/src/service/libp2p_service.ts +++ b/yarn-project/p2p/src/service/libp2p_service.ts @@ -504,8 +504,6 @@ export class LibP2PService extends WithTracer implements P2PService { } private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> { - this.node.services.pubsub.topicValidators.set; - const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1; // basic data validation const dataValidator = new DataTxValidator(); diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 75d9c38a3e3..7ea8b09e8d6 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -8,8 +8,6 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; -// import { type L1TimestampSource } from '@aztec/circuit-types'; - export async function createValidatorClient( config: ValidatorClientConfig & EpochCacheConfig, rollupAddress: EthAddress, diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 43e81e53ed0..e842480d010 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -132,7 +132,7 @@ describe('ValidationService', () => { // Setup epoch cache mocks epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([proposal.getSender(), proposal.getSender()]), + Promise.resolve([EthAddress.random(), EthAddress.random()]), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); From bcbcccd5117788657fb9f4d01ac5e5026311f489 Mon Sep 17 00:00:00 2001 From: Mitch <mitchell@aztecprotocol.com> Date: Thu, 5 Dec 2024 16:02:32 -0500 Subject: [PATCH 19/29] fix: merge conflicts --- l1-contracts/src/core/Leonidas.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 8d50e2b4cf1..3d08a1b6ef6 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -119,7 +119,9 @@ contract Leonidas is Staking, TimeFns, ILeonidas { override(ILeonidas) returns (address[] memory) { - return store.getCommitteeAt(getEpochAt(_ts), TARGET_COMMITTEE_SIZE); + return LeonidasLib.getCommitteeAt( + leonidasStore, stakingStore, getEpochAt(_ts), TARGET_COMMITTEE_SIZE + ); } /** @@ -130,7 +132,7 @@ contract Leonidas is Staking, TimeFns, ILeonidas { * @return The sample seed for the given timestamp */ function getSampleSeedAt(Timestamp _ts) external view override(ILeonidas) returns (uint256) { - return store.getSampleSeed(getEpochAt(_ts)); + return LeonidasLib.getSampleSeed(leonidasStore, getEpochAt(_ts)); } /** @@ -139,7 +141,7 @@ contract Leonidas is Staking, TimeFns, ILeonidas { * @return The sample seed for the current epoch */ function getCurrentSampleSeed() external view override(ILeonidas) returns (uint256) { - return store.getSampleSeed(getCurrentEpoch()); + return LeonidasLib.getSampleSeed(leonidasStore, getCurrentEpoch()); } /** From 1309414845b1f8eae2d1cfb4ca64c57b485b4994 Mon Sep 17 00:00:00 2001 From: Mitch <mitchell@aztecprotocol.com> Date: Thu, 5 Dec 2024 17:08:57 -0500 Subject: [PATCH 20/29] fix: function ordering and p2p tests --- l1-contracts/src/core/Leonidas.sol | 46 +++---- yarn-project/aztec/package.json | 2 +- yarn-project/cli-wallet/package.json | 2 +- yarn-project/end-to-end/package.json | 2 +- .../end-to-end/src/e2e_p2p/p2p_network.ts | 124 +++++++++++------- yarn-project/kv-store/package.json | 2 +- yarn-project/prover-client/package.json | 2 +- yarn-project/txe/package.json | 2 +- 8 files changed, 102 insertions(+), 80 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 3d08a1b6ef6..77244bec628 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -83,29 +83,6 @@ contract Leonidas is Staking, TimeFns, ILeonidas { ); } - function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) - public - override(Staking) - { - setupEpoch(); - require( - _attester != address(0) && _proposer != address(0), - Errors.Leonidas__InvalidDeposit(_attester, _proposer) - ); - super.deposit(_attester, _proposer, _withdrawer, _amount); - } - - function initiateWithdraw(address _attester, address _recipient) - public - override(Staking) - returns (bool) - { - // @note The attester might be chosen for the epoch, so the delay must be long enough - // to allow for that. - setupEpoch(); - return super.initiateWithdraw(_attester, _recipient); - } - /** * @notice Get the committee for a given timestamp * @@ -144,6 +121,29 @@ contract Leonidas is Staking, TimeFns, ILeonidas { return LeonidasLib.getSampleSeed(leonidasStore, getCurrentEpoch()); } + function initiateWithdraw(address _attester, address _recipient) + public + override(Staking) + returns (bool) + { + // @note The attester might be chosen for the epoch, so the delay must be long enough + // to allow for that. + setupEpoch(); + return super.initiateWithdraw(_attester, _recipient); + } + + function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) + public + override(Staking) + { + setupEpoch(); + require( + _attester != address(0) && _proposer != address(0), + Errors.Leonidas__InvalidDeposit(_attester, _proposer) + ); + super.deposit(_attester, _proposer, _withdrawer, _amount); + } + /** * @notice Performs a setup of an epoch if needed. The setup will * - Sample the validator set for the epoch diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index f8732bff89b..cf8c8e07636 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -114,4 +114,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/cli-wallet/package.json b/yarn-project/cli-wallet/package.json index e33bf41c805..7973038a447 100644 --- a/yarn-project/cli-wallet/package.json +++ b/yarn-project/cli-wallet/package.json @@ -100,4 +100,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index ae174abd14f..59533e26d89 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -157,4 +157,4 @@ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", "rootDir": "./src" } -} \ No newline at end of file +} diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index a40ecf98561..12ad8e7623d 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -1,7 +1,7 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node'; import { type AccountWalletWithSecretKey } from '@aztec/aztec.js'; -import { MINIMUM_STAKE, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; +import { EthCheatCodes, MINIMUM_STAKE, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import { SpamContract } from '@aztec/noir-contracts.js'; @@ -131,59 +131,81 @@ export class P2PNetworkTest { } async applyBaseSnapshots() { - await this.snapshotManager.snapshot('add-validators', async ({ deployL1ContractsValues, timer }) => { - const rollup = getContract({ - address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), - abi: RollupAbi, - client: deployL1ContractsValues.walletClient, - }); - - this.logger.verbose(`Adding ${this.numberOfNodes} validators`); - - const stakingAsset = getContract({ - address: deployL1ContractsValues.l1ContractAddresses.stakingAssetAddress.toString(), - abi: TestERC20Abi, - client: deployL1ContractsValues.walletClient, - }); - - const stakeNeeded = MINIMUM_STAKE * BigInt(this.numberOfNodes); - await Promise.all( - [ - await stakingAsset.write.mint([deployL1ContractsValues.walletClient.account.address, stakeNeeded], {} as any), - await stakingAsset.write.approve( - [deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), stakeNeeded], - {} as any, - ), - ].map(txHash => deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: txHash })), - ); - - const validators = []; - - for (let i = 0; i < this.numberOfNodes; i++) { - const attester = privateKeyToAccount(this.attesterPrivateKeys[i]!); - const proposer = privateKeyToAccount(this.proposerPrivateKeys[i]!); - validators.push({ - attester: attester.address, - proposer: proposer.address, - withdrawer: attester.address, - amount: MINIMUM_STAKE, - } as const); - - this.logger.verbose( - `Adding (attester, proposer) pair: (${attester.address}, ${proposer.address}) as validator`, + await this.snapshotManager.snapshot( + 'add-validators', + async ({ deployL1ContractsValues, aztecNodeConfig, timer }) => { + const rollup = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), + abi: RollupAbi, + client: deployL1ContractsValues.walletClient, + }); + + this.logger.verbose(`Adding ${this.numberOfNodes} validators`); + + const stakingAsset = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.stakingAssetAddress.toString(), + abi: TestERC20Abi, + client: deployL1ContractsValues.walletClient, + }); + + const stakeNeeded = MINIMUM_STAKE * BigInt(this.numberOfNodes); + await Promise.all( + [ + await stakingAsset.write.mint( + [deployL1ContractsValues.walletClient.account.address, stakeNeeded], + {} as any, + ), + await stakingAsset.write.approve( + [deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), stakeNeeded], + {} as any, + ), + ].map(txHash => deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: txHash })), ); - } - await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ - hash: await rollup.write.cheat__InitialiseValidatorSet([validators]), - }); + const validators = []; + + for (let i = 0; i < this.numberOfNodes; i++) { + const attester = privateKeyToAccount(this.attesterPrivateKeys[i]!); + const proposer = privateKeyToAccount(this.proposerPrivateKeys[i]!); + validators.push({ + attester: attester.address, + proposer: proposer.address, + withdrawer: attester.address, + amount: MINIMUM_STAKE, + } as const); + + this.logger.verbose( + `Adding (attester, proposer) pair: (${attester.address}, ${proposer.address}) as validator`, + ); + } - // Set the system time in the node, only after we have warped the time and waited for a block - // Time is only set in the NEXT block - const slotsInEpoch = await rollup.read.EPOCH_DURATION(); - const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]); - timer.setSystemTime(Number(timestamp) * 1000); - }); + await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + hash: await rollup.write.cheat__InitialiseValidatorSet([validators]), + }); + + const slotsInEpoch = await rollup.read.EPOCH_DURATION(); + const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]); + const cheatCodes = new EthCheatCodes(aztecNodeConfig.l1RpcUrl); + try { + await cheatCodes.warp(Number(timestamp)); + } catch (err) { + this.logger.debug('Warp failed, time already satisfied'); + } + + // Send and await a tx to make sure we mine a block for the warp to correctly progress. + await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ + hash: await deployL1ContractsValues.walletClient.sendTransaction({ + to: this.baseAccount.address, + value: 1n, + account: this.baseAccount, + }), + }); + + // Set the system time in the node, only after we have warped the time and waited for a block + // Time is only set in the NEXT block + timer.setSystemTime(Number(timestamp) * 1000); + }, + ); } async setupAccount() { diff --git a/yarn-project/kv-store/package.json b/yarn-project/kv-store/package.json index 96073106a08..f42e02ff786 100644 --- a/yarn-project/kv-store/package.json +++ b/yarn-project/kv-store/package.json @@ -79,4 +79,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/prover-client/package.json b/yarn-project/prover-client/package.json index 08b6b59a46d..2c01d5617c0 100644 --- a/yarn-project/prover-client/package.json +++ b/yarn-project/prover-client/package.json @@ -104,4 +104,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index ebae6269f3c..0fa9709f348 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -93,4 +93,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From 6aba5515498991467c5ce77b3104f19c34d3372b Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:02:29 +0000 Subject: [PATCH 21/29] fix: reex test - sending same proposal twice in one slot --- .../circuit-types/src/p2p/block_proposal.ts | 4 ++++ yarn-project/epoch-cache/src/epoch_cache.test.ts | 2 +- .../sequencer-client/src/sequencer/sequencer.ts | 4 ++++ .../validator-client/src/validator.test.ts | 2 +- yarn-project/validator-client/src/validator.ts | 16 +++++++++++++--- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/yarn-project/circuit-types/src/p2p/block_proposal.ts b/yarn-project/circuit-types/src/p2p/block_proposal.ts index 5d2ad7f7f46..207312ba4a1 100644 --- a/yarn-project/circuit-types/src/p2p/block_proposal.ts +++ b/yarn-project/circuit-types/src/p2p/block_proposal.ts @@ -49,6 +49,10 @@ export class BlockProposal extends Gossipable { return this.payload.archive; } + get slotNumber(): Fr { + return this.payload.header.globalVariables.slotNumber; + } + static async createProposalFromSigner( payload: ConsensusPayload, payloadSigner: (payload: Buffer32) => Promise<Signature>, diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 271d19037be..cd67093c324 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -99,7 +99,7 @@ describe('EpochCache', () => { expect(currentValidator).toEqual(testCommittee[0]); }); - it('Should request to update the validato set when on the epoch boundary', async () => { + it('Should request to update the validator set when on the epoch boundary', async () => { // Set initial time to a known slot const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds jest.setSystemTime(initialTime); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 9bb6f592d00..34c9100a4e8 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -693,6 +693,10 @@ export class Sequencer { this.log.info('Creating block proposal'); const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes); + if (!proposal) { + this.log.verbose(`Failed to create block proposal, skipping`); + return undefined; + } const slotNumber = block.header.globalVariables.slotNumber.toBigInt(); diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index e842480d010..df2bee4ccbb 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -73,7 +73,7 @@ describe('ValidationService', () => { expect(blockProposal).toBeDefined(); const validatorAddress = EthAddress.fromString(validatorAccount.address); - expect(blockProposal.getSender()).toEqual(validatorAddress); + expect(blockProposal?.getSender()).toEqual(validatorAddress); }); it('Should a timeout if we do not collect enough attestations in time', async () => { diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 8133a21b433..33a78090648 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -48,7 +48,7 @@ export interface Validator { registerBlockBuilder(blockBuilder: BlockBuilderCallback): void; // Block validation responsiblities - createBlockProposal(header: Header, archive: Fr, txs: TxHash[]): Promise<BlockProposal>; + createBlockProposal(header: Header, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined>; attestToProposal(proposal: BlockProposal): void; broadcastBlockProposal(proposal: BlockProposal): void; @@ -62,6 +62,9 @@ export class ValidatorClient extends WithTracer implements Validator { private validationService: ValidationService; private metrics: ValidatorMetrics; + // Used to check if we are sending the same proposal twice + private previousProposal?: BlockProposal; + // Callback registered to: sequencer.buildBlock private blockBuilder?: BlockBuilderCallback = undefined; @@ -233,8 +236,15 @@ export class ValidatorClient extends WithTracer implements Validator { } } - createBlockProposal(header: Header, archive: Fr, txs: TxHash[]): Promise<BlockProposal> { - return this.validationService.createBlockProposal(header, archive, txs); + async createBlockProposal(header: Header, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined> { + if (this.previousProposal?.slotNumber.equals(header.globalVariables.slotNumber)) { + this.log.verbose(`Already made a proposal for the same slot, skipping proposal`); + return Promise.resolve(undefined); + } + + const newProposal = await this.validationService.createBlockProposal(header, archive, txs); + this.previousProposal = newProposal; + return newProposal; } broadcastBlockProposal(proposal: BlockProposal): void { From 82da16ba9de6b3c17b1a37e2b4285c5f29bcc0b5 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:10:23 +0000 Subject: [PATCH 22/29] fix: for a slot ci runner, decrease the time jump length --- yarn-project/epoch-cache/src/epoch_cache.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index cd67093c324..49c629a612b 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -57,7 +57,7 @@ describe('EpochCache', () => { expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(0); // Move time forward within the same epoch (less than EPOCH_DURATION) (x 1000 for milliseconds) - jest.setSystemTime(Date.now() + (Number(EPOCH_DURATION * SLOT_DURATION) / 2) * 1000); + jest.setSystemTime(Date.now() + (Number(EPOCH_DURATION * SLOT_DURATION) / 4) * 1000); // Add another validator to the set rollupContract.getCommitteeAt.mockResolvedValue([...testCommittee, extraTestValidator].map(v => v.toString())); From 13ec8e47b9af2cca0dd8195fce2598b487729a0b Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:44:02 +0000 Subject: [PATCH 23/29] fix: epoch cache flake --- yarn-project/epoch-cache/src/epoch_cache.test.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 49c629a612b..4cde40eff1e 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -13,7 +13,8 @@ describe('EpochCache', () => { // Test constants const SLOT_DURATION = 12; const EPOCH_DURATION = 32; // 384 seconds - const L1_GENESIS_TIME = 1000n; + // const L1_GENESIS_TIME = 1000n; + let l1GenesisTime: bigint; const testCommittee = [ EthAddress.fromString('0x0000000000000000000000000000000000000001'), @@ -30,13 +31,15 @@ describe('EpochCache', () => { rollupContract.getCommitteeAt.mockResolvedValue(testCommittee.map(v => v.toString())); rollupContract.getSampleSeedAt.mockResolvedValue(0n); + l1GenesisTime = BigInt(Math.floor(Date.now() / 1000)); + // Setup fake timers jest.useFakeTimers(); // Initialize with test constants const testConstants = { l1StartBlock: 0n, - l1GenesisTime: L1_GENESIS_TIME, + l1GenesisTime, slotDuration: SLOT_DURATION, ethereumSlotDuration: SLOT_DURATION, epochDuration: EPOCH_DURATION, @@ -78,7 +81,7 @@ describe('EpochCache', () => { it('should correctly get current validator based on slot number', async () => { // Set initial time to a known slot - const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds + const initialTime = Number(l1GenesisTime) * 1000; // Convert to milliseconds jest.setSystemTime(initialTime); // The valid proposer has been calculated in advance to be [1,1,0] for the slots chosen @@ -101,7 +104,7 @@ describe('EpochCache', () => { it('Should request to update the validator set when on the epoch boundary', async () => { // Set initial time to a known slot - const initialTime = Number(L1_GENESIS_TIME) * 1000; // Convert to milliseconds + const initialTime = Number(l1GenesisTime) * 1000; // Convert to milliseconds jest.setSystemTime(initialTime); // Move forward to slot before the epoch boundary @@ -109,6 +112,6 @@ describe('EpochCache', () => { // Should request to update the validator set await epochCache.getProposerInCurrentOrNextSlot(); - expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(2); + expect(rollupContract.getCommitteeAt).toHaveBeenCalledTimes(1); }); }); From c8c5df608d0867c2483d89998d147d0fd05447ad Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:00:04 +0000 Subject: [PATCH 24/29] chore: test --- .../validator-client/src/validator.test.ts | 39 +++++++++++++++++-- .../validator-client/src/validator.ts | 4 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index df2bee4ccbb..7b6fdca5a26 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -101,7 +101,12 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([proposal.getSender(), proposal.getSender()]), + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); @@ -119,7 +124,12 @@ describe('ValidationService', () => { // Setup epoch cache mocks epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([proposal.getSender(), proposal.getSender()]), + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false)); @@ -132,7 +142,30 @@ describe('ValidationService', () => { // Setup epoch cache mocks epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([EthAddress.random(), EthAddress.random()]), + Promise.resolve({ + currentProposer: EthAddress.random(), + nextProposer: EthAddress.random(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), + ); + epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); + + const attestation = await validatorClient.attestToProposal(proposal); + expect(attestation).toBeUndefined(); + }); + + it('Should not return an attestation if the proposal is not for the current or next slot', async () => { + const proposal = makeBlockProposal(); + + // Setup epoch cache mocks + epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt() + 20n, + nextSlot: proposal.slotNumber.toBigInt() + 21n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 2a277f1ac33..428ce568647 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -142,8 +142,8 @@ export class ValidatorClient extends WithTracer implements Validator { } // Check that the proposal is for the current or next slot - const { slotNumber } = proposal.payload.header.globalVariables; - if (slotNumber !== currentSlot && slotNumber !== nextSlot) { + const slotNumberBigInt = proposal.slotNumber.toBigInt(); + if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) { this.log.verbose(`Not the current or next slot, skipping attestation`); return undefined; } From b822475ac05cc2aeff74fc53a3e350f9443d641d Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:08:42 +0000 Subject: [PATCH 25/29] fmt --- yarn-project/epoch-cache/src/epoch_cache.test.ts | 6 +++--- yarn-project/validator-client/src/validator.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 9338113f75b..f21d7a3b77c 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -88,17 +88,17 @@ describe('EpochCache', () => { // Hence the chosen values for testCommittee below // Get validator for slot 0 - let { currentProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + const { currentProposer } = await epochCache.getProposerInCurrentOrNextSlot(); expect(currentProposer).toEqual(testCommittee[1]); // Move to next slot jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); - let { currentProposer: nextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + const { currentProposer: nextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); expect(nextProposer).toEqual(testCommittee[1]); // Move to slot that wraps around validator set jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); - let { currentProposer: nextNextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + const { currentProposer: nextNextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); expect(nextNextProposer).toEqual(testCommittee[0]); }); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 428ce568647..3d6c27ea20a 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -135,7 +135,8 @@ export class ValidatorClient extends WithTracer implements Validator { // Check that the proposal is from the current proposer, or the next proposer. const proposalSender = proposal.getSender(); - const { currentProposer, nextProposer, currentSlot, nextSlot } = await this.epochCache.getProposerInCurrentOrNextSlot(); + const { currentProposer, nextProposer, currentSlot, nextSlot } = + await this.epochCache.getProposerInCurrentOrNextSlot(); if (!proposalSender.equals(currentProposer) && !proposalSender.equals(nextProposer)) { this.log.verbose(`Not the current or next proposer, skipping attestation`); return undefined; From 676b7ed5b335a79ec1a4b428473818fe619d31ab Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:53:17 +0000 Subject: [PATCH 26/29] fix: in native testnet, deploy validators at genesis --- scripts/run_interleaved.sh | 3 +- scripts/run_native_testnet.sh | 2 +- .../native-network/deploy-l1-contracts.sh | 5 +++- .../generate-aztec-validator-keys.sh | 28 +++++++++++++++++++ .../scripts/native-network/validator.sh | 9 +++--- .../scripts/native-network/validators.sh | 13 +++++---- 6 files changed, 47 insertions(+), 13 deletions(-) create mode 100755 yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh diff --git a/scripts/run_interleaved.sh b/scripts/run_interleaved.sh index 85449570cb4..1c072b7828b 100755 --- a/scripts/run_interleaved.sh +++ b/scripts/run_interleaved.sh @@ -39,7 +39,8 @@ trap cleanup SIGINT SIGTERM EXIT # Function to run a command and prefix the output with color function run_command() { - local cmd="$1" + # Take first 3 parts of command to display inline + local cmd=$(echo "$1" | awk '{print $1" "$2" "$3}') local color="$2" $cmd 2>&1 | while IFS= read -r line; do echo -e "${color}[$cmd]\e[0m $line" diff --git a/scripts/run_native_testnet.sh b/scripts/run_native_testnet.sh index 00b08f53b29..e7b0023df69 100755 --- a/scripts/run_native_testnet.sh +++ b/scripts/run_native_testnet.sh @@ -120,7 +120,7 @@ cd $(git rev-parse --show-toplevel) # Base command BASE_CMD="INTERLEAVED=$INTERLEAVED ./yarn-project/end-to-end/scripts/native_network_test.sh \ $TEST_SCRIPT \ - ./deploy-l1-contracts.sh \ + \"./deploy-l1-contracts.sh $NUM_VALIDATORS\" \ ./deploy-l2-contracts.sh \ ./boot-node.sh \ ./ethereum.sh \ diff --git a/yarn-project/end-to-end/scripts/native-network/deploy-l1-contracts.sh b/yarn-project/end-to-end/scripts/native-network/deploy-l1-contracts.sh index 014a1b2b07d..2f1d670620c 100755 --- a/yarn-project/end-to-end/scripts/native-network/deploy-l1-contracts.sh +++ b/yarn-project/end-to-end/scripts/native-network/deploy-l1-contracts.sh @@ -2,6 +2,7 @@ # Get the name of the script without the path and extension SCRIPT_NAME=$(basename "$0" .sh) +REPO=$(git rev-parse --show-toplevel) # Redirect stdout and stderr to <script_name>.log while also printing to the console exec > >(tee -a "$(dirname $0)/logs/${SCRIPT_NAME}.log") 2> >(tee -a "$(dirname $0)/logs/${SCRIPT_NAME}.log" >&2) @@ -13,7 +14,9 @@ set -eu # Check for validator addresses if [ $# -gt 0 ]; then INIT_VALIDATORS="true" - VALIDATOR_ADDRESSES="$1" + NUMBER_OF_VALIDATORS="$1" + # Generate validator keys, this will set the VALIDATOR_ADDRESSES variable + source $REPO/yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh $NUMBER_OF_VALIDATORS else INIT_VALIDATORS="false" fi diff --git a/yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh b/yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh new file mode 100755 index 00000000000..85fb8ac8a89 --- /dev/null +++ b/yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Generate Aztec validator keys + +NUMBER_OF_KEYS=${1:-16} +MNEMONIC=${2:-"test test test test test test test test test test test junk"} + +# Initialize arrays to store private keys and addresses +declare -a VALIDATOR_PRIVATE_KEYS +declare -a VALIDATOR_ADDRESSES_LIST + +for i in $(seq 0 $(($NUMBER_OF_KEYS - 1))); do + # Get private key and store it in array + PRIVATE_KEY=$(cast wallet private-key "$MNEMONIC" --mnemonic-index $i) + VALIDATOR_PRIVATE_KEYS+=("$PRIVATE_KEY") + + # Get address from private key and store it in array + ADDRESS=$(cast wallet address "$PRIVATE_KEY") + VALIDATOR_ADDRESSES_LIST+=("$ADDRESS") +done + +# Join addresses with commas +VALIDATOR_ADDRESSES=$(IFS=, ; echo "${VALIDATOR_ADDRESSES_LIST[*]}") + +# Optionally, if you need the arrays for further use, you can export them: +export VALIDATOR_PRIVATE_KEYS +export VALIDATOR_ADDRESSES_LIST +export VALIDATOR_ADDRESSES diff --git a/yarn-project/end-to-end/scripts/native-network/validator.sh b/yarn-project/end-to-end/scripts/native-network/validator.sh index 207952f9b0d..ecc04b6eedb 100755 --- a/yarn-project/end-to-end/scripts/native-network/validator.sh +++ b/yarn-project/end-to-end/scripts/native-network/validator.sh @@ -86,11 +86,12 @@ else node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js add-l1-validator --validator $ADDRESS --rollup $ROLLUP_CONTRACT_ADDRESS && break sleep 1 done -fi -# Fast forward epochs if we're on an anvil chain -if [ "$IS_ANVIL" = "true" ]; then - node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js fast-forward-epochs --rollup $ROLLUP_CONTRACT_ADDRESS --count 1 + # Fast forward epochs if we're on an anvil chain + if [ "$IS_ANVIL" = "true" ]; then + node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js fast-forward-epochs --rollup $ROLLUP_CONTRACT_ADDRESS --count 1 + fi fi + # Start the Validator Node with the sequencer and archiver node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js start --port="$PORT" --node --archiver --sequencer diff --git a/yarn-project/end-to-end/scripts/native-network/validators.sh b/yarn-project/end-to-end/scripts/native-network/validators.sh index c2454b87481..b3a75886368 100755 --- a/yarn-project/end-to-end/scripts/native-network/validators.sh +++ b/yarn-project/end-to-end/scripts/native-network/validators.sh @@ -4,6 +4,7 @@ set -eu # Get the name of the script without the path and extension SCRIPT_NAME=$(basename "$0" .sh) +REPO=$(git rev-parse --show-toplevel) # Redirect stdout and stderr to <script_name>.log while also printing to the console exec > >(tee -a "$(dirname $0)/logs/${SCRIPT_NAME}.log") 2> >(tee -a "$(dirname $0)/logs/${SCRIPT_NAME}.log" >&2) @@ -15,17 +16,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")" CMD=() +# Generate validator private keys +source $REPO/yarn-project/end-to-end/scripts/native-network/generate-aztec-validator-keys.sh $NUM_VALIDATORS + # Generate validator commands for ((i = 0; i < NUM_VALIDATORS; i++)); do PORT=$((8081 + i)) P2P_PORT=$((40401 + i)) - IDX=$((i + 1)) - # These variables should be set in public networks if we have funded validators already. Leave empty for test environments. - ADDRESS_VAR="ADDRESS_${IDX}" - PRIVATE_KEY_VAR="VALIDATOR_PRIVATE_KEY_${IDX}" - ADDRESS="${!ADDRESS_VAR:-}" - VALIDATOR_PRIVATE_KEY="${!PRIVATE_KEY_VAR:-}" + # Use the arrays generated from generate-aztec-validator-keys.sh + ADDRESS="${VALIDATOR_ADDRESSES_LIST[$i]}" + VALIDATOR_PRIVATE_KEY="${VALIDATOR_PRIVATE_KEYS[$i]}" CMD+=("./validator.sh $PORT $P2P_PORT $ADDRESS $VALIDATOR_PRIVATE_KEY") done From ca9965875797c1d9315114ddeb370ea1c6b8cf4c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:23:08 +0000 Subject: [PATCH 27/29] fix: run interleaved --- scripts/run_interleaved.sh | 5 +++-- scripts/run_native_testnet.sh | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/run_interleaved.sh b/scripts/run_interleaved.sh index 1c072b7828b..5d18e8cb486 100755 --- a/scripts/run_interleaved.sh +++ b/scripts/run_interleaved.sh @@ -39,11 +39,12 @@ trap cleanup SIGINT SIGTERM EXIT # Function to run a command and prefix the output with color function run_command() { + local cmd="$1" # Take first 3 parts of command to display inline - local cmd=$(echo "$1" | awk '{print $1" "$2" "$3}') + local cmd_prefix=$(echo "$cmd" | awk '{print $1" "$2" "$3}') local color="$2" $cmd 2>&1 | while IFS= read -r line; do - echo -e "${color}[$cmd]\e[0m $line" + echo -e "${color}[$cmd_prefix]\e[0m $line" done } diff --git a/scripts/run_native_testnet.sh b/scripts/run_native_testnet.sh index e7b0023df69..4cf6d83900a 100755 --- a/scripts/run_native_testnet.sh +++ b/scripts/run_native_testnet.sh @@ -32,6 +32,7 @@ PROVER_SCRIPT="\"./prover-node.sh 8078 false\"" NUM_VALIDATORS=3 INTERLEAVED=false METRICS=false +LOG_LEVEL="info" OTEL_COLLECTOR_ENDPOINT=${OTEL_COLLECTOR_ENDPOINT:-"http://localhost:4318"} # Function to display help message From e955e8d663bb62730387ab0b3671daa8739742f1 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:47:43 +0000 Subject: [PATCH 28/29] fix: earthfile --- yarn-project/Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/Earthfile b/yarn-project/Earthfile index c937bc7f915..0fed665d9da 100644 --- a/yarn-project/Earthfile +++ b/yarn-project/Earthfile @@ -328,7 +328,7 @@ network-test: ENV LOG_LEVEL=verbose RUN INTERLEAVED=true end-to-end/scripts/native_network_test.sh \ "$test" \ - ./deploy-l1-contracts.sh \ + "./deploy-l1-contracts.sh $validators" \ ./deploy-l2-contracts.sh \ ./boot-node.sh \ ./ethereum.sh \ From 54c71a7a0fe3bb1ba09b7b9382005ab1337a0bd0 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:11:20 +0000 Subject: [PATCH 29/29] fmt --- yarn-project/archiver/src/archiver/archiver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index fb528b90f7e..f3b25d266b4 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -1,5 +1,4 @@ import { - EmptyL1RollupConstants, type GetUnencryptedLogsResponse, type InBlock, type InboxLeaf,