github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/batch_test.go (about)

     1  package stellarsvc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/protocol/chat1"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/keybase/client/go/protocol/stellar1"
    13  	"github.com/keybase/client/go/stellar"
    14  	"github.com/keybase/stellarnet"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  // TestPrepareBatchRelays checks that a PrepareBatchPayments
    19  // with a destination username that is a valid user but someone who
    20  // doesn't have a wallet will succeed and create a relay payment.
    21  func TestPrepareBatchRelays(t *testing.T) {
    22  	tc, cleanup := setupDesktopTest(t)
    23  	defer cleanup()
    24  	require.NotNil(t, tc.Srv.walletState)
    25  
    26  	tcw, cleanupw := setupDesktopTest(t)
    27  	defer cleanupw()
    28  
    29  	mctx := libkb.NewMetaContext(context.Background(), tc.G)
    30  
    31  	acceptDisclaimer(tc)
    32  	acceptDisclaimer(tcw)
    33  	payments := []stellar1.BatchPaymentArg{
    34  		{Recipient: "t_rebecca", Amount: "3"},
    35  		{Recipient: tcw.Fu.Username, Amount: "2"},
    36  	}
    37  	batchID, err := libkb.RandHexString("", 8)
    38  	require.NoError(t, err)
    39  
    40  	_, senderAccountBundle, err := stellar.LookupSenderPrimary(mctx)
    41  	require.NoError(t, err)
    42  	senderSeed, err := stellarnet.NewSeedStr(senderAccountBundle.Signers[0].SecureNoLogString())
    43  	require.NoError(t, err)
    44  
    45  	prepared, unlock, err := stellar.PrepareBatchPayments(mctx, tc.Srv.walletState, senderSeed, payments, batchID)
    46  	defer unlock()
    47  	require.NoError(t, err)
    48  	require.Len(t, prepared, 2)
    49  	for i, p := range prepared {
    50  		t.Logf("result %d: %+v", i, p)
    51  
    52  		switch p.Username.String() {
    53  		case "t_rebecca":
    54  			require.Nil(t, p.Direct)
    55  			require.NotNil(t, p.Relay)
    56  			require.True(t, p.Relay.QuickReturn)
    57  			require.Nil(t, p.Error)
    58  			require.NotEmpty(t, p.Seqno)
    59  			require.NotEmpty(t, p.TxID)
    60  			require.Equal(t, batchID, p.Relay.BatchID)
    61  		case tcw.Fu.Username:
    62  			require.NotNil(t, p.Direct)
    63  			require.Nil(t, p.Relay)
    64  			require.True(t, p.Direct.QuickReturn)
    65  			require.Nil(t, p.Error)
    66  			require.NotEmpty(t, p.Seqno)
    67  			require.NotEmpty(t, p.TxID)
    68  			require.Equal(t, batchID, p.Direct.BatchID)
    69  		default:
    70  			t.Fatalf("unknown username in result: %s", p.Username)
    71  		}
    72  	}
    73  	if prepared[0].Seqno > prepared[1].Seqno {
    74  		t.Errorf("prepared sort failed (seqnos out of order)")
    75  	}
    76  }
    77  
    78  // TestPrepareBatchLowAmounts checks that a PrepareBatchPayments
    79  // with low amounts will return errors quickly.
    80  func TestPrepareBatchLowAmounts(t *testing.T) {
    81  	tc, cleanup := setupDesktopTest(t)
    82  	defer cleanup()
    83  	require.NotNil(t, tc.Srv.walletState)
    84  
    85  	tcw, cleanupw := setupDesktopTest(t)
    86  	defer cleanupw()
    87  
    88  	mctx := libkb.NewMetaContext(context.Background(), tc.G)
    89  
    90  	acceptDisclaimer(tc)
    91  	acceptDisclaimer(tcw)
    92  	payments := []stellar1.BatchPaymentArg{
    93  		{Recipient: "t_rebecca", Amount: "1"},
    94  		{Recipient: tcw.Fu.Username, Amount: "0.2"},
    95  	}
    96  	batchID, err := libkb.RandHexString("", 8)
    97  	require.NoError(t, err)
    98  
    99  	_, senderAccountBundle, err := stellar.LookupSenderPrimary(mctx)
   100  	require.NoError(t, err)
   101  	senderSeed, err := stellarnet.NewSeedStr(senderAccountBundle.Signers[0].SecureNoLogString())
   102  	require.NoError(t, err)
   103  
   104  	prepared, unlock, err := stellar.PrepareBatchPayments(mctx, tc.Srv.walletState, senderSeed, payments, batchID)
   105  	defer unlock()
   106  	require.NoError(t, err)
   107  	require.Len(t, prepared, 2)
   108  	for i, p := range prepared {
   109  		t.Logf("result %d: %+v", i, p)
   110  
   111  		switch p.Username.String() {
   112  		case "t_rebecca":
   113  			require.Nil(t, p.Direct)
   114  			require.Nil(t, p.Relay)
   115  			require.Error(t, p.Error)
   116  			require.Empty(t, p.Seqno)
   117  			require.Empty(t, p.TxID)
   118  		case tcw.Fu.Username:
   119  			require.Nil(t, p.Direct)
   120  			require.Nil(t, p.Relay)
   121  			require.Error(t, p.Error)
   122  			require.Empty(t, p.Seqno)
   123  			require.Empty(t, p.TxID)
   124  		default:
   125  			t.Fatalf("unknown username in result: %s", p.Username)
   126  		}
   127  	}
   128  }
   129  
   130  // TestBatchMultiDirect does a batch payment with the multi flag on
   131  // and ensures that it was successful and that the appropriate
   132  // chat messages were sent.  All the recipients have stellar accounts.
   133  func TestBatchMultiDirect(t *testing.T) {
   134  	// sender test context
   135  	tc, cleanup := setupDesktopTest(t)
   136  	defer cleanup()
   137  	acceptDisclaimer(tc)
   138  
   139  	chatHelper := &testChatHelper{}
   140  	tc.G.ChatHelper = chatHelper
   141  
   142  	// recipient test contexts
   143  	const numRecips = 3
   144  	recipTC := make([]*TestContext, numRecips)
   145  	for i := 0; i < numRecips; i++ {
   146  		var c func()
   147  		recipTC[i], c = setupDesktopTest(t)
   148  		defer c()
   149  		acceptDisclaimer(recipTC[i])
   150  	}
   151  
   152  	// make the batch payment arg
   153  	arg := stellar1.BatchLocalArg{
   154  		BatchID:     "testbatchmulti",
   155  		TimeoutSecs: 10,
   156  		UseMulti:    true,
   157  		Payments:    make([]stellar1.BatchPaymentArg, numRecips),
   158  	}
   159  	for i, rc := range recipTC {
   160  		arg.Payments[i] = stellar1.BatchPaymentArg{
   161  			Recipient: rc.Fu.Username,
   162  			Amount:    "3.198",
   163  			Message:   "batch payment message",
   164  		}
   165  	}
   166  
   167  	res, err := tc.Srv.BatchLocal(context.Background(), arg)
   168  	require.NoError(t, err)
   169  	require.Len(t, res.Payments, numRecips)
   170  	for i, p := range res.Payments {
   171  		if p.Status != stellar1.PaymentStatus_COMPLETED {
   172  			if p.Status == stellar1.PaymentStatus_ERROR {
   173  				t.Logf("payment %d error: %s (%d)", i, p.Error.Message, p.Error.Code)
   174  			}
   175  			t.Errorf("payment %d not complete: %+v", i, p)
   176  		}
   177  
   178  		var msg *paymentMsg
   179  		convName := fmt.Sprintf("%s,%s", tc.Fu.Username, p.Username)
   180  		for _, m := range chatHelper.paymentMsgs {
   181  			if m.ConvName == convName {
   182  				msg = &m
   183  			}
   184  		}
   185  		if msg == nil {
   186  			t.Errorf("payment %d no chat message found: %+v", i, p)
   187  		} else if msg.PaymentID != stellar1.PaymentID(p.TxID) {
   188  			t.Errorf("payment %d chat msg tx id: %q, expected %q", i, msg.PaymentID, p.TxID)
   189  		}
   190  	}
   191  }
   192  
   193  type paymentMsg struct {
   194  	ConvName  string
   195  	PaymentID stellar1.PaymentID
   196  }
   197  
   198  // testChatHelper is used to see if chat messages are sent.
   199  type testChatHelper struct {
   200  	libkb.ChatHelper
   201  
   202  	paymentMsgs []paymentMsg
   203  
   204  	sync.Mutex
   205  }
   206  
   207  func (tch *testChatHelper) SendMsgByName(ctx context.Context, name string, topicName *string,
   208  	membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody,
   209  	msgType chat1.MessageType) error {
   210  	tch.Lock()
   211  	defer tch.Unlock()
   212  	if msgType == chat1.MessageType_SENDPAYMENT {
   213  		tch.paymentMsgs = append(tch.paymentMsgs, paymentMsg{ConvName: name, PaymentID: body.Sendpayment().PaymentID})
   214  	}
   215  	return nil
   216  }
   217  
   218  func (tch *testChatHelper) SendMsgByNameNonblock(ctx context.Context, name string, topicName *string, membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody, msgType chat1.MessageType, outboxID *chat1.OutboxID) (chat1.OutboxID, error) {
   219  	tch.Lock()
   220  	defer tch.Unlock()
   221  	if msgType == chat1.MessageType_SENDPAYMENT {
   222  		tch.paymentMsgs = append(tch.paymentMsgs, paymentMsg{ConvName: name, PaymentID: body.Sendpayment().PaymentID})
   223  	}
   224  
   225  	return nil, nil
   226  }