Example #1
0
// ConsensusSetSubscribe accepts a new subscriber who will receive a call to
// ProcessConsensusChange every time there is a change in the consensus set.
func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber) {
	id := cs.mu.Lock()
	cs.subscribers = append(cs.subscribers, subscriber)
	for i := range cs.changeLog {
		cc, err := cs.computeConsensusChange(i)
		if err != nil && build.DEBUG {
			panic(err)
		}
		subscriber.ProcessConsensusChange(cc)
	}
	cs.mu.Unlock(id)
}
Example #2
0
// initializeSubscribe will take a subscriber and feed them all of the
// consensus changes that have occurred since the change provided.
//
// As a special case, using an empty id as the start will have all the changes
// sent to the modules starting with the genesis block.
func (cs *ConsensusSet) initializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error {
	return cs.db.View(func(tx *bolt.Tx) error {
		// 'exists' and 'entry' are going to be pointed to the first entry that
		// has not yet been seen by subscriber.
		var exists bool
		var entry changeEntry

		if start == modules.ConsensusChangeBeginning {
			// Special case: for modules.ConsensusChangeBeginning, create an
			// initial node pointing to the genesis block. The subscriber will
			// receive the diffs for all blocks in the consensus set, including
			// the genesis block.
			entry = cs.genesisEntry()
			exists = true
		} else if start == modules.ConsensusChangeRecent {
			// Special case: for modules.ConsensusChangeRecent, set up the
			// subscriber to start receiving only new blocks, but the
			// subscriber does not need to do any catch-up. For this
			// implementation, a no-op will have this effect.
			return nil
		} else {
			// The subscriber has provided an existing consensus change.
			// Because the subscriber already has this consensus change,
			// 'entry' and 'exists' need to be pointed at the next consensus
			// change.
			entry, exists = getEntry(tx, start)
			if !exists {
				// modules.ErrInvalidConsensusChangeID is a named error that
				// signals a break in synchronization between the consensus set
				// persistence and the subscriber persistence. Typically,
				// receiving this error means that the subscriber needs to
				// perform a rescan of the consensus set.
				return modules.ErrInvalidConsensusChangeID
			}
			entry, exists = entry.NextEntry(tx)
		}

		// Send all remaining consensus changes to the subscriber.
		for exists {
			cc, err := cs.computeConsensusChange(tx, entry)
			if err != nil {
				return err
			}
			subscriber.ProcessConsensusChange(cc)
			entry, exists = entry.NextEntry(tx)
		}
		return nil
	})
}
Example #3
0
// initializePersistentSubscribe will take a subscriber and feed them all of the
// consensus changes that have occurred since the change provided.
//
// As a special case, using an empty id as the start will have all the changes
// sent to the modules starting with the genesis block.
func (cs *ConsensusSet) initializePersistentSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error {
	return cs.db.View(func(tx *bolt.Tx) error {
		// 'exists' and 'entry' are going to be pointed to the first entry that
		// has not yet been seen by subscriber.
		var exists bool
		var entry changeEntry

		if start == (modules.ConsensusChangeID{}) {
			// Special case: if 'start' is blank, create an initial node
			// pointing to the genesis block. The subscriber will recieve the
			// diffs for all blocks in the consensus set, including the genesis
			// block.
			entry = cs.genesisEntry()
			exists = true
		} else {
			// The subscriber has provided an existing consensus change.
			// Because the subscriber already has this consensus change,
			// 'entry' and 'exists' need to be pointed at the next consensus
			// change.
			entry, exists = getEntry(tx, start)
			if !exists {
				// modules.ErrInvalidConsensusChangeID is a named error that
				// signals a break in synchronization between the consensus set
				// persistence and the subscriber persistence. Typically,
				// receiving this error means that the subscriber needs to
				// perform a rescan of the consensus set.
				return modules.ErrInvalidConsensusChangeID
			}
			entry, exists = entry.NextEntry(tx)
		}

		// Send all remaining consensus changes to the subscriber.
		for exists {
			cc, err := cs.computeConsensusChange(tx, entry)
			if err != nil {
				return err
			}
			subscriber.ProcessConsensusChange(cc)
			entry, exists = entry.NextEntry(tx)
		}
		return nil
	})
}
Example #4
0
func (cs *rescanCS) ConsensusSetSubscribe(s modules.ConsensusSetSubscriber, lastChange modules.ConsensusChangeID) error {
	var start int
	if lastChange != (modules.ConsensusChangeID{}) {
		start = -1
		for i, cc := range cs.changes {
			if cc.ID == lastChange {
				start = i
				break
			}
		}
		if start == -1 {
			return modules.ErrInvalidConsensusChangeID
		}
	}
	for _, cc := range cs.changes[start:] {
		s.ProcessConsensusChange(cc)
	}
	return nil
}
Example #5
0
// ConsensusSetSubscribe accepts a new subscriber who will receive a call to
// ProcessConsensusChange every time there is a change in the consensus set.
func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber) {
	cs.mu.Lock()
	cs.subscribers = append(cs.subscribers, subscriber)
	cs.mu.Demote()
	defer cs.mu.DemotedUnlock()

	err := cs.db.View(func(tx *bolt.Tx) error {
		for i := range cs.changeLog {
			cc, err := cs.computeConsensusChange(tx, cs.changeLog[i])
			if err != nil && build.DEBUG {
				panic(err)
			}
			subscriber.ProcessConsensusChange(cc)
		}
		return nil
	})
	if build.DEBUG && err != nil {
		panic(err)
	}
}
Example #6
0
// threadedSendUpdates sends updates to a specific subscriber as they become
// available. One thread is needed per subscriber. A separate function was
// needed due to race conditions; subscribers must receive updates in the
// correct order. Furthermore, a deadlocked subscriber should not interfere
// with consensus; updates cannot make blocking calls from any thread that is
// holding a lock on consensus. The result is a construction where all updates
// are added to a list of updates in the consensus set while the consensus set
// is locked. Then, a separate thread for each subscriber will be notified (via
// the update chan) that there are new updates. The thread will lock the
// consensus set for long enough to get the updates, and then will unlock the
// consensus set while it makes a blocking call to the subscriber. If the
// subscriber deadlocks or has problems, the thread will stall indefinitely,
// but the rest of consensus will not be disrupted.
func (s *State) threadedSendUpdates(update chan struct{}, subscriber modules.ConsensusSetSubscriber) {
	i := 0
	for {
		id := s.mu.RLock()
		updateCount := len(s.consensusChanges)
		s.mu.RUnlock(id)
		for i < updateCount {
			// Get the set of blocks that changed since the previous update.
			id := s.mu.RLock()
			cc := s.consensusChanges[i]
			s.mu.RUnlock(id)

			// Update the subscriber with the changes.
			subscriber.ReceiveConsensusSetUpdate(cc)
			i++
		}

		// Wait until there has been another update.
		<-update
	}
}
Example #7
0
// ConsensusSetPersistentSubscribe adds a subscriber to the list of
// subscribers, and gives them every consensus change that has occured since
// the change with the provided id.
//
// As a special case, using an empty id as the start will have all the changes
// sent to the modules starting with the genesis block.
func (cs *ConsensusSet) ConsensusSetPersistentSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error {
	// Add the subscriber to the list of subscribers under lock, and then
	// demote while sending the subscriber all of the changes they've missed.
	cs.mu.Lock()
	cs.subscribers = append(cs.subscribers, subscriber)
	cs.mu.Demote()
	defer cs.mu.DemotedUnlock()

	err := cs.db.View(func(tx *bolt.Tx) error {
		var exists bool
		var entry changeEntry
		// Special case: if 'start' is blank, create an initial node pointing to
		// the genesis block.
		if start == (modules.ConsensusChangeID{}) {
			entry = cs.genesisEntry()
			exists = true
		} else {
			entry, exists = getEntry(tx, start)
			if !exists {
				return errChangeEntryNotFound
			}
			entry, exists = entry.NextEntry(tx)
		}

		for exists {
			cc, err := cs.computeConsensusChange(tx, entry)
			if err != nil {
				return err
			}
			subscriber.ProcessConsensusChange(cc)
			entry, exists = entry.NextEntry(tx)
		}
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}