// 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 }