Skip to content

Commit

Permalink
feat(evm-consenus): implement getAllValidators and `getDirtyValidat…
Browse files Browse the repository at this point in the history
…ors` on ValidatorSet (#734)

* Add getAllValidators

* Move  method

* Update abi

* Add all validators values

* Get all validators

* style: resolve style guide violations

---------

Co-authored-by: sebastijankuzner <[email protected]>
  • Loading branch information
sebastijankuzner and sebastijankuzner authored Oct 17, 2024
1 parent 990ac1a commit 42de4ad
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 18 deletions.
11 changes: 11 additions & 0 deletions contracts/src/consensus/Consensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@ contract Consensus {
return result;
}

function getAllValidators() public view returns (Validator[] memory) {
Validator[] memory result = new Validator[](_registeredValidators.length);
for (uint i = 0; i < _registeredValidators.length; i++) {
address addr = _registeredValidators[i];
ValidatorData storage data = _registeredValidatorData[addr];
result[i] = Validator({addr: addr, data: data});
}

return result;
}

function registeredValidatorsCount() public view returns (uint256) {
return _registeredValidatorsCount;
}
Expand Down
36 changes: 36 additions & 0 deletions contracts/test/consensus/Consensus-GetAllValidators.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE
pragma solidity ^0.8.13;

import {Test, console} from "@forge-std/Test.sol";
import {Consensus, ValidatorData, Validator} from "@contracts/consensus/Consensus.sol";
import {Base} from "./Base.sol";

contract ConsensusTest is Base {
Consensus public consensus;

function setUp() public {
consensus = new Consensus();
}


function test_200_validators() public {
vm.pauseGasMetering();
assertEq(consensus.registeredValidatorsCount(), 0);


uint256 n = 200;
for (uint256 i = 0; i < n; i++) {
address addr = address(uint160(i + 1));

vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));
consensus.vote(addr);
vm.stopPrank();
}

vm.resumeGasMetering();

Validator[] memory validators = consensus.getAllValidators();
assertEq(validators.length, n);
}
}
2 changes: 2 additions & 0 deletions packages/contracts/source/contracts/state/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ export interface ValidatorWallet {
address: string;
blsPublicKey: string;
voteBalance: BigNumber;
votersCount: number;
isResigned: boolean;
}
2 changes: 2 additions & 0 deletions packages/contracts/source/contracts/validator-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ValidatorWallet } from "./state/index.js";
export interface Service extends CommitHandler {
restore(): Promise<void>;
getActiveValidators(): ValidatorWallet[];
getAllValidators(): ValidatorWallet[];
getDirtyValidators(): ValidatorWallet[];
getValidator(validatorIndex: number): ValidatorWallet;
getValidatorIndexByWalletAddress(walletAddress: string): number;
}
95 changes: 86 additions & 9 deletions packages/evm-consensus/source/validator-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,47 @@ export class ValidatorSet implements Contracts.ValidatorSet.Service {
@tagged("instance", "evm")
private readonly evm!: Contracts.Evm.Instance;

#validators: Contracts.State.ValidatorWallet[] = [];
#topValidators: Contracts.State.ValidatorWallet[] = [];
#indexByAddress: Map<string, number> = new Map();

#allValidators: Map<string, Contracts.State.ValidatorWallet> = new Map();
#dirtyValidators: Contracts.State.ValidatorWallet[] = [];

public async restore(): Promise<void> {
await this.#buildActiveValidators();

const validators = await this.#getAllValidators();
this.#allValidators = new Map(validators.map((validator) => [validator.address, validator]));
}

public async onCommit(unit: Contracts.Processor.ProcessableUnit): Promise<void> {
if (Utils.roundCalculator.isNewRound(unit.height + 1, this.configuration)) {
await this.#buildActiveValidators();
}

await this.#calculateChangedValidators();
}

public getAllValidators(): Contracts.State.ValidatorWallet[] {
return [...this.#allValidators.values()];
}

public getDirtyValidators(): Contracts.State.ValidatorWallet[] {
return this.#dirtyValidators;
}

public getActiveValidators(): Contracts.State.ValidatorWallet[] {
const { activeValidators } = this.configuration.getMilestone();

if (this.#validators.length !== activeValidators) {
throw new Exceptions.NotEnoughActiveValidatorsError(this.#validators.length, activeValidators);
if (this.#topValidators.length !== activeValidators) {
throw new Exceptions.NotEnoughActiveValidatorsError(this.#topValidators.length, activeValidators);
}

return this.#validators.slice(0, activeValidators);
return this.#topValidators.slice(0, activeValidators);
}

public getValidator(index: number): Contracts.State.ValidatorWallet {
return this.#validators[index];
return this.#topValidators[index];
}

public getValidatorIndexByWalletAddress(walletAddress: string): number {
Expand All @@ -59,11 +75,31 @@ export class ValidatorSet implements Contracts.ValidatorSet.Service {
const { activeValidators } = this.configuration.getMilestone();
const validators = await this.#getActiveValidators();
if (validators.length < activeValidators) {
throw new Exceptions.NotEnoughActiveValidatorsError(this.#validators.length, activeValidators);
throw new Exceptions.NotEnoughActiveValidatorsError(this.#topValidators.length, activeValidators);
}

this.#topValidators = validators.slice(0, activeValidators);
this.#indexByAddress = new Map(this.#topValidators.map((validator, index) => [validator.address, index]));
}

async #calculateChangedValidators(): Promise<void> {
this.#dirtyValidators = [];

const validators = await this.#getAllValidators();
for (const validator of validators) {
const currentValidator = this.#allValidators.get(validator.address);
if (
!currentValidator ||
!currentValidator.voteBalance.isEqualTo(validator.voteBalance) ||
currentValidator.isResigned !== validator.isResigned ||
currentValidator.votersCount !== validator.votersCount ||
currentValidator.blsPublicKey !== validator.blsPublicKey
) {
this.#dirtyValidators.push(validator);
}
}

this.#validators = validators.slice(0, activeValidators);
this.#indexByAddress = new Map(this.#validators.map((validator, index) => [validator.address, index]));
this.#allValidators = new Map(validators.map((validator) => [validator.address, validator]));
}

async #getActiveValidators(): Promise<Contracts.State.ValidatorWallet[]> {
Expand All @@ -89,12 +125,53 @@ export class ValidatorSet implements Contracts.ValidatorSet.Service {

const validatorWallets: Contracts.State.ValidatorWallet[] = [];
for (const [, validator] of validators.entries()) {
const [address, [, voteBalance, , blsPublicKey]] = validator;
const [address, [votersCount, voteBalance, isResigned, blsPublicKey]] = validator;

const validatorWallet: Contracts.State.ValidatorWallet = {
address,
blsPublicKey: blsPublicKey.slice(2),
isResigned,
voteBalance: Utils.BigNumber.make(voteBalance),
votersCount: Number(votersCount),
};

validatorWallets.push(validatorWallet);
}

return validatorWallets;
}

async #getAllValidators(): Promise<Contracts.State.ValidatorWallet[]> {
const consensusContractAddress = this.app.get<string>(EvmConsensusIdentifiers.Contracts.Addresses.Consensus);
const deployerAddress = this.app.get<string>(EvmConsensusIdentifiers.Internal.Addresses.Deployer);
const { evmSpec } = this.configuration.getMilestone();

const iface = new ethers.Interface(ConsensusAbi.abi);
const data = iface.encodeFunctionData("getAllValidators").slice(2);

const result = await this.evm.view({
caller: deployerAddress,
data: Buffer.from(data, "hex"),
recipient: consensusContractAddress,
specId: evmSpec,
});

if (!result.success) {
this.app.terminate("getAllValidators failed");
}

const [validators] = iface.decodeFunctionResult("getAllValidators", result.output!);

const validatorWallets: Contracts.State.ValidatorWallet[] = [];
for (const [, validator] of validators.entries()) {
const [address, [votersCount, voteBalance, isResigned, blsPublicKey]] = validator;

const validatorWallet: Contracts.State.ValidatorWallet = {
address,
blsPublicKey: blsPublicKey.slice(2),
isResigned,
voteBalance: Utils.BigNumber.make(voteBalance),
votersCount: Number(votersCount),
};

validatorWallets.push(validatorWallet);
Expand Down
Loading

0 comments on commit 42de4ad

Please sign in to comment.