Example #1
0
// take new heartbeat (our own), sign it, and package it into a signedHearteat
// I'm pretty sure this only follows a newHeartbeat() call; they can be merged
func (s *State) signHeartbeat(hb *heartbeat) (sh *signedHeartbeat, err error) {
	sh = new(signedHeartbeat)

	// confirm heartbeat and hash
	sh.heartbeat = hb
	marshalledHb := hb.marshal()
	sh.heartbeatHash, err = crypto.CalculateTruncatedHash([]byte(marshalledHb))
	if err != nil {
		return
	}

	// fill out sigantures
	sh.signatures = make([]crypto.Signature, 1)
	signedHb, err := crypto.Sign(s.secretKey, string(sh.heartbeatHash[:]))
	if err != nil {
		return
	}
	sh.signatures[0] = signedHb.Signature
	sh.signatories = make([]participantIndex, 1)
	sh.signatories[0] = s.participantIndex
	return
}
Example #2
0
// TestHandleSignedHeartbeat should probably be reviewed and rehashed
func TestHandleSignedHeartbeat(t *testing.T) {
	// create a state and populate it with the signatories as participants
	s, err := CreateState(common.NewZeroNetwork(), 0)
	if err != nil {
		t.Fatal(err)
	}

	// create keypairs
	pubKey1, secKey1, err := crypto.CreateKeyPair()
	if err != nil {
		t.Fatal(err)
	}
	pubKey2, secKey2, err := crypto.CreateKeyPair()
	if err != nil {
		t.Fatal(err)
	}

	// create participants and add them to s
	p1 := new(Participant)
	p2 := new(Participant)
	p1.PublicKey = pubKey1
	p2.PublicKey = pubKey2
	s.AddParticipant(p1, 1)
	s.AddParticipant(p2, 2)

	// create SignedHeartbeat
	var sh signedHeartbeat
	sh.heartbeat, err = s.newHeartbeat()
	if err != nil {
		t.Fatal(err)
	}
	sh.heartbeatHash, err = crypto.CalculateTruncatedHash([]byte(sh.heartbeat.marshal()))
	if err != nil {
		t.Fatal(err)
	}
	sh.signatures = make([]crypto.Signature, 2)
	sh.signatories = make([]participantIndex, 2)

	// Create a set of signatures for the SignedHeartbeat
	signature1, err := crypto.Sign(secKey1, string(sh.heartbeatHash[:]))
	if err != nil {
		t.Fatal("error signing HeartbeatHash")
	}

	signature2, err := crypto.Sign(secKey2, signature1.CombinedMessage())
	if err != nil {
		t.Fatal("error with second signing")
	}

	// build a valid SignedHeartbeat
	sh.signatures[0] = signature1.Signature
	sh.signatures[1] = signature2.Signature
	sh.signatories[0] = 1
	sh.signatories[1] = 2

	// handle the signed heartbeat, expecting code 0
	msh, err := sh.marshal()
	if err != nil {
		t.Fatal(err)
	}
	returnCode := s.handleSignedHeartbeat(msh)
	if returnCode != 0 {
		t.Fatal("expected heartbeat to succeed:", returnCode)
	}

	// verify that a repeat heartbeat gets ignored
	msh, err = sh.marshal()
	if err != nil {
		t.Fatal(err)
	}
	returnCode = s.handleSignedHeartbeat(msh)
	if returnCode != 8 {
		t.Fatal("expected heartbeat to get ignored as a duplicate:", returnCode)
	}

	// create a different heartbeat, this will be used to test the fail conditions
	sh.heartbeat, err = s.newHeartbeat()
	if err != nil {
		t.Fatal(err)
	}
	sh.heartbeatHash, err = crypto.CalculateTruncatedHash([]byte(sh.heartbeat.marshal()))
	if err != nil {
		t.Fatal(err)
	}

	// verify a heartbeat with bad signatures is rejected
	msh, err = sh.marshal()
	if err != nil {
		t.Fatal(err)
	}
	returnCode = s.handleSignedHeartbeat(msh)
	if returnCode != 6 {
		t.Fatal("expected heartbeat to get ignored as having invalid signatures: ", returnCode)
	}

	// give heartbeat repeat signatures
	signature1, err = crypto.Sign(secKey1, string(sh.heartbeatHash[:]))
	if err != nil {
		t.Fatal("error with third signing")
	}

	signature2, err = crypto.Sign(secKey1, signature1.CombinedMessage())
	if err != nil {
		t.Fatal("error with fourth signing")
	}

	// adjust signatories slice
	sh.signatures[0] = signature1.Signature
	sh.signatures[1] = signature2.Signature
	sh.signatories[0] = 1
	sh.signatories[1] = 1

	// verify repeated signatures are rejected
	msh, err = sh.marshal()
	if err != nil {
		t.Fatal(err)
	}
	returnCode = s.handleSignedHeartbeat(msh)
	if returnCode != 5 {
		t.Fatal("expected heartbeat to be rejected for duplicate signatures: ", returnCode)
	}

	// remove second signature
	sh.signatures = sh.signatures[:1]
	sh.signatories = sh.signatories[:1]

	// handle heartbeat when tick is larger than num signatures
	s.currentStep = 2
	msh, err = sh.marshal()
	if err != nil {
		t.Fatal(err)
	}
	returnCode = s.handleSignedHeartbeat(msh)
	if returnCode != 2 {
		t.Fatal("expected heartbeat to be rejected as out-of-sync: ", returnCode)
	}

	// send a heartbeat right at the edge of a new block
	// test takes time; skip in short tests
	if testing.Short() {
		t.Skip()
	}

	// put block at edge
	s.currentStep = common.QuorumSize

	// submit heartbeat in separate thread
	go func() {
		msh, err = sh.marshal()
		if err != nil {
			t.Fatal(err)
		}
		returnCode = s.handleSignedHeartbeat(msh)
		if returnCode != 0 {
			t.Fatal("expected heartbeat to succeed!: ", returnCode)
		}
	}()

	time.Sleep(time.Second)
}
Example #3
0
// 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
}
Example #4
0
// 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
}