Skip to main content

NodeOperatorsRegistry

The NodeOperatorsRegistry contract acts as a registry of Node Operators selected by the Lido DAO. Since Lido V2 upgrade NodeOperatorsRegistry contract became a module of StakingRouter and got the second name Curated staking module as part of the general Lido staking infrastructure. As a staking module, NodeOperatorsRegistry implements StakingModule interface.

NodeOperatorsRegistry keeps track of various Node Operators data, in particular limits of the allowed stake, reward addresses, penalty information, public keys of the Node Operators' validators. It defines order in which the Node Operators get the ether deposited and reward distribution between the node operators.

The Lido DAO obliges a curated node operator to exit its validators timely if requested by the Lido protocol. The exit request is formed on-chain by the ValidatorsExitBusOracle contract. If a NO doesn't fulfil the request timely, it will be reported onchain and sanctions may be applied by the DAO.

The Lido DAO can also:

  • set a target validator limit for the NO, as well as the priority exit mode. If the current active number of validators is above the target, the excess ones will be requested to exit in a prioritized manner when required to finalize withdrawal requests. Allocation of deposits above the target value is prohibited.
  • deactivate misbehaving operators by deactivateNodeOperator(). A deactivated node operator does not receive rewards or new deposits.

Glossary​

note

In the context of these terms "signing key", "key", "validator key", "validator" might be used interchangeably.

signing key. BLS12-381 public key that will be used by the protocol for making Beacon deposits to run a validator

vetted (signing key). Approved by the Lido DAO for receiving ether for deposit.

submitted (signing key). Added to the node operators registry.

depositable (signing key). Suitable for new deposits.

deposited (signing key). Ever received deposit.

unused (signing key). Submitted but not deposited yet.

exited (signing key). A validator that got into "Exited" state: either by voluntary exit or as a result of slashing. This doc might be useful regarding the validators lifecycle.

used (active) (signing key). Deposited but not yet exited.

late (validator). Not exited in proper time after an exit request from ValidatorsExitBusOracle by Lido protocol.

refunded (stuck validator). Compensated by the NO for being stuck. For more information on handling of NO misbehavior see Lido on Ethereum Validator Exits SNOP 3.0 (IPFS, GitHub).

Node operator parameters​

For each NO the contract keeps a record of at least these values:

  • active: bool active/inactive status of the NO. An active NO gets rewards and new deposits according to its staking limit. New node operators are added in active state.
  • name: string human-readable name of the NO
  • rewardAddress: address where to send stETH rewards (part of the protocol fee)
  • totalVettedValidators: uint64 Maximum number of validator keys approved for deposit by the DAO so far
  • totalExitedValidators: uint64 incremental counter of all exited validators for the NO so far
  • totalAddedValidators: uint64 incremental counter of all added to the NO validators so far
  • totalDepositedValidators: uint64 incremental counter of all deposited validators for the NO so far
  • targetValidatorsCount: uint256 target value for the number of validators for the NO. If the current active number of validators is above the value, the excess ones will be requested to exit. Allocation of deposits above the target value is prohibited. The exiting works only if targetLimitMode is non-zero. The 0 value will cause exit requests issued for all deposited validators of the NO. (see VEBO for details)
  • targetLimitMode: uint256 NO's target limitation mode value (0 = disabled, 1 = smooth exit mode, 2 = boosted exit mode), determines whether the number of NO validators is target-limited, and if so, which exit mode will be applied on the VEBO side (see also targetValidatorsCount)
  • stuckValidatorsCount: uint256 deprecated number of stuck validators. Always returns 0.
  • refundedValidatorsCount: uint256 deprecated number of refunded validators. Always returns 0.
  • depositableValidatorsCount: uint256 number of depositable validators

The values can be viewed by means of getNodeOperator() and getNodeOperatorSummary().

Except for the functions listed below, the contract has methods accessible only by StakingRouter (holder of STAKING_ROUTER_ROLE). These functions are called internally in the course of AccountingOracle report.

View Methods​

getRewardsDistribution()​

Returns the rewards distribution proportional to the effective stake for each node operator

function getRewardsDistribution(uint256 _totalRewardShares) returns (
address[] recipients,
uint256[] shares,
bool[] penalized
);
NameTypeDescription
_totalRewardSharesuint256Total amount of reward shares to distribute

getActiveNodeOperatorsCount()​

Returns the number of active node operators.

function getActiveNodeOperatorsCount() returns (uint256);

getNodeOperator()​

Returns the node operator by id.

