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

feat: add quorum #49

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ Notices are informational statements that can be proved by contracts in the base

This module is responsible for providing valid claims to DApps after reaching some form of consensus. Each DApp has its own mapping of claims, each of which is mapped by the range of input indices of an epoch.

The module's interface aims to be as generic as possible to accommodate any consensus model, since there are plenty to choose from. One type of consensus implemented by Cartesi is called Authority. It is owned by a single address, who has complete power over the consensus. It is arguably the simplest consensus to implement, although quite vulnerable.
The module's interface aims to be as generic as possible to accommodate any consensus model, since there are plenty to choose from. The types of consensus currently implemented include:
- Authority: managed by a single address, who has complete power over the consensus. It is trivial to implement, yet quite vulnerable.
- Quorum: managed by a generally small, finite set of validators. Consensus is reached when the majority of the quorum agrees on any given claim.

### Dispute Resolution

Expand Down
5 changes: 5 additions & 0 deletions onchain/rollups/.changeset/lovely-carpets-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/rollups": minor
---

Added `Quorum` consensus contract.
153 changes: 153 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/Quorum.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";

import {AbstractConsensus} from "../AbstractConsensus.sol";
import {InputRange} from "../../common/InputRange.sol";

/// @notice A consensus model controlled by a small, immutable set of `n` validators.
/// @notice You can know the value of `n` by calling the `numOfValidators` function.
/// @notice Upon construction, each validator is assigned a unique number between 1 and `n`.
/// These numbers are used internally instead of addresses for optimization reasons.
/// @notice You can list the validators in the quorum by calling the `validatorById`
/// function for each ID from 1 to `n`.
contract Quorum is AbstractConsensus {
using BitMaps for BitMaps.BitMap;

/// @notice The total number of validators.
/// @notice See the `numOfValidators` function.
uint256 private immutable _numOfValidators;

/// @notice Validator IDs indexed by address.
/// @notice See the `validatorId` function.
/// @dev Non-validators are assigned to ID zero.
guidanoli marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Validators have IDs greater than zero.
mapping(address => uint256) private _validatorId;

/// @notice Validator addresses indexed by ID.
/// @notice See the `validatorById` function.
/// @dev Invalid IDs map to address zero.
mapping(uint256 => address) private _validatorById;

/// @notice Votes in favor of a particular claim.
/// @param inFavorCount The number of validators in favor of the claim
/// @param inFavorById The set of validators in favor of the claim
/// @dev `inFavorById` is a bitmap indexed by validator IDs.
struct Votes {
uint256 inFavorCount;
BitMaps.BitMap inFavorById;
}

/// @notice Votes indexed by claim
/// (application address, first input index, last input index, and epoch hash).
/// @dev See the `numOfValidatorsInFavorOf` and `isValidatorInFavorOf` functions.
mapping(address => mapping(uint256 => mapping(uint256 => mapping(bytes32 => Votes))))
private _votes;

/// @param validators The array of validator addresses
/// @dev Duplicates in the `validators` array are ignored.
pedroargento marked this conversation as resolved.
Show resolved Hide resolved
constructor(address[] memory validators) {
uint256 n;
for (uint256 i; i < validators.length; ++i) {
address validator = validators[i];
if (_validatorId[validator] == 0) {
uint256 id = ++n;
_validatorId[validator] = id;
_validatorById[id] = validator;
}
}
_numOfValidators = n;
}

/// @notice Submit a claim.
/// @notice If the majority of the quorum submit a claim, it is accepted.
/// @param application The application address
/// @param r The input range
/// @param epochHash The epoch hash
/// @dev Can only be called by a validator.
function submitClaim(
address application,
InputRange calldata r,
bytes32 epochHash
) external {
uint256 id = _validatorId[msg.sender];
require(id > 0, "Quorum: caller is not validator");

emit ClaimSubmission(msg.sender, application, r, epochHash);

Votes storage votes = _getVotes(application, r, epochHash);

if (!votes.inFavorById.get(id)) {
votes.inFavorById.set(id);
if (++votes.inFavorCount == 1 + _numOfValidators / 2) {
_acceptClaim(application, r, epochHash);
}
}
}

/// @notice Get the number of validators.
function numOfValidators() external view returns (uint256) {
return _numOfValidators;
}

/// @notice Get the ID of a validator.
/// @param validator The validator address
/// @dev Validators have IDs greater than zero.
/// @dev Non-validators are assigned to ID zero.
guidanoli marked this conversation as resolved.
Show resolved Hide resolved
function validatorId(address validator) external view returns (uint256) {
return _validatorId[validator];
}

/// @notice Get the address of a validator by its ID.
/// @param id The validator ID
/// @dev Validator IDs range from 1 to `N`, the total number of validators.
/// @dev Invalid IDs map to address zero.
function validatorById(uint256 id) external view returns (address) {
return _validatorById[id];
}

/// @notice Get the number of validators in favor of a claim.
/// @param application The application address
/// @param r The input range
/// @param epochHash The epoch hash
/// @return Number of validators in favor of claim
function numOfValidatorsInFavorOf(
address application,
InputRange calldata r,
bytes32 epochHash
) external view returns (uint256) {
return _getVotes(application, r, epochHash).inFavorCount;
}

/// @notice Check whether a validator is in favor of a claim.
/// @param application The application address
/// @param r The input range
/// @param epochHash The epoch hash
/// @param id The ID of the validator
/// @return Whether validator is in favor of claim
/// @dev Assumes the provided ID is valid.
function isValidatorInFavorOf(
address application,
InputRange calldata r,
bytes32 epochHash,
uint256 id
) external view returns (bool) {
return _getVotes(application, r, epochHash).inFavorById.get(id);
}

/// @notice Get a `Votes` structure from storage from a given claim.
/// @param application The application address
/// @param r The input range
/// @param epochHash The epoch hash
/// @return The `Votes` structure related to given claim
function _getVotes(
address application,
InputRange calldata r,
bytes32 epochHash
) internal view returns (Votes storage) {
return _votes[application][r.firstIndex][r.lastIndex][epochHash];
}
}
Loading