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,