diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 53132bb4ef5f..86f0c8cc3e1b 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -218,6 +218,10 @@ contract RollupCore is ExtRollupLib.setupEpoch(); } + function setupSeedSnapshotForNextEpoch() public override(IValidatorSelectionCore) { + ExtRollupLib.setupSeedSnapshotForNextEpoch(); + } + /** * @notice Updates the l1 gas fee oracle * @dev This function is called by the `propose` function diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index b11251714870..411c1be0a98f 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -26,6 +26,7 @@ struct ValidatorSelectionStorage { interface IValidatorSelectionCore { function setupEpoch() external; + function setupSeedSnapshotForNextEpoch() external; } interface IValidatorSelection is IValidatorSelectionCore { diff --git a/l1-contracts/src/core/libraries/rollup/ExtRollupLib.sol b/l1-contracts/src/core/libraries/rollup/ExtRollupLib.sol index 67e78b377f03..cc36eab862d4 100644 --- a/l1-contracts/src/core/libraries/rollup/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ExtRollupLib.sol @@ -37,6 +37,11 @@ library ExtRollupLib { ValidatorSelectionLib.setupEpoch(StakingLib.getStorage(), currentEpoch); } + function setupSeedSnapshotForNextEpoch() external { + Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp(); + ValidatorSelectionLib.setSampleSeedForNextEpoch(currentEpoch); + } + function getEpochProofPublicInputs( uint256 _start, uint256 _end, diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index de33f3316a5b..95b5bb84eb4b 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -55,7 +55,7 @@ library ValidatorSelectionLib { // Set the sample seed for the next epoch if required // function handles the case where it is already set - setSampleSeedForEpoch(_epochNumber + Epoch.wrap(1)); + setSampleSeedForNextEpoch(_epochNumber); //################ Committee ################ // If the committee is not set for this epoch, we need to sample it @@ -226,6 +226,15 @@ library ValidatorSelectionLib { return epoch.committee; } + /** + * @notice Sets the sample seed for the next epoch + * + * @param _epoch - The epoch to set the sample seed for + */ + function setSampleSeedForNextEpoch(Epoch _epoch) internal { + setSampleSeedForEpoch(_epoch + Epoch.wrap(1)); + } + /** * @notice Sets the sample seed for an epoch * diff --git a/l1-contracts/test/scream-and-shout.t.sol b/l1-contracts/test/scream-and-shout.t.sol index 02358a1a764e..8e85bce4613b 100644 --- a/l1-contracts/test/scream-and-shout.t.sol +++ b/l1-contracts/test/scream-and-shout.t.sol @@ -20,7 +20,7 @@ contract ScreamAndShoutTest is Test { assertEq( codeHash, - 0x1d16acb8153d57a5977ea83c2adfd173c4299d2501a67084e54391b49f9e17a5, + 0xf5661dc2c5fc8908f0046f7f5a4bad9f2bdd569e730ecc36db30d3bb4e6fb57b, "You have changed the rollup!" ); } @@ -42,7 +42,7 @@ contract ScreamAndShoutTest is Test { assertEq( codeHash, - 0xc4ca2e1a154150d0d4d8686009de461a136716e70e1fffd561442b8bc29c7af5, + 0x6d6f5c217adc349976fc5992f0e001f83b8674c9f2206384422f8328306bb550, "You have changed the registry!" ); } diff --git a/l1-contracts/test/validator-selection/setupSampleSeed.t.sol b/l1-contracts/test/validator-selection/setupSampleSeed.t.sol new file mode 100644 index 000000000000..2e5b452b539e --- /dev/null +++ b/l1-contracts/test/validator-selection/setupSampleSeed.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {ValidatorSelectionTestBase, CheatDepositArgs} from "./ValidatorSelectionBase.sol"; +import {Epoch, Timestamp} from "@aztec/core/libraries/TimeLib.sol"; +import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; +import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {TestConstants} from "../harnesses/TestConstants.sol"; + +contract SetupSampleSeedTest is ValidatorSelectionTestBase { + function test_setupSampleSeed(uint16 _epochToTest) public setup(4) { + // Check that the epoch is not set + _epochToTest = uint16(bound(_epochToTest, 2, type(uint16).max)); + + uint256 timejump = + _epochToTest * TestConstants.AZTEC_EPOCH_DURATION * TestConstants.AZTEC_SLOT_DURATION; + uint256 originalSampleSeed = + rollup.getSampleSeedAt(Timestamp.wrap(block.timestamp + timejump + 1)); + + // Jump to just before the epoch we are testing + vm.warp( + block.timestamp + + (_epochToTest - 1) * TestConstants.AZTEC_EPOCH_DURATION * TestConstants.AZTEC_SLOT_DURATION + ); + rollup.setupSeedSnapshotForNextEpoch(); + + // The sample seed should have been updated + uint256 newSampleSeed = rollup.getSampleSeedAt(Timestamp.wrap(block.timestamp + timejump)); + assertTrue(newSampleSeed != originalSampleSeed); + } +} diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index 040c6067fc03..e99f3ef9ab18 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -352,6 +352,30 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: Logger }); }); + program + .command('trigger-seed-snapshot') + .description('Triggers a seed snapshot for the next epoch.') + .option('-pk, --private-key ', 'The private key to use for deployment', PRIVATE_KEY) + .option( + '-m, --mnemonic ', + 'The mnemonic to use in deployment', + 'test test test test test test test test test test test junk', + ) + .option('--rollup
', 'ethereum address of the rollup contract', parseEthereumAddress) + .addOption(l1RpcUrlsOption) + .addOption(l1ChainIdOption) + .action(async options => { + const { triggerSeedSnapshot } = await import('./trigger_seed_snapshot.js'); + await triggerSeedSnapshot({ + rollupAddress: options.rollup, + rpcUrls: options.l1RpcUrls, + chainId: options.l1ChainId, + privateKey: options.privateKey, + mnemonic: options.mnemonic, + log, + }); + }); + program .command('debug-rollup') .description('Debugs the rollup contract.') diff --git a/yarn-project/cli/src/cmds/l1/trigger_seed_snapshot.ts b/yarn-project/cli/src/cmds/l1/trigger_seed_snapshot.ts new file mode 100644 index 000000000000..e1bc9a31e519 --- /dev/null +++ b/yarn-project/cli/src/cmds/l1/trigger_seed_snapshot.ts @@ -0,0 +1,31 @@ +import { createEthereumChain, createExtendedL1Client } from '@aztec/ethereum'; +import type { LogFn } from '@aztec/foundation/log'; +import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi'; + +import { getContract } from 'viem'; + +import type { RollupCommandArgs } from './update_l1_validators.js'; + +export async function triggerSeedSnapshot({ + rpcUrls, + chainId, + privateKey, + mnemonic, + rollupAddress, + log, +}: RollupCommandArgs & { log: LogFn }) { + const chain = createEthereumChain(rpcUrls, chainId); + const client = createExtendedL1Client(rpcUrls, privateKey ?? mnemonic!, chain.chainInfo); + + const rollup = getContract({ + address: rollupAddress.toString(), + abi: RollupAbi, + client, + }); + + log('Triggering seed snapshot for next epoch'); + const txHash = await rollup.write.setupSeedSnapshotForNextEpoch(); + log(`Sent! | Seed snapshot setup for next epoch | tx hash: ${txHash}`); + const receipt = await client.waitForTransactionReceipt({ hash: txHash }); + log(`Done! | Seed snapshot setup for next epoch | tx hash: ${txHash} | status: ${receipt.status}`); +}