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  }