Skip to content

Commit

Permalink
feat!: refactor IConsensus interface
Browse files Browse the repository at this point in the history
  • Loading branch information
guidanoli committed Dec 14, 2023
1 parent e6110bc commit 3baed5f
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 565 deletions.
16 changes: 16 additions & 0 deletions onchain/rollups/.changeset/three-pigs-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@cartesi/rollups": major
---

Refactored the `IConsensus` interface for better interaction with the Cartesi Rollups node.
Added `InputIndexOutOfRange` error to `ICartesiDApp` interface to improve UX of voucher execution.
Updated the `AbstractConsensus` contract to partially implement the new `IConsensus` interface.
Updated the `Authority` contract to implement the new `IConsensus` interface.
Updated the `CartesiDApp` contract to call `getEpochHash` instead of `getClaim`, and to not call `join`.
Replaced the `bytes context` field from the `Proof` structure with an `InputRange inputRange` field.
Removed the `getHistory`, `setHistory` and `migrateHistoryToConsensus` functions and `NewHistory` event from the `Authority` contract.
Contracts that implemented the old `IConsensus` interface and wish to implement the new one must be adapted.
Contracts that implement the new `IConsensus` interface are not backwards compatible with old `CartesiDApp` contracts, since they expect the consensus to expose a `join` function.
Components that would call the `getClaim` function must now call the `getEpochHash` function while passing an input range instead of a "context" blob.
Components that would call the `join` function should not call it anymore, as it is no longer declared in the new interface.
Components that would listen to the `ApplicationJoined` event should not listen to it anymore, as it is no longer declared in the new interface.
6 changes: 3 additions & 3 deletions onchain/rollups/contracts/common/Proof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
pragma solidity ^0.8.8;

import {OutputValidityProof} from "./OutputValidityProof.sol";
import {InputRange} from "./InputRange.sol";

/// @notice Data for validating outputs.
/// @param validity A validity proof for the output
/// @param context Data for querying the right claim from the current consensus contract
/// @dev The encoding of `context` might vary depending on the implementation of the consensus contract.
/// @param inputRange The range of inputs accepted during the epoch
struct Proof {
OutputValidityProof validity;
bytes context;
InputRange inputRange;
}
45 changes: 40 additions & 5 deletions onchain/rollups/contracts/consensus/AbstractConsensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,47 @@
pragma solidity ^0.8.8;

import {IConsensus} from "./IConsensus.sol";
import {InputRange} from "../common/InputRange.sol";
import {LibInputRange} from "../library/LibInputRange.sol";

