Skip to content

Commit

Permalink
feat: initial validator set (#8133)
Browse files Browse the repository at this point in the history
Fixes #8102. 

Supports having an initial validator set in the rollup.

For the DevNet, this should make it slightly simpler to deploy as well
as the sequencer can just be set at deployment and don't need the extra
call. @PhilWindle this might be useful for you.
  • Loading branch information
LHerskind authored Aug 27, 2024
1 parent eba57b4 commit 6d31ad2
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 65 deletions.
9 changes: 7 additions & 2 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
IAvailabilityOracle _availabilityOracle,
IFeeJuicePortal _fpcJuicePortal,
bytes32 _vkTreeRoot,
address _ares
address _ares,
address[] memory _validators
) Leonidas(_ares) {
verifier = new MockVerifier();
REGISTRY = _registry;
Expand All @@ -97,6 +98,10 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
});
pendingBlockCount = 1;
provenBlockCount = 1;

for (uint256 i = 0; i < _validators.length; i++) {
_addValidator(_validators[i]);
}
}

/**
Expand Down Expand Up @@ -524,7 +529,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
}

if (!isValidator(msg.sender)) {
revert Errors.Leonidas__InvalidProposer(address(0), msg.sender);
revert Errors.Leonidas__InvalidProposer(getValidatorAt(0), msg.sender);
}
return;
}
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/sequencer_selection/ILeonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface ILeonidas {
function getCurrentSlot() external view returns (uint256);
function isValidator(address _validator) external view returns (bool);
function getValidatorCount() external view returns (uint256);
function getValidatorAt(uint256 _index) external view returns (address);

// Consider removing below this point
function getTimestampForSlot(uint256 _slotNumber) external view returns (uint256);
Expand Down
19 changes: 18 additions & 1 deletion l1-contracts/src/core/sequencer_selection/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ contract Leonidas is Ownable, ILeonidas {
*/
function addValidator(address _validator) external override(ILeonidas) onlyOwner {
setupEpoch();
validatorSet.add(_validator);
_addValidator(_validator);
}

/**
Expand Down Expand Up @@ -189,6 +189,15 @@ contract Leonidas is Ownable, ILeonidas {
return validatorSet.length();
}

/**
* @notice Get the number of validators in the validator set
*
* @return The number of validators in the validator set
*/
function getValidatorAt(uint256 _index) public view override(ILeonidas) returns (address) {
return validatorSet.at(_index);
}

/**
* @notice Checks if an address is in the validator set
*
Expand Down Expand Up @@ -320,6 +329,14 @@ contract Leonidas is Ownable, ILeonidas {
return committee[_computeProposerIndex(epochNumber, slot, sampleSeed, committee.length)];
}

/**
* @notice Adds a validator to the set WITHOUT setting up the epoch
* @param _validator - The validator to add
*/
function _addValidator(address _validator) internal {
validatorSet.add(_validator);
}

