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 }