Skip to content

Commit

Permalink
feat: initial validator set
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Aug 22, 2024
1 parent eb16d6e commit 08854e9
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 60 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 @@ -76,7 +76,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 @@ -95,6 +96,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 @@ -471,7 +476,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 @@ -66,7 +66,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
66 changes: 18 additions & 48 deletions yarn-project/end-to-end/src/e2e_p2p_network.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
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 { MNEMONIC } from './fixtures/fixtures.js';
Expand All @@ -45,7 +33,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 +41,26 @@ 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 });

// 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 hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 0 });
const publisherPrivKey = Buffer.from(hdAccount.getHdKey().privateKey!);
const account = privateKeyToAccount(`0x${publisherPrivKey!.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 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}`);
} 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}`);
}
initialValidators.push(EthAddress.fromString(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]);

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
11 changes: 9 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 @@ -111,7 +112,7 @@ export const setupL1Contracts = async (
l1RpcUrl: string,
account: HDAccount | PrivateKeyAccount,
logger: DebugLogger,
args: { salt?: number } = {},
args: { salt?: number; initialValidators?: EthAddress[] } = {},
) => {
const l1Artifacts: L1ContractArtifactsForDeployment = {
registry: {
Expand Down Expand Up @@ -148,6 +149,7 @@ export const setupL1Contracts = async (
l2FeeJuiceAddress: FeeJuiceAddress,
vkTreeRoot: getVKTreeRoot(),
salt: args.salt,
initialValidators: args.initialValidators,
});

return l1Data;
Expand Down Expand Up @@ -290,6 +292,8 @@ type SetupOptions = {
deployL1ContractsValues?: DeployL1Contracts;
/** Whether to skip deployment of protocol contracts (auth registry, etc) */
skipProtocolContracts?: boolean;
/** 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 @@ -368,7 +372,10 @@ export async function setup(
}

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

config.publisherPrivateKey = `0x${publisherPrivKey!.toString('hex')}`;
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 @@ -233,6 +239,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 08854e9

Please sign in to comment.