gitlab.com/jokerrs1/Sia@v1.3.2/modules/consensus/subscribe.go (about)

     1  package consensus
     2  
     3  import (
     4  	"github.com/NebulousLabs/Sia/build"
     5  	"github.com/NebulousLabs/Sia/modules"
     6  
     7  	siasync "github.com/NebulousLabs/Sia/sync"
     8  	"github.com/coreos/bbolt"
     9  )
    10  
    11  // computeConsensusChange computes the consensus change from the change entry
    12  // at index 'i' in the change log. If i is out of bounds, an error is returned.
    13  func (cs *ConsensusSet) computeConsensusChange(tx *bolt.Tx, ce changeEntry) (modules.ConsensusChange, error) {
    14  	cc := modules.ConsensusChange{
    15  		ID: ce.ID(),
    16  	}
    17  	for _, revertedBlockID := range ce.RevertedBlocks {
    18  		revertedBlock, err := getBlockMap(tx, revertedBlockID)
    19  		if err != nil {
    20  			cs.log.Critical("getBlockMap failed in computeConsensusChange:", err)
    21  			return modules.ConsensusChange{}, err
    22  		}
    23  
    24  		// Because the direction is 'revert', the order of the diffs needs to
    25  		// be flipped and the direction of the diffs also needs to be flipped.
    26  		cc.RevertedBlocks = append(cc.RevertedBlocks, revertedBlock.Block)
    27  		for i := len(revertedBlock.SiacoinOutputDiffs) - 1; i >= 0; i-- {
    28  			scod := revertedBlock.SiacoinOutputDiffs[i]
    29  			scod.Direction = !scod.Direction
    30  			cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod)
    31  		}
    32  		for i := len(revertedBlock.FileContractDiffs) - 1; i >= 0; i-- {
    33  			fcd := revertedBlock.FileContractDiffs[i]
    34  			fcd.Direction = !fcd.Direction
    35  			cc.FileContractDiffs = append(cc.FileContractDiffs, fcd)
    36  		}
    37  		for i := len(revertedBlock.SiafundOutputDiffs) - 1; i >= 0; i-- {
    38  			sfod := revertedBlock.SiafundOutputDiffs[i]
    39  			sfod.Direction = !sfod.Direction
    40  			cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod)
    41  		}
    42  		for i := len(revertedBlock.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- {
    43  			dscod := revertedBlock.DelayedSiacoinOutputDiffs[i]
    44  			dscod.Direction = !dscod.Direction
    45  			cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod)
    46  		}
    47  		for i := len(revertedBlock.SiafundPoolDiffs) - 1; i >= 0; i-- {
    48  			sfpd := revertedBlock.SiafundPoolDiffs[i]
    49  			sfpd.Direction = modules.DiffRevert
    50  			cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd)
    51  		}
    52  	}
    53  	for _, appliedBlockID := range ce.AppliedBlocks {
    54  		appliedBlock, err := getBlockMap(tx, appliedBlockID)
    55  		if err != nil {
    56  			cs.log.Critical("getBlockMap failed in computeConsensusChange:", err)
    57  			return modules.ConsensusChange{}, err
    58  		}
    59  
    60  		cc.AppliedBlocks = append(cc.AppliedBlocks, appliedBlock.Block)
    61  		for _, scod := range appliedBlock.SiacoinOutputDiffs {
    62  			cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod)
    63  		}
    64  		for _, fcd := range appliedBlock.FileContractDiffs {
    65  			cc.FileContractDiffs = append(cc.FileContractDiffs, fcd)
    66  		}
    67  		for _, sfod := range appliedBlock.SiafundOutputDiffs {
    68  			cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod)
    69  		}
    70  		for _, dscod := range appliedBlock.DelayedSiacoinOutputDiffs {
    71  			cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod)
    72  		}
    73  		for _, sfpd := range appliedBlock.SiafundPoolDiffs {
    74  			cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd)
    75  		}
    76  	}
    77  
    78  	// Grab the child target and the minimum valid child timestamp.
    79  	recentBlock := ce.AppliedBlocks[len(ce.AppliedBlocks)-1]
    80  	pb, err := getBlockMap(tx, recentBlock)
    81  	if err != nil {
    82  		cs.log.Critical("could not find process block for known block")
    83  	}
    84  	cc.ChildTarget = pb.ChildTarget
    85  	cc.MinimumValidChildTimestamp = cs.blockRuleHelper.minimumValidChildTimestamp(tx.Bucket(BlockMap), pb)
    86  
    87  	currentBlock := currentBlockID(tx)
    88  	if cs.synced && recentBlock == currentBlock {
    89  		cc.Synced = true
    90  	}
    91  
    92  	// Add the unexported tryTransactionSet function.
    93  	cc.TryTransactionSet = cs.tryTransactionSet
    94  
    95  	return cc, nil
    96  }
    97  
    98  // readLockUpdateSubscribers will inform all subscribers of a new update to the
    99  // consensus set. updateSubscribers does not alter the changelog, the changelog
   100  // must be updated beforehand.
   101  func (cs *ConsensusSet) updateSubscribers(ce changeEntry) {
   102  	if len(cs.subscribers) == 0 {
   103  		return
   104  	}
   105  	// Get the consensus change and send it to all subscribers.
   106  	var cc modules.ConsensusChange
   107  	err := cs.db.View(func(tx *bolt.Tx) error {
   108  		// Compute the consensus change so it can be sent to subscribers.
   109  		var err error
   110  		cc, err = cs.computeConsensusChange(tx, ce)
   111  		return err
   112  	})
   113  	if err != nil {
   114  		cs.log.Critical("computeConsensusChange failed:", err)
   115  		return
   116  	}
   117  	for _, subscriber := range cs.subscribers {
   118  		subscriber.ProcessConsensusChange(cc)
   119  	}
   120  }
   121  
   122  // managedInitializeSubscribe will take a subscriber and feed them all of the
   123  // consensus changes that have occurred since the change provided.
   124  //
   125  // As a special case, using an empty id as the start will have all the changes
   126  // sent to the modules starting with the genesis block.
   127  func (cs *ConsensusSet) managedInitializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID,
   128  	cancel <-chan struct{}) error {
   129  
   130  	if start == modules.ConsensusChangeRecent {
   131  		return nil
   132  	}
   133  
   134  	// 'exists' and 'entry' are going to be pointed to the first entry that
   135  	// has not yet been seen by subscriber.
   136  	var exists bool
   137  	var entry changeEntry
   138  
   139  	cs.mu.RLock()
   140  	err := cs.db.View(func(tx *bolt.Tx) error {
   141  		if start == modules.ConsensusChangeBeginning {
   142  			// Special case: for modules.ConsensusChangeBeginning, create an
   143  			// initial node pointing to the genesis block. The subscriber will
   144  			// receive the diffs for all blocks in the consensus set, including
   145  			// the genesis block.
   146  			entry = cs.genesisEntry()
   147  			exists = true
   148  		} else {
   149  			// The subscriber has provided an existing consensus change.
   150  			// Because the subscriber already has this consensus change,
   151  			// 'entry' and 'exists' need to be pointed at the next consensus
   152  			// change.
   153  			entry, exists = getEntry(tx, start)
   154  			if !exists {
   155  				// modules.ErrInvalidConsensusChangeID is a named error that
   156  				// signals a break in synchronization between the consensus set
   157  				// persistence and the subscriber persistence. Typically,
   158  				// receiving this error means that the subscriber needs to
   159  				// perform a rescan of the consensus set.
   160  				return modules.ErrInvalidConsensusChangeID
   161  			}
   162  			entry, exists = entry.NextEntry(tx)
   163  		}
   164  		return nil
   165  	})
   166  	cs.mu.RUnlock()
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	// Send all remaining consensus changes to the subscriber.
   172  	for exists {
   173  		// Send changes in batches of 100 so that we don't hold the
   174  		// lock for too long.
   175  		cs.mu.RLock()
   176  		err = cs.db.View(func(tx *bolt.Tx) error {
   177  			for i := 0; i < 100 && exists; i++ {
   178  				select {
   179  				case <-cancel:
   180  					return siasync.ErrStopped
   181  				default:
   182  				}
   183  				cc, err := cs.computeConsensusChange(tx, entry)
   184  				if err != nil {
   185  					return err
   186  				}
   187  				subscriber.ProcessConsensusChange(cc)
   188  				entry, exists = entry.NextEntry(tx)
   189  			}
   190  			return nil
   191  		})
   192  		cs.mu.RUnlock()
   193  		if err != nil {
   194  			return err
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  // ConsensusSetSubscribe adds a subscriber to the list of subscribers, and
   201  // gives them every consensus change that has occurred since the change with
   202  // the provided id.
   203  //
   204  // As a special case, using an empty id as the start will have all the changes
   205  // sent to the modules starting with the genesis block.
   206  func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID,
   207  	cancel <-chan struct{}) error {
   208  
   209  	err := cs.tg.Add()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	defer cs.tg.Done()
   214  
   215  	// Get the input module caught up to the current consensus set.
   216  	err = cs.managedInitializeSubscribe(subscriber, start, cancel)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	// Add the module to the list of subscribers.
   222  	cs.mu.Lock()
   223  	// Sanity check - subscriber should not be already subscribed.
   224  	for _, s := range cs.subscribers {
   225  		if s == subscriber {
   226  			build.Critical("refusing to double-subscribe subscriber")
   227  		}
   228  	}
   229  	cs.subscribers = append(cs.subscribers, subscriber)
   230  	cs.mu.Unlock()
   231  	return nil
   232  }
   233  
   234  // Unsubscribe removes a subscriber from the list of subscribers, allowing for
   235  // garbage collection and rescanning. If the subscriber is not found in the
   236  // subscriber database, no action is taken.
   237  func (cs *ConsensusSet) Unsubscribe(subscriber modules.ConsensusSetSubscriber) {
   238  	if cs.tg.Add() != nil {
   239  		return
   240  	}
   241  	defer cs.tg.Done()
   242  	cs.mu.Lock()
   243  	defer cs.mu.Unlock()
   244  
   245  	// Search for the subscriber in the list of subscribers and remove it if
   246  	// found.
   247  	for i := range cs.subscribers {
   248  		if cs.subscribers[i] == subscriber {
   249  			// nil the subscriber entry (otherwise it will not be GC'd if it's
   250  			// at the end of the subscribers slice).
   251  			cs.subscribers[i] = nil
   252  			// Delete the entry from the slice.
   253  			cs.subscribers = append(cs.subscribers[0:i], cs.subscribers[i+1:]...)
   254  			break
   255  		}
   256  	}
   257  }