Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: optimize quorum #56

Merged
merged 2 commits into from
Oct 25, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 46 additions & 37 deletions onchain/rollups/contracts/consensus/quorum/Quorum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,63 @@

pragma solidity ^0.8.8;

import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {PaymentSplitter} from "@openzeppelin/contracts/finance/PaymentSplitter.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";

import {AbstractConsensus} from "../AbstractConsensus.sol";
import {IConsensus} from "../IConsensus.sol";
import {IHistory} from "../../history/IHistory.sol";

/// @title Quorum consensus
/// @notice A consensus model controlled by a small set of addresses, the validators.
/// @dev This contract uses several OpenZeppelin contracts:
/// `AccessControlEnumerable`, `EnumerableSet`, and `PaymentSplitter`.
/// For more information on those, please consult OpenZeppelin's official documentation.
contract Quorum is AbstractConsensus, AccessControlEnumerable, PaymentSplitter {
using EnumerableSet for EnumerableSet.AddressSet;

/// @notice The validator role.
/// @dev Only validators can submit claims.
bytes32 public constant VALIDATOR_ROLE = keccak256("VALIDATOR_ROLE");
/// In this version, the validator set is immutable.
/// @dev This contract uses OpenZeppelin `PaymentSplitter` and `BitMaps`.
/// For more information on those, please consult OpenZeppelin's official documentation.
contract Quorum is AbstractConsensus, PaymentSplitter {
using BitMaps for BitMaps.BitMap;

/// @notice The history contract.
/// @dev See the `getHistory` function.
IHistory internal immutable history;

/// @notice For each claim, the set of validators that agree
/// that it should be submitted to the history contract.
mapping(bytes => EnumerableSet.AddressSet) internal yeas;
// Quorum members
// Map an address to its index in the validator set.
// The first validator has index 1. Thus, index 0 means the address is not in the validator set.
mapping(address => uint256) public validatorIndex;
uint256 public immutable numOfValidators;

// Quorum votes
guidanoli marked this conversation as resolved.
Show resolved Hide resolved
struct Votes {
// how many has voted
uint256 count;
// use BitMap to record who has voted
BitMaps.BitMap votedBitMap;
}
// Map a claim to struct Votes
mapping(bytes => Votes) internal votes;

/// @notice Raised if not a validator
error OnlyValidator();

/// @notice Construct a Quorum consensus
/// @param _validators the list of validators
/// @param _shares the list of shares
/// @param _history the history contract
/// @dev PaymentSplitter checks for duplicates in _validators
constructor(
address[] memory _validators,
uint256[] memory _shares,
IHistory _history
) PaymentSplitter(_validators, _shares) {
// Iterate through the array of validators,
// and grant to each the validator role.
// Add the array of validators into the quorum
for (uint256 i; i < _validators.length; ++i) {
_grantRole(VALIDATOR_ROLE, _validators[i]);
validatorIndex[_validators[i]] = i + 1; // index starts from 1
}

// Set history.
numOfValidators = _validators.length;
history = _history;
}

/// @notice Submits a claim for voting.
/// @notice Vote for a claim to be submitted.
/// If this is the claim that reaches the majority, then
/// the claim is submitted to the history contract.
/// The encoding of `_claimData` might vary depending on the
Expand All @@ -58,26 +68,21 @@ contract Quorum is AbstractConsensus, AccessControlEnumerable, PaymentSplitter {
/// @dev Can only be called by a validator,
/// and the `Quorum` contract must have ownership over
/// its current history contract.
function submitClaim(
bytes calldata _claimData
) external onlyRole(VALIDATOR_ROLE) {
// Get the set of validators in favour of the claim
EnumerableSet.AddressSet storage claimYeas = yeas[_claimData];

// Add the message sender to such set.
// If the `add` function returns `true`,
// then the message sender was not in the set.
if (claimYeas.add(msg.sender)) {
// Get the number of validators in favour of the claim,
// taking into account the message sender as well.
uint256 numOfVotesInFavour = claimYeas.length();
function submitClaim(bytes calldata _claimData) external {
// only validators can submit claims
uint256 index = validatorIndex[msg.sender];
if (index == 0) {
revert OnlyValidator();
}

// Get the number of validators in the quorum.
uint256 quorumSize = getRoleMemberCount(VALIDATOR_ROLE);
// If the msg.sender hasn't submitted the same claim before
Votes storage claimVotes = votes[_claimData];
if (!claimVotes.votedBitMap.get(index)) {
claimVotes.votedBitMap.set(index);

// If this claim has now just over half of the quorum's approval,
// If this claim has now just over half of the quorum's votes,
// then we can submit it to the history contract.
if (numOfVotesInFavour == 1 + quorumSize / 2) {
if (++claimVotes.count == 1 + numOfValidators / 2) {
history.submitClaim(_claimData);
}
}
Expand All @@ -89,6 +94,10 @@ contract Quorum is AbstractConsensus, AccessControlEnumerable, PaymentSplitter {
return history;
}

/// @notice Get a claim from the current history.
/// The encoding of `_proofContext` might vary depending on the
/// implementation of the current history contract.
/// @inheritdoc IConsensus
function getClaim(
address _dapp,
bytes calldata _proofContext
Expand Down
Loading