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

     1  package consensus
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	bolt "github.com/coreos/bbolt"
     9  )
    10  
    11  // mockSubscriber receives and holds changes to the consensus set, remembering
    12  // the order in which changes were received.
    13  type mockSubscriber struct {
    14  	updates []modules.ConsensusChange
    15  }
    16  
    17  // newMockSubscriber returns a mockSubscriber that is ready to subscribe to a
    18  // consensus set. Currently blank, but can be expanded to support more features
    19  // in the future.
    20  func newMockSubscriber() mockSubscriber {
    21  	return mockSubscriber{}
    22  }
    23  
    24  // ProcessConsensusChange adds a consensus change to the mock subscriber.
    25  func (ms *mockSubscriber) ProcessConsensusChange(cc modules.ConsensusChange) {
    26  	ms.updates = append(ms.updates, cc)
    27  }
    28  
    29  // copySub creates and returns a new mock subscriber that has identical
    30  // internals to the input mockSubscriber. The copy will not be subscribed to
    31  // the consensus set even if the original is.
    32  func (ms *mockSubscriber) copySub() (cms mockSubscriber) {
    33  	cms.updates = make([]modules.ConsensusChange, len(ms.updates))
    34  	copy(cms.updates, ms.updates)
    35  	return cms
    36  }
    37  
    38  // TestInvalidConsensusChangeSubscription checks that the consensus set returns
    39  // modules.ErrInvalidConsensusChangeID in the event of a subscriber using an
    40  // unrecognized id.
    41  func TestInvalidConsensusChangeSubscription(t *testing.T) {
    42  	if testing.Short() {
    43  		t.SkipNow()
    44  	}
    45  	t.Parallel()
    46  	cst, err := createConsensusSetTester(t.Name())
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	defer cst.Close()
    51  
    52  	ms := newMockSubscriber()
    53  	badCCID := modules.ConsensusChangeID{255, 255, 255}
    54  	err = cst.cs.ConsensusSetSubscribe(&ms, badCCID, cst.cs.tg.StopChan())
    55  	if err != modules.ErrInvalidConsensusChangeID {
    56  		t.Error("consensus set returning the wrong error during an invalid subscription:", err)
    57  	}
    58  
    59  	cst.cs.mu.Lock()
    60  	for i := range cst.cs.subscribers {
    61  		if cst.cs.subscribers[i] == &ms {
    62  			t.Fatal("subscriber was not removed from subscriber list after an erroneus subscription")
    63  		}
    64  	}
    65  	cst.cs.mu.Unlock()
    66  }
    67  
    68  // TestInvalidToValidSubscription is a regression test. Previously, the
    69  // consensus set would not unsubscribe a module if it returned an error during
    70  // subscription. When the module resubscribed, the module would be
    71  // double-subscribed to the consensus set.
    72  func TestInvalidToValidSubscription(t *testing.T) {
    73  	if testing.Short() {
    74  		t.SkipNow()
    75  	}
    76  	t.Parallel()
    77  	cst, err := createConsensusSetTester(t.Name())
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	defer cst.Close()
    82  
    83  	// Start by performing a bad subscribe.
    84  	ms := newMockSubscriber()
    85  	badCCID := modules.ConsensusChangeID{255, 255, 255}
    86  	err = cst.cs.ConsensusSetSubscribe(&ms, badCCID, cst.cs.tg.StopChan())
    87  	if err != modules.ErrInvalidConsensusChangeID {
    88  		t.Error("consensus set returning the wrong error during an invalid subscription:", err)
    89  	}
    90  
    91  	// Perform a correct subscribe.
    92  	err = cst.cs.ConsensusSetSubscribe(&ms, modules.ConsensusChangeBeginning, cst.cs.tg.StopChan())
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	// Mine a block and check that the mock subscriber only got a single
    98  	// consensus change.
    99  	numPrevUpdates := len(ms.updates)
   100  	_, err = cst.miner.AddBlock()
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  	if len(ms.updates) != numPrevUpdates+1 {
   105  		t.Error("subscriber received two consensus changes for a single block")
   106  	}
   107  }
   108  
   109  // TestUnsubscribe checks that the consensus set correctly unsubscribes a
   110  // subscriber if the Unsubscribe call is made.
   111  func TestUnsubscribe(t *testing.T) {
   112  	if testing.Short() {
   113  		t.SkipNow()
   114  	}
   115  	t.Parallel()
   116  	cst, err := createConsensusSetTester(t.Name())
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	defer cst.Close()
   121  
   122  	// Subscribe the mock subscriber to the consensus set.
   123  	ms := newMockSubscriber()
   124  	err = cst.cs.ConsensusSetSubscribe(&ms, modules.ConsensusChangeBeginning, cst.cs.tg.StopChan())
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	// Check that the subscriber is receiving updates.
   130  	msLen := len(ms.updates)
   131  	if msLen == 0 {
   132  		t.Error("mock subscriber is not receiving updates")
   133  	}
   134  	_, err = cst.miner.AddBlock() // should cause another update to be sent to the subscriber
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	if len(ms.updates) != msLen+1 {
   139  		t.Error("mock subscriber did not receive the correct number of updates")
   140  	}
   141  
   142  	// Unsubscribe the subscriber and then check that it is no longer receiving
   143  	// updates.
   144  	cst.cs.Unsubscribe(&ms)
   145  	_, err = cst.miner.AddBlock()
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	if len(ms.updates) != msLen+1 {
   150  		t.Error("mock subscriber was not correctly unsubscribed")
   151  	}
   152  }
   153  
   154  // TestModuletDesync is a reproduction test for the bug that caused a module to
   155  // desync while subscribing to the consensus set.
   156  func TestModuleDesync(t *testing.T) {
   157  	if testing.Short() {
   158  		t.SkipNow()
   159  	}
   160  	deps := &dependencySleepAfterInitializeSubscribe{}
   161  	cst, err := blankConsensusSetTester(t.Name(), deps)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	defer cst.Close()
   166  
   167  	// Mine some blocks.
   168  	for i := 0; i < 10; i++ {
   169  		if _, err := cst.miner.AddBlock(); err != nil {
   170  			t.Fatal(err)
   171  		}
   172  	}
   173  
   174  	// Enable the dependency.
   175  	ms := newMockSubscriber()
   176  	deps.enable()
   177  
   178  	// Subscribe to the consensusSet non-blocking.
   179  	var wg sync.WaitGroup
   180  	wg.Add(1)
   181  	go func() {
   182  		err = cst.cs.ConsensusSetSubscribe(&ms, modules.ConsensusChangeBeginning, cst.cs.tg.StopChan())
   183  		if err != nil {
   184  			t.Error("consensus set returning the wrong error during an invalid subscription:", err)
   185  		}
   186  		wg.Done()
   187  	}()
   188  
   189  	// Mine some more blocks to make sure the module falls behind.
   190  	for i := 0; i < 10; i++ {
   191  		if _, err := cst.miner.AddBlock(); err != nil {
   192  			t.Fatal(err)
   193  		}
   194  	}
   195  
   196  	// Wait for the module to be subscribed.
   197  	wg.Wait()
   198  
   199  	// Get all the updates from the consensusSet.
   200  	updates := make([]modules.ConsensusChange, 0)
   201  	cst.cs.mu.Lock()
   202  	err = cst.cs.db.View(func(tx *bolt.Tx) error {
   203  		entry := cst.cs.genesisEntry()
   204  		exists := true
   205  		for ; exists; entry, exists = entry.NextEntry(tx) {
   206  			cc, err := cst.cs.computeConsensusChange(tx, entry)
   207  			if err != nil {
   208  				return err
   209  			}
   210  			updates = append(updates, cc)
   211  		}
   212  		return nil
   213  	})
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  	cst.cs.mu.Unlock()
   218  
   219  	// The received updates should match.
   220  	if len(updates) != len(ms.updates) {
   221  		t.Fatal("Number of updates doesn't match")
   222  	}
   223  	for i := 0; i < len(updates); i++ {
   224  		if updates[i].ID != ms.updates[i].ID {
   225  			t.Fatal("Update IDs don't match")
   226  		}
   227  	}
   228  
   229  	// Make sure the last update is the recent one in the database.
   230  	cst.cs.mu.Lock()
   231  	recentChangeID, err := cst.cs.recentConsensusChangeID()
   232  	cst.cs.mu.Unlock()
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	if updates[len(updates)-1].ID != recentChangeID {
   237  		t.Fatal("last update doesn't equal recentChangeID")
   238  	}
   239  }