pragma solidity 0.5.10;

/// @dev Randomness test contract based on https://github.com/poanetwork/posdao-contracts.
/// Generates and stores random numbers in a RANDAO manner and accumulates a random seed.
contract Random {
    mapping(uint256 => mapping(address => bytes32)) public hashes;
    mapping(uint256 => mapping(address => bytes)) public ciphers;
    mapping(uint256 => mapping(address => uint256)) public secrets;
    uint256 public value;

    /// @dev Called by the validator's node to store a hash and a cipher of the validator's secret on each collection
    /// round. The validator's node must use its mining address to call this function.
    /// This function can only be called once per collection round (during the `commits phase`).
    /// @param _secretHash The Keccak-256 hash of the validator's secret.
    /// @param _cipher The cipher of the validator's secret. Can be used by the node to decrypt and reveal.
    function commitHash(bytes32 _secretHash, bytes calldata _cipher) external {
        require(block.coinbase == msg.sender);
        require(_isCommitPhase(block.number - 1));
        uint256 round = _collectRound(block.number - 1);
        require(!isCommitted(round, msg.sender));
        hashes[round][msg.sender] = _secretHash;
        ciphers[round][msg.sender] = _cipher;
    }

    /// @dev Called by the validator's node to XOR its secret with the current random seed.
    /// The validator's node must use its mining address to call this function.
    /// This function can only be called once per collection round (during the `reveals phase`).
    /// @param _number The validator's secret.
    function revealNumber(uint256 _number) external {
        require(block.coinbase == msg.sender);
        require(_isRevealPhase(block.number - 1));
        uint256 round = _collectRound(block.number - 1);
        require(!sentReveal(round, msg.sender));
        require(hashes[round][msg.sender] == keccak256(abi.encodePacked(_number)));
        secrets[round][msg.sender] = _number;
        value ^= _number;
    }

	/// @dev Returns the Keccak-256 hash and cipher of the validator's secret for the specified collection round
    /// and the specified validator stored by the validator through the `commitHash` function.
    /// @param _collectRound The serial number of the collection round for which hash and cipher should be retrieved.
    /// @param _miningAddress The mining address of validator.
    function getCommitAndCipher(
        uint256 _collectRound,
        address _miningAddress
    ) public view returns(bytes32, bytes memory) {
        return (hashes[_collectRound][_miningAddress], ciphers[_collectRound][_miningAddress]);
    }

    /// @dev Returns a boolean flag indicating whether the specified validator has committed their secret's hash for the
    /// specified collection round.
    /// @param _collectRound The serial number of the collection round for which the checkup should be done.
    /// @param _miningAddress The mining address of the validator.
    function isCommitted(uint256 _collectRound, address _miningAddress) public view returns(bool) {
        return hashes[_collectRound][_miningAddress] != bytes32(0);
    }

    /// @dev Returns a boolean flag indicating whether the current phase of the current collection round
    /// is a `commits phase`. Used by the validator's node to determine if it should commit the hash of
    /// the secret during the current collection round.
    function isCommitPhase() public view returns(bool) {
        return _isCommitPhase(block.number);
    }

    /// @dev Returns a boolean flag indicating whether the current phase of the current collection round
    /// is a `reveals phase`. Used by the validator's node to determine if it should reveal the secret during
    /// the current collection round.
    function isRevealPhase() public view returns(bool) {
        return _isRevealPhase(block.number);
    }

    /// @dev Returns a boolean flag of whether the specified validator has revealed their secret for the
    /// specified collection round.
    /// @param _collectRound The serial number of the collection round for which the checkup should be done.
    /// @param _miningAddress The mining address of the validator.
    function sentReveal(uint256 _collectRound, address _miningAddress) public view returns(bool) {
        return secrets[_collectRound][_miningAddress] != uint256(0);
    }

    /// @dev Returns the current collect round number.
    function currentCollectRound() public view returns(uint256) {
        return _collectRound(block.number);
    }

    /// @dev Returns the current random value.
    function getValue() public view returns(uint256) {
        return value;
    }

    function _collectRound(uint256 blockNumber) private pure returns(uint256) {
        return blockNumber / 6;
    }

    function _isCommitPhase(uint256 blockNumber) private pure returns(bool) {
        return blockNumber % 6 < 3;
    }

    function _isRevealPhase(uint256 blockNumber) private pure returns(bool) {
        return blockNumber % 6 >= 3;
    }
}
