Git Source (opens in a new tab)

Manages node lifecycle (registration, activation, deactivation)

Allows anyone to register to become an active node

Allows registered nodes to become active after a cooldown seconds waiting period

Allows any node to deactivate itself and return to an inactive state

Exposes an onlyActiveNode() modifier used to restrict functions to being called by only active nodes

Restricts addresses to 1 of 3 states: Inactive, Registered, Active

State Variables


Cooldown period, in seconds, before a node with NodeStatus.Registered can call activateNode()

type(uint32) is sufficient but we are not packing variables so control plane costs are higher because we need to cast the 32-bit type into the 256-bit type anyways. Thus, we use type(uint256).

uint256 public constant cooldown = 1 hours;


Node address => node information

mapping(address => NodeInfo) public nodeInfo;



Allow only callers that are active nodes

modifier onlyActiveNode();


Allows registering a node for activation

First-step of two-step process (followed by activateNode())

Can call on behalf of other nodes as a proxy registerer

Node must have NodeStatus.Inactive to begin registration

function registerNode(address node) external;


nodeaddressnode address to register


Allows activating a registered node after cooldown has elapsed

Second-step of two-step process (preceeded by registerNode())

Must be called by node accepting a pending registration (msg.sender == node)

Must be called at least cooldown seconds after registerNode()

function activateNode() external;


Allows deactivating a node

Can be called to set the status of any node back to NodeStatus.Inactive with no cooldown

Must be called by the node deactivating itself (msg.sender == node)

function deactivateNode() external;



Emitted when a node moves from NodeStatus.Inactive to NodeStatus.Registered

It's actually slightly more expensive (~6 gas) to emit the uint32 given the explicit conversion needed but this is necessary to have better readability and uniformity across the type (not casting in event)

event NodeRegistered(address indexed node, address indexed registerer, uint32 cooldownStart);


Emitted when a node moves from NodeStatus.Registered to NodeStatus.Active

event NodeActivated(address indexed node);


Emitted when a node moves from any status to NodeStatus.Inactive

event NodeDeactivated(address indexed node);



Thrown if attempting to call function that requires a node to have status NodeStatus.Active

Only used by modifier(onlyActiveNode)

4-byte signature: 0x8741cbb8

error NodeNotActive();


Thrown by registerNode() if attempting to register node with status that is not NodeStatus.Inactive

4-byte signature: 0x5acfd518

error NodeNotRegisterable(address node, NodeStatus status);


Thrown by activateNode() if cooldown has not elapsed since node was registered

Like NodeRegistered, slightly more expensive to use uint32 over uint256 (~6 gas) but better readability

4-byte signature: 0xc84b5bdd

error CooldownActive(uint32 cooldownStart);


Thrown by activateNode() if attempting to active node with status that is not NodeStatus.Registered

4-byte signature: 0x33daa7f9

error NodeNotActivateable(NodeStatus status);



Packed information about a node (status, cooldown start)

Cheaper to use a struct to store status + cooldownStart rather than SSTORE 2 independent mappings

Technically, could bitshift pack uint40 of data into single uint256 but readability penalty not worth it

Tightly-packed (well under 32-byte slot): [uint8, uint32] = 40 bits = 5 bytes

struct NodeInfo {
    /// @notice Node status
    NodeStatus status;
    /// @notice Cooldown start timestamp in seconds
    /// @dev Default initializes to `0`; no cooldown active to start
    /// @dev Equal to `0` if `status != NodeStatus.Registered`, else equal to cooldown start time
    /// @dev Is modified by `registerNode()` to initiate `cooldown` holding period
    /// @dev uint32 allows for a timestamp up to year ~2106, likely far beyond lifecycle of this contract
    uint32 cooldownStart;



Possible node statuses

Enums in Solidity are unsigned integers capped at 256 members, so Inactive is the 0-initialized default

Inactive (0): Default status is inactive; no status

Registered (1): Node has registered to become active, initiating a period of cooldown

Active (2): Node is active, able to fulfill subscriptions, and is part of modifier(onlyActiveNode)

enum NodeStatus {