@@ -30,8 +30,9 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
3030
3131contract ConsensusV1 is UUPSUpgradeable , OwnableUpgradeable {
3232 struct ValidatorData {
33- uint256 votersCount;
3433 uint256 voteBalance;
34+ uint128 fee;
35+ uint64 votersCount;
3536 bool isResigned;
3637 bytes blsPublicKey; // 96 bits
3738 }
@@ -63,16 +64,15 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
6364 address validator;
6465 }
6566
67+ event FeeUpdated (uint256 fee );
6668 event ValidatorRegistered (address addr , bytes blsPublicKey );
67-
6869 event ValidatorUpdated (address addr , bytes blsPublicKey );
69-
7070 event ValidatorResigned (address addr );
71-
7271 event Voted (address voter , address validator );
73-
7472 event Unvoted (address voter , address validator );
7573
74+ error InvalidFee ();
75+ error RefundFailed ();
7676 error CallerIsNotValidator ();
7777 error ValidatorNotRegistered ();
7878 error ValidatorAlreadyRegistered ();
@@ -96,8 +96,9 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
9696 mapping (address => ValidatorData) private _validatorsData;
9797 mapping (address => bool ) private _hasValidator;
9898 mapping (bytes32 => bool ) private _blsPublicKeys;
99- address [] private _validators;
100- uint256 private _validatorsCount; // Default 0
99+ address [] private _validators; // All registered validators including resigned
100+ address [] private _activeValidators; // Has valid BLS public key and is not resigned
101+ mapping (address => uint256 ) private _activeValidatorIndex; // Points to index in _activeValidators array
101102 uint256 private _resignedValidatorsCount; // Default 0
102103
103104 mapping (address => Vote) private _voters;
@@ -110,19 +111,26 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
110111 address private _roundValidatorsHead; // Default address(0)
111112 uint256 private _roundValidatorsCount; // Default 0
112113 uint256 private _minValidators; // Default 1
114+ uint128 private _fee; // Validator registration fee: Default 0
113115
114116 RoundValidator[][] private _rounds;
115117
116118 // Initializers
117- function initialize () public initializer {
119+ function initialize (uint128 registrationFee ) public initializer {
118120 __Ownable_init (msg .sender );
119121 _minValidators = 1 ;
122+ _fee = registrationFee;
120123 }
121124
122125 // Overrides
123126 function _authorizeUpgrade (address newImplementation ) internal override onlyOwner {}
124127
125128 // External functions
129+ function setFee (uint128 registrationFee ) external onlyOwner {
130+ _fee = registrationFee;
131+ emit FeeUpdated (registrationFee);
132+ }
133+
126134 function addValidator (address addr , bytes calldata blsPublicKey , bool isResigned ) external onlyOwner {
127135 if (_rounds.length > 0 ) {
128136 revert ImportIsNotAllowed ();
@@ -142,9 +150,8 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
142150 }
143151
144152 ValidatorData memory validator =
145- ValidatorData ({votersCount: 0 , voteBalance: 0 , isResigned: isResigned, blsPublicKey: blsPublicKey});
153+ ValidatorData ({votersCount: 0 , voteBalance: 0 , fee: 0 , isResigned: isResigned, blsPublicKey: blsPublicKey});
146154
147- _validatorsCount++ ;
148155 _hasValidator[addr] = true ;
149156 _validatorsData[addr] = validator;
150157 _validators.push (addr);
@@ -153,6 +160,10 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
153160 _resignedValidatorsCount++ ;
154161 }
155162
163+ if (_canBecomeActiveValidator (addr)) {
164+ _addActiveValidator (addr);
165+ }
166+
156167 emit ValidatorRegistered (addr, blsPublicKey);
157168 }
158169
@@ -197,20 +208,29 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
197208 emit Voted (voter, validator);
198209 }
199210
200- function registerValidator (bytes calldata blsPublicKey ) external {
211+ function registerValidator (bytes calldata blsPublicKey ) external payable {
212+ if (msg .value != _fee) {
213+ revert InvalidFee ();
214+ }
215+
201216 if (_hasValidator[msg .sender ]) {
202217 revert ValidatorAlreadyRegistered ();
203218 }
204219
205220 _verifyAndRegisterBlsPublicKey (blsPublicKey);
206221
207- ValidatorData memory validator =
208- ValidatorData ({votersCount: 0 , voteBalance: 0 , isResigned: false , blsPublicKey: blsPublicKey});
222+ ValidatorData memory validator = ValidatorData ({
223+ votersCount: 0 ,
224+ voteBalance: 0 ,
225+ fee: uint128 (msg .value ),
226+ isResigned: false ,
227+ blsPublicKey: blsPublicKey
228+ });
209229
210- _validatorsCount++ ;
211230 _hasValidator[msg .sender ] = true ;
212231 _validatorsData[msg .sender ] = validator;
213232 _validators.push (msg .sender );
233+ _addActiveValidator (msg .sender );
214234
215235 emit ValidatorRegistered (msg .sender , blsPublicKey);
216236 }
@@ -224,6 +244,10 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
224244
225245 _validatorsData[msg .sender ].blsPublicKey = blsPublicKey;
226246
247+ if (_canBecomeActiveValidator (msg .sender ) && ! _isActiveValidator (msg .sender )) {
248+ _addActiveValidator (msg .sender );
249+ }
250+
227251 emit ValidatorUpdated (msg .sender , blsPublicKey);
228252 }
229253
@@ -237,13 +261,25 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
237261 revert ValidatorAlreadyResigned ();
238262 }
239263
240- if (_validatorsCount - _resignedValidatorsCount <= _minValidators) {
264+ if (_activeValidators. length <= _minValidators) {
241265 revert BellowMinValidators ();
242266 }
243267
244268 validator.isResigned = true ;
245269 _resignedValidatorsCount += 1 ;
246270
271+ _removeActiveValidator (msg .sender );
272+
273+ // Refund the registration fee to the validator
274+ if (validator.fee > 0 ) {
275+ uint256 refundFee = validator.fee;
276+ validator.fee = 0 ;
277+ (bool success ,) = payable (msg .sender ).call {value: refundFee}("" );
278+ if (! success) {
279+ revert RefundFailed ();
280+ }
281+ }
282+
247283 emit ValidatorResigned (msg .sender );
248284 }
249285
@@ -305,18 +341,18 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
305341
306342 _minValidators = n;
307343
308- _shuffle (_validators );
344+ _shuffle ();
309345 _deleteRoundValidators ();
310346
311347 _roundValidatorsHead = address (0 );
312- uint8 top = uint8 (_clamp (n, 0 , _validatorsCount - _resignedValidatorsCount ));
348+ uint8 top = uint8 (_clamp (n, 0 , _activeValidators. length ));
313349
314350 if (top == 0 ) {
315351 revert NoActiveValidators ();
316352 }
317353
318- for (uint256 i = 0 ; i < _validators .length ; i++ ) {
319- address addr = _validators [i];
354+ for (uint256 i = 0 ; i < _activeValidators .length ; i++ ) {
355+ address addr = _activeValidators [i];
320356
321357 ValidatorData storage data = _validatorsData[addr];
322358
@@ -375,8 +411,16 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
375411 return 1 ;
376412 }
377413
378- function registeredValidatorsCount () external view returns (uint256 ) {
379- return _validatorsCount;
414+ function fee () external view returns (uint256 ) {
415+ return _fee;
416+ }
417+
418+ function validatorsCount () external view returns (uint256 ) {
419+ return _validators.length ;
420+ }
421+
422+ function activeValidatorsCount () external view returns (uint256 ) {
423+ return _activeValidators.length ;
380424 }
381425
382426 function resignedValidatorsCount () external view returns (uint256 ) {
@@ -475,8 +519,8 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
475519 }
476520
477521 // Internal functions
478- function _shuffle (address [] storage array ) internal {
479- uint256 n = array .length ;
522+ function _shuffle () internal {
523+ uint256 n = _activeValidators .length ;
480524 if (n == 0 ) {
481525 return ;
482526 }
@@ -485,10 +529,35 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
485529 // Get a random index between 0 and i (inclusive)
486530 uint256 j = uint256 (keccak256 (abi.encodePacked (block .timestamp , i))) % (i + 1 );
487531
532+ if (i == j) {
533+ continue ; // No need to swap if indices are the same
534+ }
535+
536+ /* Swap example
537+ i = 0; j = 2;
538+
539+ Initial state
540+ A B C
541+ A:0 B:1 C:2
542+
543+ Array SWAP
544+ C B A
545+ A:0 B:1 C:2
546+
547+ Index SWAP
548+ C B A
549+ A:2 B:1 C:0
550+ */
551+
488552 // Swap elements at index i and j
489- address temp = array[i];
490- array[i] = array[j];
491- array[j] = temp;
553+ address addrA = _activeValidators[i];
554+ address addrB = _activeValidators[j];
555+
556+ _activeValidators[i] = _activeValidators[j];
557+ _activeValidators[j] = addrA;
558+
559+ _activeValidatorIndex[addrA] = j;
560+ _activeValidatorIndex[addrB] = i;
492561 }
493562 }
494563
@@ -574,6 +643,47 @@ contract ConsensusV1 is UUPSUpgradeable, OwnableUpgradeable {
574643 _roundValidatorsCount++ ;
575644 }
576645
646+ function _addActiveValidator (address addr ) internal {
647+ _activeValidators.push (addr);
648+ _activeValidatorIndex[addr] = _activeValidators.length - 1 ;
649+ }
650+
651+ function _removeActiveValidator (address addr ) internal {
652+ if (! _isActiveValidator (addr)) {
653+ return ;
654+ }
655+
656+ uint256 index = _activeValidatorIndex[addr];
657+ uint256 lastIndex = _activeValidators.length - 1 ;
658+
659+ // Copy last address to the index of the removed address. This is not swap. Last validator occurs 2 times in the array.
660+ if (index != lastIndex) {
661+ address lastValidator = _activeValidators[lastIndex];
662+ _activeValidators[index] = lastValidator;
663+ _activeValidatorIndex[lastValidator] = index;
664+ }
665+
666+ // Remove last address
667+ _activeValidators.pop ();
668+ delete _activeValidatorIndex[addr];
669+ }
670+
671+ function _canBecomeActiveValidator (address addr ) internal view returns (bool ) {
672+ ValidatorData storage data = _validatorsData[addr];
673+ return ! data.isResigned && data.blsPublicKey.length != 0 ;
674+ }
675+
676+ function _isActiveValidator (address addr ) internal view returns (bool ) {
677+ uint256 index = _activeValidatorIndex[addr];
678+ // Support for empty array
679+ if (index >= _activeValidators.length ) {
680+ return false ;
681+ }
682+
683+ // Check if the address at the index matches the address. Required for the case when index is 0.
684+ return _activeValidators[index] == addr;
685+ }
686+
577687 function _unvote () internal returns (address ) {
578688 Vote storage voter = _voters[msg .sender ];
579689 if (voter.validator == address (0 )) {
0 commit comments