github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/flip/dealer_test.go (about) 1 package flip 2 3 import ( 4 "context" 5 "crypto/rand" 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 testDealersHelper struct { 17 clock clockwork.FakeClock 18 me UserDevice 19 ch chan GameMessageWrappedEncoded 20 } 21 22 func newTestDealersHelper(me UserDevice) *testDealersHelper { 23 return &testDealersHelper{ 24 clock: clockwork.NewFakeClock(), 25 me: me, 26 ch: make(chan GameMessageWrappedEncoded, 10), 27 } 28 } 29 30 func (t *testDealersHelper) Clock() clockwork.Clock { 31 return t.clock 32 } 33 34 func (t *testDealersHelper) ServerTime(context.Context) (time.Time, error) { 35 return t.clock.Now(), nil 36 } 37 38 func (t *testDealersHelper) CLogf(ctx context.Context, fmtString string, args ...interface{}) { 39 testPrintf(fmtString+"\n", args...) 40 } 41 42 func (t *testDealersHelper) Me() UserDevice { 43 return t.me 44 } 45 46 func (t *testDealersHelper) SendChat(ctx context.Context, initiatorUID gregor1.UID, conversationID chat1.ConversationID, 47 gameID chat1.FlipGameID, msg GameMessageEncoded) error { 48 t.ch <- GameMessageWrappedEncoded{Body: msg, GameID: gameID, Sender: t.me} 49 return nil 50 } 51 52 func (t *testDealersHelper) ShouldCommit(ctx context.Context) bool { 53 return true 54 } 55 56 func randBytes(i int) []byte { 57 ret := make([]byte, i) 58 _, err := rand.Read(ret) 59 if err != nil { 60 panic(err) 61 } 62 return ret 63 } 64 65 func newTestUser() UserDevice { 66 return UserDevice{ 67 U: randBytes(6), 68 D: randBytes(6), 69 } 70 } 71 72 type testBundle struct { 73 me UserDevice 74 dh *testDealersHelper 75 dealer *Dealer 76 conversationID chat1.ConversationID 77 start Start 78 leader *playerControl 79 followers []*playerControl 80 } 81 82 func (b *testBundle) run(ctx context.Context) { 83 go func() { _ = b.dealer.Run(ctx) }() 84 } 85 86 func setupTestBundleWithParams(ctx context.Context, t *testing.T, params FlipParameters) *testBundle { 87 me := newTestUser() 88 dh := newTestDealersHelper(me) 89 dealer := NewDealer(dh) 90 start := Start{ 91 StartTime: ToTime(dh.clock.Now()), 92 CommitmentWindowMsec: 5 * 1000, 93 RevealWindowMsec: 5 * 1000, 94 SlackMsec: 1 * 1000, 95 Params: params, 96 } 97 conversationID := genConversationID() 98 99 return &testBundle{ 100 me: me, 101 dh: dh, 102 dealer: dealer, 103 conversationID: conversationID, 104 start: start, 105 } 106 } 107 108 func setupTestBundle(ctx context.Context, t *testing.T) *testBundle { 109 return setupTestBundleWithParams(ctx, t, NewFlipParametersWithBool()) 110 } 111 112 func (b *testBundle) makeFollowers(t *testing.T, n int) { 113 for i := 0; i < n; i++ { 114 b.makeFollower(t) 115 } 116 } 117 118 func (b *testBundle) runFollowersCommit(ctx context.Context, t *testing.T) { 119 for _, f := range b.followers { 120 b.sendCommitment(ctx, t, f) 121 } 122 } 123 124 func (b *testBundle) runFollowersReveal(ctx context.Context, t *testing.T, players []UserDeviceCommitment) { 125 var reveal Reveal 126 cch, err := hashUserDeviceCommitments(players) 127 require.NoError(t, err) 128 reveal.Cch = cch 129 for _, f := range b.followers { 130 b.sendReveal(ctx, t, f, reveal) 131 } 132 } 133 134 func (b *testBundle) sendReveal(ctx context.Context, t *testing.T, p *playerControl, reveal Reveal) { 135 reveal.Secret = p.secret 136 msg, err := NewGameMessageBodyWithReveal(reveal).Encode(p.md) 137 require.NoError(t, err) 138 _ = b.dealer.InjectIncomingChat(ctx, p.me, p.md.ConversationID, p.md.GameID, msg, false) 139 b.receiveRevealFrom(t, p) 140 } 141 142 func (b *testBundle) sendCommitment(ctx context.Context, t *testing.T, p *playerControl) { 143 msg, err := NewGameMessageBodyWithCommitment(p.commitment).Encode(p.md) 144 require.NoError(t, err) 145 _ = b.dealer.InjectIncomingChat(ctx, p.me, p.md.ConversationID, p.md.GameID, msg, false) 146 b.receiveCommitmentFrom(t, p) 147 } 148 149 func (b *testBundle) receiveCommitmentFrom(t *testing.T, p *playerControl) { 150 res := <-b.dealer.UpdateCh() 151 require.NotNil(t, res.Commitment) 152 require.Equal(t, p.me, res.Commitment.User) 153 } 154 155 func (b *testBundle) receiveRevealFrom(t *testing.T, p *playerControl) { 156 res := <-b.dealer.UpdateCh() 157 require.NotNil(t, res.Reveal) 158 require.Equal(t, p.me, res.Reveal.User) 159 } 160 161 func (b *testBundle) makeFollower(t *testing.T) { 162 f, err := b.dealer.newPlayerControl(newTestUser(), b.leader.GameMetadata(), b.start) 163 require.NoError(t, err) 164 b.followers = append(b.followers, f) 165 } 166 167 func (b *testBundle) stop() { 168 b.dealer.Stop() 169 } 170 171 func (b *testBundle) assertOutgoingChatSent(t *testing.T, typ MessageType) GameMessageWrappedEncoded { 172 msg := <-b.dh.ch 173 v1, err := msg.Decode() 174 require.NoError(t, err) 175 imt, err := v1.Msg.Body.T() 176 require.NoError(t, err) 177 require.Equal(t, imt, typ) 178 return msg 179 } 180 181 func TestLeader3Followers(t *testing.T) { 182 testLeader(t, 3) 183 } 184 185 func TestLeader10Followers(t *testing.T) { 186 testLeader(t, 10) 187 } 188 189 func TestLeader100Followers(t *testing.T) { 190 testLeader(t, 100) 191 } 192 193 func TestLeader1000Followers(t *testing.T) { 194 testLeader(t, 1000) 195 } 196 197 func testLeader(t *testing.T, nFollowers int) { 198 ctx := context.Background() 199 b := setupTestBundle(ctx, t) 200 b.run(ctx) 201 defer b.stop() 202 leader, err := b.dealer.startFlip(ctx, b.start, b.conversationID) 203 require.NoError(t, err) 204 b.leader = leader 205 b.assertOutgoingChatSent(t, MessageType_START) 206 b.receiveCommitmentFrom(t, leader) 207 b.assertOutgoingChatSent(t, MessageType_COMMITMENT) 208 b.makeFollowers(t, nFollowers) 209 b.runFollowersCommit(ctx, t) 210 b.dh.clock.Advance(time.Duration(6001) * time.Millisecond) 211 msg := <-b.dealer.UpdateCh() 212 require.NotNil(t, msg.CommitmentComplete) 213 require.Equal(t, (nFollowers + 1), len(msg.CommitmentComplete.Players)) 214 b.assertOutgoingChatSent(t, MessageType_COMMITMENT_COMPLETE) 215 b.assertOutgoingChatSent(t, MessageType_REVEAL) 216 b.receiveRevealFrom(t, leader) 217 b.runFollowersReveal(ctx, t, msg.CommitmentComplete.Players) 218 msg = <-b.dealer.UpdateCh() 219 require.NotNil(t, msg.Result) 220 require.NotNil(t, msg.Result.Bool) 221 } 222 223 type breakpoint func(t *testing.T, b *testBundle, c *testBundle) bool 224 225 type testController struct { 226 b1 breakpoint 227 b2 breakpoint 228 } 229 230 func pi() *big.Int { 231 var m big.Int 232 m.SetString("3141592653589793238462643383279502884197169399375", 10) 233 return &m 234 } 235 236 func testLeaderFollowerPair(t *testing.T, testController testController) { 237 ctx := context.Background() 238 239 // The leader's state machine 240 mb := pi().Bytes() 241 242 b := setupTestBundleWithParams(ctx, t, NewFlipParametersWithBig(mb)) 243 b.run(ctx) 244 defer b.stop() 245 err := b.dealer.StartFlip(ctx, b.start, b.conversationID) 246 require.NoError(t, err) 247 248 // The follower's state machine 249 c := setupTestBundle(ctx, t) 250 c.run(ctx) 251 defer c.stop() 252 253 verifyMyCommitment := func(who *testBundle) { 254 msg := <-who.dealer.UpdateCh() 255 require.NotNil(t, msg.Commitment) 256 require.Equal(t, msg.Commitment.User, who.dh.Me()) 257 } 258 259 verifyTheirCommitment := func(me *testBundle, them *testBundle) { 260 msg := <-me.dealer.UpdateCh() 261 require.NotNil(t, msg.Commitment) 262 require.Equal(t, msg.Commitment.User, them.dh.Me()) 263 } 264 265 verifyCommitmentComplete := func() { 266 msg := <-b.dealer.UpdateCh() 267 require.NotNil(t, msg.CommitmentComplete) 268 checkPlayers := func(v []UserDeviceCommitment) { 269 require.Equal(t, 2, len(v)) 270 find := func(p UserDevice) { 271 require.True(t, v[0].Ud.Eq(p) || v[1].Ud.Eq(p)) 272 } 273 find(b.dh.Me()) 274 find(c.dh.Me()) 275 } 276 checkPlayers(msg.CommitmentComplete.Players) 277 msg = <-c.dealer.UpdateCh() 278 require.NotNil(t, msg.CommitmentComplete) 279 checkPlayers(msg.CommitmentComplete.Players) 280 } 281 282 verifyMyReveal := func(who *testBundle) { 283 msg := <-who.dealer.UpdateCh() 284 require.NotNil(t, msg.Reveal) 285 require.Equal(t, msg.Reveal.User, who.dh.Me()) 286 } 287 288 verifyTheirReveal := func(me *testBundle, them *testBundle) { 289 msg := <-me.dealer.UpdateCh() 290 require.NotNil(t, msg.Reveal) 291 require.Equal(t, msg.Reveal.User, them.dh.Me()) 292 } 293 294 getResult := func(who *testBundle) *big.Int { 295 msg := <-who.dealer.UpdateCh() 296 require.NotNil(t, msg.Result) 297 require.NotNil(t, msg.Result.Big) 298 return msg.Result.Big 299 } 300 301 chatMsg := b.assertOutgoingChatSent(t, MessageType_START) 302 err = c.dealer.InjectIncomingChat(ctx, chatMsg.Sender, b.conversationID, chatMsg.GameID, chatMsg.Body, true) 303 require.NoError(t, err) 304 305 cB := b.assertOutgoingChatSent(t, MessageType_COMMITMENT) 306 cC := c.assertOutgoingChatSent(t, MessageType_COMMITMENT) 307 verifyMyCommitment(b) 308 verifyMyCommitment(c) 309 310 err = c.dealer.InjectIncomingChat(ctx, cB.Sender, b.conversationID, cB.GameID, cB.Body, false) 311 require.NoError(t, err) 312 err = b.dealer.InjectIncomingChat(ctx, cC.Sender, b.conversationID, cC.GameID, cC.Body, false) 313 require.NoError(t, err) 314 verifyTheirCommitment(b, c) 315 verifyTheirCommitment(c, b) 316 317 if testController.b1 != nil { 318 ret := testController.b1(t, b, c) 319 if !ret { 320 return 321 } 322 } 323 324 b.dh.clock.Advance(time.Duration(6001) * time.Millisecond) 325 chatMsg = b.assertOutgoingChatSent(t, MessageType_COMMITMENT_COMPLETE) 326 err = c.dealer.InjectIncomingChat(ctx, chatMsg.Sender, b.conversationID, chatMsg.GameID, chatMsg.Body, false) 327 require.NoError(t, err) 328 verifyCommitmentComplete() 329 330 // Both B & C reveal their messages 331 rB := b.assertOutgoingChatSent(t, MessageType_REVEAL) 332 rC := c.assertOutgoingChatSent(t, MessageType_REVEAL) 333 verifyMyReveal(b) 334 verifyMyReveal(c) 335 336 err = c.dealer.InjectIncomingChat(ctx, rB.Sender, b.conversationID, rB.GameID, rB.Body, false) 337 require.NoError(t, err) 338 339 err = b.dealer.InjectIncomingChat(ctx, rC.Sender, b.conversationID, rC.GameID, rC.Body, false) 340 require.NoError(t, err) 341 342 verifyTheirReveal(b, c) 343 verifyTheirReveal(c, b) 344 345 if testController.b2 != nil { 346 ret := testController.b2(t, b, c) 347 if !ret { 348 return 349 } 350 } 351 352 resB := getResult(b) 353 resC := getResult(c) 354 require.Equal(t, 0, resB.Cmp(resC)) 355 } 356 357 func TestLeaderFollowerPair(t *testing.T) { 358 testLeaderFollowerPair(t, testController{}) 359 }