File | Type | Proxy? |
---|---|---|
IndexRegistry.sol |
Singleton | Transparent proxy |
The IndexRegistry
provides an operatorIndex
for every registered Operator in every quorum. For example, if a quorum has n
Operators, every Operator registered for that quorum will have an operatorIndex
in the range [0:n-1]
. The role of this contract is to provide an AVS with a common, on-chain ordering of Operators within a quorum.
In EigenDA, the Operator ordering properties of the IndexRegistry
will eventually be used in proofs of custody, though this feature is not implemented yet.
/// @notice maps quorumNumber => operator id => current operatorIndex
/// NOTE: This mapping is NOT updated when an operator is deregistered,
/// so it's possible that an index retrieved from this mapping is inaccurate.
/// If you're querying for an operator that might be deregistered, ALWAYS
/// check this index against the latest `_operatorIndexHistory` entry
mapping(uint8 => mapping(bytes32 => uint32)) public currentOperatorIndex;
/// @notice maps quorumNumber => operatorIndex => historical operator ids at that index
mapping(uint8 => mapping(uint32 => OperatorUpdate[])) internal _operatorIndexHistory;
/// @notice maps quorumNumber => historical number of unique registered operators
mapping(uint8 => QuorumUpdate[]) internal _operatorCountHistory;
struct OperatorUpdate {
uint32 fromBlockNumber;
bytes32 operatorId;
}
struct QuorumUpdate {
uint32 fromBlockNumber;
uint32 numOperators;
}
Operators are assigned a unique operatorIndex
in each quorum they're registered for. If a quorum has n
registered Operators, every Operator in that quorum will have an operatorIndex
in the range [0:n-1]
. To accomplish this, the IndexRegistry
uses the three mappings listed above:
currentOperatorIndex
is a straightforward mapping of an Operator's currentoperatorIndex
in a specific quorum. It is updated when an Operator registers for a quorum._operatorIndexHistory
keeps track of theoperatorIds
assigned to anoperatorIndex
at various points in time. This is used by offchain code to determine whatoperatorId
belonged to anoperatorIndex
at a specific block._operatorCountHistory
keeps track of the number of Operators registered to each quorum over time. Note that a quorum's Operator count is also its "maxoperatorIndex
+ 1". Paired with_operatorIndexHistory
, this allows offchain code to query the entire Operator set registered for a quorum at a given block number. For an example of this in the code, seeIndexRegistry.getOperatorListAtBlockNumber
.
Note: currentOperatorIndex
is ONLY updated when an Operator is assigned to an operatorIndex
. When an Operator deregisters and is removed, we don't update currentOperatorIndex
because their operatorIndex
is not "0" - that's held by another Operator. Their operatorIndex
is also not the operatorIndex
they currently have. There's not really a "right answer" for this - see Layr-Labs#126 for more details.
This document organizes methods according to the following themes (click each to be taken to the relevant section):
These methods are ONLY called through the RegistryCoordinator
- when an Operator registers for or deregisters from one or more quorums:
function registerOperator(
bytes32 operatorId,
bytes calldata quorumNumbers
)
public
virtual
onlyRegistryCoordinator
returns(uint32[] memory)
When an Operator registers for a quorum, the following things happen:
- The current Operator count for the quorum is increased.
- This updates
_operatorCountHistory[quorum]
. The quorum's new "maxoperatorIndex
" is equal to the quorum Operator count - 1. - Additionally, if the
_operatorIndexHistory
for the quorum indicates that this is the first time the quorum has reached a given Operator count, an initialOperatorUpdate
is pushed to_operatorIndexHistory
for the new operator count. This is to maintain an invariant: that existing quorum indices have nonzero history.
- This updates
- The quorum's max index (Operator count - 1) is assigned to the registering Operator as their current
operatorIndex
.- This updates
currentOperatorIndex[quorum][operatorId]
- This also updates
_operatorIndexHistory[quorum][prevOperatorCount]
, recording theoperatorId
as the latest holder of theoperatorIndex
in question.
- This updates
This method is ONLY callable by the RegistryCoordinator
, and is called when an Operator registers for one or more quorums. This method assumes that the operatorId
is not already registered for any of quorumNumbers
, and that there are no duplicates in quorumNumbers
. These properties are enforced by the RegistryCoordinator
.
Entry Points:
RegistryCoordinator.registerOperator
RegistryCoordinator.registerOperatorWithChurn
Effects:
- For each
quorum
inquorumNumbers
:- Updates
_operatorCountHistory[quorum]
, increasing the quorum'snumOperators
by 1.- Note that if the most recent update for the quorum is from the current block number, the entry is updated. Otherwise, a new entry is pushed.
- Updates
_operatorIndexHistory[quorum][newOperatorCount - 1]
, recording theoperatorId
as the latest holder of the new maxoperatorIndex
.- Note that if the most recent update for the quorum's index is from the current block number, the entry is updated. Otherwise, a new entry is pushed.
- Updates
currentOperatorIndex[quorum][operatorId]
, assigning theoperatorId
to the new maxoperatorIndex
.
- Updates
Requirements:
- Caller MUST be the
RegistryCoordinator
- Each quorum in
quorumNumbers
MUST be initialized (seeinitializeQuorum
below)
function deregisterOperator(
bytes32 operatorId,
bytes calldata quorumNumbers
)
public
virtual
onlyRegistryCoordinator
When an Operator deregisters from a quorum, the following things happen:
- The current Operator count for the quorum is decreased, updating
_operatorCountHistory[quorum]
. The new "maxoperatorIndex
" is equal to the new Operator count (minus 1). - The Operator currently assigned to the now-invalid
operatorIndex
is "popped".- This updates
_operatorIndexHistory[quorum][newOperatorCount]
, recording that the Operator assigned to thisoperatorIndex
isOPERATOR_DOES_NOT_EXIST_ID
- This updates
- If the deregistering Operator and the popped Operator are not the same, the popped Operator is assigned a new
operatorIndex
: the deregistering Operator's previousoperatorIndex
.- This updates
_operatorIndexHistory[quorum][removedOperatorIndex]
, recording that the popped Operator is assigned to thisoperatorIndex
. - This also updates
currentOperatorIndex[quorum][removedOperator]
, assigning the popped Operator to the old Operator'soperatorIndex
.
- This updates
This method is ONLY callable by the RegistryCoordinator
, and is called when an Operator deregisters from one or more quorums. This method assumes that the operatorId
is currently registered for each quorum in quorumNumbers
, and that there are no duplicates in quorumNumbers
. These properties are enforced by the RegistryCoordinator
.
Entry Points:
RegistryCoordinator.registerOperatorWithChurn
RegistryCoordinator.deregisterOperator
RegistryCoordinator.ejectOperator
RegistryCoordinator.updateOperators
RegistryCoordinator.updateOperatorsForQuorum
Effects:
- For each
quorum
inquorumNumbers
:- Updates
_operatorCountHistory[quorum]
, decreasing the quorum'snumOperators
by 1.- Note that if the most recent update for the quorum is from the current block number, the entry is updated. Otherwise, a new entry is pushed.
- Updates
_operatorIndexHistory[quorum][newOperatorCount]
, "popping" the Operator that currently holds thisoperatorIndex
, and marking it as assigned toOPERATOR_DOES_NOT_EXIST_ID
.- Note that if the most recent update for the quorum's
operatorIndex
is from the current block number, the entry is updated. Otherwise, a new entry is pushed.
- Note that if the most recent update for the quorum's
- If
operatorId
is NOT the popped Operator, the popped Operator is assigned tooperatorId's
currentoperatorIndex
. (Updates_operatorIndexHistory
andcurrentOperatorIndex
)
- Updates
Requirements:
- Caller MUST be the
RegistryCoordinator
- Each quorum in
quorumNumbers
MUST be initialized (seeinitializeQuorum
below)
function initializeQuorum(
uint8 quorumNumber
)
public
virtual
onlyRegistryCoordinator
This method is ONLY callable by the RegistryCoordinator
. It is called when the RegistryCoordinator
Owner creates a new quorum.
initializeQuorum
initializes a new quorum by pushing an initial QuorumUpdate
to _operatorCountHistory[quorumNumber]
, setting the initial numOperators
for the quorum to 0.
Other methods can validate that a quorum exists by checking whether _operatorCountHistory[quorumNumber]
has a nonzero length.
Entry Points:
RegistryCoordinator.createQuorum
Effects:
- Pushes a
QuorumUpdate
to_operatorCountHistory[quorumNumber]
. The update'supdateBlockNumber
is set to the current block, andnumOperators
is set to 0.
Requirements:
- Caller MUST be the
RegistryCoordinator
_operatorCountHistory[quorumNumber].length
MUST be zero