Skip to content

Commit

Permalink
Merge pull request #3682 from rlan35/main
Browse files Browse the repository at this point in the history
Add vrf proposal and verification in block logic
  • Loading branch information
rlan35 committed May 6, 2021
2 parents d8b5d16 + a0cc096 commit a503ea7
Show file tree
Hide file tree
Showing 21 changed files with 313 additions and 238 deletions.
55 changes: 1 addition & 54 deletions consensus/consensus_service.go
Expand Up @@ -12,7 +12,6 @@ import (
protobuf "github.com/golang/protobuf/proto"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
"github.com/harmony-one/harmony/block"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/consensus/signature"
Expand Down Expand Up @@ -242,49 +241,6 @@ func (consensus *Consensus) ReadSignatureBitmapPayload(
)
}

// retrieve corresponding blsPublicKey from Coinbase Address
func (consensus *Consensus) getLeaderPubKeyFromCoinbase(
header *block.Header,
) (*bls.PublicKeyWrapper, error) {
shardState, err := consensus.Blockchain.ReadShardState(header.Epoch())
if err != nil {
return nil, errors.Wrapf(err, "cannot read shard state %v %s",
header.Epoch(),
header.Coinbase().Hash().Hex(),
)
}

committee, err := shardState.FindCommitteeByID(header.ShardID())
if err != nil {
return nil, err
}

committerKey := new(bls_core.PublicKey)
isStaking := consensus.Blockchain.Config().IsStaking(header.Epoch())
for _, member := range committee.Slots {
if isStaking {
// After staking the coinbase address will be the address of bls public key
if utils.GetAddressFromBLSPubKeyBytes(member.BLSPublicKey[:]) == header.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
} else {
if member.EcdsaAddress == header.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
}
}
return nil, errors.Errorf(
"cannot find corresponding BLS Public Key coinbase %s",
header.Coinbase().Hex(),
)
}

// UpdateConsensusInformation will update shard information (epoch, publicKeys, blockNum, viewID)
// based on the local blockchain. It is called in two cases for now:
// 1. consensus object initialization. because of current dependency where chainreader is only available
Expand Down Expand Up @@ -424,7 +380,7 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
// a solution to take care of this case because the coinbase of the latest block doesn't really represent the
// the real current leader in case of M1 view change.
if !curHeader.IsLastBlockInEpoch() && curHeader.Number().Uint64() != 0 {
leaderPubKey, err := consensus.getLeaderPubKeyFromCoinbase(curHeader)
leaderPubKey, err := chain.GetLeaderPubKeyFromCoinbase(consensus.Blockchain, curHeader)
if err != nil || leaderPubKey == nil {
consensus.getLogger().Error().Err(err).
Msg("[UpdateConsensusInformation] Unable to get leaderPubKey from coinbase")
Expand Down Expand Up @@ -481,15 +437,6 @@ func (consensus *Consensus) IsLeader() bool {
return false
}

// NeedsRandomNumberGeneration returns true if the current epoch needs random number generation
func (consensus *Consensus) NeedsRandomNumberGeneration(epoch *big.Int) bool {
if consensus.ShardID == 0 && epoch.Uint64() >= shard.Schedule.RandomnessStartingEpoch() {
return true
}

return false
}

// SetViewIDs set both current view ID and view changing ID to the height
// of the blockchain. It is used during client startup to recover the state
func (consensus *Consensus) SetViewIDs(height uint64) {
Expand Down
144 changes: 13 additions & 131 deletions consensus/consensus_v2.go
Expand Up @@ -304,7 +304,6 @@ func (consensus *Consensus) Start(
consensus.consensusTimeout[timeoutBootstrap].Start()
consensus.getLogger().Info().Msg("[ConsensusMainLoop] Start bootstrap timeout (only once)")

vdfInProgress := false
// Set up next block due time.
consensus.NextBlockDue = time.Now().Add(consensus.BlockPeriod)
start := false
Expand Down Expand Up @@ -402,79 +401,6 @@ func (consensus *Consensus) Start(
// Update time due for next block
consensus.NextBlockDue = time.Now().Add(consensus.BlockPeriod)

//VRF/VDF is only generated in the beacon chain
if consensus.NeedsRandomNumberGeneration(newBlock.Header().Epoch()) {
// generate VRF if the current block has a new leader
if !consensus.Blockchain.IsSameLeaderAsPreviousBlock(newBlock) {
vrfBlockNumbers, err := consensus.Blockchain.ReadEpochVrfBlockNums(newBlock.Header().Epoch())
if err != nil {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] no VRF block number from local db")
}

//check if VRF is already generated for the current block
vrfAlreadyGenerated := false
for _, v := range vrfBlockNumbers {
if v == newBlock.NumberU64() {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] VRF is already generated for this block")
vrfAlreadyGenerated = true
break
}
}

if !vrfAlreadyGenerated {
//generate a new VRF for the current block
vrfBlockNumbers := consensus.GenerateVrfAndProof(newBlock, vrfBlockNumbers)

//generate a new VDF for the current epoch if there are enough VRFs in the current epoch
//note that >= instead of == is used, because it is possible the current leader
//can commit this block, go offline without finishing VDF
if (!vdfInProgress) && len(vrfBlockNumbers) >= consensus.VdfSeedSize() {
//check local database to see if there's a VDF generated for this epoch
//generate a VDF if no blocknum is available
_, err := consensus.Blockchain.ReadEpochVdfBlockNum(newBlock.Header().Epoch())
if err != nil {
consensus.GenerateVdfAndProof(newBlock, vrfBlockNumbers)
vdfInProgress = true
}
}
}
}

vdfOutput, seed, err := consensus.GetNextRnd()
if err == nil {
vdfInProgress = false
// Verify the randomness
vdfObject := vdf_go.New(shard.Schedule.VdfDifficulty(), seed)
if !vdfObject.Verify(vdfOutput) {
consensus.getLogger().Warn().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] failed to verify the VDF output")
} else {
//write the VDF only if VDF has not been generated
_, err := consensus.Blockchain.ReadEpochVdfBlockNum(newBlock.Header().Epoch())
if err == nil {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] VDF has already been generated previously")
} else {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] Generated a new VDF")
newBlock.AddVdf(vdfOutput[:])
}
}
}
}

