github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/flip/chat_test.go (about)

     1  package flip
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"testing"
     8  	"time"
     9  
    10  	chat1 "github.com/keybase/client/go/protocol/chat1"
    11  	gregor1 "github.com/keybase/client/go/protocol/gregor1"
    12  	clockwork "github.com/keybase/clockwork"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  type chatServer struct {
    17  	shutdownCh       chan struct{}
    18  	inputCh          chan GameMessageWrappedEncoded
    19  	chatClients      []*chatClient
    20  	clock            clockwork.FakeClock
    21  	clockForArchiver clockwork.FakeClock
    22  	corruptor        func(GameMessageWrappedEncoded) GameMessageWrappedEncoded
    23  	gameHistories    map[GameIDKey]GameHistory
    24  }
    25  
    26  type chatClient struct {
    27  	shutdownCh chan struct{}
    28  	me         UserDevice
    29  	ch         chan GameMessageWrappedEncoded
    30  	server     *chatServer
    31  	dealer     *Dealer
    32  	history    map[chat1.ConvIDStr]bool
    33  	clock      clockwork.FakeClock
    34  	deliver    func(m GameMessageWrappedEncoded)
    35  }
    36  
    37  var _ DealersHelper = (*chatClient)(nil)
    38  var _ ReplayHelper = (*chatClient)(nil)
    39  
    40  func (c *chatClient) Clock() clockwork.Clock {
    41  	if c.clock != nil {
    42  		return c.clock
    43  	}
    44  	return c.server.clock
    45  }
    46  
    47  func (c *chatClient) ServerTime(context.Context) (time.Time, error) {
    48  	return c.Clock().Now(), nil
    49  }
    50  
    51  func testPrintf(fmtString string, args ...interface{}) {
    52  	if testing.Verbose() {
    53  		fmt.Printf(fmtString, args...)
    54  	}
    55  }
    56  
    57  func (c *chatClient) CLogf(ctx context.Context, fmtString string, args ...interface{}) {
    58  	testPrintf(fmtString+"\n", args...)
    59  }
    60  
    61  func (c *chatClient) Me() UserDevice {
    62  	return c.me
    63  }
    64  
    65  func (c *chatClient) SendChat(ctx context.Context, initiatorUID gregor1.UID, conversationID chat1.ConversationID,
    66  	gameID chat1.FlipGameID, msg GameMessageEncoded) error {
    67  	c.server.inputCh <- GameMessageWrappedEncoded{Body: msg, GameID: gameID, Sender: c.me}
    68  	return nil
    69  }
    70  
    71  func (c *chatClient) ShouldCommit(ctx context.Context) bool {
    72  	return true
    73  }
    74  
    75  func (s *chatServer) archive(msg GameMessageWrappedEncoded) {
    76  	v := s.gameHistories[GameIDToKey(msg.GameID)]
    77  	cl := s.clock
    78  	if s.clockForArchiver != nil {
    79  		cl = s.clockForArchiver
    80  	}
    81  	if len(v) == 0 {
    82  		msg.FirstInConversation = true
    83  	}
    84  	v = append(v, GameMessageReplayed{GameMessageWrappedEncoded: msg, Time: cl.Now()})
    85  	s.gameHistories[GameIDToKey(msg.GameID)] = v
    86  }
    87  
    88  func (s *chatServer) run(ctx context.Context) {
    89  	for {
    90  		select {
    91  		case <-s.shutdownCh:
    92  			return
    93  		case msg := <-s.inputCh:
    94  			if s.corruptor != nil {
    95  				msg = s.corruptor(msg)
    96  			}
    97  			s.archive(msg)
    98  			for _, cli := range s.chatClients {
    99  				if !cli.me.Eq(msg.Sender) {
   100  					cli.deliver(msg)
   101  				}
   102  			}
   103  		}
   104  	}
   105  }
   106  
   107  func (s *chatServer) stop() {
   108  	close(s.shutdownCh)
   109  }
   110  
   111  func newChatServer() *chatServer {
   112  	return &chatServer{
   113  		clock:         clockwork.NewFakeClock(),
   114  		shutdownCh:    make(chan struct{}),
   115  		inputCh:       make(chan GameMessageWrappedEncoded, 1000),
   116  		gameHistories: make(map[GameIDKey]GameHistory),
   117  	}
   118  }
   119  
   120  func (s *chatServer) newClient() *chatClient {
   121  	ret := &chatClient{
   122  		shutdownCh: make(chan struct{}),
   123  		me:         newTestUser(),
   124  		ch:         make(chan GameMessageWrappedEncoded, 1000),
   125  		server:     s,
   126  		history:    make(map[chat1.ConvIDStr]bool),
   127  	}
   128  	ret.dealer = NewDealer(ret)
   129  	ret.deliver = func(m GameMessageWrappedEncoded) {
   130  		ret.ch <- m
   131  	}
   132  	s.chatClients = append(s.chatClients, ret)
   133  	return ret
   134  }
   135  
   136  func (c *chatClient) run(ctx context.Context, ch chat1.ConversationID) {
   137  	go func() {
   138  		_ = c.dealer.Run(ctx)
   139  	}()
   140  	for {
   141  		select {
   142  		case <-c.shutdownCh:
   143  			return
   144  		case msg := <-c.ch:
   145  			chKey := ch.ConvIDStr()
   146  			_ = c.dealer.InjectIncomingChat(ctx, msg.Sender, ch, msg.GameID, msg.Body, !c.history[chKey])
   147  			c.history[chKey] = true
   148  		}
   149  	}
   150  }
   151  
   152  func (s *chatServer) makeAndRunClients(ctx context.Context, ch chat1.ConversationID, nClients int) []*chatClient {
   153  	for i := 0; i < nClients; i++ {
   154  		cli := s.newClient()
   155  		go cli.run(ctx, ch)
   156  	}
   157  	return s.chatClients
   158  }
   159  
   160  func forAllClients(clients []*chatClient, f func(c *chatClient)) {
   161  	for _, cli := range clients {
   162  		f(cli)
   163  	}
   164  }
   165  
   166  func nTimes(n int, f func()) {
   167  	for i := 0; i < n; i++ {
   168  		f()
   169  	}
   170  }
   171  
   172  func (c *chatClient) consumeCommitment(t *testing.T) {
   173  	msg := <-c.dealer.UpdateCh()
   174  	require.NotNil(t, msg.Commitment)
   175  }
   176  
   177  func (c *chatClient) consumeCommitmentComplete(t *testing.T, n int) {
   178  	msg := <-c.dealer.UpdateCh()
   179  	require.NotNil(t, msg.CommitmentComplete)
   180  	require.Equal(t, n, len(msg.CommitmentComplete.Players))
   181  }
   182  
   183  func (c *chatClient) consumeReveal(t *testing.T) {
   184  	msg := <-c.dealer.UpdateCh()
   185  	require.NotNil(t, msg.Reveal)
   186  }
   187  
   188  func (c *chatClient) consumeAbsteneesError(t *testing.T, n int) {
   189  	msg := <-c.dealer.UpdateCh()
   190  	require.Error(t, msg.Err)
   191  	ae, ok := msg.Err.(AbsenteesError)
   192  	require.True(t, ok)
   193  	require.Equal(t, n, len(ae.Absentees))
   194  }
   195  
   196  func (c *chatClient) consumeResult(t *testing.T, r **big.Int) {
   197  	msg := <-c.dealer.UpdateCh()
   198  	require.NotNil(t, msg.Result)
   199  	require.NotNil(t, msg.Result.Big)
   200  	if *r == nil {
   201  		*r = msg.Result.Big
   202  	}
   203  	require.Equal(t, 0, msg.Result.Big.Cmp(*r))
   204  }
   205  
   206  func (c *chatClient) consumeError(t *testing.T, e error) {
   207  	msg := <-c.dealer.UpdateCh()
   208  	require.NotNil(t, msg.Err)
   209  	require.IsType(t, e, msg.Err)
   210  }
   211  
   212  func (c *chatClient) consumeRevealsAndError(t *testing.T, nReveals int) {
   213  	revealsReceived := 0
   214  	errorsReceived := 0
   215  	for errorsReceived == 0 {
   216  		testPrintf("[%s] waiting for msg....\n", c.me)
   217  		msg := <-c.dealer.UpdateCh()
   218  		testPrintf("[%s] msg gotten: %+v\n", c.me, msg)
   219  		switch {
   220  		case msg.Reveal != nil:
   221  			revealsReceived++
   222  		case msg.Err != nil:
   223  			errorsReceived++
   224  			require.IsType(t, BadRevealError{}, msg.Err)
   225  		default:
   226  			require.Fail(t, "unexpected msg type received: %+v", msg)
   227  		}
   228  	}
   229  	require.True(t, revealsReceived <= nReveals)
   230  }
   231  
   232  func (c *chatClient) consumeTimeoutError(t *testing.T) {
   233  	msg := <-c.dealer.UpdateCh()
   234  	testPrintf("ERR %+v\n", msg)
   235  }
   236  
   237  func (c *chatClient) stop() {
   238  	close(c.shutdownCh)
   239  }
   240  
   241  func (s *chatServer) stopClients() {
   242  	for _, cli := range s.chatClients {
   243  		cli.stop()
   244  	}
   245  }
   246  
   247  func TestHappyChat10(t *testing.T) {
   248  	testHappyChat(t, 10)
   249  }
   250  
   251  func TestHappyChat100(t *testing.T) {
   252  	testHappyChat(t, 100)
   253  }
   254  
   255  func testHappyChat(t *testing.T, n int) {
   256  	srv := newChatServer()
   257  	ctx := context.Background()
   258  	go srv.run(ctx)
   259  	defer srv.stop()
   260  	conversationID := genConversationID()
   261  	gameID := GenerateGameID()
   262  	clients := srv.makeAndRunClients(ctx, conversationID, n)
   263  	defer srv.stopClients()
   264  
   265  	require.False(t, clients[0].dealer.IsGameActive(ctx, conversationID, gameID))
   266  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   267  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   268  	require.NoError(t, err)
   269  	forAllClients(clients, func(c *chatClient) { nTimes(n, func() { c.consumeCommitment(t) }) })
   270  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   271  	require.True(t, clients[0].dealer.IsGameActive(ctx, conversationID, gameID))
   272  	forAllClients(clients, func(c *chatClient) { c.consumeCommitmentComplete(t, n) })
   273  	require.False(t, clients[0].dealer.IsGameActive(ctx, genConversationID(), gameID))
   274  	forAllClients(clients, func(c *chatClient) { nTimes(n, func() { c.consumeReveal(t) }) })
   275  	var b *big.Int
   276  	forAllClients(clients, func(c *chatClient) { c.consumeResult(t, &b) })
   277  
   278  	res, err := Replay(ctx, clients[0], srv.gameHistories[GameIDToKey(gameID)])
   279  	require.NoError(t, err)
   280  	require.Equal(t, 0, b.Cmp(res.Result.Big))
   281  }
   282  
   283  func getType(t *testing.T, m GameMessageWrappedEncoded) MessageType {
   284  	w, err := m.Decode()
   285  	require.NoError(t, err)
   286  	body := w.Msg.Body
   287  	typ, err := body.T()
   288  	require.NoError(t, err)
   289  	return typ
   290  }
   291  
   292  func TestReorder(t *testing.T) {
   293  	srv := newChatServer()
   294  	ctx := context.Background()
   295  	go srv.run(ctx)
   296  	defer srv.stop()
   297  	conversationID := genConversationID()
   298  	gameID := GenerateGameID()
   299  	n := 25
   300  	clients := srv.makeAndRunClients(ctx, conversationID, n)
   301  	defer srv.stopClients()
   302  
   303  	last := n - 1
   304  	delays := 5                // 5 messages get delayed
   305  	normals := clients[0:last] // these guys work as normal
   306  	testee := clients[last]    // the guy who is being tested --- he sees reorderer messages
   307  
   308  	// for the testee, let the first (n-delay) commitments go through, them we send through
   309  	// the commitmentComplete message, and then the delayed commitments
   310  	var msgBuffer []GameMessageWrappedEncoded
   311  	testee.deliver = func(m GameMessageWrappedEncoded) {
   312  		typ := getType(t, m)
   313  
   314  		if typ == MessageType_COMMITMENT && len(msgBuffer) < delays {
   315  			msgBuffer = append(msgBuffer, m)
   316  			return
   317  		}
   318  		testee.ch <- m
   319  		if typ == MessageType_COMMITMENT_COMPLETE {
   320  			for _, b := range msgBuffer {
   321  				testee.ch <- b
   322  			}
   323  		}
   324  	}
   325  
   326  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   327  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   328  	require.NoError(t, err)
   329  	forAllClients(normals, func(c *chatClient) { nTimes(n, func() { c.consumeCommitment(t) }) })
   330  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   331  	forAllClients(normals, func(c *chatClient) { c.consumeCommitmentComplete(t, n) })
   332  	forAllClients(normals, func(c *chatClient) { nTimes(n, func() { c.consumeReveal(t) }) })
   333  
   334  	// Now, make sure that the messages made it to the reordered guy,
   335  	// but in the reordered order.
   336  	nTimes(n-delays, func() { testee.consumeCommitment(t) })
   337  	testee.consumeCommitmentComplete(t, n)
   338  	nTimes(delays, func() { testee.consumeCommitment(t) })
   339  	nTimes(n, func() { testee.consumeReveal(t) })
   340  
   341  	var b *big.Int
   342  	forAllClients(clients, func(c *chatClient) { c.consumeResult(t, &b) })
   343  }
   344  
   345  func TestReorderBadCommitment(t *testing.T) {
   346  	srv := newChatServer()
   347  	ctx := context.Background()
   348  	go srv.run(ctx)
   349  	defer srv.stop()
   350  	conversationID := genConversationID()
   351  	gameID := GenerateGameID()
   352  	n := 25
   353  	clients := srv.makeAndRunClients(ctx, conversationID, n)
   354  	defer srv.stopClients()
   355  
   356  	last := n - 1
   357  	normals := clients[0:last] // these guys work as normal
   358  	testee := clients[last]    // the guy who is being tested --- he sees reorderer messages
   359  
   360  	corruptCommitment := func(m GameMessageWrappedEncoded) GameMessageWrappedEncoded {
   361  		w, err := m.Decode()
   362  		require.NoError(t, err)
   363  		c := w.Msg.Body.Commitment()
   364  		corruptBytes(c[:])
   365  		w.Msg.Body = NewGameMessageBodyWithCommitment(c)
   366  		enc, err := w.Encode()
   367  		require.NoError(t, err)
   368  		m.Body = enc
   369  		return m
   370  	}
   371  
   372  	// for the testee, let the first (n-1) commitments go through, then we send through
   373  	// the commitmentComplete message, and then the delayed commitment, but corrupted.
   374  	var badMsg *GameMessageWrappedEncoded
   375  	testee.deliver = func(m GameMessageWrappedEncoded) {
   376  		typ := getType(t, m)
   377  
   378  		if typ == MessageType_COMMITMENT && badMsg == nil {
   379  			badMsg = &m
   380  			return
   381  		}
   382  		testee.ch <- m
   383  		if typ == MessageType_COMMITMENT_COMPLETE {
   384  			b := corruptCommitment(*badMsg)
   385  			testee.ch <- b
   386  		}
   387  	}
   388  
   389  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   390  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   391  	require.NoError(t, err)
   392  	forAllClients(normals, func(c *chatClient) { nTimes(n, func() { c.consumeCommitment(t) }) })
   393  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   394  	forAllClients(normals, func(c *chatClient) { c.consumeCommitmentComplete(t, n) })
   395  
   396  	// Now, make sure that the messages made it to the reordered guy,
   397  	// but in the reordered order.
   398  	nTimes(n-1, func() { testee.consumeCommitment(t) })
   399  	testee.consumeCommitmentComplete(t, n)
   400  	testee.consumeError(t, CommitmentMismatchError{})
   401  }
   402  
   403  func TestSadChatOneAbsentee(t *testing.T) {
   404  	testAbsentees(t, 10, 1)
   405  }
   406  
   407  func TestSadChatFiveAbsentees(t *testing.T) {
   408  	testAbsentees(t, 20, 5)
   409  }
   410  
   411  func TestSadChatOneCorruption(t *testing.T) {
   412  	testCorruptions(t, 10, 1)
   413  }
   414  
   415  func TestSadChatFiveCorruptions(t *testing.T) {
   416  	testCorruptions(t, 30, 5)
   417  }
   418  
   419  func TestBadLeaderTenFollowers(t *testing.T) {
   420  	testBadLeader(t, 10)
   421  }
   422  
   423  func testAbsentees(t *testing.T, nTotal int, nAbsentees int) {
   424  	srv := newChatServer()
   425  	ctx := context.Background()
   426  	go srv.run(ctx)
   427  	defer srv.stop()
   428  	conversationID := genConversationID()
   429  	clients := srv.makeAndRunClients(ctx, conversationID, nTotal)
   430  	defer srv.stopClients()
   431  
   432  	gameID := GenerateGameID()
   433  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   434  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   435  	require.NoError(t, err)
   436  	present := nTotal - nAbsentees
   437  	forAllClients(clients, func(c *chatClient) { nTimes(nTotal, func() { c.consumeCommitment(t) }) })
   438  	forAllClients(clients[present:], func(c *chatClient) { c.dealer.Stop() })
   439  	clients = clients[0:present]
   440  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   441  	forAllClients(clients, func(c *chatClient) { c.consumeCommitmentComplete(t, nTotal) })
   442  	forAllClients(clients, func(c *chatClient) { nTimes(present, func() { c.consumeReveal(t) }) })
   443  	srv.clock.Advance(time.Duration(31001) * time.Millisecond)
   444  	forAllClients(clients, func(c *chatClient) { c.consumeAbsteneesError(t, nAbsentees) })
   445  
   446  	_, err = Replay(ctx, clients[0], srv.gameHistories[GameIDToKey(gameID)])
   447  	require.Error(t, err)
   448  	require.IsType(t, AbsenteesError{}, err)
   449  	ae, ok := err.(AbsenteesError)
   450  	require.True(t, ok)
   451  	require.Equal(t, nAbsentees, len(ae.Absentees))
   452  }
   453  
   454  func corruptBytes(b []byte) {
   455  	b[0] ^= 0x1
   456  }
   457  
   458  func TestBadCommitmentComplete(t *testing.T) {
   459  	srv := newChatServer()
   460  	ctx := context.Background()
   461  	go srv.run(ctx)
   462  	defer srv.stop()
   463  	conversationID := genConversationID()
   464  	n := 10
   465  	clients := srv.makeAndRunClients(ctx, conversationID, n)
   466  	defer srv.stopClients()
   467  
   468  	srv.corruptor = func(m GameMessageWrappedEncoded) GameMessageWrappedEncoded {
   469  		typ := getType(t, m)
   470  		if typ != MessageType_COMMITMENT_COMPLETE {
   471  			return m
   472  		}
   473  		w, err := m.Decode()
   474  		require.NoError(t, err)
   475  		cc := w.Msg.Body.CommitmentComplete()
   476  		com := cc.Players[1].C
   477  		corruptBytes(com[:])
   478  		cc.Players[1].C = com
   479  		w.Msg.Body = NewGameMessageBodyWithCommitmentComplete(cc)
   480  		enc, err := w.Encode()
   481  		require.NoError(t, err)
   482  		m.Body = enc
   483  		return m
   484  	}
   485  
   486  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   487  	gameID := GenerateGameID()
   488  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   489  	require.NoError(t, err)
   490  	forAllClients(clients, func(c *chatClient) { nTimes(n, func() { c.consumeCommitment(t) }) })
   491  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   492  	forAllClients(clients[1:], func(c *chatClient) { c.consumeError(t, CommitmentMismatchError{}) })
   493  }
   494  
   495  func testCorruptions(t *testing.T, nTotal int, nCorruptions int) {
   496  	srv := newChatServer()
   497  	ctx := context.Background()
   498  	go srv.run(ctx)
   499  	defer srv.stop()
   500  	conversationID := genConversationID()
   501  	clients := srv.makeAndRunClients(ctx, conversationID, nTotal)
   502  	defer srv.stopClients()
   503  
   504  	good := nTotal - nCorruptions
   505  	isBad := func(u UserDevice) bool {
   506  		for i := good; i < nTotal; i++ {
   507  			if clients[i].me.Eq(u) {
   508  				return true
   509  			}
   510  		}
   511  		return false
   512  	}
   513  
   514  	srv.corruptor = func(m GameMessageWrappedEncoded) GameMessageWrappedEncoded {
   515  		w, err := m.Decode()
   516  		require.NoError(t, err)
   517  		body := w.Msg.Body
   518  		typ, err := body.T()
   519  		require.NoError(t, err)
   520  		if typ != MessageType_REVEAL {
   521  			return m
   522  		}
   523  		if !isBad(m.Sender) {
   524  			return m
   525  		}
   526  		reveal := body.Reveal()
   527  		corruptBytes(reveal.Secret[:])
   528  		w.Msg.Body = NewGameMessageBodyWithReveal(reveal)
   529  		enc, err := w.Encode()
   530  		require.NoError(t, err)
   531  		m.Body = enc
   532  		return m
   533  	}
   534  
   535  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   536  	gameID := GenerateGameID()
   537  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   538  	require.NoError(t, err)
   539  	forAllClients(clients, func(c *chatClient) { nTimes(nTotal, func() { c.consumeCommitment(t) }) })
   540  	srv.clock.Advance(time.Duration(4001) * time.Millisecond)
   541  	forAllClients(clients, func(c *chatClient) { c.consumeCommitmentComplete(t, nTotal) })
   542  	forAllClients(clients[0:good], func(c *chatClient) { c.consumeRevealsAndError(t, good) })
   543  
   544  	_, err = Replay(ctx, clients[0], srv.gameHistories[GameIDToKey(gameID)])
   545  	require.Error(t, err)
   546  	require.IsType(t, BadRevealError{}, err)
   547  }
   548  
   549  func testBadLeader(t *testing.T, nTotal int) {
   550  	srv := newChatServer()
   551  	ctx := context.Background()
   552  	go srv.run(ctx)
   553  	defer srv.stop()
   554  	conversationID := genConversationID()
   555  	clients := srv.makeAndRunClients(ctx, conversationID, nTotal)
   556  	defer srv.stopClients()
   557  
   558  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   559  	err := clients[0].dealer.StartFlip(ctx, start, conversationID)
   560  	require.NoError(t, err)
   561  	forAllClients(clients, func(c *chatClient) { nTimes(nTotal, func() { c.consumeCommitment(t) }) })
   562  	clients[0].dealer.Stop()
   563  	srv.clock.Advance(time.Duration(DefaultSlackMsec+DefaultCommitmentCompleteWindowMsec) * time.Millisecond)
   564  	forAllClients(clients[1:], func(c *chatClient) { c.consumeTimeoutError(t) })
   565  }
   566  
   567  func TestRepeatedGame(t *testing.T) {
   568  
   569  	srv := newChatServer()
   570  	ctx := context.Background()
   571  	go srv.run(ctx)
   572  	defer srv.stop()
   573  	conversationID := genConversationID()
   574  	clients := srv.makeAndRunClients(ctx, conversationID, 5)
   575  	defer srv.stopClients()
   576  
   577  	gameID := GenerateGameID()
   578  	forAllClients(clients[1:], func(c *chatClient) { c.history[conversationID.ConvIDStr()] = true })
   579  	start := NewStartWithBigInt(srv.clock.Now(), pi(), 5)
   580  	_, err := clients[0].dealer.startFlipWithGameID(ctx, start, conversationID, gameID)
   581  	require.NoError(t, err)
   582  	clients[0].consumeCommitment(t)
   583  	forAllClients(clients[1:], func(c *chatClient) { c.consumeError(t, GameReplayError{}) })
   584  }
   585  
   586  func genConversationID() chat1.ConversationID {
   587  	return chat1.ConversationID(randBytes(12))
   588  }
   589  
   590  func testLeaderClockSkew(t *testing.T, skew time.Duration) {
   591  
   592  	srv := newChatServer()
   593  	ctx := context.Background()
   594  	go srv.run(ctx)
   595  	defer srv.stop()
   596  	conversationID := genConversationID()
   597  	n := 6
   598  	clients := srv.makeAndRunClients(ctx, conversationID, n)
   599  	defer srv.stopClients()
   600  
   601  	srv.clock = clockwork.NewFakeClockAt(time.Now())
   602  	now := srv.clock.Now()
   603  	start := NewStartWithBigInt(now, pi(), 5)
   604  	correctClock := clockwork.NewFakeClockAt(now.Add(skew))
   605  	srv.clockForArchiver = correctClock
   606  	forAllClients(clients[1:], func(c *chatClient) { c.clock = correctClock })
   607  	gameID := GenerateGameID()
   608  	err := clients[0].dealer.StartFlipWithGameID(ctx, start, conversationID, gameID)
   609  	require.NoError(t, err)
   610  	forAllClients(clients[1:], func(c *chatClient) { c.consumeError(t, BadLeaderClockError{}) })
   611  
   612  	_, err = Replay(ctx, clients[0], srv.gameHistories[GameIDToKey(gameID)])
   613  	require.Error(t, err)
   614  	require.IsType(t, BadLeaderClockError{}, err)
   615  }
   616  
   617  func TestLeaderClockSkewFast(t *testing.T) {
   618  	testLeaderClockSkew(t, 2*time.Hour)
   619  }
   620  
   621  func TestLeaderClockSkewSlow(t *testing.T) {
   622  	testLeaderClockSkew(t, -2*time.Hour)
   623  }