Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 63 additions & 59 deletions contracts/src/consensus/ConsensusV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

// Validators:
// - Registered -> All validators that are registered including resigned validators
// - Active -> Top N validators with the highest vote balance, that participate in the consensus
// - Resigned -> Validators that resigned from the consensus

// Voter calls vote funtion
// Vote function includes valdiator address and balance, whole balance is added to the validator voteBalance
// - Registered validators -> All validators that are registered including resigned validators
// - Round validators -> Top N validators with the highest vote balance, that participate in the consensus
// - Resigned validators -> Validators that resigned from the consensus
// - Active validators -> Round validators that are not resigned and have a valid BLS public key
// - Inactive validators -> Validators that are resigned or do not have a valid BLS public key

// Inclusions:
// Round validators ⊆ Active validators ⊆ Registered validators
// Resigned validators ⊆ Inactive validators ⊆ Registered validators

// Voter calls vote function
// Vote function includes validator address and balance, whole balance is added to the validator voteBalance
// Voter can unvote, whole balance is removed from validator voteBalance
// Voter balance is changed (fee & send amount) - validator voteBalance is decreased (for sender) and i ncreased (for recipients)
// Voter balance is changed (fee & send amount) - validator voteBalance is decreased (for sender) and increased (for recipients)

// Scenario 1 - First evm transfer, then vote
// Wallet balance: 100
Expand Down Expand Up @@ -99,10 +105,10 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
address private _votersTail; // Default address(0)
uint256 private _votersCount; // Default 0

mapping(address => address) private _activeValidatorsMap;
address[] private _activeValidators;
address private _activeValidatorsHead; // Default address(0)
uint256 private _activeValidatorsCount; // Default 0
mapping(address => address) private _roundValidatorsMap;
address[] private _roundValidators;
address private _roundValidatorsHead; // Default address(0)
uint256 private _roundValidatorsCount; // Default 0
uint256 private _minValidators; // Default 1

RoundValidator[][] private _rounds;
Expand Down Expand Up @@ -292,17 +298,17 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
}
}

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

_minValidators = n;

_shuffle(_validators);
_deleteActiveValidators();
_deleteRoundValidators();

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

if (top == 0) {
Expand All @@ -318,50 +324,48 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
continue;
}