startTime = time.Now()
consensus.msgSender.Reset(newBlock.NumberU64())

Expand Down Expand Up @@ -774,78 +700,34 @@ func (consensus *Consensus) postCatchup(initBN uint64) {
}

// GenerateVrfAndProof generates new VRF/Proof from hash of previous block
func (consensus *Consensus) GenerateVrfAndProof(newBlock *types.Block, vrfBlockNumbers []uint64) []uint64 {
func (consensus *Consensus) GenerateVrfAndProof(newHeader *block.Header) error {
key, err := consensus.GetConsensusLeaderPrivateKey()
if err != nil {
consensus.getLogger().Error().
Err(err).
Msg("[GenerateVrfAndProof] VRF generation error")
return vrfBlockNumbers
return errors.New("[GenerateVrfAndProof] no leader private key provided")
}
sk := vrf_bls.NewVRFSigner(key.Pri)
blockHash := [32]byte{}
previousHeader := consensus.Blockchain.GetHeaderByNumber(
newBlock.NumberU64() - 1,
)
if previousHeader == nil {
return vrfBlockNumbers
}
previousHash := previousHeader.Hash()
copy(blockHash[:], previousHash[:])

vrf, proof := sk.Evaluate(blockHash[:])
newBlock.AddVrf(append(vrf[:], proof...))

consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Int("Num of VRF", len(vrfBlockNumbers)).
Msg("[ConsensusMainLoop] Leader generated a VRF")

return vrfBlockNumbers
}

// ValidateVrfAndProof validates a VRF/Proof from hash of previous block
func (consensus *Consensus) ValidateVrfAndProof(headerObj *block.Header) bool {
vrfPk := vrf_bls.NewVRFVerifier(consensus.LeaderPubKey.Object)
var blockHash [32]byte
previousHeader := consensus.Blockchain.GetHeaderByNumber(
headerObj.Number().Uint64() - 1,
newHeader.Number().Uint64() - 1,
)
if previousHeader == nil {
return false
return errors.New("[GenerateVrfAndProof] no parent header found")
}

previousHash := previousHeader.Hash()
copy(blockHash[:], previousHash[:])
vrfProof := [96]byte{}
copy(vrfProof[:], headerObj.Vrf()[32:])
hash, err := vrfPk.ProofToHash(blockHash[:], vrfProof[:])

if err != nil {
consensus.getLogger().Warn().
Err(err).
Str("MsgBlockNum", headerObj.Number().String()).
Msg("[OnAnnounce] VRF verification error")
return false
vrf, proof := sk.Evaluate(previousHash[:])
if proof == nil {
return errors.New("[GenerateVrfAndProof] failed to generate vrf")
}

if !bytes.Equal(hash[:], headerObj.Vrf()[:32]) {
consensus.getLogger().Warn().
Str("MsgBlockNum", headerObj.Number().String()).
Msg("[OnAnnounce] VRF proof is not valid")
return false
}
newHeader.SetVrf(append(vrf[:], proof...))

vrfBlockNumbers, _ := consensus.Blockchain.ReadEpochVrfBlockNums(
headerObj.Epoch(),
)
consensus.getLogger().Info().
Str("MsgBlockNum", headerObj.Number().String()).
Int("Number of VRF", len(vrfBlockNumbers)).
Msg("[OnAnnounce] validated a new VRF")
Uint64("BlockNum", newHeader.Number().Uint64()).
Uint64("Epoch", newHeader.Epoch().Uint64()).
Bytes("VRF+Proof", newHeader.Vrf()).
Msg("[GenerateVrfAndProof] Leader generated a VRF")

return true
return nil
}

// GenerateVdfAndProof generates new VDF/Proof from VRFs in the current epoch
Expand Down
3 changes: 3 additions & 0 deletions consensus/engine/consensus_engine.go
Expand Up @@ -105,6 +105,9 @@ type Engine interface {
// VerifyShardState verifies the shard state during epoch transition is valid
VerifyShardState(chain ChainReader, beacon ChainReader, header *block.Header) error

// VerifyVRF verifies the vrf of the block
VerifyVRF(chain ChainReader, header *block.Header) error

// Beaconchain provides the handle for Beaconchain
Beaconchain() ChainReader

Expand Down
4 changes: 3 additions & 1 deletion consensus/view_change.go
Expand Up @@ -5,6 +5,8 @@ import (
"sync"
"time"

"github.com/harmony-one/harmony/internal/chain"

"github.com/harmony-one/harmony/crypto/bls"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -174,7 +176,7 @@ func (consensus *Consensus) getNextLeaderKey(viewID uint64) *bls.PublicKeyWrappe
stuckBlockViewID := curHeader.ViewID().Uint64() + 1
gap = int(viewID - stuckBlockViewID)
// this is the truth of the leader based on blockchain blocks
lastLeaderPubKey, err = consensus.getLeaderPubKeyFromCoinbase(curHeader)
lastLeaderPubKey, err = chain.GetLeaderPubKeyFromCoinbase(consensus.Blockchain, curHeader)
if err != nil || lastLeaderPubKey == nil {
consensus.getLogger().Error().Err(err).
Msg("[getNextLeaderKey] Unable to get leaderPubKey from coinbase. Set it to consensus.LeaderPubKey")
Expand Down
1 change: 1 addition & 0 deletions core/blockchain.go
Expand Up @@ -2890,6 +2890,7 @@ func (bc *BlockChain) DelegatorsInformation(addr common.Address) []*staking.Dele
}

// GetECDSAFromCoinbase retrieve corresponding ecdsa address from Coinbase Address
// TODO: optimize this func by adding cache etc.
func (bc *BlockChain) GetECDSAFromCoinbase(header *block.Header) (common.Address, error) {
// backward compatibility: before isStaking epoch, coinbase address is the ecdsa address
coinbase := header.Coinbase()
Expand Down
3 changes: 3 additions & 0 deletions crypto/vrf/bls/bls_vrf.go
Expand Up @@ -65,6 +65,9 @@ func (k *PrivateKey) Evaluate(alpha []byte) ([32]byte, []byte) {
//pi = VRF_prove(SK, alpha)
msgHash := sha256.Sum256(alpha)
pi := k.SignHash(msgHash[:])
if pi == nil {
return [32]byte{}, nil
}

//hash the signature and output as VRF beta
//beta = VRF_proof2hash(pi)
Expand Down
3 changes: 3 additions & 0 deletions hmy/downloader/adapter_test.go
Expand Up @@ -134,6 +134,9 @@ func (e *dummyEngine) VerifyHeaderSignature(engine.ChainReader, *block.Header, b
func (e *dummyEngine) VerifyCrossLink(engine.ChainReader, types.CrossLink) error {
return nil
}
func (e *dummyEngine) VerifyVRF(chain engine.ChainReader, header *block.Header) error {
return nil
}
func (e *dummyEngine) VerifyHeaders(engine.ChainReader, []*block.Header, []bool) (chan<- struct{}, <-chan error) {
return nil, nil
}
Expand Down

0 comments on commit a503ea7

Please sign in to comment.