From 5e4ca6ad88a358ae36daee6de1e8bc9b7b58e5db Mon Sep 17 00:00:00 2001 From: Zehui Zheng Date: Wed, 30 Aug 2023 21:36:30 +0800 Subject: [PATCH] perf: optimize quorum using bitmaps --- .../contracts/consensus/quorum/Quorum.sol | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/onchain/rollups/contracts/consensus/quorum/Quorum.sol b/onchain/rollups/contracts/consensus/quorum/Quorum.sol index 7bed8357..526ad925 100644 --- a/onchain/rollups/contracts/consensus/quorum/Quorum.sol +++ b/onchain/rollups/contracts/consensus/quorum/Quorum.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.8; 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"; @@ -12,23 +13,27 @@ import {IHistory} from "../../history/IHistory.sol"; /// @title Quorum consensus /// @notice A consensus model controlled by a small set of addresses, the validators. /// In this version, the validator set is immutable. -/// @dev This contract uses OpenZeppelin `PaymentSplitter`. -/// For more information on `PaymentSplitter`, please consult OpenZeppelin's official documentation. +/// @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; // Quorum members - // Map an address to true if it's a validator - mapping(address => bool) public validators; + // 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 quorumSize; // Quorum votes struct Voted { + // how many has voted yea uint256 count; - // Map an address to true if it has voted yea - mapping(address => bool) voted; + // use BitMap to record who has voted + BitMaps.BitMap votedBitMap; } // Map a claim to struct Voted mapping(bytes => Voted) internal yeas; @@ -36,7 +41,7 @@ contract Quorum is AbstractConsensus, PaymentSplitter { /// @notice Raised if not a validator error OnlyValidator(); modifier onlyValidator() { - if (!validators[msg.sender]) { + if (validatorIndex[msg.sender] == 0) { revert OnlyValidator(); } _; @@ -46,6 +51,7 @@ contract Quorum is AbstractConsensus, PaymentSplitter { /// @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, @@ -53,7 +59,7 @@ contract Quorum is AbstractConsensus, PaymentSplitter { ) PaymentSplitter(_validators, _shares) { // Add the array of validators into the quorum for (uint256 i; i < _validators.length; ++i) { - validators[_validators[i]] = true; + validatorIndex[_validators[i]] = i + 1; // index starts from 1 } quorumSize = _validators.length; history = _history; @@ -70,10 +76,11 @@ contract Quorum is AbstractConsensus, PaymentSplitter { /// its current history contract. function submitClaim(bytes calldata _claimData) external onlyValidator { Voted storage claimYeas = yeas[_claimData]; + uint256 index = validatorIndex[msg.sender]; // If the msg.sender hasn't submitted the same claim before - if (!claimYeas.voted[msg.sender]) { - claimYeas.voted[msg.sender] = true; + if (!claimYeas.votedBitMap.get(index)) { + claimYeas.votedBitMap.set(index); // If this claim has now just over half of the quorum's votes, // then we can submit it to the history contract.