function getNodeOperator(uint256 _nodeOperatorId, bool _fullInfo) returns (
bool active,
string name,
address rewardAddress,
uint64 totalVettedValidators,
uint64 totalExitedValidators,
uint64 totalAddedValidators,
uint64 totalDepositedValidators
);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_fullInfoboolIf true, name will be returned as well

getTotalSigningKeyCount()​

Returns the total number of signing keys of the node operator.

function getTotalSigningKeyCount(uint256 _nodeOperatorId) returns (uint256);
NameTypeDescription
_nodeOperatorIduint256Node operator id

getUnusedSigningKeyCount()​

Returns the number of usable signing keys of the node operator.

function getUnusedSigningKeyCount(uint256 _nodeOperatorId) returns (uint256);
NameTypeDescription
_nodeOperatorIduint256Node operator id

getSigningKey()​

Returns n-th signing key of the node operator.

function getSigningKey(uint256 _nodeOperatorId, uint256 _index) returns (
bytes key,
bytes depositSignature,
bool used
);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_indexuint256Index of the key, starting with 0

Returns:

NameTypeDescription
keybytesKey
depositSignaturebytesSignature needed for a depositContract.deposit call
usedboolFlag indicating whether the key was used for staking

getSigningKeys()​

Returns subset of the signing keys of the node operator corresponding to the specified range [_offset, _offset + _limit). If the requested range is out of bounds of the range [0, <total keys number>), the call reverts with an OUT_OF_RANGE error.

