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 }