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  }