Skip to content

Commit

Permalink
feat(contracts): add ValidatorRegistered, ValidatorResigned event…
Browse files Browse the repository at this point in the history
…s and improve resignation logic (#733)

* Remove todo

* Test validator registrations

* Check validator data

* Emit event

* Remove performValidatorResignations

* Extract base contract

* Test validator resignation with emit

* Test requires

* Skip resigned validators from topValidatorsCalculation

* Add resignedValidatorsCount

* Update abi
  • Loading branch information
sebastijankuzner authored Oct 16, 2024
1 parent 35cd52c commit 990ac1a
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 105 deletions.
92 changes: 31 additions & 61 deletions contracts/src/consensus/Consensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ struct Vote {
uint256 balance;
}

event ValidatorRegistered(address addr, bytes bls12_381_public_key);
event ValidatorResigned(address addr);

event Voted(address voter, address validator);
event Unvoted(address voter, address validator);
event VoteSwapped(address voter, address previousValidator, address newValidator);
Expand All @@ -43,6 +46,7 @@ contract Consensus {
address immutable _owner;

uint256 private _registeredValidatorsCount = 0;
uint256 private _resignedValidatorsCount = 0;
mapping(address => ValidatorData) private _registeredValidatorData;
mapping(address => bool) private _hasRegisteredValidator;
mapping(bytes32 => bool) private _registeredPublicKeys;
Expand All @@ -55,8 +59,6 @@ contract Consensus {
uint256 private _topValidatorsCount = 0;
address[] private _calculatedTopValidators;

address[] private _resignedValidators;

constructor() {
_owner = msg.sender;
}
Expand Down Expand Up @@ -89,23 +91,33 @@ contract Consensus {
shuffle();
deleteTopValidators();

uint8 top = uint8(_clamp(n, 0, _registeredValidators.length));
_head = address(0);

uint8 top = uint8(_clamp(n, 0, _registeredValidatorsCount - _resignedValidatorsCount)); // TODO: Use new method that returns registered validators
if (top == 0) {
return;
}

_head = _registeredValidators[0];
_topValidatorsCount = 1;

for (uint i = 1; i < _registeredValidators.length; i++) {
for (uint i = 0; i < _registeredValidators.length; i++) {
address addr = _registeredValidators[i];

ValidatorData storage data = _registeredValidatorData[addr];
if(data.isResigned) {
continue;
}

if(_head == address(0)) {
_head = addr;
_topValidatorsCount = 1;
continue;
}


if (_topValidatorsCount < top) {
insertTopValidator(addr, top);
continue;
}

ValidatorData storage data = _registeredValidatorData[addr];
ValidatorData storage headData = _registeredValidatorData[_head];

if (_isGreater(Validator({addr: addr, data: data}), Validator({addr: _head, data: headData}))) {
Expand Down Expand Up @@ -192,13 +204,17 @@ contract Consensus {
return _registeredValidatorsCount;
}

function resignedValidatorsCount() public view returns (uint256) {
return _resignedValidatorsCount;
}

function activeValidatorsCount() public view returns (uint256) {
return _calculatedTopValidators.length;
}

function registerValidator(bytes calldata bls12_381_public_key) external {
require(msg.sender != _owner, "Invalid caller");
require(!_hasRegisteredValidator[msg.sender], "ValidatorData is already registered");
require(!_hasRegisteredValidator[msg.sender], "Validator is already registered");

bytes32 bls_public_key_hash = keccak256(bls12_381_public_key);

Expand All @@ -217,67 +233,21 @@ contract Consensus {
_hasRegisteredValidator[msg.sender] = true;
_registeredValidatorData[msg.sender] = validator;
_registeredPublicKeys[bls_public_key_hash] = true;

// TODO
// if (_registeredValidatorsCount < MIN_VALIDATORS) {
_registeredValidators.push(msg.sender);
// }

emit ValidatorRegistered(msg.sender, bls12_381_public_key);
}

function deregisterValidator() external {
require(isValidatorRegistered(msg.sender), "Caller not a validator");
function resignValidator() external {
require(isValidatorRegistered(msg.sender), "Caller is not a validator");

ValidatorData storage validator = _registeredValidatorData[msg.sender];
require(!validator.isResigned, "Validator is already resigned");

validator.isResigned = true;
_resignedValidators.push(msg.sender);
}

function performValidatorResignations() external {
// TODO: optimize removal from activeValidators array
uint256[] memory indexesToRemove = new uint256[](_resignedValidators.length);

for (uint256 i = 0; i < _resignedValidators.length; i++) {
address addr = _resignedValidators[i];
ValidatorData storage validator = _registeredValidatorData[addr];
require(validator.isResigned, "Validator is not resigned");

bytes32 bls_public_key_hash = keccak256(validator.bls12_381_public_key);

_registeredValidatorsCount--;
delete _hasRegisteredValidator[addr];
delete _registeredValidatorData[addr];
delete _registeredPublicKeys[bls_public_key_hash];

// find index
for (uint256 j = 0; i < _registeredValidators.length - 1; j++) {
if (_registeredValidators[j] != addr) {
continue;
}

indexesToRemove[i] = j;
break;
}
}

// Sort indexes in descending order to avoid shifting issues
for (uint256 i = 0; i < indexesToRemove.length; i++) {
for (uint256 j = i + 1; j < indexesToRemove.length; j++) {
if (indexesToRemove[i] < indexesToRemove[j]) {
(indexesToRemove[i], indexesToRemove[j]) = (indexesToRemove[j], indexesToRemove[i]);
}
}
}

for (uint256 i = 0; i < indexesToRemove.length; i++) {
uint256 index = indexesToRemove[i];
_registeredValidators[index] = _registeredValidators[_registeredValidators.length - 1];
_registeredValidators.pop();
}
_resignedValidatorsCount += 1;

// clear resigned validators
delete _resignedValidators;
emit ValidatorResigned(msg.sender);
}

function isValidatorRegistered(address addr) public view returns (bool) {
Expand Down
19 changes: 19 additions & 0 deletions contracts/test/consensus/Base.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE
pragma solidity ^0.8.13;

import {Test, console} from "@forge-std/Test.sol";

contract Base is Test {
function prepareBLSKey(address addr, uint8 lenght) public pure returns (bytes memory) {
bytes32 h = keccak256(abi.encode(addr));
bytes memory validatorKey = new bytes(lenght);
for (uint256 j = 0; j < 32; j++) {
validatorKey[j] = h[j];
}
return validatorKey;
}

function prepareBLSKey(address addr) public pure returns (bytes memory) {
return prepareBLSKey(addr, 48);
}
}
37 changes: 28 additions & 9 deletions contracts/test/consensus/Consensus-CalculateTop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,40 @@ 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 Test {
contract ConsensusTest is Base {
Consensus public consensus;

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

function test_should_work_with_one_validator() public {
address addr = address(1);
vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));
vm.stopPrank();

consensus.calculateTopValidators(1);
Validator[] memory validators = consensus.getTopValidators();
assertEq(validators.length, 1);
assertEq(validators[0].addr, addr);
}

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

vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));
consensus.resignValidator();
vm.stopPrank();

consensus.calculateTopValidators(1);
Validator[] memory validators = consensus.getTopValidators();
assertEq(validators.length, 0);
}

function test_consensus_200_topValidators() public {
vm.pauseGasMetering();
assertEq(consensus.registeredValidatorsCount(), 0);
Expand All @@ -37,13 +63,7 @@ contract ConsensusTest is Test {

vm.startPrank(addr);

bytes32 h = keccak256(abi.encode(addr));
bytes memory validatorKey = new bytes(48);
for (uint256 j = 0; j < 32; j++) {
validatorKey[j] = h[j];
}

consensus.registerValidator(validatorKey);
consensus.registerValidator(prepareBLSKey(addr));
consensus.vote(addr);
vm.stopPrank();
}
Expand All @@ -57,7 +77,6 @@ contract ConsensusTest is Test {
assertEq(validators.length, activeValidators);
assertEq(validators[activeValidators - 1].addr, highest);

console.logString("RESET");
consensus.calculateTopValidators(uint8(activeValidators));

validators = consensus.getTopValidators();
Expand Down
74 changes: 74 additions & 0 deletions contracts/test/consensus/Consensus-ValidatorRegistration.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE
pragma solidity ^0.8.13;

import {Consensus, ValidatorData, Validator, ValidatorRegistered} 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_validator_registration_pass() public {
assertEq(consensus.registeredValidatorsCount(), 0);
address addr = address(1);

// Act
vm.startPrank(addr);
vm.expectEmit(address(consensus));
emit ValidatorRegistered(addr, prepareBLSKey(addr));
consensus.registerValidator(prepareBLSKey(addr));
vm.stopPrank();

// Assert
assertEq(consensus.registeredValidatorsCount(), 1);
Validator memory validator = consensus.getValidator(addr);
assertEq(validator.addr, addr);
assertEq(validator.data.bls12_381_public_key, prepareBLSKey(addr));
assertEq(validator.data.voteBalance, 0);
assertEq(validator.data.votersCount, 0);
assertEq(validator.data.isResigned, false);
}

function test_validator_registration_revert_if_caller_is_owner() public {
vm.expectRevert("Invalid caller");
consensus.registerValidator(prepareBLSKey(address(1)));
}

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

vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));

vm.expectRevert("Validator is already registered");
consensus.registerValidator(prepareBLSKey(address(2)));
}

function test_validator_registration_revert_if_bls_key_is_already_registered() public {
address addr = address(1);
vm.startPrank(addr);

consensus.registerValidator(prepareBLSKey(addr));

vm.startPrank(address(2));
vm.expectRevert("BLS12-381 key is already registered");
consensus.registerValidator(prepareBLSKey(addr));
}

function test_validator_registration_revert_if_bls_key_length_is_invalid() public {
address addr = address(1);
vm.startPrank(addr);

vm.expectRevert("BLS12-381 publicKey length is invalid");
consensus.registerValidator(prepareBLSKey(addr, 46));
vm.expectRevert("BLS12-381 publicKey length is invalid");
consensus.registerValidator(prepareBLSKey(addr, 47));
vm.expectRevert("BLS12-381 publicKey length is invalid");
consensus.registerValidator(prepareBLSKey(addr, 49));
vm.expectRevert("BLS12-381 publicKey length is invalid");
consensus.registerValidator(prepareBLSKey(addr, 50));
}
}
75 changes: 75 additions & 0 deletions contracts/test/consensus/Consensus-ValidatorResignation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE
pragma solidity ^0.8.13;

import {Consensus, ValidatorData, Validator, ValidatorResigned} 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_validator_resignation_pass() public {
assertEq(consensus.registeredValidatorsCount(), 0);
address addr = address(1);

// Act
vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));
vm.stopPrank();

// Assert
assertEq(consensus.registeredValidatorsCount(), 1);
Validator memory validator = consensus.getValidator(addr);
assertEq(validator.addr, addr);
assertEq(validator.data.bls12_381_public_key, prepareBLSKey(addr));
assertEq(validator.data.voteBalance, 0);
assertEq(validator.data.votersCount, 0);
assertEq(validator.data.isResigned, false);

// Act
vm.startPrank(addr);
vm.expectEmit(address(consensus));
emit ValidatorResigned(addr);
consensus.resignValidator();
vm.stopPrank();


assertEq(consensus.registeredValidatorsCount(), 1);
validator = consensus.getValidator(addr);
assertEq(validator.addr, addr);
assertEq(validator.data.bls12_381_public_key, prepareBLSKey(addr));
assertEq(validator.data.voteBalance, 0);
assertEq(validator.data.votersCount, 0);
assertEq(validator.data.isResigned, true);
}

function test_validator_resignation_revert_if_caller_is_not_validator() public {
vm.expectRevert("Caller is not a validator");
consensus.resignValidator();
}

function test_validator_resignation_revert_if_resigned() public {
assertEq(consensus.registeredValidatorsCount(), 0);
address addr = address(1);

// Act
vm.startPrank(addr);
consensus.registerValidator(prepareBLSKey(addr));
vm.stopPrank();

// Assert
assertEq(consensus.registeredValidatorsCount(), 1);

// Act
vm.startPrank(addr);
vm.expectEmit(address(consensus));
emit ValidatorResigned(addr);
consensus.resignValidator();

vm.expectRevert("Validator is already resigned");
consensus.resignValidator();
}
}
Loading

0 comments on commit 990ac1a

Please sign in to comment.