// compile() takes the list of heartbeats and uses them to advance the state. func (s *State) compile() { // fetch a participant ordering participantOrdering := s.participantOrdering() // Lock down s.participants and s.heartbeats for editing s.participantsLock.Lock() s.heartbeatsLock.Lock() // Read heartbeats, process them, then archive them. for _, participant := range participantOrdering { if s.participants[participant] == nil { continue } // each participant must submit exactly 1 heartbeat if len(s.heartbeats[participant]) != 1 { s.tossParticipant(participant) continue } // this is the only way I know to access the only element of a map; // the key is unknown for _, hb := range s.heartbeats[participant] { s.processHeartbeat(hb, participant) } // archive heartbeats (unimplemented) // clear heartbeat list for next block s.heartbeats[participant] = make(map[crypto.TruncatedHash]*heartbeat) } // move UpcomingEntropy to CurrentEntropy s.currentEntropy = s.upcomingEntropy // generate a new heartbeat and add it to s.Heartbeats hb, err := s.newHeartbeat() if err != nil { log.Fatalln(err) } hash, err := crypto.CalculateTruncatedHash(hb.marshal()) if err != nil { log.Fatalln(err) } s.heartbeats[s.participantIndex][hash] = hb // sign and annouce the heartbeat shb, err := s.signHeartbeat(hb) if err != nil { log.Fatalln(err) } err = s.announceSignedHeartbeat(shb) if err != nil { log.Fatalln(err) } }
// compile() takes the list of heartbeats and uses them to advance the state. func (s *State) compile() { participantOrdering := s.participantOrdering() // Read read heartbeats, process them, then archive them. Other functions // concurrently access the heartbeats, so mutexes are needed. for _, participant := range participantOrdering { if s.participants[participant] == nil { continue } // each participant must submit exactly 1 heartbeat if len(s.heartbeats[participant]) != 1 { s.tossParticipant(participant) continue } for _, hb := range s.heartbeats[participant] { s.processHeartbeat(hb, participant) } // archive heartbeats // currently, archives are sent to /dev/null s.heartbeats[participant] = make(map[crypto.TruncatedHash]*heartbeat) } // move UpcomingEntropy to CurrentEntropy s.currentEntropy = s.upcomingEntropy // generate a new heartbeat and add it to s.Heartbeats hb, err := s.newHeartbeat() if err != nil { log.Fatalln(err) } hash, err := crypto.CalculateTruncatedHash(hb.marshal()) if err != nil { log.Fatalln(err) } s.heartbeats[s.participantIndex][hash] = hb // sign and annouce the heartbeat shb, err := s.signHeartbeat(hb) if err != nil { log.Fatalln(err) } s.announceSignedHeartbeat(shb) }
func (s *State) announceSignedHeartbeat(sh *signedHeartbeat) { for i := range s.participants { if s.participants[i] != nil { payload, err := sh.marshal() if err != nil { log.Fatalln(err) } m := new(common.Message) m.Payload = append([]byte{byte(1)}, payload...) m.Destination = s.participants[i].Address //time.Sleep(time.Millisecond) // prevents panics. No idea where original source of bug is. err = s.messageSender.SendMessage(m) if err != nil { log.Fatalln("Error while sending message") } } } }
// participants are processed in a random order each block, determined by the // entropy for the block. participantOrdering() deterministically picks that // order, using entropy from the state. func (s *State) participantOrdering() (participantOrdering [common.QuorumSize]byte) { // create an in-order list of participants for i := range participantOrdering { participantOrdering[i] = byte(i) } // shuffle the list of participants for i := range participantOrdering { newIndex, err := s.randInt(i, common.QuorumSize) if err != nil { log.Fatalln(err) } tmp := participantOrdering[newIndex] participantOrdering[newIndex] = participantOrdering[i] participantOrdering[i] = tmp } return }
// Update the state according to the information presented in the heartbeat // processHeartbeat uses return codes for testing purposes func (s *State) processHeartbeat(hb *heartbeat, i participantIndex) int { // compare EntropyStage2 to the hash from the previous heartbeat expectedHash, err := crypto.CalculateTruncatedHash(hb.entropyStage2[:]) if err != nil { log.Fatalln(err) } if expectedHash != s.previousEntropyStage1[i] { s.tossParticipant(i) return 1 } // Add the EntropyStage2 to UpcomingEntropy th, err := crypto.CalculateTruncatedHash(append(s.upcomingEntropy[:], hb.entropyStage2[:]...)) s.upcomingEntropy = common.Entropy(th) // store entropyStage1 to compare with next heartbeat from this participant s.previousEntropyStage1[i] = hb.entropyStage1 return 0 }
// HandleSignedHeartbeat takes the payload of an incoming message of type // 'incomingSignedHeartbeat' and verifies it according to the specification // // What sort of input error checking is needed for this function? func (s *State) HandleSignedHeartbeat(sh SignedHeartbeat, arb *struct{}) error { // Check that the slices of signatures and signatories are of the same length if len(sh.signatures) != len(sh.signatories) { return hsherrMismatchedSignatures } // check that there are not too many signatures and signatories if len(sh.signatories) > common.QuorumSize { return hsherrOversigned } s.stepLock.Lock() // prevents a benign race condition; is here to follow best practices currentStep := s.currentStep s.stepLock.Unlock() // s.CurrentStep must be less than or equal to len(sh.Signatories), unless // there is a new block and s.CurrentStep is common.QuorumSize if currentStep > len(sh.signatories) { if currentStep == common.QuorumSize && len(sh.signatories) == 1 { // by waiting common.StepDuration, the new block will be compiled time.Sleep(common.StepDuration) // now continue to rest of function } else { return hsherrNoSync } } // Check bounds on first signatory if int(sh.signatories[0]) >= common.QuorumSize { return hsherrBounds } // we are starting to read from memory, initiate locks s.participantsLock.RLock() s.heartbeatsLock.Lock() defer s.participantsLock.RUnlock() defer s.heartbeatsLock.Unlock() // check that first signatory is a participant if s.participants[sh.signatories[0]] == nil { return hsherrNonParticipant } // Check if we have already received this heartbeat _, exists := s.heartbeats[sh.signatories[0]][sh.heartbeatHash] if exists { return hsherrHaveHeartbeat } // Check if we already have two heartbeats from this host if len(s.heartbeats[sh.signatories[0]]) >= 2 { return hsherrManyHeartbeats } // iterate through the signatures and make sure each is legal var signedMessage crypto.SignedMessage // grows each iteration signedMessage.Message = sh.heartbeatHash[:] previousSignatories := make(map[byte]bool) // which signatories have already signed for i, signatory := range sh.signatories { // Check bounds on the signatory if int(signatory) >= common.QuorumSize { return hsherrBounds } // Verify that the signatory is a participant in the quorum if s.participants[signatory] == nil { return hsherrNonParticipant } // Verify that the signatory has only been seen once in the current SignedHeartbeat if previousSignatories[signatory] { return hsherrDoubleSigned } // record that we've seen this signatory in the current SignedHeartbeat previousSignatories[signatory] = true // verify the signature signedMessage.Signature = sh.signatures[i] verification := s.participants[signatory].publicKey.Verify(&signedMessage) // check status of verification if !verification { return hsherrInvalidSignature } // throwing the signature into the message here makes code cleaner in the loop // and after we sign it to send it to everyone else newMessage, err := signedMessage.CombinedMessage() signedMessage.Message = newMessage if err != nil { return err } } // Add heartbeat to list of seen heartbeats s.heartbeats[sh.signatories[0]][sh.heartbeatHash] = sh.heartbeat // Sign the stack of signatures and send it to all hosts signedMessage, err := s.secretKey.Sign(signedMessage.Message) if err != nil { log.Fatalln(err) } // add our signature to the signedHeartbeat sh.signatures = append(sh.signatures, signedMessage.Signature) sh.signatories = append(sh.signatories, s.self.index) // broadcast the message to the quorum err = s.announceSignedHeartbeat(&sh) if err != nil { log.Fatalln(err) return err } return nil }
// handleSignedHeartbeat takes the payload of an incoming message of type // 'incomingSignedHeartbeat' and verifies it according to rules established by // the specification. // // The return code is currently purely for the testing suite, the numbers // have been chosen arbitrarily func (s *State) handleSignedHeartbeat(payload []byte) (returnCode int) { // covert payload to SignedHeartbeat sh, err := unmarshalSignedHeartbeat(payload) if err != nil { log.Infoln("Received bad message SignedHeartbeat: ", err) returnCode = 11 return } // Check that the slices of signatures and signatories are of the same length if len(sh.signatures) != len(sh.signatories) { log.Infoln("SignedHeartbeat has mismatched signatures") returnCode = 1 return } // check that there are not too many signatures and signatories if len(sh.signatories) > common.QuorumSize { log.Infoln("Received an over-signed signedHeartbeat") returnCode = 12 return } s.stepLock.Lock() // prevents a benign race condition; is here to follow best practices // s.CurrentStep must be less than or equal to len(sh.Signatories), unless // there is a new block and s.CurrentStep is common.QuorumSize if s.currentStep > len(sh.signatories) { if s.currentStep == common.QuorumSize && len(sh.signatories) == 1 { // by waiting common.StepDuration, the new block will be compiled time.Sleep(common.StepDuration) // now continue to rest of function } else { log.Infoln("Received an out-of-sync SignedHeartbeat") returnCode = 2 return } } s.stepLock.Unlock() // Check bounds on first signatory if int(sh.signatories[0]) >= common.QuorumSize { log.Infoln("Received an out of bounds index") returnCode = 9 return } // we are starting to read from memory, initiate locks s.participantsLock.RLock() s.heartbeatsLock.Lock() // check that first sigatory is a participant if s.participants[sh.signatories[0]] == nil { log.Infoln("Received heartbeat from non-participant") returnCode = 10 return } // Check if we have already received this heartbeat _, exists := s.heartbeats[sh.signatories[0]][sh.heartbeatHash] if exists { returnCode = 8 return } // Check if we already have two heartbeats from this host if len(s.heartbeats[sh.signatories[0]]) >= 2 { log.Infoln("Received many invalid heartbeats from one host") returnCode = 13 return } // iterate through the signatures and make sure each is legal var signedMessage crypto.SignedMessage // grows each iteration signedMessage.Message = string(sh.heartbeatHash[:]) previousSignatories := make(map[participantIndex]bool) // which signatories have already signed for i, signatory := range sh.signatories { // Check bounds on the signatory if int(signatory) >= common.QuorumSize { log.Infoln("Received an out of bounds index") returnCode = 9 return } // Verify that the signatory is a participant in the quorum if s.participants[signatory] == nil { log.Infoln("Received a heartbeat signed by an invalid signatory") returnCode = 4 return } // Verify that the signatory has only been seen once in the current SignedHeartbeat if previousSignatories[signatory] { log.Infoln("Received a double-signed heartbeat") returnCode = 5 return } // record that we've seen this signatory in the current SignedHeartbeat previousSignatories[signatory] = true // verify the signature signedMessage.Signature = sh.signatures[i] verification, err := crypto.Verify(s.participants[signatory].publicKey, signedMessage) if err != nil { log.Fatalln(err) return } // check status of verification if !verification { log.Infoln("Received invalid signature in SignedHeartbeat") returnCode = 6 return } // throwing the signature into the message here makes code cleaner in the loop // and after we sign it to send it to everyone else signedMessage.Message = signedMessage.CombinedMessage() } // Add heartbeat to list of seen heartbeats s.heartbeats[sh.signatories[0]][sh.heartbeatHash] = sh.heartbeat // Sign the stack of signatures and send it to all hosts signedMessage, err = crypto.Sign(s.secretKey, signedMessage.Message) if err != nil { log.Fatalln(err) } // add our signature to the signedHeartbeat sh.signatures = append(sh.signatures, signedMessage.Signature) sh.signatories = append(sh.signatories, s.participantIndex) // broadcast the message to the quorum err = s.announceSignedHeartbeat(sh) if err != nil { log.Fatalln(err) } returnCode = 0 return }
// HandleSignedHeartbeat takes a heartbeat that has been signed // as a part of the concensus algorithm, and follows all the rules // that are necessary to ensure that all honest hosts arrive at // the same conclusions about the actions of their peers. // // See the paper 'The Byzantine Generals Problem' for more insight // on the algorithms used here. Paper can be found in // doc/The Byzantine Generals Problem // // This function is called concurrently, mutexes will be needed when // accessing or altering the State // // It is assumed that when this function is called, the Heartbeat in // question will already be in memory, and was correctly signed by the // first signatory, the the first signatory is a participant, and that // it matches its hash. And that the first signatory is used to store // the heartbeat // // The return code is purely for the testing suite. The numbers are chosen // arbitrarily func (s *State) handleSignedHeartbeat(message []byte) (returnCode int) { // covert message to SignedHeartbeat sh, err := unmarshalSignedHeartbeat(message) if err != nil { log.Infoln("Received bad message SignedHeartbeat: ", err) return } // Check that the slices of signatures and signatories are of the same length if len(sh.signatures) != len(sh.signatories) { log.Infoln("SignedHeartbeat has mismatched signatures") returnCode = 1 return } // s.CurrentStep must be less than or equal to len(sh.Signatories), unless the // current step is common.QuorumSize and len(sh.Signatories) == 1 if s.currentStep > len(sh.signatories) { if s.currentStep == common.QuorumSize && len(sh.signatories) == 1 { // sleep long enough to pass the first requirement time.Sleep(common.StepDuration) // now continue to rest of function } else { log.Infoln("Received an out-of-sync SignedHeartbeat") returnCode = 2 return } } // Check bounds on first signatory if int(sh.signatories[0]) >= common.QuorumSize { log.Infoln("Received an out of bounds index") returnCode = 9 return } // Check existence of first signatory if s.participants[sh.signatories[0]] == nil { log.Infoln("Received heartbeat from non-participant") returnCode = 10 return } // Check if we have already received this heartbeat _, exists := s.heartbeats[sh.signatories[0]][sh.heartbeatHash] if exists { returnCode = 8 return } // while processing signatures, signedMessage will keep growing var signedMessage crypto.SignedMessage signedMessage.Message = string(sh.heartbeatHash[:]) // keep a map of which signatories have already been confirmed previousSignatories := make(map[participantIndex]bool) for i, signatory := range sh.signatories { // Check bounds on the signatory if int(signatory) >= common.QuorumSize { log.Infoln("Received an out of bounds index") returnCode = 9 return } // Verify that the signatory is a participant in the quorum if s.participants[signatory] == nil { log.Infoln("Received a heartbeat signed by an invalid signatory") returnCode = 4 return } // Verify that the signatory has only been seen once in the current SignedHeartbeat if previousSignatories[signatory] { log.Infoln("Received a double-signed heartbeat") returnCode = 5 return } // record that we've seen this signatory in the current SignedHeartbeat previousSignatories[signatory] = true // verify the signature signedMessage.Signature = sh.signatures[i] verification, err := crypto.Verify(s.participants[signatory].PublicKey, signedMessage) if err != nil { log.Fatalln(err) return } // check status of verification if !verification { log.Infoln("Received invalid signature in SignedHeartbeat") returnCode = 6 return } // throwing the signature into the message here makes code cleaner in the loop // and after we sign it to send it to everyone else signedMessage.Message = signedMessage.CombinedMessage() } // Add heartbeat to list of seen heartbeats // Don't check if heartbeat is valid, that's for compile() s.heartbeats[sh.signatories[0]][sh.heartbeatHash] = sh.heartbeat // Sign the stack of signatures and send it to all hosts _, err = crypto.Sign(s.secretKey, signedMessage.Message) if err != nil { log.Fatalln(err) } // Send the new message to everybody returnCode = 0 return }