package drand
import (
"bytes"
"time"
protobuf "github.com/golang/protobuf/proto"
drand_proto "github.com/harmony-one/harmony/api/drand"
"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 ( ) . RandPreimage
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 . nodeID ] = append ( rand [ : ] , proof ... )
if utils . UseLibP2P {
dRand . host . SendMessageToGroups ( [ ] p2p . GroupID { p2p . GroupIDBeacon } , host . ConstructP2pMessage ( byte ( 17 ) , msgToSend ) )
} else {
host . BroadcastMessageFromLeader ( dRand . host , dRand . GetValidatorPeers ( ) , msgToSend , nil )
}
}
// ProcessMessageLeader dispatches messages for the leader to corresponding processors.
func ( dRand * DRand ) ProcessMessageLeader ( payload [ ] byte ) {
message := drand_proto . Message { }
err := protobuf . Unmarshal ( payload , & message )
if err != nil {
utils . GetLogInstance ( ) . Error ( "Failed to unmarshal message payload." , "err" , err , "dRand" , dRand )
}
switch message . Type {
case drand_proto . MessageType_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 drand_proto . Message ) {
if message . Type != drand_proto . MessageType_COMMIT {
utils . GetLogInstance ( ) . Error ( "Wrong message type received" , "expected" , drand_proto . MessageType_COMMIT , "got" , message . Type )
return
}
dRand . mutex . Lock ( )
defer dRand . mutex . Unlock ( )
validatorID := message . SenderId
validatorPeer := dRand . getValidatorPeerByID ( validatorID )
vrfs := dRand . vrfs
if len ( ( * vrfs ) ) >= ( ( len ( dRand . PublicKeys ) ) / 3 + 1 ) {
utils . GetLogInstance ( ) . Debug ( "Received additional randomness commit message" , "validatorID" , validatorID )
return
}
// Verify message signature
err := verifyMessageSig ( validatorPeer . PubKey , message )
if err != nil {
utils . GetLogInstance ( ) . Warn ( "[DRAND] failed to verify the message signature" , "Error" , err , "PubKey" , validatorPeer . PubKey )
return
}
rand := message . Payload [ : 32 ]
proof := message . Payload [ 32 : len ( message . Payload ) - 64 ]
pubKeyBytes := message . Payload [ len ( message . Payload ) - 64 : ]
_ , pubKey := p256 . GenerateKey ( )
pubKey . Deserialize ( pubKeyBytes )
expectedRand , err := pubKey . ProofToHash ( dRand . blockHash [ : ] , proof )
if err != nil || ! bytes . Equal ( expectedRand [ : ] , rand ) {
utils . GetLogInstance ( ) . Error ( "[DRAND] Failed to verify the VRF" , "error" , err , "validatorID" , validatorID , "expectedRand" , expectedRand , "receivedRand" , rand )
return
}
utils . GetLogInstance ( ) . Debug ( "Received new VRF commit" , "numReceivedSoFar" , len ( ( * vrfs ) ) , "validatorID" , validatorID , "PublicKeys" , len ( dRand . PublicKeys ) )
( * vrfs ) [ validatorID ] = message . Payload
dRand . bitmap . SetKey ( validatorPeer . PubKey , 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 ) ) , "validatorID" , validatorID , "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 ... )
}
}