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  }