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 }