/**
* @notice Process 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)
Expand Down
3 changes: 2 additions & 1 deletion l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ contract RollupTest is DecoderBase {
availabilityOracle,
IFeeJuicePortal(address(feeJuicePortal)),
bytes32(0),
address(this)
address(this),
new address[](0)
);
inbox = Inbox(address(rollup.INBOX()));
outbox = Outbox(address(rollup.OUTBOX()));
Expand Down
7 changes: 6 additions & 1 deletion l1-contracts/test/portals/TokenPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ contract TokenPortalTest is Test {
registry = new Registry(address(this));
portalERC20 = new PortalERC20();
rollup = new Rollup(
registry, new AvailabilityOracle(), IFeeJuicePortal(address(0)), bytes32(0), address(this)
registry,
new AvailabilityOracle(),
IFeeJuicePortal(address(0)),
bytes32(0),
address(this),
new address[](0)
);
inbox = rollup.INBOX();
outbox = rollup.OUTBOX();
Expand Down
7 changes: 6 additions & 1 deletion l1-contracts/test/portals/UniswapPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ contract UniswapPortalTest is Test {

registry = new Registry(address(this));
rollup = new Rollup(
registry, new AvailabilityOracle(), IFeeJuicePortal(address(0)), bytes32(0), address(this)
registry,
new AvailabilityOracle(),
IFeeJuicePortal(address(0)),
bytes32(0),
address(this),
new address[](0)
);
registry.upgrade(address(rollup));

Expand Down
11 changes: 9 additions & 2 deletions l1-contracts/test/sparta/DevNet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ contract DevNetTest is DecoderBase {
registry = new Registry(address(this));
availabilityOracle = new AvailabilityOracle();
rollup = new Rollup(
registry, availabilityOracle, IFeeJuicePortal(address(0)), bytes32(0), address(this)
registry,
availabilityOracle,
IFeeJuicePortal(address(0)),
bytes32(0),
address(this),
new address[](0)
);
inbox = Inbox(address(rollup.INBOX()));
outbox = Outbox(address(rollup.OUTBOX()));
Expand Down Expand Up @@ -160,7 +165,9 @@ contract DevNetTest is DecoderBase {
ree.proposer = address(uint160(uint256(keccak256(abi.encode("invalid", ree.proposer)))));
// Why don't we end up here?
vm.expectRevert(
abi.encodeWithSelector(Errors.Leonidas__InvalidProposer.selector, address(0), ree.proposer)
abi.encodeWithSelector(
Errors.Leonidas__InvalidProposer.selector, rollup.getValidatorAt(0), ree.proposer
)
);
ree.shouldRevert = true;
}
Expand Down
7 changes: 6 additions & 1 deletion l1-contracts/test/sparta/Sparta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ contract SpartaTest is DecoderBase {
availabilityOracle = new AvailabilityOracle();
portalERC20 = new PortalERC20();
rollup = new Rollup(
registry, availabilityOracle, IFeeJuicePortal(address(0)), bytes32(0), address(this)
registry,
availabilityOracle,
IFeeJuicePortal(address(0)),
bytes32(0),
address(this),
new address[](0)
);
inbox = Inbox(address(rollup.INBOX()));
outbox = Outbox(address(rollup.OUTBOX()));
Expand Down
71 changes: 18 additions & 53 deletions yarn-project/end-to-end/src/e2e_p2p_network.test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node';
import {
CompleteAddress,
type DebugLogger,
type DeployL1Contracts,
EthCheatCodes,
Fr,
GrumpkinScalar,
type SentTx,
TxStatus,
sleep,
} from '@aztec/aztec.js';
import { IS_DEV_NET } from '@aztec/circuits.js';
import { RollupAbi } from '@aztec/l1-artifacts';
import { CompleteAddress, type DebugLogger, Fr, GrumpkinScalar, type SentTx, TxStatus, sleep } from '@aztec/aztec.js';
import { EthAddress, IS_DEV_NET } from '@aztec/circuits.js';
import { type BootstrapNode } from '@aztec/p2p';
import { type PXEService, createPXEService, getPXEServiceConfig as getRpcConfig } from '@aztec/pxe';

import { jest } from '@jest/globals';
import fs from 'fs';
import { getContract } from 'viem';
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
import { privateKeyToAccount } from 'viem/accounts';

import { MNEMONIC } from './fixtures/fixtures.js';
import {
type NodeContext,
createBootstrapNode,
createNode,
createNodes,
generatePeerIdPrivateKeys,
} from './fixtures/setup_p2p_test.js';
import { setup } from './fixtures/utils.js';
import { getPrivateKeyFromIndex, setup } from './fixtures/utils.js';

// Don't set this to a higher value than 9 because each node will use a different L1 publisher account and anvil seeds
const NUM_NODES = 4;
Expand All @@ -45,7 +32,6 @@ describe('e2e_p2p_network', () => {
let teardown: () => Promise<void>;
let bootstrapNode: BootstrapNode;
let bootstrapNodeEnr: string;
let deployL1ContractsValues: DeployL1Contracts;

beforeEach(async () => {
// If we want to test with interval mining, we can use the local host and start `anvil --block-time 12`
Expand All @@ -54,43 +40,22 @@ describe('e2e_p2p_network', () => {
jest.setTimeout(300_000);
}
const options = useLocalHost ? { l1RpcUrl: 'http://127.0.0.1:8545' } : {};
({ teardown, config, logger, deployL1ContractsValues } = await setup(0, options));
// It would likely be useful if we had the sequencers in such that they don't spam each other.
// However, even if they do, it should still work. Not sure what caused the failure
// Would be easier if I could see the errors from anvil as well, but those seem to be hidden.

const rollup = getContract({
address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(),
abi: RollupAbi,
client: deployL1ContractsValues.walletClient,
});

if (IS_DEV_NET) {
// Add just ONE of the peers as sequencer, he will be the proposer all blocks.
const hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 1 });
const publisherPrivKey = Buffer.from(hdAccount.getHdKey().privateKey!);
const account = privateKeyToAccount(`0x${publisherPrivKey!.toString('hex')}`);
await rollup.write.addValidator([account.address], { gas: 1_000_000n });
logger.info(`Adding sequencer ${account.address}`);
} else {
// Add all nodes as validators - they will all sign attestations of each other's proposals
for (let i = 0; i < NUM_NODES; i++) {
const hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: i + 1 });
const publisherPrivKey = Buffer.from(hdAccount.getHdKey().privateKey!);
const account = privateKeyToAccount(`0x${publisherPrivKey!.toString('hex')}`);
await rollup.write.addValidator([account.address], { gas: 1_000_000n });
logger.info(`Adding sequencer ${account.address}`);
}
}

//@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]);
// We need the very first node to be the sequencer for this is the one doing everything throughout the setup.
// Without it we will wait forever.
const account = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`);

const initialValidators = [EthAddress.fromString(account.address)];

// Add 1 extra validator if in devnet or NUM_NODES if not.
// Each of these will become a validator and sign attestations.
const limit = IS_DEV_NET ? 1 : NUM_NODES;
for (let i = 0; i < limit; i++) {
const account = privateKeyToAccount(`0x${getPrivateKeyFromIndex(i + 1)!.toString('hex')}`);
initialValidators.push(EthAddress.fromString(account.address));
}

const cheatCodes = new EthCheatCodes(config.l1RpcUrl);
await cheatCodes.warp(Number(timestamp));
({ teardown, config, logger } = await setup(0, { initialValidators, ...options }));

bootstrapNode = await createBootstrapNode(BOOT_NODE_UDP_PORT);
bootstrapNodeEnr = bootstrapNode.getENR().encodeTxt();
Expand Down
14 changes: 12 additions & 2 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { type BBNativePrivateKernelProver } from '@aztec/bb-prover';
import {
CANONICAL_AUTH_REGISTRY_ADDRESS,
CANONICAL_KEY_REGISTRY_ADDRESS,
type EthAddress,
GasSettings,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
computeContractAddressFromInstance,
Expand Down Expand Up @@ -113,7 +114,7 @@ export const setupL1Contracts = async (
l1RpcUrl: string,
account: HDAccount | PrivateKeyAccount,
logger: DebugLogger,
args: { salt?: number } = {},
args: { salt?: number; initialValidators?: EthAddress[] } = {},
chain: Chain = foundry,
) => {
const l1Artifacts: L1ContractArtifactsForDeployment = {
Expand Down Expand Up @@ -151,6 +152,7 @@ export const setupL1Contracts = async (
l2FeeJuiceAddress: FeeJuiceAddress,
vkTreeRoot: getVKTreeRoot(),
salt: args.salt,
initialValidators: args.initialValidators,
});

return l1Data;
Expand Down Expand Up @@ -295,6 +297,8 @@ type SetupOptions = {
skipProtocolContracts?: boolean;
/** Salt to use in L1 contract deployment */
salt?: number;
/** An initial set of validators */
initialValidators?: EthAddress[];
} & Partial<AztecNodeConfig>;

