github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/subscribe.go (about)

     1  package consensus
     2  
     3  import (
     4  	"github.com/NebulousLabs/Sia/modules"
     5  
     6  	"github.com/NebulousLabs/bolt"
     7  )
     8  
     9  // computeConsensusChange computes the consensus change from the change entry
    10  // at index 'i' in the change log. If i is out of bounds, an error is returned.
    11  func (cs *ConsensusSet) computeConsensusChange(tx *bolt.Tx, ce changeEntry) (modules.ConsensusChange, error) {
    12  	cc := modules.ConsensusChange{
    13  		ID: ce.ID(),
    14  	}
    15  	for _, revertedBlockID := range ce.RevertedBlocks {
    16  		revertedBlock, err := getBlockMap(tx, revertedBlockID)
    17  		if err != nil {
    18  			cs.log.Critical("getBlockMap failed in computeConsensusChange:", err)
    19  			return modules.ConsensusChange{}, err
    20  		}
    21  
    22  		// Because the direction is 'revert', the order of the diffs needs to
    23  		// be flipped and the direction of the diffs also needs to be flipped.
    24  		cc.RevertedBlocks = append(cc.RevertedBlocks, revertedBlock.Block)
    25  		for i := len(revertedBlock.SiacoinOutputDiffs) - 1; i >= 0; i-- {
    26  			scod := revertedBlock.SiacoinOutputDiffs[i]
    27  			scod.Direction = !scod.Direction
    28  			cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod)
    29  		}
    30  		for i := len(revertedBlock.FileContractDiffs) - 1; i >= 0; i-- {
    31  			fcd := revertedBlock.FileContractDiffs[i]
    32  			fcd.Direction = !fcd.Direction
    33  			cc.FileContractDiffs = append(cc.FileContractDiffs, fcd)
    34  		}
    35  		for i := len(revertedBlock.SiafundOutputDiffs) - 1; i >= 0; i-- {
    36  			sfod := revertedBlock.SiafundOutputDiffs[i]
    37  			sfod.Direction = !sfod.Direction
    38  			cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod)
    39  		}
    40  		for i := len(revertedBlock.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- {
    41  			dscod := revertedBlock.DelayedSiacoinOutputDiffs[i]
    42  			dscod.Direction = !dscod.Direction
    43  			cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod)
    44  		}
    45  		for i := len(revertedBlock.SiafundPoolDiffs) - 1; i >= 0; i-- {
    46  			sfpd := revertedBlock.SiafundPoolDiffs[i]
    47  			sfpd.Direction = modules.DiffRevert
    48  			cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd)
    49  		}
    50  	}
    51  	for _, appliedBlockID := range ce.AppliedBlocks {
    52  		appliedBlock, err := getBlockMap(tx, appliedBlockID)
    53  		if err != nil {
    54  			cs.log.Critical("getBlockMap failed in computeConsensusChange:", err)
    55  			return modules.ConsensusChange{}, err
    56  		}
    57  
    58  		cc.AppliedBlocks = append(cc.AppliedBlocks, appliedBlock.Block)
    59  		for _, scod := range appliedBlock.SiacoinOutputDiffs {
    60  			cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod)
    61  		}
    62  		for _, fcd := range appliedBlock.FileContractDiffs {
    63  			cc.FileContractDiffs = append(cc.FileContractDiffs, fcd)
    64  		}
    65  		for _, sfod := range appliedBlock.SiafundOutputDiffs {
    66  			cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod)
    67  		}
    68  		for _, dscod := range appliedBlock.DelayedSiacoinOutputDiffs {
    69  			cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod)
    70  		}
    71  		for _, sfpd := range appliedBlock.SiafundPoolDiffs {
    72  			cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd)
    73  		}
    74  	}
    75  	return cc, nil
    76  }
    77  
    78  // readlockUpdateSubscribers will inform all subscribers of a new update to the
    79  // consensus set. The call must be made with a demoted lock or a readlock.
    80  // readlockUpdateSubscribers does not alter the changelog, the changelog must
    81  // be updated beforehand.
    82  func (cs *ConsensusSet) readlockUpdateSubscribers(ce changeEntry) {
    83  	// Get the consensus change and send it to all subscribers.
    84  	var cc modules.ConsensusChange
    85  	err := cs.db.View(func(tx *bolt.Tx) error {
    86  		// Compute the consensus change so it can be sent to subscribers.
    87  		var err error
    88  		cc, err = cs.computeConsensusChange(tx, ce)
    89  		return err
    90  	})
    91  	if err != nil {
    92  		cs.log.Critical("computeConsensusChange failed:", err)
    93  		return
    94  	}
    95  	for _, subscriber := range cs.subscribers {
    96  		subscriber.ProcessConsensusChange(cc)
    97  	}
    98  }
    99  
   100  // initializeSubscribe will take a subscriber and feed them all of the
   101  // consensus changes that have occurred since the change provided.
   102  //
   103  // As a special case, using an empty id as the start will have all the changes
   104  // sent to the modules starting with the genesis block.
   105  func (cs *ConsensusSet) initializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error {
   106  	return cs.db.View(func(tx *bolt.Tx) error {
   107  		// 'exists' and 'entry' are going to be pointed to the first entry that
   108  		// has not yet been seen by subscriber.
   109  		var exists bool
   110  		var entry changeEntry
   111  
   112  		if start == modules.ConsensusChangeBeginning {
   113  			// Special case: for modules.ConsensusChangeBeginning, create an
   114  			// initial node pointing to the genesis block. The subscriber will
   115  			// receive the diffs for all blocks in the consensus set, including
   116  			// the genesis block.
   117  			entry = cs.genesisEntry()
   118  			exists = true
   119  		} else if start == modules.ConsensusChangeRecent {
   120  			// Special case: for modules.ConsensusChangeRecent, set up the
   121  			// subscriber to start receiving only new blocks, but the
   122  			// subscriber does not need to do any catch-up. For this
   123  			// implementation, a no-op will have this effect.
   124  			return nil
   125  		} else {
   126  			// The subscriber has provided an existing consensus change.
   127  			// Because the subscriber already has this consensus change,
   128  			// 'entry' and 'exists' need to be pointed at the next consensus
   129  			// change.
   130  			entry, exists = getEntry(tx, start)
   131  			if !exists {
   132  				// modules.ErrInvalidConsensusChangeID is a named error that
   133  				// signals a break in synchronization between the consensus set
   134  				// persistence and the subscriber persistence. Typically,
   135  				// receiving this error means that the subscriber needs to
   136  				// perform a rescan of the consensus set.
   137  				return modules.ErrInvalidConsensusChangeID
   138  			}
   139  			entry, exists = entry.NextEntry(tx)
   140  		}
   141  
   142  		// Send all remaining consensus changes to the subscriber.
   143  		for exists {
   144  			cc, err := cs.computeConsensusChange(tx, entry)
   145  			if err != nil {
   146  				return err
   147  			}
   148  			subscriber.ProcessConsensusChange(cc)
   149  			entry, exists = entry.NextEntry(tx)
   150  		}
   151  		return nil
   152  	})
   153  }
   154  
   155  // ConsensusSetSubscribe adds a subscriber to the list of subscribers, and
   156  // gives them every consensus change that has occurred since the change with
   157  // the provided id.
   158  //
   159  // As a special case, using an empty id as the start will have all the changes
   160  // sent to the modules starting with the genesis block.
   161  func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error {
   162  	cs.mu.Lock()
   163  	cs.subscribers = append(cs.subscribers, subscriber)
   164  	cs.mu.Demote()
   165  	defer cs.mu.DemotedUnlock()
   166  
   167  	// Get the input module caught up to the currenct consnesus set.
   168  	err := cs.initializeSubscribe(subscriber, start)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	// Only add the module as a subscriber if there was no error.
   173  	return nil
   174  }
   175  
   176  // Unsubscribe removes a subscriber from the list of subscribers, allowing for
   177  // garbage collection and rescanning. If the subscriber is not found in the
   178  // subscriber database, no action is taken.
   179  func (cs *ConsensusSet) Unsubscribe(subscriber modules.ConsensusSetSubscriber) {
   180  	cs.mu.Lock()
   181  	defer cs.mu.Unlock()
   182  
   183  	// Search for the subscriber in the list of subscribers and remove it if
   184  	// found.
   185  	for i := range cs.subscribers {
   186  		if cs.subscribers[i] == subscriber {
   187  			cs.subscribers = append(cs.subscribers[0:i], cs.subscribers[i+1:]...)
   188  			break
   189  		}
   190  	}
   191  }