diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index 3c892a77..8f0df968 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.8; import {IInputBox} from "rollups-contracts/inputs/IInputBox.sol"; -import {Inputs} from "rollups-contracts/common/Inputs.sol"; import {IDataProvider} from "prt-contracts/IDataProvider.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; @@ -38,9 +37,6 @@ import {Merkle} from "./Merkle.sol"; contract DaveConsensus is IDataProvider { using Merkle for bytes; - /// @notice GIO namespace for getting advance requests from the InputBox contract - uint16 constant INPUT_BOX_NAMESPACE = 0; - /// @notice The input box contract IInputBox immutable _inputBox; @@ -53,11 +49,11 @@ contract DaveConsensus is IDataProvider { /// @notice Current sealed epoch number uint256 _epochNumber; - /// @notice Block number (inclusive) lower bound of the current sealed epoch - uint256 _blockNumberLowerBound; + /// @notice Input index (inclusive) lower bound of the current sealed epoch + uint256 _inputIndexLowerBound; - /// @notice Block number (exclusive) upper bound of the current sealed epoch - uint256 _blockNumberUpperBound; + /// @notice Input index (exclusive) upper bound of the current sealed epoch + uint256 _inputIndexUpperBound; /// @notice Current sealed epoch tournament ITournament _tournament; @@ -70,18 +66,31 @@ contract DaveConsensus is IDataProvider { /// @notice An epoch was sealed /// @param epochNumber the sealed epoch number - /// @param blockNumberLowerBound the block number (inclusive) lower bound in the sealed epoch - /// @param blockNumberUpperBound the block number (exclusive) upper bound in the sealed epoch + /// @param inputIndexLowerBound the input index (inclusive) lower bound in the sealed epoch + /// @param inputIndexUpperBound the input index (exclusive) upper bound in the sealed epoch /// @param initialMachineStateHash the initial machine state hash /// @param tournament the sealed epoch tournament contract event EpochSealed( uint256 epochNumber, - uint256 blockNumberLowerBound, - uint256 blockNumberUpperBound, + uint256 inputIndexLowerBound, + uint256 inputIndexUpperBound, Machine.Hash initialMachineStateHash, ITournament tournament ); + /// @notice Received epoch number is different from actual + /// @param received The epoch number received as argument + /// @param actual The actual epoch number in storage + error IncorrectEpochNumber(uint256 received, uint256 actual); + + /// @notice Tournament is not finished yet + error TournamentNotFinishedYet(); + + /// @notice Hash of received input blob is different from stored on-chain + /// @param fromReceivedInput Hash of received input blob + /// @param fromInputBox Hash of input stored on the input box contract + error InputHashMismatch(bytes32 fromReceivedInput, bytes32 fromInputBox); + constructor( IInputBox inputBox, address appContract, @@ -95,11 +104,11 @@ contract DaveConsensus is IDataProvider { emit ConsensusCreation(inputBox, appContract, tournamentFactory); // Initialize first sealed epoch - uint256 blockNumberUpperBound = block.number; - _blockNumberUpperBound = blockNumberUpperBound; + uint256 inputIndexUpperBound = inputBox.getNumberOfInputs(appContract); + _inputIndexUpperBound = inputIndexUpperBound; ITournament tournament = tournamentFactory.instantiate(initialMachineStateHash, this); _tournament = tournament; - emit EpochSealed(0, 0, blockNumberUpperBound, initialMachineStateHash, tournament); + emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, tournament); } function canSettle() external view returns (bool isFinished, uint256 epochNumber) { @@ -109,19 +118,20 @@ contract DaveConsensus is IDataProvider { function settle(uint256 epochNumber) external { // Check tournament settlement - require(epochNumber == _epochNumber, "Dave: incorrect epoch number"); + uint256 actualEpochNumber = _epochNumber; + require(epochNumber == actualEpochNumber, IncorrectEpochNumber(epochNumber, actualEpochNumber)); (bool isFinished,, Machine.Hash finalMachineStateHash) = _tournament.arbitrationResult(); - require(isFinished, "Dave: tournament not finished"); + require(isFinished, TournamentNotFinishedYet()); // Seal current accumulating epoch _epochNumber = ++epochNumber; - uint256 blockNumberLowerBound = _blockNumberUpperBound; - _blockNumberLowerBound = blockNumberLowerBound; - uint256 blockNumberUpperBound = block.number; - _blockNumberUpperBound = blockNumberUpperBound; + uint256 inputIndexLowerBound = _inputIndexUpperBound; + _inputIndexLowerBound = inputIndexLowerBound; + uint256 inputIndexUpperBound = _inputBox.getNumberOfInputs(_appContract); + _inputIndexUpperBound = inputIndexUpperBound; ITournament tournament = _tournamentFactory.instantiate(finalMachineStateHash, this); _tournament = tournament; - emit EpochSealed(epochNumber, blockNumberLowerBound, blockNumberUpperBound, finalMachineStateHash, tournament); + emit EpochSealed(epochNumber, inputIndexLowerBound, inputIndexUpperBound, finalMachineStateHash, tournament); } function getCurrentSealedEpoch() @@ -129,14 +139,14 @@ contract DaveConsensus is IDataProvider { view returns ( uint256 epochNumber, - uint256 blockNumberLowerBound, - uint256 blockNumberUpperBound, + uint256 inputIndexLowerBound, + uint256 inputIndexUpperBound, ITournament tournament ) { epochNumber = _epochNumber; - blockNumberLowerBound = _blockNumberLowerBound; - blockNumberUpperBound = _blockNumberUpperBound; + inputIndexLowerBound = _inputIndexLowerBound; + inputIndexUpperBound = _inputIndexUpperBound; tournament = _tournament; } @@ -153,36 +163,23 @@ contract DaveConsensus is IDataProvider { } /// @inheritdoc IDataProvider - function gio(uint16 namespace, bytes calldata id, bytes calldata input) + function provideMerkleRootOfInput(uint256 inputIndexWithinEpoch, bytes calldata input) external view override - returns (bytes32, uint256) + returns (bytes32) { - require(namespace == INPUT_BOX_NAMESPACE, "Dave: bad namespace"); - uint256 inputIndex = abi.decode(id, (uint256)); - uint256 inputCount = _inputBox.getNumberOfInputs(_appContract); + uint256 inputIndex = _inputIndexLowerBound + inputIndexWithinEpoch; - if (inputIndex >= inputCount) { + if (inputIndex >= _inputIndexUpperBound) { // out-of-bounds index: repeat the state (as a fixpoint function) - return (bytes32(0), 0); + return bytes32(0); } - bytes32 inputHash = _inputBox.getInputHash(_appContract, inputIndex); - require(keccak256(input) == inputHash, "Dave: bad input hash"); - require(input.length >= 4, "Dave: bad input length"); + bytes32 calculatedInputHash = keccak256(input); + bytes32 realInputHash = _inputBox.getInputHash(_appContract, inputIndex); + require(calculatedInputHash == realInputHash, InputHashMismatch(calculatedInputHash, realInputHash)); - bytes4 selector = bytes4(input[:4]); - bytes calldata args = input[4:]; - require(selector == Inputs.EvmAdvance.selector, "Dave: bad input selector"); - (,,, uint256 blockNumber,,,,) = - abi.decode(args, (uint256, address, address, uint256, uint256, uint256, uint256, bytes)); - - if (_blockNumberLowerBound <= blockNumber && blockNumber < _blockNumberUpperBound) { - return (input.getSmallestMerkleRootFromBytes(), input.length); - } else { - // out-of-bounds index: repeat the state (as a fixpoint function) - return (bytes32(0), 0); - } + return input.getSmallestMerkleRootFromBytes(); } } diff --git a/prt/contracts/src/IDataProvider.sol b/prt/contracts/src/IDataProvider.sol index 367c0b12..dda57019 100644 --- a/prt/contracts/src/IDataProvider.sol +++ b/prt/contracts/src/IDataProvider.sol @@ -4,14 +4,12 @@ pragma solidity ^0.8.17; interface IDataProvider { - /// @notice Provides the Merkle root of the response to a Generic I/O request - /// @param namespace The request namespace - /// @param id The request ID - /// @param extra Extra data (e.g. proofs) - /// @return Merkle root of response - /// @return Size of the response (in bytes) - function gio(uint16 namespace, bytes calldata id, bytes calldata extra) - external - view - returns (bytes32, uint256); + /// @notice Provides the Merkle root of an input + /// @param inputIndexWithinEpoch The index of the input within the epoch + /// @param input The input blob (to hash and check against the input box) + /// @return The root of smallest Merkle tree that fits the input + function provideMerkleRootOfInput( + uint256 inputIndexWithinEpoch, + bytes calldata input + ) external view returns (bytes32); } diff --git a/prt/contracts/src/tournament/abstracts/LeafTournament.sol b/prt/contracts/src/tournament/abstracts/LeafTournament.sol index eaca500e..c2a1f6e0 100644 --- a/prt/contracts/src/tournament/abstracts/LeafTournament.sol +++ b/prt/contracts/src/tournament/abstracts/LeafTournament.sol @@ -151,17 +151,17 @@ abstract contract LeafTournament is Tournament { if (inputLength > 0) { bytes calldata input = proofs[32:32 + inputLength]; - uint256 inputIndex = counter + uint256 inputIndexWithinEpoch = counter >> ( ArbitrationConstants.LOG2_EMULATOR_SPAN + ArbitrationConstants.LOG2_UARCH_SPAN - ); // TODO: add input index offset of the epoch + ); // TODO: maybe assert retrieved input length matches? - (bytes32 inputMerkleRoot, uint256 retrievedInputLength) = - provider.gio(0, abi.encode(inputIndex), input); + bytes32 inputMerkleRoot = provider.provideMerkleRootOfInput( + inputIndexWithinEpoch, input + ); - require(inputLength == retrievedInputLength); require(inputMerkleRoot != bytes32(0)); SendCmioResponse.sendCmioResponse( accessLogs,