diff --git a/api/service/consensus/service.go b/api/service/consensus/service.go index 2cb9c7c68..ce64d354d 100644 --- a/api/service/consensus/service.go +++ b/api/service/consensus/service.go @@ -25,6 +25,7 @@ func (s *Service) StartService() { s.stopChan = make(chan struct{}) s.stoppedChan = make(chan struct{}) s.consensus.WaitForNewBlock(s.blockChannel, s.stopChan, s.stoppedChan, s.startChan) + s.consensus.WaitForNewRandomness() } // StopService stops consensus service. diff --git a/cmd/harmony.go b/cmd/harmony.go index fa260b10a..2a2727864 100644 --- a/cmd/harmony.go +++ b/cmd/harmony.go @@ -270,6 +270,7 @@ func main() { // TODO: put this in a better place other than main. dRand := drand.New(host, shardID, peers, leader, currentNode.ConfirmedBlockChannel) currentNode.Consensus.RegisterPRndChannel(dRand.PRndChannel) + currentNode.Consensus.RegisterRndChannel(dRand.RndChannel) currentNode.DRand = dRand // If there is a client configured in the node list. diff --git a/consensus/consensus.go b/consensus/consensus.go index 7eae8f694..4c985c0d1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -99,6 +99,9 @@ type Consensus struct { // Channel for DRG protocol to send pRnd (preimage of randomness resulting from combined vrf randomnesses) to consensus. The first 32 bytes are randomness, the rest is for bitmap. PRndChannel chan []byte + // Channel for DRG protocol to send the final randomness to consensus. The first 32 bytes are the randomness and the last 32 bytes are the hash of the block where the corresponding pRnd was generated + RndChannel chan [64]byte + pendingRnds [][64]byte // A list of pending randomness uniqueIDInstance *utils.UniqueValidatorID @@ -140,6 +143,29 @@ func (consensus *Consensus) UpdateConsensusID(consensusID uint32) { } } +// WaitForNewRandomness listens to the RndChannel to receive new VDF randomness. +func (consensus *Consensus) WaitForNewRandomness() { + go func() { + for { + vdfOutput := <-consensus.RndChannel + consensus.pendingRnds = append(consensus.pendingRnds, vdfOutput) + } + }() +} + +// GetNextRnd returns the oldest available randomness along with the hash of the block there randomness preimage is committed. +func (consensus *Consensus) GetNextRnd() ([32]byte, [32]byte, error) { + if len(consensus.pendingRnds) == 0 { + return [32]byte{}, [32]byte{}, errors.New("No available randomness") + } + vdfOutput := consensus.pendingRnds[0] + rnd := [32]byte{} + blockHash := [32]byte{} + copy(rnd[:], vdfOutput[:32]) + copy(blockHash[:], vdfOutput[32:]) + return rnd, blockHash, nil +} + // New creates a new Consensus object func New(host p2p.Host, ShardID string, peers []p2p.Peer, leader p2p.Peer) *Consensus { consensus := Consensus{} @@ -222,6 +248,11 @@ func (consensus *Consensus) RegisterPRndChannel(pRndChannel chan []byte) { consensus.PRndChannel = pRndChannel } +// RegisterRndChannel registers the channel for receiving final randomness from DRG protocol +func (consensus *Consensus) RegisterRndChannel(rndChannel chan [64]byte) { + consensus.RndChannel = rndChannel +} + // Checks the basic meta of a consensus message, including the signature. func (consensus *Consensus) checkConsensusMessage(message consensus_proto.Message, publicKey *bls.PublicKey) error { consensusID := message.ConsensusId diff --git a/consensus/consensus_leader.go b/consensus/consensus_leader.go index 478bae0ae..8d904d8d4 100644 --- a/consensus/consensus_leader.go +++ b/consensus/consensus_leader.go @@ -78,6 +78,15 @@ func (consensus *Consensus) WaitForNewBlock(blockChannel chan *types.Block, stop // TODO: check validity of pRnd newBlock.AddRandPreimage(binary.BigEndian.Uint32(pRnd)) } + rnd, blockHash, err := consensus.GetNextRnd() + if err == nil { + // Verify the randomness + _ = blockHash + utils.GetLogInstance().Info("Adding randomness into new block", "rnd", rnd) + newBlock.AddRandSeed(binary.BigEndian.Uint32(rnd[:])) + } else { + utils.GetLogInstance().Info("Failed to get randomness", "error", err) + } startTime = time.Now() utils.GetLogInstance().Debug("STARTING CONSENSUS", "numTxs", len(newBlock.Transactions()), "consensus", consensus, "startTime", startTime, "publicKeys", len(consensus.PublicKeys)) for { // Wait until last consensus is finished diff --git a/crypto/vdf/vdf.go b/crypto/vdf/vdf.go new file mode 100644 index 000000000..30792982d --- /dev/null +++ b/crypto/vdf/vdf.go @@ -0,0 +1,54 @@ +// Package vdf is a proof-of-concept implementation of a delay function +// and the security properties are not guaranteed. +// A more secure implementation of the VDF by Wesolowski (https://eprint.iacr.org/2018/623.pdf) +// will be done soon. +package vdf + +import "golang.org/x/crypto/sha3" + +// VDF is the struct holding necessary state for a hash chain delay function. +type VDF struct { + difficulty int + input [32]byte + output [32]byte + outputChan chan [32]byte + finished bool +} + +// New create a new instance of VDF. +func New(difficulty int, input [32]byte) *VDF { + return &VDF{ + difficulty: difficulty, + input: input, + outputChan: make(chan [32]byte), + } +} + +// GetOutputChannel returns the vdf output channel. +func (vdf *VDF) GetOutputChannel() chan [32]byte { + return vdf.outputChan +} + +// Execute runs the VDF until it's finished and put the result into output channel. +func (vdf *VDF) Execute() { + vdf.finished = false + tempResult := vdf.input + for i := 0; i < vdf.difficulty; i++ { + tempResult = sha3.Sum256(tempResult[:]) + } + vdf.output = tempResult + go func() { + vdf.outputChan <- vdf.output + }() + vdf.finished = true +} + +// IsFinished returns whether the vdf execution is finished or not. +func (vdf *VDF) IsFinished() bool { + return vdf.finished +} + +// GetOutput returns the vdf output, which can be bytes of 0s is the vdf is not finished. +func (vdf *VDF) GetOutput() [32]byte { + return vdf.output +} diff --git a/drand/drand.go b/drand/drand.go index 561d76a52..2d5e37586 100644 --- a/drand/drand.go +++ b/drand/drand.go @@ -28,6 +28,7 @@ type DRand struct { rand *[32]byte ConfirmedBlockChannel chan *types.Block // Channel to receive confirmed blocks PRndChannel chan []byte // Channel to send pRnd (preimage of randomness resulting from combined vrf randomnesses) to consensus. The first 32 bytes are randomness, the rest is for bitmap. + RndChannel chan [64]byte // Channel for DRG protocol to send the final randomness to consensus. The first 32 bytes are the randomness and the last 32 bytes are the hash of the block where the corresponding pRnd was generated // global consensus mutex mutex sync.Mutex @@ -77,6 +78,7 @@ func New(host p2p.Host, ShardID string, peers []p2p.Peer, leader p2p.Peer, confi } dRand.PRndChannel = make(chan []byte) + dRand.RndChannel = make(chan [64]byte) selfPeer := host.GetSelfPeer() if leader.Port == selfPeer.Port && leader.IP == selfPeer.IP { diff --git a/drand/drand_leader.go b/drand/drand_leader.go index 0753152e9..a710e87b0 100644 --- a/drand/drand_leader.go +++ b/drand/drand_leader.go @@ -2,6 +2,10 @@ package drand import ( "bytes" + "encoding/binary" + "time" + + "github.com/harmony-one/harmony/crypto/vdf" protobuf "github.com/golang/protobuf/proto" drand_proto "github.com/harmony-one/harmony/api/drand" @@ -13,6 +17,10 @@ import ( "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() { @@ -25,6 +33,29 @@ func (dRand *DRand) WaitForEpochBlock(blockChannel chan *types.Block, stopChan c if core.IsEpochLastBlock(newBlock) { dRand.init(newBlock) } + if core.IsEpochBlock(newBlock) && newBlock.Header().RandPreimage != 0 { + // The epoch block should contain the randomness preimage pRnd + go func() { + input := [32]byte{} + binary.BigEndian.PutUint32(input[:], newBlock.Header().RandPreimage) + + vdf := vdf.New(vdfDifficulty, input) + 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 } diff --git a/node/node.go b/node/node.go index f6ffaf703..8341b0114 100644 --- a/node/node.go +++ b/node/node.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" - "encoding/hex" "fmt" "math/big" "os" @@ -25,7 +24,7 @@ import ( proto_node "github.com/harmony-one/harmony/api/proto/node" "github.com/harmony-one/harmony/api/service" service_manager "github.com/harmony-one/harmony/api/service" - blockproposal "github.com/harmony-one/harmony/api/service/blockproposal" + "github.com/harmony-one/harmony/api/service/blockproposal" "github.com/harmony-one/harmony/api/service/clientsupport" consensus_service "github.com/harmony-one/harmony/api/service/consensus" "github.com/harmony-one/harmony/api/service/discovery" @@ -287,8 +286,6 @@ func New(host p2p.Host, consensus *bft.Consensus, db ethdb.Database) *Node { if node.Role == BeaconLeader || node.Role == BeaconValidator { node.CurrentStakes = make(map[common.Address]int64) } - bytes, _ := rlp.EncodeToBytes(chain.GetBlockByNumber(0)) - utils.GetLogInstance().Debug("TESTTEST", "block", hex.EncodeToString(bytes)) utils.GetLogInstance().Debug("Received", "blockHash", chain.GetBlockByNumber(0).Hash().Hex()) node.Consensus.ConsensusBlock = make(chan *bft.BFTBlockInfo) node.Consensus.VerifiedNewBlock = make(chan *types.Block)