function getSigningKeys(uint256 _nodeOperatorId, uint256 _offset, uint256 _limit) returns (
bytes memory pubkeys,
bytes memory signatures,
bool[] memory used
);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_offsetuint256Offset of the key in the array of all NO keys (0 means the first key, 1 the second, etc
_limituint256Number of keys to return

Returns:

NameTypeDescription
pubkeysbytesKeys concatenated into the bytes batch: [ 48 bytes key | 48 bytes key | ... ]
signaturesbytesSignatures needed for a depositContract.deposit call, concatenated as [ 96 bytes | 96 bytes | ... ]
usedbool[]Array of flags indicating whether the key was used for staking

getNodeOperatorsCount()​

Returns the total number of node operators.

function getNodeOperatorsCount() returns (uint256);

getNonce()​

Returns a counter that increments whenever the deposit data set changes. Namely, it increments every time when for a node operator:

  • staking limit changed;
  • target validators limit changed;
  • stuck validators count changed;
  • exited validators count changed;
  • validator signing keys added/removed;
  • penalty is cleared;
  • ready to deposit keys invalidated (due to withdrawal credentials change or due to manual invalidation by call of invalidateReadyToDepositKeysRange);
  • ether deposited.
function getNonce() view returns (uint256);

getType()​

Returns the type of the staking module.

function getType() view returns (bytes32);

getStakingModuleSummary()​

Returns some statistics of the staking module.

function getStakingModuleSummary() view returns (
uint256 totalExitedValidators,
uint256 totalDepositedValidators,
uint256 depositableValidatorsCount
);
NameTypeDescription
totalExitedValidatorsuint256Total number of exited validators
totalDepositedValidatorsuint256Total number of deposited validators
depositableValidatorsCountuint256Number of validators which can be deposited

getNodeOperatorIsActive()​

Returns if the node operator with given id is active.

function getNodeOperatorIsActive(uint256 _nodeOperatorId) view returns (bool);
NameTypeDescription
_nodeOperatorIduint256Node operator id

getNodeOperatorIds()​

Returns up to _limit node operator ids starting from the _offset.

function getNodeOperatorIds(uint256 _offset, uint256 _limit) view
returns (uint256[] memory nodeOperatorIds);
NameTypeDescription
_offsetuint256Offset of the first element of the range
_limituint256Max number of NO ids to return

getNodeOperatorSummary()​

Returns some statistics of the node operator.

function getNodeOperatorSummary(uint256 _nodeOperatorId) view returns (
uint256 targetLimitMode,
uint256 targetValidatorsCount,
uint256 stuckValidatorsCount,
uint256 refundedValidatorsCount,
uint256 stuckPenaltyEndTimestamp,
uint256 totalExitedValidators,
uint256 totalDepositedValidators,
uint256 depositableValidatorsCount
);
NameTypeDescription
targetLimitModeuint256Current target limit mode applied to the NO (0 = disabled, 1 = soft, 2 = boosted)
targetValidatorsCountuint256Target validators count for full description see parameters section
stuckValidatorsCountuint256deprecated Number of stuck keys from oracle report
refundedValidatorsCountuint256deprecated Number of refunded keys
stuckPenaltyEndTimestampuint256deprecated Extra penalty time after stuck keys refunded
totalExitedValidatorsuint256Number of keys in the EXITED state of the NO for all time
totalDepositedValidatorsuint256Number of keys of the NO which were in DEPOSITED state for all time
depositableValidatorsCountuint256Number of validators which can be deposited

getStuckPenaltyDelay()​

Deprecated

Returns value of the stuck penalty delay (in seconds). This parameter defines how long a penalized NO stays in penalty state after the stuck keys were refunded.

function getStuckPenaltyDelay() view returns (uint256);

isOperatorPenalized()​

Deprecated

Returns flag whether the NO is penalized.

function isOperatorPenalized(uint256 _nodeOperatorId) view returns (bool)

isOperatorPenaltyCleared()​

Deprecated

Returns whether the NO penalty is cleared.

function isOperatorPenaltyCleared(uint256 _nodeOperatorId) view returns (bool)

getLocator()​

Returns the address of LidoLocator.

function getLocator() view returns (ILidoLocator)

getRewardDistributionState()​

Gets the current reward distribution state. Anyone can monitor this state and distribute rewards (by calling distributeReward) among operators when it is ReadyForDistribution.

enum RewardDistributionState {
TransferredToModule,
ReadyForDistribution,
Distributed
}
function getRewardDistributionState() public view returns (RewardDistributionState);

Returns:

NameTypeDescription
stateRewardDistributionStateCurrent reward distribution state

Methods​

addNodeOperator()​

Add node operator named _name with reward address _rewardAddress and staking limit = 0.

Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role.

function addNodeOperator(
string _name,
address _rewardAddress
) returns (uint256 id);
NameTypeDescription
_namestringHuman-readable name
_rewardAddressaddressAddress which receives stETH rewards for this operator

Returns:

NameTypeDescription
iduint256A unique key of the added operator

activateNodeOperator()​

Activates deactivated node operator with given id.

Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role.

note

Increases the validators keys nonce

function activateNodeOperator(uint256 _nodeOperatorId);
NameTypeDescription
_nodeOperatorIduint256Node operator id

deactivateNodeOperator()​

Deactivates active node operator with given id.

Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role

note

Increases the validators keys nonce

function deactivateNodeOperator(uint256 _nodeOperatorId);
NameTypeDescription
_nodeOperatorIduint256Node operator id

setNodeOperatorName()​

Change human-readable name of the node operator with given id.

Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role.

function setNodeOperatorName(uint256 _nodeOperatorId, string _name);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_namestringHuman-readable name

setNodeOperatorRewardAddress()​

Change reward address of the node operator with given id.

Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role.

function setNodeOperatorRewardAddress(uint256 _nodeOperatorId, address _rewardAddress);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_rewardAddressaddressNew reward address

setNodeOperatorStakingLimit()​

Set the maximum number of validators to stake for the node operator with given id.

Executed on behalf of holder of SET_NODE_OPERATOR_LIMIT_ROLE role.

note

Current implementation preserves invariant: depositedSigningKeysCount <= vettedSigningKeysCount <= totalSigningKeysCount. If _vettedSigningKeysCount out of range [depositedSigningKeysCount, totalSigningKeysCount], the new vettedSigningKeysCount value will be set to the nearest range border.

note

Increases the validators keys nonce

function setNodeOperatorStakingLimit(uint256 _nodeOperatorId, uint64 _vettedSigningKeysCount);
NameTypeDescription
_nodeOperatorIduint256Node operator id to set staking limit for
_vettedSigningKeysCountuint64New staking limit of the node operator

addSigningKeys()​

Add _keysCount validator signing keys to the keys of the node operator _nodeOperatorId.

Can be executed for the given NO if called from the NO's reward address or by the holder of MANAGE_SIGNING_KEYS role.

note

Along with each key pubkey, a signature must be provided for the (pubkey, withdrawal_credentials, 32000000000) message. For details, see the [keys section in the NO guide].

Given that information, the contract will be able to call depositContract.deposit on-chain.

note

Increases the validators keys nonce

function addSigningKeys(
uint256 _nodeOperatorId,
uint256 _keysCount,
bytes _publicKeys,
bytes _signatures
);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_keysCountuint256Number of signing keys provided
_publicKeysbytesSeveral concatenated validator signing public keys
_signaturesbytesSeveral concatenated signatures for the DepositContract messages see the keys section in NO guide

removeSigningKeys()​

Removes an _keysCount of validator signing keys starting from _fromIndex of operator _nodeOperatorId usable keys.

Can be executed for the given NO if called from the NO's reward address or by the holder of MANAGE_SIGNING_KEYS role.

Keys are removed starting from the last index toward the highest one, so we won't go outside the array.

note

Increases the validators keys nonce

function removeSigningKeys(uint256 _nodeOperatorId, uint256 _fromIndex, uint256 _keysCount);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_fromIndexuint256Index of the key, starting with 0
_keysCountuint256Number of keys to remove

invalidateReadyToDepositKeysRange()​

Invalidates all unused validator keys for node operators in the given range. Executed on behalf of holder of MANAGE_NODE_OPERATOR_ROLE role.

function invalidateReadyToDepositKeysRange(uint256 _indexFrom, uint256 _indexTo);
NameTypeDescription
_indexFromuint256The first index (inclusive) of the NO to invalidate keys for
_indexTouint256The last index (inclusive) of the NO to invalidate keys for

distributeReward()​

Permissionless method for distributing all accumulated module rewards among node operators based on the latest accounting report.

Rewards can be distributed after all necessary data required to distribute rewards among operators has been delivered, including exited and stuck keys.

The reward distribution lifecycle (see also getRewardDistributionState):

  1. TransferredToModule: Rewards are transferred to the module during an oracle main report.
  2. ReadyForDistribution: All necessary data required to distribute rewards among operators has been delivered.
  3. Distributed: Rewards have been successfully distributed.

The function can only be called when the state is ReadyForDistribution.

function distributeReward() external;

reportValidatorExitDelay()​

Handles the tracking and penalization logic for a node operator who fails to exit their validator within the defined exit window.

Marks a validator as late for the specified node operator using a proof timestamp and the elapsed eligibility-to-exit time. This information is then used by the module to apply exit-delay penalties to the node operator, if applicable.

Called by the StakingRouter.

function reportValidatorExitDelay(
uint256 _nodeOperatorId,
uint256 _proofSlotTimestamp,
bytes _publicKey,
uint256 _eligibleToExitInSec
) external;
NameTypeDescription
_nodeOperatorIduint256Node operator id
_proofSlotTimestampuint256Beacon slot timestamp used as a proof reference for the validator status
_publicKeybytesValidator BLS public key
_eligibleToExitInSecuint256How many seconds the validator has been eligible to exit up to the proof

onValidatorExitTriggered()​

Handles a triggerable exit event for a validator belonging to a specific node operator.

Called by the StakingRouter when a validator exit is initiated on the Execution Layer (via a withdrawal request). Records the trigger context and may affect the applicability of exit-delay penalties for the operator.

function onValidatorExitTriggered(
uint256 _nodeOperatorId,
bytes _publicKey,
uint256 _withdrawalRequestPaidFee,
uint256 _exitType
) external;
NameTypeDescription
_nodeOperatorIduint256Node operator id
_publicKeybytesValidator BLS public key
_withdrawalRequestPaidFeeuint256Fee paid to submit the withdrawal/exit request (units as defined by SR/WQ)
_exitTypeuint256Exit trigger type code as defined by the StakingRouter

isValidatorExitDelayPenaltyApplicable()​

Determines whether a validator's exit delay should be considered when penalizing the node operator.

Use this view to check if the module expects an update for the given validator and whether the elapsed eligibility-to-exit time indicates that an exit-delay penalty may apply.

function isValidatorExitDelayPenaltyApplicable(
uint256 _nodeOperatorId,
uint256 _proofSlotTimestamp,
bytes _publicKey,
uint256 _eligibleToExitInSec
) external view returns (bool);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_proofSlotTimestampuint256Beacon slot timestamp reference
_publicKeybytesValidator BLS public key
_eligibleToExitInSecuint256How many seconds the validator has been eligible to exit

Returns:

NameTypeDescription
isPenaltyApplicableboolTrue if the exit-delay update should be accepted and may affect penalties

exitDeadlineThreshold()​

Returns the number of seconds after which a validator is considered late for a specified node operator.

function exitDeadlineThreshold(uint256 _nodeOperatorId) external view returns (uint256);
NameTypeDescription
_nodeOperatorIduint256Node operator id

Returns:

NameTypeDescription
deadlineInSecondsuint256Exit deadline threshold in seconds for specified NO

isValidatorExitingKeyReported()​

Returns whether a validator's key has already been reported as late for a specified node operator.

function isValidatorExitingKeyReported(uint256 _nodeOperatorId, bytes _publicKey) external view returns (bool);
NameTypeDescription
_nodeOperatorIduint256Node operator id
_publicKeybytesValidator BLS public key

Returns:

NameTypeDescription
isKeyReportedboolTrue if the validator exit delay was reported