Skip to content

Commit

Permalink
feat: add quorum
Browse files Browse the repository at this point in the history
  • Loading branch information
guidanoli committed Sep 21, 2023
1 parent 73cee6a commit 8cce1d9
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `abi` directory to the `@cartesi/rollups` package, ideal for language bindings.
- `Quorum`: Consensus reached by the majority of signers

### Removed

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

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 {AbstractConsensus} from "../AbstractConsensus.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");

/// @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;

/// @notice Construct a Quorum consensus
/// @param _validators the list of validators
/// @param _shares the list of shares
/// @param _history the history contract
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.
for (uint256 i; i < _validators.length; ++i) {
_grantRole(VALIDATOR_ROLE, _validators[i]);
}

// Set history.
history = _history;
}

/// @notice Submits a claim for voting.
/// 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
/// implementation of the current history contract.
/// @param _claimData Data for submitting a claim
/// @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();

// Get the number of validators in the quorum.
uint256 quorumSize = getRoleMemberCount(VALIDATOR_ROLE);

// If this claim has now just over half of the quorum's approval,
// then we can submit it to the history contract.
if (numOfVotesInFavour == 1 + quorumSize / 2) {
history.submitClaim(_claimData);
}
}
}

/// @notice Get the history contract.
/// @return The history contract
function getHistory() external view returns (IHistory) {
return history;
}

function getClaim(
address _dapp,
bytes calldata _proofContext
) external view override returns (bytes32, uint256, uint256) {
return history.getClaim(_dapp, _proofContext);
}
}
45 changes: 45 additions & 0 deletions onchain/rollups/test/foundry/consensus/quorum/Quorum.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {Vm} from "forge-std/Vm.sol";

import {Quorum} from "contracts/consensus/quorum/Quorum.sol";
import {History} from "contracts/history/History.sol";
import {IHistory} from "contracts/history/IHistory.sol";

import {TestBase} from "../../util/TestBase.sol";

import "forge-std/console.sol";

contract QuorumTest is TestBase {
Quorum quorum;
uint256 constant numOfValidators = 3;
address[] validators;
uint256[] shares;

function setUp() external {
for (uint256 i; i < numOfValidators; ++i) {
validators.push(vm.addr(i + 1));
shares.push(i + 1);
}

quorum = new Quorum(validators, shares, IHistory(vm.addr(1)));
}

function testSubmitClaim(address _dapp, History.Claim calldata _claim) external {
bytes memory claimData = abi.encode(_dapp, _claim);

vm.mockCall(
address(quorum.getHistory()),
abi.encodeWithSelector(IHistory.submitClaim.selector, claimData),
abi.encode()
);

for (uint256 i; i < numOfValidators; ++i) {
vm.prank(validators[i]);
quorum.submitClaim(claimData);
}
}
}

0 comments on commit 8cce1d9

Please sign in to comment.