/** Context for an end-to-end test as returned by the `setup` function */
Expand Down Expand Up @@ -388,7 +392,13 @@ export async function setup(

const deployL1ContractsValues =
opts.deployL1ContractsValues ??
(await setupL1Contracts(config.l1RpcUrl, publisherHdAccount!, logger, { salt: opts.salt }, chain));
(await setupL1Contracts(
config.l1RpcUrl,
publisherHdAccount!,
logger,
{ salt: opts.salt, initialValidators: opts.initialValidators },
chain,
));

config.l1Contracts = deployL1ContractsValues.l1ContractAddresses;

Expand Down
9 changes: 8 additions & 1 deletion yarn-project/ethereum/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,13 @@ export const deployL1Contracts = async (
chain: Chain,
logger: DebugLogger,
contractsToDeploy: L1ContractArtifactsForDeployment,
args: { l2FeeJuiceAddress: AztecAddress; vkTreeRoot: Fr; assumeProvenUntil?: number; salt: number | undefined },
args: {
l2FeeJuiceAddress: AztecAddress;
vkTreeRoot: Fr;
assumeProvenUntil?: number;
salt: number | undefined;
initialValidators?: EthAddress[];
},
): Promise<DeployL1Contracts> => {
// We are assuming that you are running this on a local anvil node which have 1s block times
// To align better with actual deployment, we update the block interval to 12s
Expand Down Expand Up @@ -234,6 +240,7 @@ export const deployL1Contracts = async (
getAddress(feeJuicePortalAddress.toString()),
args.vkTreeRoot.toString(),
account.address.toString(),
args.initialValidators?.map(v => v.toString()) ?? [],
]);
logger.info(`Deployed Rollup at ${rollupAddress}`);

Expand Down

0 comments on commit 6d31ad2

Please sign in to comment.