github.com/decred/dcrlnd@v0.7.6/chanfitness/chaneventstore_testctx_test.go (about) 1 package chanfitness 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/decred/dcrd/chaincfg/chainhash" 8 "github.com/decred/dcrd/dcrec/secp256k1/v4" 9 "github.com/decred/dcrd/wire" 10 "github.com/decred/dcrlnd/channeldb" 11 "github.com/decred/dcrlnd/channelnotifier" 12 "github.com/decred/dcrlnd/clock" 13 "github.com/decred/dcrlnd/peernotifier" 14 "github.com/decred/dcrlnd/routing/route" 15 "github.com/decred/dcrlnd/subscribe" 16 "github.com/decred/dcrlnd/ticker" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // timeout is the amount of time we allow our blocking test calls. 21 var timeout = time.Second 22 23 // chanEventStoreTestCtx is a helper struct which can be used to test the 24 // channel event store. 25 type chanEventStoreTestCtx struct { 26 t *testing.T 27 28 store *ChannelEventStore 29 30 channelSubscription *mockSubscription 31 peerSubscription *mockSubscription 32 33 // testVarIdx is an index which will be used to deterministically add 34 // channels and public keys to our test context. We use a single value 35 // for a single pubkey + channel combination because its actual value 36 // does not matter. 37 testVarIdx int 38 39 // clock is the clock that our test store will use. 40 clock *clock.TestClock 41 42 // flapUpdates stores our most recent set of updates flap counts. 43 flapUpdates peerFlapCountMap 44 45 // flapCountUpdates is a channel which receives new flap counts. 46 flapCountUpdates chan peerFlapCountMap 47 48 // stopped is closed when our test context is fully shutdown. It is 49 // used to prevent calling of functions which can only be called after 50 // shutdown. 51 stopped chan struct{} 52 } 53 54 // newChanEventStoreTestCtx creates a test context which can be used to test 55 // the event store. 56 func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { 57 testCtx := &chanEventStoreTestCtx{ 58 t: t, 59 channelSubscription: newMockSubscription(t), 60 peerSubscription: newMockSubscription(t), 61 clock: clock.NewTestClock(testNow), 62 flapUpdates: make(peerFlapCountMap), 63 flapCountUpdates: make(chan peerFlapCountMap), 64 stopped: make(chan struct{}), 65 } 66 67 cfg := &Config{ 68 Clock: testCtx.clock, 69 SubscribeChannelEvents: func() (subscribe.Subscription, error) { 70 return testCtx.channelSubscription, nil 71 }, 72 SubscribePeerEvents: func() (subscribe.Subscription, error) { 73 return testCtx.peerSubscription, nil 74 }, 75 GetOpenChannels: func() ([]*channeldb.OpenChannel, error) { 76 return nil, nil 77 }, 78 WriteFlapCount: func(updates map[route.Vertex]*channeldb.FlapCount) error { 79 // Send our whole update map into the test context's 80 // updates channel. The test will need to assert flap 81 // count updated or this send will timeout. 82 select { 83 case testCtx.flapCountUpdates <- updates: 84 85 case <-time.After(timeout): 86 t.Fatalf("WriteFlapCount timeout") 87 } 88 89 return nil 90 }, 91 ReadFlapCount: func(peer route.Vertex) (*channeldb.FlapCount, error) { 92 count, ok := testCtx.flapUpdates[peer] 93 if !ok { 94 return nil, channeldb.ErrNoPeerBucket 95 } 96 97 return count, nil 98 }, 99 FlapCountTicker: ticker.NewForce(FlapCountFlushRate), 100 } 101 102 testCtx.store = NewChannelEventStore(cfg) 103 104 return testCtx 105 } 106 107 // start starts the test context's event store. 108 func (c *chanEventStoreTestCtx) start() { 109 require.NoError(c.t, c.store.Start()) 110 } 111 112 // stop stops the channel event store's subscribe servers and the store itself. 113 func (c *chanEventStoreTestCtx) stop() { 114 // On shutdown of our event store, we write flap counts to disk. In our 115 // test context, this write function is blocked on asserting that the 116 // update has occurred. We stop our store in a goroutine so that we 117 // can shut it down and assert that it performs these on-shutdown 118 // updates. The stopped channel is used to ensure that we do not finish 119 // our test before this shutdown has completed. 120 go func() { 121 c.store.Stop() 122 close(c.stopped) 123 }() 124 125 // We write our flap count to disk on shutdown, assert that the most 126 // recent record that the server has is written on shutdown. Calling 127 // this assert unblocks the stop function above. We don't check values 128 // here, so that our tests don't all require providing an expected swap 129 // count, but at least assert that the write occurred. 130 c.assertFlapCountUpdated() 131 132 <-c.stopped 133 134 // Make sure that the cancel function was called for both of our 135 // subscription mocks. 136 c.channelSubscription.assertCancelled() 137 c.peerSubscription.assertCancelled() 138 } 139 140 // newChannel creates a new, unique test channel. Note that this function 141 // does not add it to the test event store, it just creates mocked values. 142 func (c *chanEventStoreTestCtx) newChannel() (route.Vertex, *secp256k1.PublicKey, 143 wire.OutPoint) { 144 145 // Create a pubkey for our channel peer. 146 privKey, _ := secp256k1.GeneratePrivateKey() 147 pubKey := privKey.PubKey() 148 149 // Create vertex from our pubkey. 150 vertex, err := route.NewVertexFromBytes(pubKey.SerializeCompressed()) 151 require.NoError(c.t, err) 152 153 // Create a channel point using our channel index, then increment it. 154 chanPoint := wire.OutPoint{ 155 Hash: [chainhash.HashSize]byte{1, 2, 3}, 156 Index: uint32(c.testVarIdx), 157 } 158 159 // Increment the index we use so that the next channel and pubkey we 160 // create will be unique. 161 c.testVarIdx++ 162 163 return vertex, pubKey, chanPoint 164 } 165 166 // createChannel creates a new channel, notifies the event store that it has 167 // been created and returns the peer vertex, pubkey and channel point. 168 func (c *chanEventStoreTestCtx) createChannel() (route.Vertex, *secp256k1.PublicKey, 169 wire.OutPoint) { 170 171 vertex, pubKey, chanPoint := c.newChannel() 172 c.sendChannelOpenedUpdate(pubKey, chanPoint) 173 174 return vertex, pubKey, chanPoint 175 } 176 177 // closeChannel sends a close channel event to our subscribe server. 178 func (c *chanEventStoreTestCtx) closeChannel(channel wire.OutPoint, 179 peer *secp256k1.PublicKey) { 180 181 update := channelnotifier.ClosedChannelEvent{ 182 CloseSummary: &channeldb.ChannelCloseSummary{ 183 ChanPoint: channel, 184 RemotePub: peer, 185 }, 186 } 187 188 c.channelSubscription.sendUpdate(update) 189 } 190 191 // tickFlapCount forces a tick for our flap count ticker with the current time. 192 func (c *chanEventStoreTestCtx) tickFlapCount() { 193 testTicker := c.store.cfg.FlapCountTicker.(*ticker.Force) 194 195 select { 196 case testTicker.Force <- c.store.cfg.Clock.Now(): 197 198 case <-time.After(timeout): 199 c.t.Fatalf("could not tick flap count ticker") 200 } 201 } 202 203 // peerEvent sends a peer online or offline event to the store for the peer 204 // provided. 205 func (c *chanEventStoreTestCtx) peerEvent(peer route.Vertex, online bool) { 206 var update interface{} 207 if online { 208 update = peernotifier.PeerOnlineEvent{PubKey: peer} 209 } else { 210 update = peernotifier.PeerOfflineEvent{PubKey: peer} 211 } 212 213 c.peerSubscription.sendUpdate(update) 214 } 215 216 // sendChannelOpenedUpdate notifies the test event store that a channel has 217 // been opened. 218 func (c *chanEventStoreTestCtx) sendChannelOpenedUpdate(pubkey *secp256k1.PublicKey, 219 channel wire.OutPoint) { 220 221 update := channelnotifier.OpenChannelEvent{ 222 Channel: &channeldb.OpenChannel{ 223 FundingOutpoint: channel, 224 IdentityPub: pubkey, 225 }, 226 } 227 228 c.channelSubscription.sendUpdate(update) 229 } 230 231 // assertFlapCountUpdated asserts that our store has made an attempt to write 232 // our current set of flap counts to disk and sets this value in our test ctx. 233 // Note that it does not check the values of the update. 234 func (c *chanEventStoreTestCtx) assertFlapCountUpdated() { 235 select { 236 case c.flapUpdates = <-c.flapCountUpdates: 237 238 case <-time.After(timeout): 239 c.t.Fatalf("assertFlapCountUpdated timeout") 240 } 241 } 242 243 // assertFlapCountUpdates asserts that out current record of flap counts is 244 // as expected. 245 func (c *chanEventStoreTestCtx) assertFlapCountUpdates(expected peerFlapCountMap) { 246 require.Equal(c.t, expected, c.flapUpdates) 247 } 248 249 // mockSubscription is a mock subscription client that blocks on sends into the 250 // updates channel. We use this mock rather than an actual subscribe client 251 // because they do not block, which makes tests race (because we have no way 252 // to guarantee that the test client consumes the update before shutdown). 253 type mockSubscription struct { 254 t *testing.T 255 updates chan interface{} 256 257 // Embed the subscription interface in this mock so that we satisfy it. 258 subscribe.Subscription 259 } 260 261 // newMockSubscription creates a mock subscription. 262 func newMockSubscription(t *testing.T) *mockSubscription { 263 return &mockSubscription{ 264 t: t, 265 updates: make(chan interface{}), 266 } 267 } 268 269 // sendUpdate sends an update into our updates channel, mocking the dispatch of 270 // an update from a subscription server. This call will fail the test if the 271 // update is not consumed within our timeout. 272 func (m *mockSubscription) sendUpdate(update interface{}) { 273 select { 274 case m.updates <- update: 275 276 case <-time.After(timeout): 277 m.t.Fatalf("update: %v timeout", update) 278 } 279 } 280 281 // Updates returns the updates channel for the mock. 282 func (m *mockSubscription) Updates() <-chan interface{} { 283 return m.updates 284 } 285 286 // Cancel should be called in case the client no longer wants to subscribe for 287 // updates from the server. 288 func (m *mockSubscription) Cancel() { 289 close(m.updates) 290 } 291 292 // assertCancelled asserts that the cancel function has been called for this 293 // mock. 294 func (m *mockSubscription) assertCancelled() { 295 select { 296 case _, open := <-m.updates: 297 require.False(m.t, open, "subscription not cancelled") 298 299 case <-time.After(timeout): 300 m.t.Fatalf("assert cancelled timeout") 301 } 302 }