Skip to content

Commit

Permalink
wip(registry): new storage layout with indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Oct 28, 2024
1 parent d5450f0 commit e2a00d9
Show file tree
Hide file tree
Showing 6 changed files with 5,675 additions and 1 deletion.
2 changes: 1 addition & 1 deletion bolt-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Bolt CLI is a collection of command-line tools for interacting with Bolt pro
Prerequisites:

- [Rust toolchain][rust]
- [Protoc][protoc]
- [Protoc][protoc] (as well as `libprotobuf-dev` for some Linux distributions)

Once you have the necessary prerequisites, you can build the binary in the following way:

Expand Down
383 changes: 383 additions & 0 deletions bolt-contracts/src/contracts/BoltValidatorsV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,383 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

import {BLS12381} from "../lib/bls/BLS12381.sol";
import {BLSSignatureVerifier} from "../lib/bls/BLSSignatureVerifier.sol";
import {IBoltValidatorsV2} from "../interfaces/IBoltValidatorsV2.sol";
import {IBoltParametersV1} from "../interfaces/IBoltParametersV1.sol";

/// @title Bolt Validators
/// @notice This contract is responsible for registering validators and managing their configuration
/// @dev This contract is upgradeable using the UUPSProxy pattern. Storage layout remains fixed across upgrades
/// with the use of storage gaps.
/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
/// To validate the storage layout, use the Openzeppelin Foundry Upgrades toolkit.
/// You can also validate manually with forge: forge inspect <contract> storage-layout --pretty
contract BoltValidatorsV2 is IBoltValidatorsV2, BLSSignatureVerifier, OwnableUpgradeable, UUPSUpgradeable {
using BLS12381 for BLS12381.G1Point;

// ========= STORAGE =========

/// @notice Bolt Parameters contract.
IBoltParametersV1 public parameters;

/// @notice Validators (aka Blockspace providers)
/// @dev Validators are blockspace providers for commitments.
///
/// Validators in this mapping are identified by their sequence number.
/// The sequence number is an incremental index assigned to each validator
/// in the registry and is guaranteed to be unique.
mapping(uint32 => _Validator) internal VALIDATORS;

/// @notice Mapping of BLS public key hash to the sequence number of the validator
/// in the VALIDATORS mapping. This mapping is used to quickly lookup a validator
/// by its BLS public key hash without iterating over the VALIDATORS mapping.
mapping(bytes20 => uint32) internal validatorPubkeyHashToSequenceNumber;

/// @notice Sequence number of the next validator to be registered
uint32 internal nextValidatorSequenceNumber;

/// @notice Mapping of controller index to its address. A controller map is
/// used to identify entities with a 32-bit index instead of their full address.
mapping(uint32 => address) internal controllerIndexToAddress;
mapping(address => uint32) internal controllerAddressToIndex;
uint32 internal nextControllerIndex;

/// @notice Mapping of authorized operator index to its address. An authorized operator map is
/// used to identify entities with a 32-bit index instead of their full address.
mapping(uint32 => address) internal authorizedOperatorIndexToAddress;
mapping(address => uint32) internal authorizedOperatorToIndex;
uint32 internal nextAuthorizedOperatorIndex;

// TODO: adjust
// --> Storage layout marker: 4 slots

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
* This can be validated with the Openzeppelin Foundry Upgrades toolkit.
*
* Total storage slots: 50
*/
uint256[46] private __gap;

// ========= EVENTS =========

/// @notice Emitted when a validator is registered
/// @param pubkeyHash BLS public key hash of the validator
event ValidatorRegistered(bytes32 indexed pubkeyHash, uint32 indexed sequenceNumber);

// ========= INITIALIZER =========

/// @notice Initializer
/// @param _owner Address of the owner of the contract
/// @param _parameters Address of the Bolt Parameters contract
function initialize(address _owner, address _parameters) public initializer {
__Ownable_init(_owner);

parameters = IBoltParametersV1(_parameters);
}

function initializeV2(address _owner, address _parameters) public reinitializer(2) {
__Ownable_init(_owner);

parameters = IBoltParametersV1(_parameters);
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}

// ========= VIEW FUNCTIONS =========

/// @notice Get all validators
/// @dev This function should be used with caution as it can return a large amount of data.
/// @return Validator[] memory Array of validator structs
function getAllValidators() public view returns (ValidatorInfo[] memory) {
uint32 validatorCount = nextValidatorSequenceNumber;
ValidatorInfo[] memory validators = new ValidatorInfo[](validatorCount);
for (uint32 i = 0; i < validatorCount; i++) {
validators[i] = _getValidatorInfo(VALIDATORS[i]);
}
return validators;
}

/// @notice Get a validator by its BLS public key
/// @param pubkey BLS public key of the validator
/// @return Validator memory Validator struct
function getValidatorByPubkey(
BLS12381.G1Point calldata pubkey
) public view returns (ValidatorInfo memory) {
return getValidatorByPubkeyHash(hashPubkey(pubkey));
}

/// @notice Get a validator by its BLS public key hash
/// @param pubkeyHash BLS public key hash of the validator
/// @return Validator memory Validator struct
function getValidatorByPubkeyHash(
bytes20 pubkeyHash
) public view returns (ValidatorInfo memory) {
uint32 sequenceNumber = validatorPubkeyHashToSequenceNumber[pubkeyHash];
_Validator memory _val = VALIDATORS[sequenceNumber];
if (_val.pubkeyHash == bytes20(0)) {
revert ValidatorDoesNotExist();
}
return _getValidatorInfo(_val);
}

/// @notice Get a validator by its sequence number
/// @param sequenceNumber Sequence number of the validator
/// @return Validator memory Validator struct
function getValidatorBySequenceNumber(
uint32 sequenceNumber
) public view returns (ValidatorInfo memory) {
_Validator memory _val = VALIDATORS[sequenceNumber];
if (_val.pubkeyHash == bytes20(0)) {
revert ValidatorDoesNotExist();
}
return _getValidatorInfo(_val);
}

// ========= REGISTRATION LOGIC =========

/// @notice Register a single Validator and authorize a Collateral Provider and Operator for it
/// @dev This function allows anyone to register a single Validator. We do not perform any checks.
/// @param pubkeyHash BLS public key hash for the Validator to be registered
/// @param maxCommittedGasLimit The maximum gas that the Validator can commit for preconfirmations
/// @param authorizedOperator The address of the authorized operator
function registerValidatorUnsafe(
bytes20 pubkeyHash,
uint32 maxCommittedGasLimit,
address authorizedOperator
) public {
if (!parameters.ALLOW_UNSAFE_REGISTRATION()) {
revert UnsafeRegistrationNotAllowed();
}

_registerValidator(pubkeyHash, authorizedOperator, maxCommittedGasLimit);
}

/// @notice Register a single Validator and authorize an Operator for it.
/// @dev This function allows anyone to register a single Validator. We perform an important check:
/// The owner of the Validator (controller) must have signed the message with its BLS private key.
///
/// Message format: `chainId || controller || sequenceNumber`
/// @param pubkey BLS public key for the Validator to be registered
/// @param signature BLS signature of the registration message for the Validator
/// @param maxCommittedGasLimit The maximum gas that the Validator can commit for preconfirmations
/// @param authorizedOperator The address of the authorized operator
function registerValidator(
BLS12381.G1Point calldata pubkey,
BLS12381.G2Point calldata signature,
uint32 maxCommittedGasLimit,
address authorizedOperator
) public {
bytes memory message = abi.encodePacked(block.chainid, msg.sender, nextValidatorSequenceNumber);
if (!_verifySignature(message, signature, pubkey)) {
revert InvalidBLSSignature();
}

_registerValidator(hashPubkey(pubkey), authorizedOperator, maxCommittedGasLimit);
}

/// @notice Register a batch of Validators and authorize a Collateral Provider and Operator for them
/// @dev This function allows anyone to register a list of Validators.
/// @param pubkeys List of BLS public keys for the Validators to be registered
/// @param signature BLS aggregated signature of the registration message for this batch of Validators
/// @param maxCommittedGasLimit The maximum gas that the Validator can commit for preconfirmations
/// @param authorizedOperator The address of the authorized operator
function batchRegisterValidators(
BLS12381.G1Point[] calldata pubkeys,
BLS12381.G2Point calldata signature,
uint32 maxCommittedGasLimit,
address authorizedOperator
) public {
uint32[] memory expectedValidatorSequenceNumbers = new uint32[](pubkeys.length);
for (uint32 i = 0; i < pubkeys.length; i++) {
expectedValidatorSequenceNumbers[i] = nextValidatorSequenceNumber + i;
}

// Reconstruct the unique message for which we expect an aggregated signature.
// We need the msg.sender to prevent a front-running attack by an EOA that may
// try to register the same validators
bytes memory message = abi.encodePacked(block.chainid, msg.sender, expectedValidatorSequenceNumbers);

// Aggregate the pubkeys into a single pubkey to verify the aggregated signature once
BLS12381.G1Point memory aggPubkey = _aggregatePubkeys(pubkeys);

if (!_verifySignature(message, signature, aggPubkey)) {
revert InvalidBLSSignature();
}

bytes20[] memory pubkeyHashes = new bytes20[](pubkeys.length);
for (uint256 i = 0; i < pubkeys.length; i++) {
pubkeyHashes[i] = hashPubkey(pubkeys[i]);
}

_batchRegisterValidators(pubkeyHashes, authorizedOperator, maxCommittedGasLimit);
}

/// @notice Register a batch of Validators and authorize a Collateral Provider and Operator for them
/// @dev This function allows anyone to register a list of Validators.
/// @param pubkeyHashes List of BLS public key hashes for the Validators to be registered
/// @param maxCommittedGasLimit The maximum gas that the Validator can commit for preconfirmations
/// @param authorizedOperator The address of the authorized operator
function batchRegisterValidatorsUnsafe(
bytes20[] calldata pubkeyHashes,
uint32 maxCommittedGasLimit,
address authorizedOperator
) public {
if (!parameters.ALLOW_UNSAFE_REGISTRATION()) {
revert UnsafeRegistrationNotAllowed();
}

_batchRegisterValidators(pubkeyHashes, authorizedOperator, maxCommittedGasLimit);
}

// ========= UPDATE FUNCTIONS =========

/// @notice Update the maximum gas limit that a validator can commit for preconfirmations
/// @dev Only the `controller` of the validator can update this value.
/// @param pubkeyHash The hash of the BLS public key of the validator
/// @param maxCommittedGasLimit The new maximum gas limit
function updateMaxCommittedGasLimit(bytes20 pubkeyHash, uint32 maxCommittedGasLimit) public {
uint32 sequenceNumber = validatorPubkeyHashToSequenceNumber[pubkeyHash];
_Validator storage _val = VALIDATORS[sequenceNumber];

if (_val.pubkeyHash == bytes20(0)) {
revert ValidatorDoesNotExist();
}

address controller = controllerIndexToAddress[_val.controllerIndex];
if (msg.sender != controller) {
revert UnauthorizedCaller();
}

_val.maxCommittedGasLimit = maxCommittedGasLimit;
}

// ========= HELPERS =========

function _registerValidator(bytes20 pubkeyHash, address authorizedOperator, uint32 maxCommittedGasLimit) internal {
if (authorizedOperator == address(0)) {
revert InvalidAuthorizedOperator();
}
if (pubkeyHash == bytes20(0)) {
revert InvalidPubkey();
}
if (validatorPubkeyHashToSequenceNumber[pubkeyHash] != 0) {
revert ValidatorAlreadyExists();
}

VALIDATORS[nextValidatorSequenceNumber] = _Validator({
pubkeyHash: pubkeyHash,
maxCommittedGasLimit: maxCommittedGasLimit,
authorizedOperatorIndex: _getOrCreateAuthorizedOperatorIndex(authorizedOperator),
controllerIndex: _getOrCreateControllerIndex(msg.sender)
});
emit ValidatorRegistered(pubkeyHash, nextValidatorSequenceNumber);

validatorPubkeyHashToSequenceNumber[pubkeyHash] = nextValidatorSequenceNumber;
nextValidatorSequenceNumber += 1;
}

function _batchRegisterValidators(
bytes20[] memory pubkeyHashes,
address authorizedOperator,
uint32 maxCommittedGasLimit
) internal {
if (authorizedOperator == address(0)) {
revert InvalidAuthorizedOperator();
}

uint32 authorizedOperatorIndex = _getOrCreateAuthorizedOperatorIndex(authorizedOperator);
uint32 controllerIndex = _getOrCreateControllerIndex(msg.sender);
uint256 pubkeysLength = pubkeyHashes.length;

for (uint32 i; i < pubkeysLength; i++) {
bytes20 pubkeyHash = pubkeyHashes[i];
uint32 sequenceNumber = nextValidatorSequenceNumber + i;

if (pubkeyHash == bytes20(0)) {
revert InvalidPubkey();
}
if (validatorPubkeyHashToSequenceNumber[pubkeyHash] != 0) {
revert ValidatorAlreadyExists();
}

VALIDATORS[sequenceNumber] = _Validator({
pubkeyHash: pubkeyHash,
maxCommittedGasLimit: maxCommittedGasLimit,
authorizedOperatorIndex: authorizedOperatorIndex,
controllerIndex: controllerIndex
});
emit ValidatorRegistered(pubkeyHash, sequenceNumber);

validatorPubkeyHashToSequenceNumber[pubkeyHash] = sequenceNumber;
}

nextValidatorSequenceNumber += uint32(pubkeysLength);
}

/// @notice Internal helper to get the index of a new or existing operator by its address.
/// @param authorizedOperator Address of the operator
/// @return Index of the operator
function _getOrCreateAuthorizedOperatorIndex(
address authorizedOperator
) internal returns (uint32) {
uint32 authorizedOperatorIndex = authorizedOperatorToIndex[authorizedOperator];
if (authorizedOperatorIndex == 0) {
authorizedOperatorIndex = nextAuthorizedOperatorIndex;
authorizedOperatorToIndex[authorizedOperator] = authorizedOperatorIndex;
authorizedOperatorIndexToAddress[authorizedOperatorIndex] = authorizedOperator;
nextAuthorizedOperatorIndex += 1;
}

return authorizedOperatorIndex;
}

/// @notice Internal helper to get the index of a new or existing controller by its address.
/// @param controller Address of the controller
/// @return Index of the controller
function _getOrCreateControllerIndex(
address controller
) internal returns (uint32) {
uint32 controllerIndex = controllerAddressToIndex[controller];
if (controllerIndex == 0) {
controllerIndex = nextControllerIndex;
controllerAddressToIndex[controller] = controllerIndex;
controllerIndexToAddress[controllerIndex] = controller;
nextControllerIndex += 1;
}

return controllerIndex;
}

/// @notice Internal helper to get the ValidatorInfo struct from a _Validator struct
/// @param _val Validator struct
/// @return ValidatorInfo struct
function _getValidatorInfo(_Validator memory _val) internal view returns (ValidatorInfo memory) {
return ValidatorInfo({
pubkeyHash: _val.pubkeyHash,
maxCommittedGasLimit: _val.maxCommittedGasLimit,
authorizedOperator: authorizedOperatorIndexToAddress[_val.authorizedOperatorIndex],
controller: controllerIndexToAddress[_val.controllerIndex]
});
}

/// @notice Compute the hash of a BLS public key
/// @param pubkey Decompressed BLS public key
/// @return Hash of the public key in compressed form
function hashPubkey(
BLS12381.G1Point memory pubkey
) public pure returns (bytes20) {
uint256[2] memory compressedPubKey = pubkey.compress();
bytes32 fullHash = keccak256(abi.encodePacked(compressedPubKey));
// take the leftmost 20 bytes of the keccak256 hash
return bytes20(uint160(uint256(fullHash)));
}
}
Loading

0 comments on commit e2a00d9

Please sign in to comment.