Skip to content

Commit

Permalink
bulk validator registration (init)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Dec 12, 2023
1 parent 61fd8ed commit c5b63db
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 28 deletions.
4 changes: 2 additions & 2 deletions contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ contract SSVNetwork is
/*******************************/

function registerValidator(
bytes calldata publicKey,
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
bytes calldata sharesData,
bytes[] calldata shares,
uint256 amount,
ISSVNetworkCore.Cluster memory cluster
) external override {
Expand Down
8 changes: 4 additions & 4 deletions contracts/interfaces/ISSVClusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import "./ISSVNetworkCore.sol";

interface ISSVClusters is ISSVNetworkCore {
/// @notice Registers a new validator on the SSV Network
/// @param publicKey The public key of the new validator
/// @param publicKeys The public key of the new validator
/// @param operatorIds Array of IDs of operators managing this validator
/// @param sharesData Encrypted shares related to the new validator
/// @param shares Encrypted shares related to the new validator
/// @param amount Amount of SSV tokens to be deposited
/// @param cluster Cluster to be used with the new validator
function registerValidator(
bytes calldata publicKey,
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
bytes calldata sharesData,
bytes[] calldata shares,
uint256 amount,
Cluster memory cluster
) external;
Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/ISSVNetworkCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@ interface ISSVNetworkCore {
error TargetModuleDoesNotExist(); // 0x8f9195fb
error MaxValueExceeded(); // 0x91aa3017
error FeeTooHigh(); // 0xcd4e6167
error PublicKeysSharesLengthMismatch(); // 0x9ad467b8
}
18 changes: 18 additions & 0 deletions contracts/libraries/ValidatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ import "../interfaces/ISSVNetworkCore.sol";
import "./SSVStorage.sol";

library ValidatorLib {
uint64 private constant PUBLIC_KEY_LENGTH = 48;

function registerPublicKeys(bytes[] calldata publicKeys, uint64[] memory operatorIds, StorageData storage s) internal {
for (uint i; i < publicKeys.length; ++i) {
if (publicKeys[i].length != PUBLIC_KEY_LENGTH) {
revert ISSVNetworkCore.InvalidPublicKeyLength();
}

bytes32 hashedPk = keccak256(abi.encodePacked(publicKeys[i], msg.sender));

if (s.validatorPKs[hashedPk] != bytes32(0)) {
revert ISSVNetworkCore.ValidatorAlreadyExists();
}

s.validatorPKs[hashedPk] = bytes32(uint256(keccak256(abi.encodePacked(operatorIds))) | uint256(0x01)); // set LSB to 1
}
}

function validateState(
bytes calldata publicKey,
uint64[] calldata operatorIds,
Expand Down
36 changes: 20 additions & 16 deletions contracts/modules/SSVClusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@ contract SSVClusters is ISSVClusters {
uint64 private constant MIN_OPERATORS_LENGTH = 4;
uint64 private constant MAX_OPERATORS_LENGTH = 13;
uint64 private constant MODULO_OPERATORS_LENGTH = 3;
uint64 private constant PUBLIC_KEY_LENGTH = 48;

function registerValidator(
bytes calldata publicKey,
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
bytes calldata sharesData,
bytes[] calldata shares,
uint256 amount,
Cluster memory cluster
) external override {
if (publicKeys.length != shares.length) revert PublicKeysSharesLengthMismatch();

StorageData storage s = SSVStorage.load();
StorageProtocol storage sp = SSVStorageProtocol.load();

uint256 operatorsLength = operatorIds.length;
uint32 validatorsLength = uint32(publicKeys.length);

{
if (
operatorsLength < MIN_OPERATORS_LENGTH ||
Expand All @@ -39,15 +42,7 @@ contract SSVClusters is ISSVClusters {
revert InvalidOperatorIdsLength();
}

if (publicKey.length != PUBLIC_KEY_LENGTH) revert InvalidPublicKeyLength();

bytes32 hashedPk = keccak256(abi.encodePacked(publicKey, msg.sender));

if (s.validatorPKs[hashedPk] != bytes32(0)) {
revert ValidatorAlreadyExists();
}

s.validatorPKs[hashedPk] = bytes32(uint256(keccak256(abi.encodePacked(operatorIds))) | uint256(0x01)); // set LSB to 1
ValidatorLib.registerPublicKeys(publicKeys, operatorIds, s);
}
bytes32 hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds));

Expand Down Expand Up @@ -100,7 +95,8 @@ contract SSVClusters is ISSVClusters {
}
}
operator.updateSnapshot();
if (++operator.validatorCount > sp.validatorsPerOperatorLimit) {
operator.validatorCount += validatorsLength;
if (operator.validatorCount > sp.validatorsPerOperatorLimit) {
revert ExceedValidatorLimit();
}
clusterIndex += operator.snapshot.index;
Expand All @@ -114,10 +110,10 @@ contract SSVClusters is ISSVClusters {
}
cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex());

sp.updateDAO(true, 1);
sp.updateDAO(true, validatorsLength);
}

++cluster.validatorCount;
cluster.validatorCount += validatorsLength;

if (
cluster.isLiquidatable(
Expand All @@ -136,7 +132,15 @@ contract SSVClusters is ISSVClusters {
CoreLib.deposit(amount);
}

emit ValidatorAdded(msg.sender, operatorIds, publicKey, sharesData, cluster);
for (uint i; i < validatorsLength; ) {
bytes memory pk = publicKeys[i];
bytes memory sh = shares[i];
emit ValidatorAdded(msg.sender, operatorIds, pk, sh, cluster);

unchecked {
++i;
}
}
}

function removeValidator(
Expand Down
8 changes: 4 additions & 4 deletions contracts/test/SSVNetworkUpgrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,19 +189,19 @@ contract SSVNetworkUpgrade is
}

function registerValidator(
bytes calldata publicKey,
bytes[] calldata publicKey,
uint64[] memory operatorIds,
bytes calldata sharesData,
bytes[] calldata shares,
uint256 amount,
ISSVNetworkCore.Cluster memory cluster
) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS],
abi.encodeWithSignature(
"registerValidator(bytes,uint64[],bytes,uint256,(uint32,uint64,uint64,bool,uint256))",
"registerValidator(bytes[],uint64[],bytes,uint256,(uint32,uint64,uint64,bool,uint256))",
publicKey,
operatorIds,
sharesData,
shares,
amount,
cluster
)
Expand Down
53 changes: 51 additions & 2 deletions test/validators/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as helpers from '../helpers/contract-helpers';
import * as utils from '../helpers/utils';
import { expect } from 'chai';
import { trackGas, GasGroup } from '../helpers/gas-usage';
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";

let ssvNetworkContract: any, ssvViews: any, ssvToken: any, minDepositAmount: any, cluster1: any;

Expand All @@ -22,9 +23,9 @@ describe('Register Validator Tests', () => {
// cold register
await helpers.DB.ssvToken.connect(helpers.DB.owners[6]).approve(helpers.DB.ssvNetwork.contract.address, '1000000000000000');
cluster1 = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[6]).registerValidator(
helpers.DataGenerator.publicKey(90),
[helpers.DataGenerator.publicKey(90)],
[1, 2, 3, 4],
helpers.DataGenerator.shares(4),
[helpers.DataGenerator.shares(4)],
'1000000000000000',
{
validatorCount: 0,
Expand Down Expand Up @@ -171,6 +172,54 @@ describe('Register Validator Tests', () => {
), [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT]);
});

it('Bulk register 2 validators with 4 operators into the same cluster', async () => {
const pks = [helpers.DataGenerator.publicKey(1), helpers.DataGenerator.publicKey(2)];
const operatorIds = [1, 2, 3, 4];
const shares = [helpers.DataGenerator.shares(4), helpers.DataGenerator.shares(4)];
const depositAmount = minDepositAmount * 2;

await helpers.DB.ssvToken.approve(ssvNetworkContract.address, depositAmount);
await expect(ssvNetworkContract.registerValidator(
pks,
operatorIds,
shares,
depositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true
}
)).to.emit(ssvNetworkContract, 'ValidatorAdded');
});

it.only('Bulk register 10 validators with 4 operators into the same cluster', async () => {
const pks = Array.from({ length: 10 }, (_, index) => helpers.DataGenerator.publicKey(index + 1));
const operatorIds = [1, 2, 3, 4];
const shares = Array.from({ length: 10 }, (_, __) => helpers.DataGenerator.shares(4));

const depositAmount = minDepositAmount * 10;

await helpers.DB.ssvToken.approve(ssvNetworkContract.address, depositAmount);
await expect(ssvNetworkContract.registerValidator(
pks,
operatorIds,
shares,
depositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true
}
)).to.emit(ssvNetworkContract, 'ValidatorAdded')
.withArgs(helpers.DB.owners[0].address,operatorIds, pks[0], shares[0],anyValue)
.and.to.emit(ssvNetworkContract, 'ValidatorAdded')
.withArgs(helpers.DB.owners[0].address,operatorIds, pks[1], shares[1],anyValue);
});

// 7 operators

it('Register validator with 7 operators gas limit', async () => {
Expand Down

0 comments on commit c5b63db

Please sign in to comment.