Skip to content

Commit

Permalink
feat(contracts): allow importing validators and votes in Consensus co…
Browse files Browse the repository at this point in the history
…ntract (#771)

* Implement addValidator

* Skip validators without blsKey

* Check parameters for calculateActiveValidators

* Test add vote

* Add test

* Update abi

* Update genesis

* E2e

* Add ImportIsNotAllowed

* Update abi

* Prevent voting for validator without bls key

* Update abi

* Update crypto

* E2e

* Rebuild json changes
  • Loading branch information
sebastijankuzner authored Nov 20, 2024
1 parent a543d23 commit 04b8fe5
Show file tree
Hide file tree
Showing 21 changed files with 1,975 additions and 1,302 deletions.
106 changes: 99 additions & 7 deletions contracts/src/consensus/ConsensusV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,20 @@ error ValidatorNotRegistered();
error ValidatorAlreadyRegistered();
error ValidatorAlreadyResigned();
error BellowMinValidators();
error NoActiveValidators();

error BlsKeyAlreadyRegistered();
error BlsKeyIsInvalid();

error VoteResignedValidator();
error VoteSameValidator();
error VoteValidatorWithoutBlsPublicKey();
error AlreadyVoted();
error MissingVote();

error InvalidRange(uint256 min, uint256 max);
error InvalidParameters();
error ImportIsNotAllowed();

// Validators:
// - Registered -> All validators that are registered including resigned validators
Expand Down Expand Up @@ -131,6 +136,72 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

// External functions
function addValidator(address addr, bytes calldata blsPublicKey, bool isResigned) external onlyOwner {
if (_rounds.length > 0) {
revert ImportIsNotAllowed();
}

if (_hasValidator[addr]) {
revert ValidatorAlreadyRegistered();
}

if (_blsPublicKeys[keccak256(blsPublicKey)]) {
revert BlsKeyAlreadyRegistered();
}

// Allow empty blsPublicKey for imports
if (blsPublicKey.length != 0) {
_verifyAndRegisterBlsPublicKey(blsPublicKey);
}

ValidatorData memory validator =
ValidatorData({votersCount: 0, voteBalance: 0, isResigned: isResigned, blsPublicKey: blsPublicKey});

_validatorsCount++;
_hasValidator[addr] = true;
_validatorsData[addr] = validator;
_validators.push(addr);

if (isResigned) {
_resignedValidatorsCount++;
}

emit ValidatorRegistered(addr, blsPublicKey);
}

function addVote(address voter, address validator) external onlyOwner {
if (_rounds.length > 0) {
revert ImportIsNotAllowed();
}

if (!isValidatorRegistered(validator)) {
revert ValidatorNotRegistered();
}

Vote storage voterData = _voters[voter];
if (voterData.validator != address(0)) {
revert AlreadyVoted();
}

_voters[voter] = Vote({validator: validator, balance: voter.balance, prev: address(0), next: address(0)});

if (_votersHead == address(0)) {
_votersHead = voter;
_votersTail = voter;
} else {
_voters[_votersTail].next = voter;
_voters[voter].prev = _votersTail;
_votersTail = voter;
}
_votersCount++;

ValidatorData storage validatorData = _validatorsData[validator];
validatorData.voteBalance += voter.balance;
validatorData.votersCount += 1;

emit Voted(voter, validator);
}

function registerValidator(bytes calldata blsPublicKey) external preventOwner {
if (_hasValidator[msg.sender]) {
revert ValidatorAlreadyRegistered();
Expand Down Expand Up @@ -191,6 +262,10 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
revert VoteResignedValidator();
}

if (validatorData.blsPublicKey.length == 0) {
revert VoteValidatorWithoutBlsPublicKey();
}

Vote storage voter = _voters[msg.sender];
if (voter.validator == addr) {
revert VoteSameValidator();
Expand Down Expand Up @@ -229,23 +304,28 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
}

function calculateActiveValidators(uint8 n) external onlyOwner {
if (n == 0) {
revert InvalidParameters();
}

_minValidators = n;

_shuffle(_validators);
_deleteActiveValidators();

_activeValidatorsHead = address(0);

uint8 top = uint8(_clamp(n, 0, _validatorsCount - _resignedValidatorsCount));

if (top == 0) {
return;
revert NoActiveValidators();
}

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

ValidatorData storage data = _validatorsData[addr];
if (data.isResigned) {

if (data.isResigned || data.blsPublicKey.length == 0) {
continue;
}

Expand All @@ -271,11 +351,15 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
}
}

// Prepare temp array. Used when top < _minValidators
if (_activeValidatorsCount == 0) {
revert NoActiveValidators();
}

// Prepare temp array. Used when _activeValidatorsCount < _minValidators
address next = _activeValidatorsHead;
address[] memory tmpValidators = new address[](top);
address[] memory tmpValidators = new address[](_activeValidatorsCount);

for (uint256 i = 0; i < top; i++) {
for (uint256 i = 0; i < _activeValidatorsCount; i++) {
tmpValidators[i] = next;
next = _activeValidatorsMap[next];
}
Expand All @@ -287,7 +371,7 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
_activeValidators = new address[](_minValidators);

for (uint256 i = 0; i < _minValidators; i++) {
address addr = tmpValidators[i % top];
address addr = tmpValidators[i % _activeValidatorsCount];
_activeValidators[i] = addr;
round.push(RoundValidator({addr: addr, voteBalance: _validatorsData[addr].voteBalance}));
}
Expand Down Expand Up @@ -400,6 +484,10 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {
// Internal functions
function _shuffle(address[] storage array) internal {
uint256 n = array.length;
if (n == 0) {
return;
}

for (uint256 i = n - 1; i > 0; i--) {
// Get a random index between 0 and i (inclusive)
uint256 j = uint256(keccak256(abi.encodePacked(block.timestamp, i))) % (i + 1);
Expand All @@ -413,6 +501,10 @@ contract ConsensusV1 is Initializable, UUPSUpgradeable {

function _shuffleMem(address[] memory array) internal view {
uint256 n = array.length;
if (n == 0) {
return;
}

for (uint256 i = n - 1; i > 0; i--) {
// Get a random index between 0 and i (inclusive)
uint256 j = uint256(keccak256(abi.encodePacked(block.timestamp, i))) % (i + 1);
Expand Down
63 changes: 62 additions & 1 deletion contracts/test/consensus/Consensus-CalculateTop.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE
pragma solidity ^0.8.13;

import {ConsensusV1, ValidatorData, Validator, CallerIsNotOwner} from "@contracts/consensus/ConsensusV1.sol";
import {
ConsensusV1,
ValidatorData,
Validator,
CallerIsNotOwner,
InvalidParameters,
NoActiveValidators
} from "@contracts/consensus/ConsensusV1.sol";
import {Base} from "./Base.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

Expand All @@ -23,6 +30,32 @@ contract ConsensusTest is Base {
consensus.calculateActiveValidators(1);
}

function test_should_revert_with_0_parameter() public {
registerValidator(address(1));

vm.expectRevert(InvalidParameters.selector);
consensus.calculateActiveValidators(0);
}

function test_should_revert_without_validators() public {
vm.expectRevert(NoActiveValidators.selector);
consensus.calculateActiveValidators(1);
}

function test_should_revert_with_only_resigned_validators() public {
consensus.addValidator(address(2), prepareBLSKey(address(2)), true);

vm.expectRevert(NoActiveValidators.selector);
consensus.calculateActiveValidators(1);
}

function test_should_revert_with_only_validators_without_public_key() public {
consensus.addValidator(address(1), new bytes(0), false);

vm.expectRevert(NoActiveValidators.selector);
consensus.calculateActiveValidators(1);
}

function test_should_ignore_resigned_validators() public {
address addr = address(1);

Expand All @@ -37,6 +70,34 @@ contract ConsensusTest is Base {
assertEq(validators[1].addr, address(2)); // Second validator is duplicated
}

// Inverted order
function test_should_ignore_resigned_validators_2() public {
address addr = address(1);

registerValidator(addr);
registerValidator(address(2));
resignValidator(address(2));

consensus.calculateActiveValidators(2);
Validator[] memory validators = consensus.getActiveValidators();
assertEq(validators.length, 2);
assertEq(validators[0].addr, addr);
assertEq(validators[1].addr, addr); // Second validator is duplicated
}

function test_should_ignore_validators_without_bls_public_key() public {
address addr = address(1);

registerValidator(addr);
consensus.addValidator(address(2), new bytes(0), false);

consensus.calculateActiveValidators(2);
Validator[] memory validators = consensus.getActiveValidators();
assertEq(validators.length, 2);
assertEq(validators[0].addr, addr);
assertEq(validators[1].addr, addr); // Second validator is duplicated
}

function test_consensus_200_topValidators() public {
vm.pauseGasMetering();
assertEq(consensus.registeredValidatorsCount(), 0);
Expand Down
Loading

0 comments on commit 04b8fe5

Please sign in to comment.