github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/seqno_test.go (about) 1 package stellarsvc 2 3 import ( 4 "context" 5 "math/rand" 6 "sort" 7 "testing" 8 "time" 9 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/stellar1" 12 "github.com/keybase/client/go/stellar" 13 "github.com/stretchr/testify/require" 14 ) 15 16 // TestSeqno tests the seqno provider that predicts seqnos for 17 // rapid payments. 18 // 19 // In particular, it is going to test the following scenario: 20 // Each time a user does an in-chat send, they make a new 21 // seqno provider and use it. There is currently a race 22 // where the "pending" tx doesn't make it to wallet state 23 // before the second seqno provider refreshes the seqno 24 // from the network. 25 func TestSeqno(t *testing.T) { 26 tcs, cleanup := setupNTests(t, 1) 27 defer cleanup() 28 29 acceptDisclaimer(tcs[0]) 30 rm := tcs[0].Backend 31 accountID1 := rm.AddAccount(tcs[0].Fu.GetUID()) 32 err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{ 33 SecretKey: rm.SecretKey(accountID1), 34 MakePrimary: true, 35 Name: "qq", 36 }) 37 require.NoError(t, err) 38 39 mctx := tcs[0].MetaContext() 40 ws := tcs[0].Srv.walletState 41 42 // in-chat send creates a new seqno provider for each message, so this 43 // is simulating three in-chat send messages starting before the submit 44 // payment happens. 45 46 sp0, unlock := stellar.NewSeqnoProvider(mctx, ws) 47 seqno0, err := sp0.SequenceForAccount(accountID1.String()) 48 unlock() 49 require.NoError(t, err) 50 51 sp1, unlock := stellar.NewSeqnoProvider(mctx, ws) 52 seqno1, err := sp1.SequenceForAccount(accountID1.String()) 53 unlock() 54 require.NoError(t, err) 55 56 sp2, unlock := stellar.NewSeqnoProvider(mctx, ws) 57 seqno2, err := sp2.SequenceForAccount(accountID1.String()) 58 unlock() 59 require.NoError(t, err) 60 61 t.Logf("seqno0: %d", seqno0) 62 t.Logf("seqno1: %d", seqno1) 63 t.Logf("seqno2: %d", seqno2) 64 65 require.Equal(t, seqno0+1, seqno1, "seqno1") 66 require.Equal(t, seqno0+2, seqno2, "seqno2") 67 68 } 69 70 // TestSeqnoConcurrent will check that concurrent seqno attempts 71 // arrive at submitpayment in order. 72 func TestSeqnoConcurrent(t *testing.T) { 73 tcs, cleanup := setupNTests(t, 1) 74 defer cleanup() 75 76 acceptDisclaimer(tcs[0]) 77 rm := tcs[0].Backend 78 accountID1 := rm.AddAccount(tcs[0].Fu.GetUID()) 79 err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{ 80 SecretKey: rm.SecretKey(accountID1), 81 MakePrimary: true, 82 Name: "qq", 83 }) 84 require.NoError(t, err) 85 86 ws := tcs[0].Srv.walletState 87 submits := make(chan uint64, 100) 88 89 // fakePayment simulates getting a seqno and then "submitting" that 90 // seqno after a delay. 91 var fakePayment = func(t *testing.T) { 92 mctx := libkb.NewMetaContextBackground(tcs[0].G) 93 sp, unlock := stellar.NewSeqnoProvider(mctx, ws) 94 defer unlock() 95 seqno, err := sp.SequenceForAccount(accountID1.String()) 96 require.NoError(t, err) 97 time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond) 98 submits <- uint64(seqno) 99 } 100 101 numPayments := 10 102 for i := 0; i < numPayments; i++ { 103 go fakePayment(t) 104 } 105 106 seqnos := make([]uint64, numPayments) 107 for i := 0; i < numPayments; i++ { 108 seqnos[i] = <-submits 109 } 110 111 require.True(t, sort.SliceIsSorted(seqnos, func(i, j int) bool { return seqnos[i] < seqnos[j] }), "seqnos in order") 112 }