From 2092c1d2de05d353306dfaeb1f9f0b3493d8de7f Mon Sep 17 00:00:00 2001 From: Kevin Jue Date: Thu, 7 Jan 2021 11:46:07 -0800 Subject: [PATCH] random beacon cache changes (#1278) * update the randomness commitment cache when inserting blocks into the blockchain * added comments * reverse iteration search for the commitment cache entry * unsubscribe from the newblock subscription * fixed bug and added comments * handled randomness beacon case when smart contracts are being migrated * fixed bug * fixed a bug * fixed a bug and added comments to non intuitive code * addressed from PR comments * addressed PR comments Co-authored-by: Kevin Jue --- consensus/consensus.go | 12 ++-- consensus/istanbul/backend/backend.go | 22 +++--- consensus/istanbul/backend/random.go | 69 +++++++++++++++++++ consensus/istanbul/backend/snapshot_test.go | 2 +- consensus/istanbul/backend/test_utils.go | 15 ++++- consensus/istanbul/types.go | 4 ++ consensus/istanbul/utils.go | 7 ++ contract_comm/random/random.go | 41 ++--------- core/blockchain.go | 75 ++++++++++++++++++++- core/rawdb/accessors_chain.go | 19 ++++++ eth/backend.go | 5 +- miner/miner.go | 40 ++++++++++- miner/worker.go | 55 ++++++++------- miner/worker_test.go | 5 +- 14 files changed, 287 insertions(+), 84 deletions(-) create mode 100644 consensus/istanbul/backend/random.go diff --git a/consensus/consensus.go b/consensus/consensus.go index eac3eb88db..41c6e86ae2 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -201,12 +201,16 @@ type Istanbul interface { // StopProxiedValidatorEngine stops the proxied validator engine StopProxiedValidatorEngine() error - // This is only implemented for Istanbul. - // It will update the validator set diff in the header, if the mined header is the last block of the epoch. + // UpdateValSetDiff will update the validator set diff in the header, if the mined header is the last block of the epoch. // The changes are executed inline. UpdateValSetDiff(chain ChainReader, header *types.Header, state *state.StateDB) error - // This is only implemented for Istanbul. - // It will check to see if the header is from the last block of an epoch + // IsLastBlockOfEpoch will check to see if the header is from the last block of an epoch IsLastBlockOfEpoch(header *types.Header) bool + + // ValidatorAddress will return the istanbul engine's validator address + ValidatorAddress() common.Address + + // GenerateRandomness will generate the random beacon randomness + GenerateRandomness(parentHash common.Hash) (common.Hash, common.Hash, error) } diff --git a/consensus/istanbul/backend/backend.go b/consensus/istanbul/backend/backend.go index c2fbab4a98..2c5482597f 100644 --- a/consensus/istanbul/backend/backend.go +++ b/consensus/istanbul/backend/backend.go @@ -163,13 +163,14 @@ type Backend struct { config *istanbul.Config istanbulEventMux *event.TypeMux - address common.Address // Ethereum address of the ECDSA signing key - blsAddress common.Address // Ethereum address of the BLS signing key - publicKey *ecdsa.PublicKey // The signer public key - decryptFn istanbul.DecryptFn // Decrypt function to decrypt ECIES ciphertext - signFn istanbul.SignerFn // Signer function to authorize hashes with - signBLSFn istanbul.BLSSignerFn // Signer function to authorize BLS messages - signFnMu sync.RWMutex // Protects the signer fields + address common.Address // Ethereum address of the ECDSA signing key + blsAddress common.Address // Ethereum address of the BLS signing key + publicKey *ecdsa.PublicKey // The signer public key + decryptFn istanbul.DecryptFn // Decrypt function to decrypt ECIES ciphertext + signFn istanbul.SignerFn // Signer function to authorize hashes with + signBLSFn istanbul.BLSSignerFn // Signer function to authorize BLS messages + signHashFn istanbul.HashSignerFn // Signer function to create random seed + signFnMu sync.RWMutex // Protects the signer fields core istanbulCore.Engine logger log.Logger @@ -291,6 +292,10 @@ type Backend struct { proxiedValidatorEngine proxy.ProxiedValidatorEngine proxiedValidatorEngineRunning bool proxiedValidatorEngineMu sync.RWMutex + + // RandomSeed (and it's mutex) used to generate the random beacon randomness + randomSeed []byte + randomSeedMu sync.Mutex } // IsProxy returns true if instance has proxy flag @@ -349,7 +354,7 @@ func (sb *Backend) SendDelegateSignMsgToProxiedValidator(msg []byte) error { } // Authorize implements istanbul.Backend.Authorize -func (sb *Backend) Authorize(ecdsaAddress, blsAddress common.Address, publicKey *ecdsa.PublicKey, decryptFn istanbul.DecryptFn, signFn istanbul.SignerFn, signBLSFn istanbul.BLSSignerFn) { +func (sb *Backend) Authorize(ecdsaAddress, blsAddress common.Address, publicKey *ecdsa.PublicKey, decryptFn istanbul.DecryptFn, signFn istanbul.SignerFn, signBLSFn istanbul.BLSSignerFn, signHashFn istanbul.HashSignerFn) { sb.signFnMu.Lock() defer sb.signFnMu.Unlock() @@ -359,6 +364,7 @@ func (sb *Backend) Authorize(ecdsaAddress, blsAddress common.Address, publicKey sb.decryptFn = decryptFn sb.signFn = signFn sb.signBLSFn = signBLSFn + sb.signHashFn = signHashFn sb.core.SetAddress(ecdsaAddress) } diff --git a/consensus/istanbul/backend/random.go b/consensus/istanbul/backend/random.go new file mode 100644 index 0000000000..062794281d --- /dev/null +++ b/consensus/istanbul/backend/random.go @@ -0,0 +1,69 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package backend + +import ( + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contract_comm/random" + "github.com/ethereum/go-ethereum/crypto" +) + +// String for creating the random seed +var randomSeedString = []byte("Randomness seed string") + +// GenerateRandomness will generate the random beacon randomness +func (sb *Backend) GenerateRandomness(parentHash common.Hash) (common.Hash, common.Hash, error) { + logger := sb.logger.New("func", "GenerateRandomness") + + if !random.IsRunning() { + return common.Hash{}, common.Hash{}, nil + } + + sb.randomSeedMu.Lock() + if sb.randomSeed == nil { + var err error + sb.randomSeed, err = sb.signHashFn(accounts.Account{Address: sb.address}, common.BytesToHash(randomSeedString).Bytes()) + if err != nil { + logger.Error("Failed to create randomSeed", "err", err) + sb.randomSeedMu.Unlock() + return common.Hash{}, common.Hash{}, err + } + } + sb.randomSeedMu.Unlock() + + randomness := crypto.Keccak256Hash(append(sb.randomSeed, parentHash.Bytes()...)) + + // Retrieve the head block's header and state. + // The logic to compute the commitment via the randomness is in the random smart contract. + // That logic is stateless, so passing in any block header and state is fine. There is a TODO for + // that commitment computation logic to be removed fromthe random smart contract. + currentBlock := sb.currentBlock() + currentState, err := sb.stateAt(currentBlock.Hash()) + if err != nil { + logger.Error("Failed to retrieve current state", "err", err) + return common.Hash{}, common.Hash{}, err + } + + commitment, err := random.ComputeCommitment(currentBlock.Header(), currentState, randomness) + if err != nil { + logger.Error("Failed to compute commitment", "err", err) + return common.Hash{}, common.Hash{}, err + } + + return randomness, commitment, nil +} diff --git a/consensus/istanbul/backend/snapshot_test.go b/consensus/istanbul/backend/snapshot_test.go index 4e172179b1..1055ffe9ba 100644 --- a/consensus/istanbul/backend/snapshot_test.go +++ b/consensus/istanbul/backend/snapshot_test.go @@ -260,7 +260,7 @@ func TestValSetChange(t *testing.T) { privateKey := accounts.accounts[tt.validators[0]] address := crypto.PubkeyToAddress(privateKey.PublicKey) - engine.Authorize(address, address, &privateKey.PublicKey, DecryptFn(privateKey), SignFn(privateKey), SignBLSFn(privateKey)) + engine.Authorize(address, address, &privateKey.PublicKey, DecryptFn(privateKey), SignFn(privateKey), SignBLSFn(privateKey), SignHashFn(privateKey)) chain.AddHeader(0, genesis.ToBlock(nil).Header()) diff --git a/consensus/istanbul/backend/test_utils.go b/consensus/istanbul/backend/test_utils.go index fdd84c7f1a..3e60237374 100644 --- a/consensus/istanbul/backend/test_utils.go +++ b/consensus/istanbul/backend/test_utils.go @@ -57,7 +57,8 @@ func newBlockChainWithKeys(isProxy bool, proxiedValAddress common.Address, isPro decryptFn := DecryptFn(privateKey) signerFn := SignFn(privateKey) signerBLSFn := SignBLSFn(privateKey) - b.Authorize(address, address, &publicKey, decryptFn, signerFn, signerBLSFn) + signerHashFn := SignHashFn(privateKey) + b.Authorize(address, address, &publicKey, decryptFn, signerFn, signerBLSFn, signerHashFn) } else { proxyNodeKey, _ := crypto.GenerateKey() publicKey = proxyNodeKey.PublicKey @@ -301,6 +302,16 @@ func SignBLSFn(key *ecdsa.PrivateKey) istanbul.BLSSignerFn { } } +func SignHashFn(key *ecdsa.PrivateKey) istanbul.HashSignerFn { + if key == nil { + key, _ = generatePrivateKey() + } + + return func(_ accounts.Account, data []byte) ([]byte, error) { + return crypto.Sign(data, key) + } +} + // this will return an aggregate sig by the BLS keys corresponding to the `keys` array over the // block's hash, on consensus round 0, without a composite hasher func signBlock(keys []*ecdsa.PrivateKey, block *types.Block) types.IstanbulAggregatedSeal { @@ -346,7 +357,7 @@ func newBackend() (b *Backend) { key, _ := generatePrivateKey() address := crypto.PubkeyToAddress(key.PublicKey) - b.Authorize(address, address, &key.PublicKey, DecryptFn(key), SignFn(key), SignBLSFn(key)) + b.Authorize(address, address, &key.PublicKey, DecryptFn(key), SignFn(key), SignBLSFn(key), SignHashFn(key)) return } diff --git a/consensus/istanbul/types.go b/consensus/istanbul/types.go index 0b8365cccf..63da317ee0 100644 --- a/consensus/istanbul/types.go +++ b/consensus/istanbul/types.go @@ -44,6 +44,10 @@ type SignerFn func(accounts.Account, string, []byte) ([]byte, error) // backing account using BLS with a direct or composite hasher type BLSSignerFn func(accounts.Account, []byte, []byte, bool) (blscrypto.SerializedSignature, error) +// HashSignerFn is a signer callback function to request a hash to be signed by a +// backing account. +type HashSignerFn func(accounts.Account, []byte) ([]byte, error) + // UptimeEntry contains the uptime score of a validator during an epoch as well as the // last block they signed on type UptimeEntry struct { diff --git a/consensus/istanbul/utils.go b/consensus/istanbul/utils.go index 2f552c8911..2a37cc3678 100644 --- a/consensus/istanbul/utils.go +++ b/consensus/istanbul/utils.go @@ -216,3 +216,10 @@ func GetNodeID(enodeURL string) (*enode.ID, error) { id := node.ID() return &id, nil } + +// RandomnessCommitmentDBLocation will return the key for where the +// given commitment's cached key-value entry +func RandomnessCommitmentDBLocation(commitment common.Hash) []byte { + dbRandomnessPrefix := []byte("db-randomness-prefix") + return append(dbRandomnessPrefix, commitment.Bytes()...) +} diff --git a/contract_comm/random/random.go b/contract_comm/random/random.go index 155dbc50a2..e33a6b8cb8 100644 --- a/contract_comm/random/random.go +++ b/contract_comm/random/random.go @@ -7,13 +7,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/contract_comm" "github.com/ethereum/go-ethereum/contract_comm/errors" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) @@ -133,13 +130,8 @@ var ( randomFuncABI, _ = abi.JSON(strings.NewReader(randomAbi)) historicRandomFuncABI, _ = abi.JSON(strings.NewReader(historicRandomAbi)) zeroValue = common.Big0 - dbRandomnessPrefix = []byte("db-randomness-prefix") ) -func commitmentDbLocation(commitment common.Hash) []byte { - return append(dbRandomnessPrefix, commitment.Bytes()...) -} - func address() *common.Address { randomAddress, err := contract_comm.GetRegisteredAddress(params.RandomRegistryId, nil, nil) if err == errors.ErrSmartContractNotDeployed || err == errors.ErrRegistryContractNotDeployed { @@ -155,11 +147,8 @@ func IsRunning() bool { return randomAddress != nil && *randomAddress != common.ZeroAddress } -// GetLastRandomness returns up the last randomness we committed to by first -// looking up our last commitment in the smart contract, and then finding the -// corresponding preimage in a (commitment => randomness) mapping we keep in the -// database. -func GetLastRandomness(coinbase common.Address, db *ethdb.Database, header *types.Header, state vm.StateDB, chain consensus.ChainReader, seed []byte) (common.Hash, error) { +// GetLastCommitment returns up the last commitment in the smart contract +func GetLastCommitment(coinbase common.Address, header *types.Header, state vm.StateDB) (common.Hash, error) { lastCommitment := common.Hash{} _, err := contract_comm.MakeStaticCall(params.RandomRegistryId, commitmentsFuncABI, "commitments", []interface{}{coinbase}, &lastCommitment, params.MaxGasForCommitments, header, state) if err != nil { @@ -169,30 +158,14 @@ func GetLastRandomness(coinbase common.Address, db *ethdb.Database, header *type if (lastCommitment == common.Hash{}) { log.Debug("Unable to find last randomness commitment in smart contract") - return common.Hash{}, nil } - parentBlockHashBytes, err := (*db).Get(commitmentDbLocation(lastCommitment)) - if err != nil { - log.Warn("Failed to get last block proposed from database", "commitment", lastCommitment.Hex(), "err", err) - parentBlockHash := header.ParentHash - for { - blockHeader := chain.GetHeaderByHash(parentBlockHash) - parentBlockHash = blockHeader.ParentHash - if blockHeader.Coinbase == coinbase { - break - } - } - parentBlockHashBytes = parentBlockHash.Bytes() - } - return crypto.Keccak256Hash(append(seed, parentBlockHashBytes...)), nil + return lastCommitment, nil } -// GenerateNewRandomnessAndCommitment generates a new random number and a corresponding commitment. -// The random number is stored in the database, keyed by the corresponding commitment. -func GenerateNewRandomnessAndCommitment(header *types.Header, state vm.StateDB, db *ethdb.Database, seed []byte) (common.Hash, error) { +// ComputeCommitment calulcates the commitment for a given randomness. +func ComputeCommitment(header *types.Header, state vm.StateDB, randomness common.Hash) (common.Hash, error) { commitment := common.Hash{} - randomness := crypto.Keccak256Hash(append(seed, header.ParentHash.Bytes()...)) // TODO(asa): Make an issue to not have to do this via StaticCall _, err := contract_comm.MakeStaticCall(params.RandomRegistryId, computeCommitmentFuncABI, "computeCommitment", []interface{}{randomness}, &commitment, params.MaxGasForComputeCommitment, header, state) if err != nil { @@ -200,10 +173,6 @@ func GenerateNewRandomnessAndCommitment(header *types.Header, state vm.StateDB, return common.Hash{}, err } - err = (*db).Put(commitmentDbLocation(commitment), header.ParentHash.Bytes()) - if err != nil { - log.Error("Failed to save last block parentHash to the database", "err", err) - } return commitment, err } diff --git a/core/blockchain.go b/core/blockchain.go index c4a7efe4fc..5a3daccb37 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -74,6 +74,7 @@ var ( blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) errInsertionInterrupted = errors.New("insertion is interrupted") + errCommitmentNotFound = errors.New("randomness commitment not found") ) const ( @@ -1321,7 +1322,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // We are going to update the uptime tally. // TODO find a better way of checking if it's istanbul - if _, isIstanbul := bc.engine.(consensus.Istanbul); isIstanbul { + randomCommitment := common.Hash{} + if istEngine, isIstanbul := bc.engine.(consensus.Istanbul); isIstanbul { if hash := bc.GetCanonicalHash(block.NumberU64()); (hash != common.Hash{} && hash != block.Hash()) { log.Error("Found two blocks with same height", "old", hash, "new", block.Hash()) @@ -1355,6 +1357,21 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. log.Trace("WritingBlockWithState with block number less than a block we previously wrote", "latestUptimeBlock", uptime.LatestBlock, "blockNumber", block.NumberU64()) } } + + blockAuthor, err := istEngine.Author(block.Header()) + if err != nil { + log.Warn("Unable to retrieve the author for block", "blockNum", block.NumberU64(), "err", err) + } + + if blockAuthor == istEngine.ValidatorAddress() { + // Calculate the randomness commitment + _, randomCommitment, err = istEngine.GenerateRandomness(block.ParentHash()) + + if err != nil { + log.Error("Couldn't generate the randomness for the block", "blockNum", block.NumberU64(), "err", err) + return NonStatTy, err + } + } } // Calculate the total difficulty of the block @@ -1376,6 +1393,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. rawdb.WriteBlock(blockBatch, block) rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) rawdb.WritePreimages(blockBatch, state.Preimages()) + if (randomCommitment != common.Hash{}) { + // Note that the random commitment cache entry is never transferred over to the freezer, + // unlike all of the other saved data within this batch write + rawdb.WriteRandomCommitmentCache(blockBatch, randomCommitment, block.ParentHash()) + } if err := blockBatch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } @@ -2316,3 +2338,54 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription { return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) } + +// RecoverRandomnessCache will do a search for the block that was used to generate the given commitment. +// Specifically, it will find the block that this node authored and that block's parent hash is used to +// created the commitment. The search is a reverse iteration of the node's local chain starting at +// the block where it's hash is the given commitmentBlockHash. +func (bc *BlockChain) RecoverRandomnessCache(commitment common.Hash, commitmentBlockHash common.Hash) error { + istEngine, isIstanbul := bc.engine.(consensus.Istanbul) + if !isIstanbul { + return nil + } + + log.Info("Recovering randomness cache entry", "commitment", commitment.Hex(), "initial block search", commitmentBlockHash.Hex()) + + blockHashIter := commitmentBlockHash + var parentHash common.Hash + for { + blockHeader := bc.GetHeaderByHash(blockHashIter) + + // We got to the genesis block, so search didn't find the latest + // block authored by this validator. + if blockHeader.Number.Uint64() == 0 { + return errCommitmentNotFound + } + + blockAuthor, err := istEngine.Author(blockHeader) + if err != nil { + log.Error("Error is retrieving block author", "block number", blockHeader.Number.Uint64(), "block hash", blockHeader.Hash(), "error", err) + return err + } + + if blockAuthor == istEngine.ValidatorAddress() { + parentHash = blockHeader.ParentHash + break + } + + blockHashIter = blockHeader.ParentHash + } + + // Calculate the randomness commitment + // The calculation is stateless (e.g. it's just a hash operation of a string), so any passed in block header and state + // will do. Will use the previously fetched current header and state. + _, randomCommitment, err := istEngine.GenerateRandomness(parentHash) + if err != nil { + log.Error("Couldn't generate the randomness from the parent hash", "parent hash", parentHash, "err", err) + return err + } + + rawdb.WriteRandomCommitmentCache(bc.db, randomCommitment, parentHash) + + return nil +} diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index d4feb2fb5c..113578d38e 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -659,6 +659,25 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number DeleteTd(db, hash, number) } +// WriteRandomCommitmentCache will write a random beacon commitment's associated block parent hash +// (which is used to calculate the commitmented random number). +func WriteRandomCommitmentCache(db ethdb.KeyValueWriter, commitment common.Hash, parentHash common.Hash) { + if err := db.Put(istanbul.RandomnessCommitmentDBLocation(commitment), parentHash.Bytes()); err != nil { + log.Crit("Failed to store randomness commitment cache entry", "err", err) + } +} + +// ReadRandomCommitmentCache will retun the random beacon commit's associated block parent hash. +func ReadRandomCommitmentCache(db ethdb.Reader, commitment common.Hash) common.Hash { + parentHash, err := db.Get(istanbul.RandomnessCommitmentDBLocation(commitment)) + if err != nil { + log.Warn("Error in trying to retrieve randomness commitment cache entry", "error", err) + return common.Hash{} + } + + return common.BytesToHash(parentHash) +} + // FindCommonAncestor returns the last common ancestor of two block headers func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { diff --git a/eth/backend.go b/eth/backend.go index bcaadbb16e..7951af9744 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -236,7 +236,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { }) } - eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, &chainDb) + eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, chainDb) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth} @@ -487,7 +487,8 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("BLSbase account unavailable locally", "err", err) return fmt.Errorf("BLS signer missing: %v", err) } - istanbul.Authorize(eb, blsbase, publicKey, wallet.Decrypt, wallet.SignData, blswallet.SignBLS) + + istanbul.Authorize(eb, blsbase, publicKey, wallet.Decrypt, wallet.SignData, blswallet.SignBLS, wallet.SignHash) if istanbul.IsProxiedValidator() { if err := istanbul.StartProxiedValidatorEngine(); err != nil { diff --git a/miner/miner.go b/miner/miner.go index d919eaa6c3..2a5bd6c5cf 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -27,7 +27,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/contract_comm/random" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" @@ -65,18 +67,20 @@ type Miner struct { eth Backend engine consensus.Engine exitCh chan struct{} + db ethdb.Database // Needed for randomness canStart int32 // can start indicates whether we can start the mining operation shouldStart int32 // should start indicates whether we should start after sync } -func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool, db *ethdb.Database) *Miner { +func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool, db ethdb.Database) *Miner { miner := &Miner{ eth: eth, mux: mux, engine: engine, exitCh: make(chan struct{}), worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, db, true), + db: db, canStart: 1, } go miner.update() @@ -107,8 +111,40 @@ func (miner *Miner) update() { log.Info("Mining aborted due to sync") } case downloader.DoneEvent, downloader.FailedEvent: - shouldStart := atomic.LoadInt32(&miner.shouldStart) == 1 + // If this is using the istanbul consensus engine, then we need to check + // for the randomness cache for the randomness beacon protocol + _, isIstanbul := miner.engine.(consensus.Istanbul) + if isIstanbul { + // getCurrentBlockAndState + currentBlock := miner.eth.BlockChain().CurrentBlock() + currentHeader := currentBlock.Header() + currentState, err := miner.eth.BlockChain().StateAt(currentBlock.Root()) + if err != nil { + log.Error("Error in retrieving state", "block hash", currentHeader.Hash(), "error", err) + return + } + + if currentHeader.Number.Uint64() > 0 { + // Check to see if we already have the commitment cache + lastCommitment, err := random.GetLastCommitment(miner.coinbase, currentHeader, currentState) + if err != nil { + log.Error("Error in retrieving last commitment", "error", err) + return + } + + // If there is a non empty last commitment and if we don't have that commitment's + // cache entry, then we need to recover it. + if (lastCommitment != common.Hash{}) && (rawdb.ReadRandomCommitmentCache(miner.db, lastCommitment) == common.Hash{}) { + err := miner.eth.BlockChain().RecoverRandomnessCache(lastCommitment, currentBlock.Hash()) + if err != nil { + log.Error("Error in recovering randomness cache", "error", err) + return + } + } + } + } + shouldStart := atomic.LoadInt32(&miner.shouldStart) == 1 atomic.StoreInt32(&miner.canStart, 1) atomic.StoreInt32(&miner.shouldStart, 0) if shouldStart { diff --git a/miner/worker.go b/miner/worker.go index 8c4c8f64fe..3e2af761dd 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -24,7 +24,6 @@ import ( "time" mapset "github.com/deckarep/golang-set" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -32,6 +31,7 @@ import ( gpm "github.com/ethereum/go-ethereum/contract_comm/gasprice_minimum" "github.com/ethereum/go-ethereum/contract_comm/random" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -80,11 +80,6 @@ const ( staleThreshold = 7 ) -var ( - randomSeedString = []byte("Randomness seed string") - randomSeed []byte -) - // environment is the worker's current environment and holds all of the current state information. type environment struct { signer types.Signer @@ -186,10 +181,10 @@ type worker struct { resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval. // Needed for randomness - db *ethdb.Database + db ethdb.Database } -func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool, db *ethdb.Database, init bool) *worker { +func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool, db ethdb.Database, init bool) *worker { worker := &worker{ config: config, chainConfig: chainConfig, @@ -867,40 +862,48 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) // Play our part in generating the random beacon. if w.isRunning() && random.IsRunning() { - if randomSeed == nil { - account := accounts.Account{Address: w.coinbase} - wallet, err := w.eth.AccountManager().Find(account) - if err == nil { - // TODO: Use SignData instead - randomSeed, err = wallet.SignHash(account, common.BytesToHash(randomSeedString).Bytes()) - } - if err != nil { - log.Error("Unable to create random seed", "err", err) - return - } + istanbul, ok := w.engine.(consensus.Istanbul) + if !ok { + log.Crit("Istanbul consensus engine must be in use for the randomness beacon") } - lastRandomness, err := random.GetLastRandomness(w.coinbase, w.db, w.current.header, w.current.state, w.chain, randomSeed) + lastCommitment, err := random.GetLastCommitment(w.coinbase, w.current.header, w.current.state) if err != nil { - log.Error("Failed to get last randomness", "err", err) + log.Error("Failed to get last commitment", "err", err) return } - commitment, err := random.GenerateNewRandomnessAndCommitment(w.current.header, w.current.state, w.db, randomSeed) + lastRandomness := common.Hash{} + if (lastCommitment != common.Hash{}) { + lastRandomnessParentHash := rawdb.ReadRandomCommitmentCache(w.db, lastCommitment) + if (lastRandomnessParentHash == common.Hash{}) { + log.Error("Failed to get last randomness cache entry") + return + } + + var err error + lastRandomness, _, err = istanbul.GenerateRandomness(lastRandomnessParentHash) + if err != nil { + log.Error("Failed to generate last randomness", "err", err) + return + } + } + + _, newCommitment, err := istanbul.GenerateRandomness(w.current.header.ParentHash) if err != nil { - log.Error("Failed to generate randomness commitment", "err", err) + log.Error("Failed to generate new randomness", "err", err) return } - err = random.RevealAndCommit(lastRandomness, commitment, w.coinbase, w.current.header, w.current.state) + err = random.RevealAndCommit(lastRandomness, newCommitment, w.coinbase, w.current.header, w.current.state) if err != nil { - log.Error("Failed to reveal and commit randomness", "randomness", lastRandomness.Hex(), "commitment", commitment.Hex(), "err", err) + log.Error("Failed to reveal and commit randomness", "randomness", lastRandomness.Hex(), "commitment", newCommitment.Hex(), "err", err) return } // always true (EIP158) w.current.state.IntermediateRoot(true) - w.current.randomness = &types.Randomness{Revealed: lastRandomness, Committed: commitment} + w.current.randomness = &types.Randomness{Revealed: lastRandomness, Committed: newCommitment} } else { w.current.randomness = &types.EmptyRandomness } diff --git a/miner/worker_test.go b/miner/worker_test.go index 20a930512c..5bc2f3fd81 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -181,7 +181,7 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens if shouldAddPendingTxs { backend.txPool.AddLocals(pendingTxs) } - w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, &backend.db, false) + w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, backend.db, false) w.setEtherbase(testBankAddress) return w, backend } @@ -247,6 +247,7 @@ func getAuthorizedIstanbulEngine() consensus.Istanbul { signerFn := backend.SignFn(testBankKey) signBLSFn := backend.SignBLSFn(testBankKey) + signHashFn := backend.SignHashFn(testBankKey) address := crypto.PubkeyToAddress(testBankKey.PublicKey) config := istanbul.DefaultConfig @@ -258,7 +259,7 @@ func getAuthorizedIstanbulEngine() consensus.Istanbul { engine := istanbulBackend.New(config, rawdb.NewMemoryDatabase()) engine.(*istanbulBackend.Backend).SetBroadcaster(&consensustest.MockBroadcaster{}) engine.(*istanbulBackend.Backend).SetP2PServer(consensustest.NewMockP2PServer(nil)) - engine.(*istanbulBackend.Backend).Authorize(address, address, &testBankKey.PublicKey, decryptFn, signerFn, signBLSFn) + engine.(*istanbulBackend.Backend).Authorize(address, address, &testBankKey.PublicKey, decryptFn, signerFn, signBLSFn, signHashFn) engine.(*istanbulBackend.Backend).StartAnnouncing() return engine }