if (_activeValidatorsHead == address(0)) {
_activeValidatorsHead = addr;
_activeValidatorsCount = 1;
if (_roundValidatorsHead == address(0)) {
_roundValidatorsHead = addr;
_roundValidatorsCount = 1;
continue;
}

if (_activeValidatorsCount < top) {
if (_roundValidatorsCount < top) {
_insertValidator(addr, top);
continue;
}

ValidatorData storage headData = _validatorsData[_activeValidatorsHead];
ValidatorData storage headData = _validatorsData[_roundValidatorsHead];

if (
_isGreater(
Validator({addr: addr, data: data}), Validator({addr: _activeValidatorsHead, data: headData})
)
_isGreater(Validator({addr: addr, data: data}), Validator({addr: _roundValidatorsHead, data: headData}))
) {
_insertValidator(addr, top);
}
}

if (_activeValidatorsCount == 0) {
if (_roundValidatorsCount == 0) {
revert NoActiveValidators();
}

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

for (uint256 i = 0; i < _activeValidatorsCount; i++) {
for (uint256 i = 0; i < _roundValidatorsCount; i++) {
tmpValidators[i] = next;
next = _activeValidatorsMap[next];
next = _roundValidatorsMap[next];
}
_shuffleMem(tmpValidators);

// Fill round & _activeValidators
// Fill round & _roundValidators
RoundValidator[] storage round = _rounds.push();
delete _activeValidators;
_activeValidators = new address[](_minValidators);
delete _roundValidators;
_roundValidators = new address[](_minValidators);

for (uint256 i = 0; i < _minValidators; i++) {
address addr = tmpValidators[i % _activeValidatorsCount];
_activeValidators[i] = addr;
address addr = tmpValidators[i % _roundValidatorsCount];
_roundValidators[i] = addr;
round.push(RoundValidator({addr: addr, voteBalance: _validatorsData[addr].voteBalance}));
}
}
Expand All @@ -379,8 +383,8 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
return _resignedValidatorsCount;
}

function activeValidatorsCount() external view returns (uint256) {
return _activeValidatorsCount;
function roundValidatorsCount() external view returns (uint256) {
return _roundValidatorsCount;
}

function isValidatorRegistered(address addr) public view returns (bool) {
Expand All @@ -395,10 +399,10 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
return Validator({addr: addr, data: _validatorsData[addr]});
}

function getActiveValidators() external view returns (Validator[] memory) {
Validator[] memory result = new Validator[](_activeValidators.length);
for (uint256 i = 0; i < _activeValidators.length; i++) {
address addr = _activeValidators[i];
function getRoundValidators() external view returns (Validator[] memory) {
Validator[] memory result = new Validator[](_roundValidators.length);
for (uint256 i = 0; i < _roundValidators.length; i++) {
address addr = _roundValidators[i];
ValidatorData storage data = _validatorsData[addr];
result[i] = Validator({addr: addr, data: data});
}
Expand Down Expand Up @@ -505,30 +509,30 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
}
}

function _deleteActiveValidators() internal {
address next = _activeValidatorsHead;
function _deleteRoundValidators() internal {
address next = _roundValidatorsHead;

while (next != address(0)) {
address current = next;
next = _activeValidatorsMap[current];
delete _activeValidatorsMap[current];
next = _roundValidatorsMap[current];
delete _roundValidatorsMap[current];
}
_activeValidatorsCount = 0;
_roundValidatorsCount = 0;
}

function _insertValidator(address addr, uint8 top) internal {
ValidatorData memory data = _validatorsData[addr];

if (
_isGreater(
Validator({addr: _activeValidatorsHead, data: _validatorsData[_activeValidatorsHead]}),
Validator({addr: _roundValidatorsHead, data: _validatorsData[_roundValidatorsHead]}),
Validator({addr: addr, data: data})
)
) {
_insertHead(addr);
} else {
address current = _activeValidatorsMap[_activeValidatorsHead];
address previous = _activeValidatorsHead;
address current = _roundValidatorsMap[_roundValidatorsHead];
address previous = _roundValidatorsHead;

while (true) {
if (current == address(0)) {
Expand All @@ -546,28 +550,28 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
}

previous = current;
current = _activeValidatorsMap[current];
current = _roundValidatorsMap[current];
}
}

if (_activeValidatorsCount > top) {
address next = _activeValidatorsMap[_activeValidatorsHead];
delete _activeValidatorsMap[_activeValidatorsHead];
_activeValidatorsHead = next;
_activeValidatorsCount--;
if (_roundValidatorsCount > top) {
address next = _roundValidatorsMap[_roundValidatorsHead];
delete _roundValidatorsMap[_roundValidatorsHead];
_roundValidatorsHead = next;
_roundValidatorsCount--;
}
}

function _insertHead(address addr) internal {
_activeValidatorsMap[addr] = _activeValidatorsHead;
_activeValidatorsHead = addr;
_activeValidatorsCount++;
_roundValidatorsMap[addr] = _roundValidatorsHead;
_roundValidatorsHead = addr;
_roundValidatorsCount++;
}

function _insertAfter(address prev, address addr) internal {
_activeValidatorsMap[addr] = _activeValidatorsMap[prev];
_activeValidatorsMap[prev] = addr;
_activeValidatorsCount++;
_roundValidatorsMap[addr] = _roundValidatorsMap[prev];
_roundValidatorsMap[prev] = addr;
_roundValidatorsCount++;
}

function _unvote() internal returns (address) {
Expand Down
38 changes: 19 additions & 19 deletions contracts/test/consensus/Consensus-CalculateTop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ contract ConsensusTest is Base {
address addr = address(1);
registerValidator(addr);

consensus.calculateActiveValidators(1);
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(1);
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();
assertEq(validators.length, 1);
assertEq(validators[0].addr, addr);
}
Expand All @@ -21,33 +21,33 @@ contract ConsensusTest is Base {
address addr = address(1);
vm.startPrank(addr);
vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, addr));
consensus.calculateActiveValidators(1);
consensus.calculateRoundValidators(1);
}

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

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

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

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

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

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

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

function test_should_ignore_resigned_validators() public {
Expand All @@ -57,8 +57,8 @@ contract ConsensusTest is Base {
registerValidator(address(2));
resignValidator(addr);

consensus.calculateActiveValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();
assertEq(validators.length, 2);
assertEq(validators[0].addr, address(2));
assertEq(validators[1].addr, address(2)); // Second validator is duplicated
Expand All @@ -72,8 +72,8 @@ contract ConsensusTest is Base {
registerValidator(address(2));
resignValidator(address(2));

consensus.calculateActiveValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();
assertEq(validators.length, 2);
assertEq(validators[0].addr, addr);
assertEq(validators[1].addr, addr); // Second validator is duplicated
Expand All @@ -85,8 +85,8 @@ contract ConsensusTest is Base {
registerValidator(addr);
consensus.addValidator(address(2), new bytes(0), false);

consensus.calculateActiveValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(2);
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();
assertEq(validators.length, 2);
assertEq(validators[0].addr, addr);
assertEq(validators[1].addr, addr); // Second validator is duplicated
Expand Down Expand Up @@ -116,8 +116,8 @@ contract ConsensusTest is Base {

uint160 activeValidators = 53;

consensus.calculateActiveValidators(uint8(activeValidators));
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(uint8(activeValidators));
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();

for (uint256 i = 0; i < activeValidators; i++) {
ConsensusV1.Validator memory validator = validators[i];
Expand Down Expand Up @@ -173,8 +173,8 @@ contract ConsensusTest is Base {

uint160 activeValidators = 53;

consensus.calculateActiveValidators(uint8(activeValidators));
ConsensusV1.Validator[] memory validators = consensus.getActiveValidators();
consensus.calculateRoundValidators(uint8(activeValidators));
ConsensusV1.Validator[] memory validators = consensus.getRoundValidators();
assertEq(validators.length, activeValidators);

assertEq(validators[activeValidators - 1].addr, address(0x1B)); // Shuffled address
Expand All @@ -183,9 +183,9 @@ contract ConsensusTest is Base {
assertEq(validators[activeValidators - 1].addr, address(53));

// Second attempt should return the same result
consensus.calculateActiveValidators(uint8(activeValidators));
consensus.calculateRoundValidators(uint8(activeValidators));

validators = consensus.getActiveValidators();
validators = consensus.getRoundValidators();
assertEq(validators[activeValidators - 1].addr, address(0x1B)); // Shuffled address
validators = sortValidators(validators);
assertEq(validators.length, activeValidators);
Expand Down
10 changes: 5 additions & 5 deletions contracts/test/consensus/Consensus-Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ contract ConsensusTest is Base {
consensus.vote(address(1));

vm.stopPrank();
consensus.calculateActiveValidators(2);
consensus.calculateRoundValidators(2);

assertEq(consensus.version(), 1);
assertEq(consensus.registeredValidatorsCount(), 3);
assertEq(consensus.resignedValidatorsCount(), 1);
assertEq(consensus.activeValidatorsCount(), 2);
assertEq(consensus.roundValidatorsCount(), 2);
assertEq(consensus.getVotesCount(), 1);
assertEq(consensus.getActiveValidators().length, 2);
assertEq(consensus.getRoundValidators().length, 2);
ConsensusV1.Validator[] memory validatorsBefore = consensus.getAllValidators();
assertEq(validatorsBefore.length, 3);

Expand All @@ -77,9 +77,9 @@ contract ConsensusTest is Base {
assertEq(consensusNew.version(), 1);
assertEq(consensusNew.registeredValidatorsCount(), 3);
assertEq(consensusNew.resignedValidatorsCount(), 1);
assertEq(consensusNew.activeValidatorsCount(), 2);
assertEq(consensusNew.roundValidatorsCount(), 2);
assertEq(consensusNew.getVotesCount(), 1);
assertEq(consensus.getActiveValidators().length, 2);
assertEq(consensus.getRoundValidators().length, 2);
ConsensusV1.Validator[] memory validatorsAfter = consensusNew.getAllValidators();
assertEq(validatorsAfter.length, 3);

Expand Down
Loading
Loading