github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/subscribe.go (about)

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