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
	gobHb, err := hb.GobEncode()
	if err != nil {
		return
	}
	sh.heartbeatHash, err = crypto.CalculateTruncatedHash(gobHb)
	if err != nil {
		return
	}

	// fill out signatures
	sh.signatures = make([]crypto.Signature, 1)
	signedHb, err := s.secretKey.Sign(sh.heartbeatHash[:])
	if err != nil {
		return
	}
	sh.signatures[0] = signedHb.Signature
	sh.signatories = make([]byte, 1)
	sh.signatories[0] = s.self.index
	return
}
Example #2
0
// Create a state, check the defaults
func TestCreateState(t *testing.T) {
	// does a state create without errors?
	s, err := CreateState(common.NewZeroNetwork())
	if err != nil {
		t.Fatal(err)
	}

	// check that previousEntropyStage1 is initialized correctly
	var emptyEntropy common.Entropy
	emptyHash, err := crypto.CalculateTruncatedHash(emptyEntropy[:])
	if err != nil {
		t.Fatal(err)
	}
	for i := range s.previousEntropyStage1 {
		if s.previousEntropyStage1[i] != emptyHash {
			t.Error("previousEntropyStage1 initialized incorrectly at index ", i)
		}
	}

	// sanity check the default values
	if s.participantIndex != 255 {
		t.Error("s.participantIndex initialized to ", s.participantIndex)
	}
	if s.currentStep != 1 {
		t.Error("s.currentStep should be initialized to 1!")
	}
	if s.wallets == nil {
		t.Error("s.wallets was not initialized")
	}
}
Example #3
0
File: state.go Project: Radzell/Sia
// Create and initialize a state object.
func CreateState(messageRouter common.MessageRouter) (s *State, err error) {
	s = new(State)
	// check that we have a non-nil messageSender
	if messageRouter == nil {
		err = fmt.Errorf("Cannot initialize with a nil messageRouter")
		return
	}

	// create a signature keypair for this state
	pubKey, secKey, err := crypto.CreateKeyPair()
	if err != nil {
		return
	}

	// calculate the value of an empty hash (default for storedEntropyStage2 on all hosts is a blank array)
	emptyHash, err := crypto.CalculateTruncatedHash(s.storedEntropyStage2[:])
	if err != nil {
		return
	}

	// set state variables to their defaults
	s.messageRouter = messageRouter
	s.self = new(participant)
	s.self.address = messageRouter.AddMessageHandler(s)
	s.self.publicKey = pubKey
	s.secretKey = secKey
	for i := range s.previousEntropyStage1 {
		s.previousEntropyStage1[i] = emptyHash
	}
	s.participantIndex = 255
	s.currentStep = 1
	s.wallets = make(map[string]uint64)

	return
}
Example #4
0
// 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)
	}
}
Example #5
0
// 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 byte) (err error) {
	print("Confirming Participant ")
	println(i)

	// Add the entropy to UpcomingEntropy
	th, err := crypto.CalculateTruncatedHash(append(s.upcomingEntropy[:], hb.entropy[:]...))
	s.upcomingEntropy = common.Entropy(th)

	return
}
Example #6
0
// 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
}
Example #7
0
// Removes all traces of a participant from the State
func (s *State) tossParticipant(pi participantIndex) {
	// remove from s.Participants
	s.participants[pi] = nil

	// remove from s.PreviousEntropyStage1
	var emptyEntropy common.Entropy
	zeroHash, err := crypto.CalculateTruncatedHash(emptyEntropy[:])
	if err != nil {
		log.Fatal(err)
	}
	s.previousEntropyStage1[pi] = zeroHash

	// nil map in s.Heartbeats
	s.heartbeats[pi] = nil
}
Example #8
0
// 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)
}
Example #9
0
// convert string to signedHeartbeat
func unmarshalSignedHeartbeat(msh []byte) (sh *signedHeartbeat, err error) {
	// we reference the nth element in the []byte, make sure there is an nth element
	if len(msh) <= marshalledHeartbeatLen() {
		err = fmt.Errorf("input for unmarshalSignedHeartbeat is too short")
		return
	}
	numSignatures := int(msh[marshalledHeartbeatLen()]) // the nth element

	// verify that the total length of msh is what is expected
	signatureSectionLen := numSignatures * (crypto.SignatureSize + 1)
	totalLen := marshalledHeartbeatLen() + 1 + signatureSectionLen
	if len(msh) != totalLen {
		err = fmt.Errorf("input for UnmarshalSignedHeartbeat is incorrect length, expecting ", totalLen, " bytes")
		return
	}

	// get sh.Heartbeat and sh.HeartbeatHash
	sh = new(signedHeartbeat)
	index := 0
	heartbeat, err := unmarshalHeartbeat(msh[index:marshalledHeartbeatLen()])
	if err != nil {
		return
	}
	heartbeatHash, err := crypto.CalculateTruncatedHash(msh[index:marshalledHeartbeatLen()])
	if err != nil {
		return
	}
	sh.heartbeat = heartbeat
	sh.heartbeatHash = heartbeatHash

	// get sh.Signatures and sh.Signatories
	index += marshalledHeartbeatLen()
	index += 1 // skip the numSignatures byte
	sh.signatures = make([]crypto.Signature, numSignatures)
	sh.signatories = make([]participantIndex, numSignatures)
	for i := 0; i < numSignatures; i++ {
		copy(sh.signatures[i][:], msh[index:])
		index += crypto.SignatureSize
		sh.signatories[i] = participantIndex(msh[index])
		index += 1
	}

	return
}
Example #10
0
// Create and initialize a state object
func CreateState(messageSender common.MessageSender, participantIndex participantIndex) (s State, err error) {
	// check that we have a non-nil messageSender
	if messageSender == nil {
		err = fmt.Errorf("Cannot initialize with a nil messageSender")
		return
	}

	// check that participantIndex is legal
	if int(participantIndex) >= common.QuorumSize {
		err = fmt.Errorf("Invalid participant index!")
		return
	}

	// initialize crypto keys
	pubKey, secKey, err := crypto.CreateKeyPair()
	if err != nil {
		return
	}

	// create and fill out the participant object
	self := new(Participant)
	self.Address = messageSender.Address()
	self.Address.Id = common.Identifier(participantIndex)
	self.PublicKey = pubKey

	// calculate the value of an empty hash (default for storedEntropyStage2 on all hosts is a blank array)
	emptyHash, err := crypto.CalculateTruncatedHash(s.storedEntropyStage2[:])
	if err != nil {
		return
	}

	// set state variables to their defaults
	s.messageSender = messageSender
	s.AddParticipant(self, participantIndex)
	s.secretKey = secKey
	for i := range s.previousEntropyStage1 {
		s.previousEntropyStage1[i] = emptyHash
	}
	s.participantIndex = participantIndex
	s.currentStep = 1
	s.wallets = make(map[string]uint64)

	return
}
Example #11
0
// Verify that newHeartbeat() produces valid heartbeats
func TestnewHeartbeat(t *testing.T) {
	// create a state, and then a heartbeat
	s, err := CreateState(common.NewZeroNetwork(), 0)
	if err != nil {
		t.Fatal(err)
	}
	hb, err := s.newHeartbeat()
	if err != nil {
		t.Fatal(err)
	}

	// verify that entropy is being properly generated when making the heartbeat
	storedEntropyHash, err := crypto.CalculateTruncatedHash(s.storedEntropyStage2[:])
	if err != nil {
		t.Fatal(err)
	} else if hb.entropyStage1 != storedEntropyHash {
		t.Fatal("newHeartbeat() incorrectly producing EntropyStage1 from s.StoredEntropyStage2")
	}
}
Example #12
0
// Take an unstarted State and begin the consensus algorithm cycle
func (s *State) Start() {
	// start the ticker to progress the state
	go s.tick()

	s.lock.Lock()
	// create first heartbeat and add it to heartbeat map, then announce it
	hb, err := s.newHeartbeat()
	if err != nil {
		return
	}
	heartbeatHash, err := crypto.CalculateTruncatedHash([]byte(hb.marshal()))
	s.heartbeats[s.participantIndex][heartbeatHash] = hb
	shb, err := s.signHeartbeat(hb)
	if err != nil {
		return
	}
	s.announceSignedHeartbeat(shb)
	s.lock.Unlock()
}
Example #13
0
File: state.go Project: Radzell/Sia
// Use the entropy stored in the state to generate a random integer [low, high)
// randInt only runs during compile(), when the mutexes are already locked
//
// needs to be converted to return uint64
func (s *State) randInt(low int, high int) (randInt int, err error) {
	// verify there's a gap between the numbers
	if low == high {
		err = fmt.Errorf("low and high cannot be the same number")
		return
	}

	// Convert CurrentEntropy into an int
	rollingInt := 0
	for i := 0; i < 4; i++ {
		rollingInt = rollingInt << 8
		rollingInt += int(s.currentEntropy[i])
	}

	randInt = (rollingInt % (high - low)) + low

	// Convert random number seed to next value
	truncatedHash, err := crypto.CalculateTruncatedHash(s.currentEntropy[:])
	s.currentEntropy = common.Entropy(truncatedHash)
	return
}
Example #14
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 #15
0
File: state.go Project: Radzell/Sia
// Add a participant to the state, tell the participant about ourselves
func (s *State) addNewParticipant(payload []byte) {
	// extract index and participant object from payload
	participantIndex := payload[0]
	p, err := unmarshalParticipant(payload[1:])
	if err != nil {
		return
	}

	// for this participant, make the heartbeat map and add the default heartbeat
	hb := new(heartbeat)
	emptyHash, err := crypto.CalculateTruncatedHash(hb.entropyStage2[:])
	hb.entropyStage1 = emptyHash
	s.heartbeatsLock.Lock()
	s.participantsLock.Lock()
	s.heartbeats[participantIndex] = make(map[crypto.TruncatedHash]*heartbeat)
	s.heartbeats[participantIndex][emptyHash] = hb
	s.heartbeatsLock.Unlock()

	if *p == *s.self {
		// add our self object to the correct index in participants
		s.participants[participantIndex] = s.self
		s.tickingLock.Lock()
		s.ticking = true
		s.tickingLock.Unlock()

		go s.tick()
	} else {
		// add the participant to participants
		s.participants[participantIndex] = p

		// tell the new guy about ourselves
		m := new(common.Message)
		m.Destination = p.address
		m.Payload = append([]byte(string(newParticipant)), s.self.marshal()...)
		s.messageRouter.SendMessage(m)
	}
	s.participantsLock.Unlock()
}
Example #16
0
// ensures Tick() calles compile() and then resets the counter to step 1
func TestCompilationTick(t *testing.T) {
	// test takes common.StepDuration seconds; skip for short testing
	if testing.Short() {
		t.Skip()
	}

	// create state and give it a heartbeat to prevent it from pruning itself
	s, err := CreateState(common.NewZeroNetwork(), 0)
	if err != nil {
		t.Fatal(err)
	}
	hb, err := s.newHeartbeat()
	if err != nil {
		return
	}
	heartbeatHash, err := crypto.CalculateTruncatedHash([]byte(hb.marshal()))
	s.heartbeats[s.participantIndex][heartbeatHash] = hb

	// remember entropy to verify that compile() gets called
	currentEntropy := s.currentEntropy

	// verify that tick is wrapping around properly
	s.currentStep = common.QuorumSize
	go s.tick()
	time.Sleep(common.StepDuration)
	time.Sleep(time.Second)

	s.lock.Lock()
	if s.currentStep != 1 {
		t.Fatal("s.currentStep failed to roll over: ", s.currentStep)
	}

	// check if s.compile() got called
	if currentEntropy == s.currentEntropy {
		t.Fatal("Entropy did not change after tick wrapped around")
	}
	s.lock.Unlock()
}
Example #17
0
// Using the current State, newHeartbeat() creates a heartbeat that fulfills all
// of the requirements of the quorum.
func (s *State) newHeartbeat() (hb *heartbeat, err error) {
	hb = new(heartbeat)

	// Fetch value used to produce EntropyStage1 in prev. heartbeat
	hb.entropyStage2 = s.storedEntropyStage2

	// Generate EntropyStage2 for next heartbeat
	entropy, err := crypto.RandomByteSlice(common.EntropyVolume)
	if err != nil {
		return
	}
	copy(s.storedEntropyStage2[:], entropy) // convert entropy from slice to byte array

	// Use EntropyStage2 to generate EntropyStage1 for this heartbeat
	hb.entropyStage1, err = crypto.CalculateTruncatedHash(s.storedEntropyStage2[:])
	if err != nil {
		return
	}

	// more code will be added here

	return
}
Example #18
0
func TestHandleSignedHeartbeat(t *testing.T) {
	// create a state and populate it with the signatories as participants
	s, err := CreateState(common.NewZeroNetwork())
	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
	var p1 Participant
	var p2 Participant
	p1.index = 1
	p2.index = 2
	p1.publicKey = pubKey1
	p2.publicKey = pubKey2
	err = s.AddNewParticipant(p1, nil)
	if err != nil {
		t.Fatal(err)
	}
	s.AddNewParticipant(p2, nil)
	if err != nil {
		t.Fatal(err)
	}

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

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

	combinedMessage, err := signature1.CombinedMessage()
	if err != nil {
		t.Fatal(err)
	}
	signature2, err := secKey2.Sign(combinedMessage)
	if err != nil {
		t.Fatal(err)
	}

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

	// delete existing heartbeat from state; makes the remaining tests easier
	s.heartbeats[sh.signatories[0]] = make(map[crypto.TruncatedHash]*heartbeat)

	// handle the signed heartbeat, expecting nil error
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != nil {
		t.Fatal(err)
	}

	// verify that a repeat heartbeat gets ignored
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != hsherrHaveHeartbeat {
		t.Error("expected heartbeat to get ignored as a duplicate:", err)
	}

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

	// verify a heartbeat with bad signatures is rejected
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != hsherrInvalidSignature {
		t.Error("expected heartbeat to get ignored as having invalid signatures: ", err)
	}

	// verify that a non-participant gets rejected
	sh.signatories[0] = 3
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != hsherrNonParticipant {
		t.Error("expected non-participant to be rejected: ", err)
	}

	// give heartbeat repeat signatures
	signature1, err = secKey1.Sign(sh.heartbeatHash[:])
	if err != nil {
		t.Fatal(err)
	}

	combinedMessage, err = signature1.CombinedMessage()
	if err != nil {
		t.Fatal(err)
	}
	signature2, err = secKey1.Sign(combinedMessage)
	if err != nil {
		t.Error(err)
	}

	// 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
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != hsherrDoubleSigned {
		t.Error("expected heartbeat to be rejected for duplicate signatures: ", err)
	}

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

	// handle heartbeat when tick is larger than num signatures
	s.stepLock.Lock()
	s.currentStep = 2
	s.stepLock.Unlock()
	err = s.HandleSignedHeartbeat(sh, nil)
	if err != hsherrNoSync {
		t.Error("expected heartbeat to be rejected as out-of-sync: ", err)
	}

	// remaining tests require sleep
	if testing.Short() {
		t.Skip()
	}

	// send a heartbeat right at the edge of a new block
	s.stepLock.Lock()
	s.currentStep = common.QuorumSize
	s.stepLock.Unlock()

	// submit heartbeat in separate thread
	go func() {
		err = s.HandleSignedHeartbeat(sh, nil)
		if err != nil {
			t.Fatal("expected heartbeat to succeed!: ", err)
		}
		// need some way to verify with the test that the funcion gets here
	}()

	s.stepLock.Lock()
	s.currentStep = 1
	s.stepLock.Unlock()
	time.Sleep(time.Second)
	time.Sleep(common.StepDuration)
}
Example #19
0
File: state.go Project: Jonbeek/Sia
	joinSia byte = iota
	incomingSignedHeartbeat
	addressChangeNotification
	newParticipant
)

// Bootstrapping
var bootstrapAddress = common.Address{
	ID:   1,
	Host: "localhost",
	Port: 9988,
}

// empty hash value
var emptyEntropy = common.Entropy{}
var emptyHash, _ = crypto.CalculateTruncatedHash(emptyEntropy[:])

// Identifies other members of the quorum
type Participant struct {
	index     byte
	address   common.Address
	publicKey *crypto.PublicKey
}

// The state provides persistence to the consensus algorithms. Every participant
// should have an identical state.
type State struct {
	// Network Variables
	messageRouter    common.MessageRouter
	participants     [common.QuorumSize]*Participant // list of participants
	participantsLock sync.RWMutex                    // write-locks for compile only
Example #20
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)
}