The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
woop/drand/drand_leader.go

176 lines
5.9 KiB

package drand
import (
"bytes"
"time"
"github.com/harmony-one/harmony/crypto/bls"
protobuf "github.com/golang/protobuf/proto"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/vdf"
"github.com/harmony-one/harmony/crypto/vrf/p256"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/p2p/host"
)
const (
vdfDifficulty = 5000000 // This takes about 20s to finish the vdf
)
// WaitForEpochBlock waits for the first epoch block to run DRG on
func (dRand *DRand) WaitForEpochBlock(blockChannel chan *types.Block, stopChan chan struct{}, stoppedChan chan struct{}) {
go func() {
defer close(stoppedChan)
for {
select {
default:
// keep waiting for epoch block
newBlock := <-blockChannel
if core.IsEpochLastBlock(newBlock) {
dRand.init(newBlock)
}
pRnd := newBlock.Header().Vrf
zeros := [32]byte{}
if core.IsEpochBlock(newBlock) && !bytes.Equal(pRnd[:], zeros[:]) {
// The epoch block should contain the randomness preimage pRnd
go func() {
vdf := vdf.New(vdfDifficulty, pRnd)
outputChannel := vdf.GetOutputChannel()
start := time.Now()
vdf.Execute()
duration := time.Now().Sub(start)
utils.GetLogInstance().Info("VDF computation finished", "time spent", duration.String())
output := <-outputChannel
rndBytes := [64]byte{} // The first 32 bytes are the randomness and the last 32 bytes are the hash of the block where the corresponding pRnd was generated
copy(rndBytes[:32], output[:])
blockHash := newBlock.Hash()
copy(rndBytes[32:], blockHash[:])
dRand.RndChannel <- rndBytes
}()
}
case <-stopChan:
return
}
}
}()
}
func (dRand *DRand) init(epochBlock *types.Block) {
utils.GetLogInstance().Debug("INITING DRAND")
dRand.ResetState()
// Copy over block hash and block header data
blockHash := epochBlock.Hash()
copy(dRand.blockHash[:], blockHash[:])
msgToSend := dRand.constructInitMessage()
// Leader commit vrf itself
rand, proof := dRand.vrf(dRand.blockHash)
(*dRand.vrfs)[dRand.SelfAddress] = append(rand[:], proof...)
utils.GetLogInstance().Info("[DRG] sent init", "msg", msgToSend, "leader.PubKey", dRand.leader.ConsensusPubKey)
dRand.host.SendMessageToGroups([]p2p.GroupID{p2p.NewGroupIDByShardID(p2p.ShardID(dRand.ShardID))}, host.ConstructP2pMessage(byte(17), msgToSend))
}
// ProcessMessageLeader dispatches messages for the leader to corresponding processors.
func (dRand *DRand) ProcessMessageLeader(payload []byte) {
message := &msg_pb.Message{}
err := protobuf.Unmarshal(payload, message)
if err != nil {
utils.GetLogInstance().Error("Failed to unmarshal message payload.", "err", err, "dRand", dRand)
}
if message.GetDrand().ShardId != dRand.ShardID {
utils.GetLogInstance().Warn("Received drand message from different shard",
"myShardId", dRand.ShardID, "receivedShardId", message.GetDrand().ShardId)
return
}
switch message.Type {
case msg_pb.MessageType_DRAND_COMMIT:
dRand.processCommitMessage(message)
default:
utils.GetLogInstance().Error("Unexpected message type", "msgType", message.Type, "dRand", dRand)
}
}
// ProcessMessageValidator dispatches validator's consensus message.
func (dRand *DRand) processCommitMessage(message *msg_pb.Message) {
utils.GetLogInstance().Info("[DRG] Leader received commit")
if message.Type != msg_pb.MessageType_DRAND_COMMIT {
utils.GetLogInstance().Error("Wrong message type received", "expected", msg_pb.MessageType_DRAND_COMMIT, "got", message.Type)
return
}
dRand.mutex.Lock()
defer dRand.mutex.Unlock()
drandMsg := message.GetDrand()
senderPubKey, err := bls.BytesToBlsPublicKey(drandMsg.SenderPubkey)
if err != nil {
utils.GetLogInstance().Debug("Failed to deserialize BLS public key", "error", err)
return
}
validatorAddress := utils.GetBlsAddress(senderPubKey)
if !dRand.IsValidatorInCommittee(validatorAddress) {
utils.GetLogInstance().Error("Invalid validator", "validatorAddress", validatorAddress)
return
}
vrfs := dRand.vrfs
if len((*vrfs)) >= ((len(dRand.PublicKeys))/3 + 1) {
utils.GetLogInstance().Debug("Received additional randomness commit message", "validatorAddress", validatorAddress)
return
}
// Verify message signature
err = verifyMessageSig(senderPubKey, message)
if err != nil {
utils.GetLogInstance().Warn("[DRAND] failed to verify the message signature", "Error", err, "PubKey", senderPubKey)
return
}
rand := drandMsg.Payload[:32]
proof := drandMsg.Payload[32 : len(drandMsg.Payload)-64]
pubKeyBytes := drandMsg.Payload[len(drandMsg.Payload)-64:]
_, vrfPubKey := p256.GenerateKey()
vrfPubKey.Deserialize(pubKeyBytes)
expectedRand, err := vrfPubKey.ProofToHash(dRand.blockHash[:], proof)
if err != nil || !bytes.Equal(expectedRand[:], rand) {
utils.GetLogInstance().Error("[DRAND] Failed to verify the VRF", "error", err, "validatorAddress", validatorAddress, "expectedRand", expectedRand, "receivedRand", rand)
return
}
utils.GetLogInstance().Debug("Received new VRF commit", "numReceivedSoFar", len((*vrfs)), "validatorAddress", validatorAddress, "PublicKeys", len(dRand.PublicKeys))
(*vrfs)[validatorAddress] = drandMsg.Payload
dRand.bitmap.SetKey(senderPubKey, true) // Set the bitmap indicating that this validator signed.
if len((*vrfs)) >= ((len(dRand.PublicKeys))/3 + 1) {
// Construct pRand and initiate consensus on it
utils.GetLogInstance().Debug("[DRAND] {BINGO} Received enough randomness commit", "numReceivedSoFar", len((*vrfs)), "validatorAddress", validatorAddress, "PublicKeys", len(dRand.PublicKeys))
pRnd := [32]byte{}
// Bitwise XOR on all the submitted vrfs
for _, vrf := range *vrfs {
for i := 0; i < len(pRnd); i++ {
pRnd[i] = pRnd[i] ^ vrf[i]
}
}
dRand.PRndChannel <- append(pRnd[:], dRand.bitmap.Bitmap...)
}
}