/// @title Abstract Consensus
/// @notice An abstract contract that partially implements `IConsensus`.
/// @notice Stores epoch hashes for several DApps and input ranges.
/// @dev This contract was designed to be inherited by implementations of the `IConsensus` interface
/// that only need a simple mechanism of storage and retrieval of epoch hashes.
abstract contract AbstractConsensus is IConsensus {
/// @notice Emits an `ApplicationJoined` event with the message sender.
function join() external override {
emit ApplicationJoined(msg.sender);
using LibInputRange for InputRange;

/// @notice Indexes epoch hashes by DApp address, first input index and last input index.
mapping(address => mapping(uint256 => mapping(uint256 => bytes32)))
private _epochHashes;

/// @notice Get the epoch hash for a certain DApp and input range.
/// @param dapp The DApp contract address
/// @param r The input range
/// @return epochHash The epoch hash
/// @dev For claimed epochs, returns the epoch hash of the last accepted claim.
/// @dev For unclaimed epochs, returns `bytes32(0)`.
function getEpochHash(
address dapp,
InputRange calldata r
) public view override returns (bytes32 epochHash) {
epochHash = _epochHashes[dapp][r.firstInputIndex][r.lastInputIndex];
}

/// @notice Accept a claim.
/// @param dapp The DApp contract address
/// @param r The input range
/// @param epochHash The epoch hash
/// @dev Raises an `InputRangeIsEmptySet` error if `r` represents the empty set.
/// @dev On successs, emits a `ClaimAcceptance` event.
function _acceptClaim(
address dapp,
InputRange calldata r,
bytes32 epochHash
) internal {
if (r.isEmptySet()) {
revert InputRangeIsEmptySet(dapp, r, epochHash);
}
_epochHashes[dapp][r.firstInputIndex][r.lastInputIndex] = epochHash;
emit ClaimAcceptance(dapp, r, epochHash);
}
}
141 changes: 74 additions & 67 deletions onchain/rollups/contracts/consensus/IConsensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,81 @@

pragma solidity ^0.8.8;

/// @title Consensus interface
///
/// @notice This contract defines a generic interface for consensuses.
/// We use the word "consensus" to designate a contract that provides claims
/// in the base layer regarding the state of off-chain machines running in
/// the execution layer. How this contract is able to reach consensus, who is
/// able to submit claims, and how are claims stored in the base layer are
/// some of the implementation details left unspecified by this interface.
///
/// From the point of view of a DApp, these claims are necessary to validate
/// on-chain action allowed by the off-chain machine in the form of vouchers
/// and notices. Each claim is composed of three parts: an epoch hash, a first
/// index, and a last index. We'll explain each of these parts below.
///
/// First, let us define the word "epoch". For finality reasons, we need to
/// divide the stream of inputs being fed into the off-chain machine into
/// batches of inputs, which we call "epoches". At the end of every epoch,
/// we summarize the state of the off-chain machine in a single hash, called
/// "epoch hash". Please note that this interface does not define how this
/// stream of inputs is being chopped up into epoches.
///
/// The other two parts are simply the indices of the first and last inputs
/// accepted during the epoch. Logically, the first index MUST BE less than
/// or equal to the last index. As a result, every epoch MUST accept at least
/// one input. This assumption stems from the fact that the state of a machine
/// can only change after an input is fed into it.
///
/// Examples of possible implementations of this interface include:
///
/// * An authority consensus, controlled by a single address who has full
/// control over epoch boundaries, claim submission, asset management, etc.
///
/// * A quorum consensus, controlled by a limited set of validators, that
/// vote on the state of the machine at the end of every epoch. Also, epoch
/// boundaries are determined by the timestamp in the base layer, and assets
/// are split equally amongst the validators.
///
/// * An NxN consensus, which allows anyone to submit and dispute claims
/// in the base layer. Epoch boundaries are determined in the same fashion
/// as in the quorum example.
///
import {InputRange} from "../common/InputRange.sol";

/// @notice Provides epoch hashes for DApps.
/// @notice An epoch hash is produced after the machine processes a range of inputs and the epoch is finalized.
/// This hash can be later used to prove that any given output was produced by the machine during the epoch.
/// @notice After an epoch is finalized, a validator may submit a claim containing: the address of the DApp contract,
/// the range of inputs accepted during the epoch, and the epoch hash.
/// @notice Input ranges cannot represent the empty set, since at least one input is necessary to advance the state of the machine.
/// @notice Validators may synchronize epoch finalization, but such mechanism is not specified by this interface.
/// @notice A validator should be able to save transaction fees by not submitting a claim if it was...
/// - already submitted by the validator (see the `ClaimSubmission` event) or;
/// - already accepted by the consensus (see the `ClaimAcceptance` event).
/// @notice The acceptance criteria for claims may depend on the type of consensus, and is not specified by this interface.
/// For example, a claim may be accepted if it was...
/// - submitted by an authority or;
/// - submitted by the majority of a quorum or;
/// - submitted and not proven wrong after some period of time.
interface IConsensus {
/// @notice An application has joined the consensus' validation set.
/// @param application The application
/// @dev MUST be triggered on a successful call to `join`.
event ApplicationJoined(address application);
/// @notice Tried to submit a claim with an input range that represents the empty set.
/// @param inputRange The input range
/// @dev An input range represents the empty set if, and only if, the first input index is
/// greater than the last input index.
error InputRangeIsEmptySet(
address dapp,
InputRange inputRange,
bytes32 epochHash
);

/// @notice A claim was submitted to the consensus.
/// @param submitter The submitter address
/// @param dapp The DApp contract address
/// @param inputRange The input range
/// @param epochHash The epoch hash
/// @dev The input range MUST NOT represent the empty set.
/// @dev Overwrites any previous submissions regarding `submitter`, `dapp` and `inputRange`.
event ClaimSubmission(
address indexed submitter,
address indexed dapp,
InputRange inputRange,
bytes32 epochHash
);

/// @notice A claim was accepted by the consensus.
/// @param dapp The DApp contract address
/// @param inputRange The input range
/// @param epochHash The epoch hash
/// @dev The input range MUST NOT represent the empty set.
/// @dev MUST be triggered after some `ClaimSubmission` event regarding `dapp`, `inputRange` and `epochHash`.
/// @dev Overwrites any previous acceptances regarding `dapp` and `inputRange`.
event ClaimAcceptance(
address indexed dapp,
InputRange inputRange,
bytes32 epochHash
);

/// @notice Get a specific claim regarding a specific DApp.
/// The encoding of `_proofContext` might vary
/// depending on the implementation.
/// @param _dapp The DApp address
/// @param _proofContext Data for retrieving the desired claim
/// @return epochHash_ The claimed epoch hash
/// @return firstInputIndex_ The index of the first input of the epoch in the input box
/// @return lastInputIndex_ The index of the last input of the epoch in the input box
function getClaim(
address _dapp,
bytes calldata _proofContext
)
external
view
returns (
bytes32 epochHash_,
uint256 firstInputIndex_,
uint256 lastInputIndex_
);
/// @notice Submit a claim to the consensus.
/// @param dapp The DApp contract address
/// @param inputRange The input range
/// @param epochHash The epoch hash
/// @dev MAY raise an `InputRangeIsEmptySet` error if the input range represents the empty set.
/// @dev On success, MUST trigger a `ClaimSubmission` event.
function submitClaim(
address dapp,
InputRange calldata inputRange,
bytes32 epochHash
) external;

/// @notice Signal the consensus that the message sender wants to join its validation set.
/// @dev MUST fire an `ApplicationJoined` event with the message sender as argument.
function join() external;
/// @notice Get the epoch hash for a certain DApp and input range.
/// @param dapp The DApp contract address
/// @param inputRange The input range
/// @return epochHash The epoch hash
/// @dev For claimed epochs, must return the epoch hash of the last accepted claim.
/// @dev For unclaimed epochs, MUST either revert or return `bytes32(0)`.
function getEpochHash(
address dapp,
InputRange calldata inputRange
) external view returns (bytes32 epochHash);
}
82 changes: 20 additions & 62 deletions onchain/rollups/contracts/consensus/authority/Authority.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,28 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

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

/// @title Authority consensus
/// @notice A consensus model controlled by a single address, the owner.
/// Claims are stored in an auxiliary contract called `History`.
/// @dev This contract inherits `AbstractConsensus` and OpenZeppelin's `Ownable` contract.
/// @notice A consensus contract controlled by a single address, the owner.
/// @dev This contract inherits from OpenZeppelin's `Ownable` contract.
/// For more information on `Ownable`, please consult OpenZeppelin's official documentation.
contract Authority is AbstractConsensus, Ownable {
/// @notice The current history contract.
/// @dev See the `getHistory` and `setHistory` functions.
IHistory internal history;

/// @notice A new history contract is used to store claims.
/// @param history The new history contract
/// @dev MUST be triggered on a successful call to `setHistory`.
event NewHistory(IHistory history);

/// @notice Constructs an `Authority` contract.
/// @param _initialOwner The initial contract owner
constructor(address _initialOwner) Ownable(_initialOwner) {}

/// @notice Submits a claim to the current 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 the `Authority` owner,
/// and the `Authority` contract must have ownership over
/// its current history contract.
function submitClaim(bytes calldata _claimData) external onlyOwner {
history.submitClaim(_claimData);
}

/// @notice Transfer ownership over the current history contract to `_consensus`.
/// @param _consensus The new owner of the current history contract
/// @dev Can only be called by the `Authority` owner,
/// and the `Authority` contract must have ownership over
/// its current history contract.
function migrateHistoryToConsensus(address _consensus) external onlyOwner {
history.migrateToConsensus(_consensus);
}

/// @notice Make `Authority` point to another history contract.
/// @param _history The new history contract
/// @dev Emits a `NewHistory` event.
/// Can only be called by the `Authority` owner.
function setHistory(IHistory _history) external onlyOwner {
history = _history;
emit NewHistory(_history);
}

/// @notice Get the current history contract.
/// @return The current history contract
function getHistory() external view returns (IHistory) {
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
) external view override returns (bytes32, uint256, uint256) {
return history.getClaim(_dapp, _proofContext);
/// @param initialOwner The initial contract owner
constructor(address initialOwner) Ownable(initialOwner) {}

/// @notice Submit a claim.
/// @param dapp The DApp contract address
/// @param inputRange The input range
/// @param epochHash The epoch hash
/// @dev The input range MUST NOT represent the empty set.
/// @dev On success, triggers a `ClaimSubmission` event and a `ClaimAcceptance` event.
/// @dev Can only be called by the owner.
function submitClaim(
address dapp,
InputRange calldata inputRange,
bytes32 epochHash
) external override onlyOwner {
emit ClaimSubmission(msg.sender, dapp, inputRange, epochHash);
_acceptClaim(dapp, inputRange, epochHash);
}
}
Loading

0 comments on commit 3baed5f

Please sign in to comment.