github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/contract_comm/random/random.go (about)

     1  package random
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/ethereum/go-ethereum/accounts/abi"
     7  	"github.com/ethereum/go-ethereum/common"
     8  	"github.com/ethereum/go-ethereum/common/hexutil"
     9  	"github.com/ethereum/go-ethereum/consensus"
    10  	"github.com/ethereum/go-ethereum/contract_comm"
    11  	"github.com/ethereum/go-ethereum/contract_comm/errors"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/ethereum/go-ethereum/core/vm"
    14  	"github.com/ethereum/go-ethereum/crypto"
    15  	"github.com/ethereum/go-ethereum/ethdb"
    16  	"github.com/ethereum/go-ethereum/log"
    17  	"github.com/ethereum/go-ethereum/params"
    18  )
    19  
    20  const (
    21  	// This is taken from celo-monorepo/packages/protocol/build/<env>/contracts/Random.json
    22  	revealAndCommitABI = `[
    23  	{
    24  		"constant": false,
    25  		"inputs": [
    26  			{
    27  				"name": "randomness",
    28  				"type": "bytes32"
    29  			},
    30  			{
    31  				"name": "newCommitment",
    32  				"type": "bytes32"
    33  			},
    34  			{
    35  				"name": "proposer",
    36  				"type": "address"
    37  			}
    38  		],
    39  		"name": "revealAndCommit",
    40  		"outputs": [],
    41  		"payable": false,
    42  		"stateMutability": "nonpayable",
    43  		"type": "function"
    44  	}
    45  ]`
    46  	commitmentsAbi = `[
    47  	{
    48  		"constant": true,
    49  		"inputs": [
    50  			{
    51  				"name": "",
    52  				"type": "address"
    53  			}
    54  		],
    55  		"name": "commitments",
    56  		"outputs": [
    57  			{
    58  				"name": "",
    59  				"type": "bytes32"
    60  			}
    61  		],
    62  		"payable": false,
    63  		"stateMutability": "view",
    64  		"type": "function"
    65  	}
    66  ]`
    67  	computeCommitmentAbi = `[
    68      {
    69        "constant": true,
    70        "inputs": [
    71          {
    72            "name": "randomness",
    73            "type": "bytes32"
    74          }
    75        ],
    76        "name": "computeCommitment",
    77        "outputs": [
    78          {
    79            "name": "",
    80            "type": "bytes32"
    81          }
    82        ],
    83        "payable": false,
    84        "stateMutability": "view",
    85        "type": "function"
    86      }
    87  ]`
    88  	randomAbi = `[
    89      {
    90        "constant": true,
    91        "inputs": [],
    92        "name": "random",
    93        "outputs": [
    94          {
    95            "name": "",
    96            "type": "bytes32"
    97          }
    98        ],
    99        "payable": false,
   100        "stateMutability": "view",
   101        "type": "function"
   102      }
   103  ]`
   104  )
   105  
   106  var (
   107  	revealAndCommitFuncABI, _   = abi.JSON(strings.NewReader(revealAndCommitABI))
   108  	commitmentsFuncABI, _       = abi.JSON(strings.NewReader(commitmentsAbi))
   109  	computeCommitmentFuncABI, _ = abi.JSON(strings.NewReader(computeCommitmentAbi))
   110  	randomFuncABI, _            = abi.JSON(strings.NewReader(randomAbi))
   111  	zeroValue                   = common.Big0
   112  	dbRandomnessPrefix          = []byte("db-randomness-prefix")
   113  )
   114  
   115  func commitmentDbLocation(commitment common.Hash) []byte {
   116  	return append(dbRandomnessPrefix, commitment.Bytes()...)
   117  }
   118  
   119  func address() *common.Address {
   120  	randomAddress, err := contract_comm.GetRegisteredAddress(params.RandomRegistryId, nil, nil)
   121  	if err == errors.ErrSmartContractNotDeployed || err == errors.ErrRegistryContractNotDeployed {
   122  		log.Debug("Registry address lookup failed", "err", err, "contract", hexutil.Encode(params.RandomRegistryId[:]))
   123  	} else if err != nil {
   124  		log.Error(err.Error())
   125  	}
   126  	return randomAddress
   127  }
   128  
   129  func IsRunning() bool {
   130  	randomAddress := address()
   131  	return randomAddress != nil && *randomAddress != common.ZeroAddress
   132  }
   133  
   134  // GetLastRandomness returns up the last randomness we committed to by first
   135  // looking up our last commitment in the smart contract, and then finding the
   136  // corresponding preimage in a (commitment => randomness) mapping we keep in the
   137  // database.
   138  func GetLastRandomness(coinbase common.Address, db *ethdb.Database, header *types.Header, state vm.StateDB, chain consensus.ChainReader, seed []byte) (common.Hash, error) {
   139  	lastCommitment := common.Hash{}
   140  	_, err := contract_comm.MakeStaticCall(params.RandomRegistryId, commitmentsFuncABI, "commitments", []interface{}{coinbase}, &lastCommitment, params.MaxGasForCommitments, header, state)
   141  	if err != nil {
   142  		log.Error("Failed to get last commitment", "err", err)
   143  		return lastCommitment, err
   144  	}
   145  
   146  	if (lastCommitment == common.Hash{}) {
   147  		log.Debug("Unable to find last randomness commitment in smart contract")
   148  		return common.Hash{}, nil
   149  	}
   150  
   151  	parentBlockHashBytes, err := (*db).Get(commitmentDbLocation(lastCommitment))
   152  	if err != nil {
   153  		log.Error("Failed to get last block proposed from database", "commitment", lastCommitment.Hex(), "err", err)
   154  		parentBlockHash := header.ParentHash
   155  		for {
   156  			blockHeader := chain.GetHeaderByHash(parentBlockHash)
   157  			parentBlockHash = blockHeader.ParentHash
   158  			if blockHeader.Coinbase == coinbase {
   159  				break
   160  			}
   161  		}
   162  		parentBlockHashBytes = parentBlockHash.Bytes()
   163  	}
   164  	return crypto.Keccak256Hash(append(seed, parentBlockHashBytes...)), nil
   165  }
   166  
   167  // GenerateNewRandomnessAndCommitment generates a new random number and a corresponding commitment.
   168  // The random number is stored in the database, keyed by the corresponding commitment.
   169  func GenerateNewRandomnessAndCommitment(header *types.Header, state vm.StateDB, db *ethdb.Database, seed []byte) (common.Hash, error) {
   170  	commitment := common.Hash{}
   171  	randomness := crypto.Keccak256Hash(append(seed, header.ParentHash.Bytes()...))
   172  	// TODO(asa): Make an issue to not have to do this via StaticCall
   173  	_, err := contract_comm.MakeStaticCall(params.RandomRegistryId, computeCommitmentFuncABI, "computeCommitment", []interface{}{randomness}, &commitment, params.MaxGasForComputeCommitment, header, state)
   174  	err = (*db).Put(commitmentDbLocation(commitment), header.ParentHash.Bytes())
   175  	if err != nil {
   176  		log.Error("Failed to save last block parentHash to the database", "err", err)
   177  	}
   178  	return commitment, err
   179  }
   180  
   181  // RevealAndCommit performs an internal call to the EVM that reveals a
   182  // proposer's previously committed to randomness, and commits new randomness for
   183  // a future block.
   184  func RevealAndCommit(randomness, newCommitment common.Hash, proposer common.Address, header *types.Header, state vm.StateDB) error {
   185  	args := []interface{}{randomness, newCommitment, proposer}
   186  	log.Trace("Revealing and committing randomness", "randomness", randomness.Hex(), "commitment", newCommitment.Hex())
   187  	_, err := contract_comm.MakeCall(params.RandomRegistryId, revealAndCommitFuncABI, "revealAndCommit", args, nil, params.MaxGasForRevealAndCommit, zeroValue, header, state, true)
   188  	return err
   189  }
   190  
   191  // Random performs an internal call to the EVM to retrieve the current randomness from the official Random contract.
   192  func Random(header *types.Header, state vm.StateDB) (common.Hash, error) {
   193  	randomness := common.Hash{}
   194  	_, err := contract_comm.MakeStaticCall(params.RandomRegistryId, randomFuncABI, "random", []interface{}{}, &randomness, params.MaxGasForComputeCommitment, header, state)
   195  	return randomness, err
   196  }