github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/service_test.go (about) 1 package stellarsvc 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/keybase/client/go/engine" 16 "github.com/keybase/client/go/externalstest" 17 "github.com/keybase/client/go/kbtest" 18 "github.com/keybase/client/go/libkb" 19 "github.com/keybase/client/go/protocol/chat1" 20 "github.com/keybase/client/go/protocol/gregor1" 21 "github.com/keybase/client/go/protocol/keybase1" 22 "github.com/keybase/client/go/protocol/stellar1" 23 "github.com/keybase/client/go/stellar" 24 "github.com/keybase/client/go/stellar/bundle" 25 "github.com/keybase/client/go/stellar/relays" 26 "github.com/keybase/client/go/stellar/remote" 27 "github.com/keybase/client/go/stellar/stellarcommon" 28 "github.com/keybase/client/go/teams" 29 insecureTriplesec "github.com/keybase/go-triplesec-insecure" 30 "github.com/keybase/stellarnet" 31 "github.com/stellar/go/keypair" 32 "github.com/stellar/go/xdr" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func SetupTest(t *testing.T, name string, depth int) (tc libkb.TestContext) { 37 tc = externalstest.SetupTest(t, name, depth+1) 38 stellar.ServiceInit(tc.G, nil, nil) 39 teams.ServiceInit(tc.G) 40 // use an insecure triplesec in tests 41 tc.G.NewTriplesec = func(passphrase []byte, salt []byte) (libkb.Triplesec, error) { 42 warner := func() { tc.G.Log.Warning("Installing insecure Triplesec with weak stretch parameters") } 43 isProduction := func() bool { 44 return tc.G.Env.GetRunMode() == libkb.ProductionRunMode 45 } 46 return insecureTriplesec.NewCipher(passphrase, salt, libkb.ClientTriplesecVersion, warner, isProduction) 47 } 48 49 tc.G.SetService() 50 51 tc.G.ChatHelper = kbtest.NewMockChatHelper() 52 53 return tc 54 } 55 56 func TestCreateWallet(t *testing.T) { 57 tcs, cleanup := setupTestsWithSettings(t, []usetting{usettingFull, usettingFull}) 58 defer cleanup() 59 60 t.Logf("Lookup for a bogus address") 61 _, _, err := stellar.LookupUserByAccountID(tcs[0].MetaContext(), "GCCJJFCRCQAWDWRAZ3R6235KCQ4PQYE5KEWHGE5ICVTZLTMRKVWAWP7N") 62 require.Error(t, err) 63 require.IsType(t, libkb.NotFoundError{}, err) 64 65 t.Logf("Create an initial wallet") 66 acceptDisclaimer(tcs[0]) 67 68 created, err := stellar.CreateWallet(tcs[0].MetaContext()) 69 require.NoError(t, err) 70 require.False(t, created) 71 72 mctx := libkb.NewMetaContextBackground(tcs[0].G) 73 74 t.Logf("Fetch the bundle") 75 bundle, err := remote.FetchSecretlessBundle(mctx) 76 require.NoError(t, err) 77 require.Equal(t, stellar1.BundleRevision(1), bundle.Revision) 78 require.Nil(t, bundle.Prev) 79 require.NotNil(t, bundle.OwnHash) 80 require.Len(t, bundle.Accounts, 1) 81 require.True(t, len(bundle.Accounts[0].AccountID) > 0) 82 require.Equal(t, stellar1.AccountMode_USER, bundle.Accounts[0].Mode) 83 require.True(t, bundle.Accounts[0].IsPrimary) 84 require.Equal(t, firstAccountName(t, tcs[0]), bundle.Accounts[0].Name) 85 accountID := bundle.Accounts[0].AccountID 86 require.Len(t, bundle.AccountBundles[accountID].Signers, 0) 87 bundle, err = remote.FetchAccountBundle(mctx, accountID) 88 require.NoError(t, err) 89 require.Len(t, bundle.AccountBundles[accountID].Signers, 1) 90 91 t.Logf("Lookup the user by public address as another user") 92 a1 := bundle.Accounts[0].AccountID 93 uv, username, err := stellar.LookupUserByAccountID(tcs[1].MetaContext(), a1) 94 require.NoError(t, err) 95 require.Equal(t, tcs[0].Fu.GetUserVersion(), uv) 96 require.Equal(t, tcs[0].Fu.Username, username.String()) 97 t.Logf("and as self") 98 uv, _, err = stellar.LookupUserByAccountID(tcs[0].MetaContext(), a1) 99 require.NoError(t, err) 100 require.Equal(t, tcs[0].Fu.GetUserVersion(), uv) 101 102 t.Logf("Lookup the address by user as another user") 103 u0, err := tcs[1].G.LoadUserByUID(tcs[0].G.ActiveDevice.UID()) 104 require.NoError(t, err) 105 addr := u0.StellarAccountID() 106 t.Logf("Found account: %v", addr) 107 require.NotNil(t, addr) 108 _, err = libkb.MakeNaclSigningKeyPairFromStellarAccountID(*addr) 109 require.NoError(t, err, "stellar key should be nacl pubable") 110 require.Equal(t, bundle.Accounts[0].AccountID.String(), addr.String(), "addr looked up should match secret bundle") 111 112 t.Logf("Change primary accounts") 113 a2, s2 := randomStellarKeypair() 114 err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{ 115 SecretKey: s2, 116 MakePrimary: true, 117 Name: "uu", 118 }) 119 require.NoError(t, err) 120 121 t.Logf("Lookup by the new primary") 122 uv, _, err = stellar.LookupUserByAccountID(tcs[1].MetaContext(), a2) 123 require.NoError(t, err) 124 require.Equal(t, tcs[0].Fu.GetUserVersion(), uv) 125 126 t.Logf("Looking up by the old address no longer works") 127 _, _, err = stellar.LookupUserByAccountID(tcs[1].MetaContext(), a1) 128 require.Error(t, err) 129 require.IsType(t, libkb.NotFoundError{}, err) 130 } 131 132 func setupWithNewBundle(t *testing.T, tc *TestContext) { 133 acceptDisclaimer(tc) 134 } 135 136 func assertCorrectPukGens(t *testing.T, m libkb.MetaContext, expectedPukGen keybase1.PerUserKeyGeneration) { 137 bundle, parentPukGen, accountPukGens, err := remote.FetchBundleWithGens(m) 138 require.NoError(t, err) 139 require.Equal(t, expectedPukGen, parentPukGen) 140 for _, acct := range bundle.Accounts { 141 acctPukGen := accountPukGens[acct.AccountID] 142 require.Equal(t, expectedPukGen, acctPukGen) 143 } 144 } 145 146 func rotatePuk(t *testing.T, m libkb.MetaContext) { 147 engArg := &engine.PerUserKeyRollArgs{} 148 eng := engine.NewPerUserKeyRoll(m.G(), engArg) 149 err := engine.RunEngine2(m, eng) 150 require.NoError(t, err) 151 require.True(t, eng.DidNewKey) 152 } 153 154 func TestUpkeep(t *testing.T) { 155 tcs, cleanup := setupNTests(t, 1) 156 defer cleanup() 157 srv := tcs[0].Srv 158 m := tcs[0].MetaContext() 159 // create a wallet with two accounts 160 setupWithNewBundle(t, tcs[0]) 161 a1, s1 := randomStellarKeypair() 162 argS1 := stellar1.ImportSecretKeyLocalArg{ 163 SecretKey: s1, 164 MakePrimary: false, 165 Name: "qq", 166 } 167 err := srv.ImportSecretKeyLocal(m.Ctx(), argS1) 168 require.NoError(t, err) 169 // verify that the pukGen is 1 everywhere 170 assertCorrectPukGens(t, m, keybase1.PerUserKeyGeneration(1)) 171 172 // call Upkeep. Nothing should change because no keys were rotated. 173 err = stellar.Upkeep(m) 174 require.NoError(t, err) 175 assertCorrectPukGens(t, m, keybase1.PerUserKeyGeneration(1)) 176 177 // rotate the puk and verify that Upkeep bumps the generation. 178 rotatePuk(t, m) 179 err = stellar.Upkeep(m) 180 require.NoError(t, err) 181 assertCorrectPukGens(t, m, keybase1.PerUserKeyGeneration(2)) 182 183 // verify that Upkeep can run on just a single account by rotating the 184 // puk, pushing an unrelated update to one account (this will implicitly 185 // do the generation bump on just that account as well as the parent bundle 186 // but not on unrelated accounts) and then calling Upkeep. The untouched 187 // account should also get updated to the generation of the parent bundle 188 // and the other account. 189 rotatePuk(t, m) 190 prevBundle, err := remote.FetchAccountBundle(m, a1) 191 require.NoError(t, err) 192 nextBundle := bundle.AdvanceAccounts(*prevBundle, []stellar1.AccountID{a1}) 193 err = remote.Post(m, nextBundle) 194 require.NoError(t, err) 195 err = stellar.Upkeep(m) 196 require.NoError(t, err) 197 assertCorrectPukGens(t, m, keybase1.PerUserKeyGeneration(3)) 198 } 199 200 func TestImportExport(t *testing.T) { 201 tcs, cleanup := setupNTests(t, 2) 202 defer cleanup() 203 204 srv := tcs[0].Srv 205 m := tcs[0].MetaContext() 206 207 acceptDisclaimer(tcs[0]) 208 209 mustAskForPassphrase := func(f func()) { 210 ui := tcs[0].Fu.NewSecretUI() 211 tcs[0].Srv.uiSource.(*testUISource).secretUI = ui 212 f() 213 require.True(t, ui.CalledGetPassphrase, "operation should ask for passphrase") 214 tcs[0].Srv.uiSource.(*testUISource).secretUI = nullSecretUI{} 215 } 216 217 mustAskForPassphrase(func() { 218 _, err := srv.ExportSecretKeyLocal(m.Ctx(), stellar1.AccountID("")) 219 require.Error(t, err, "export empty specifier") 220 }) 221 222 bundle, err := fetchWholeBundleForTesting(m) 223 require.NoError(t, err) 224 225 mustAskForPassphrase(func() { 226 accountID := bundle.Accounts[0].AccountID 227 exported, err := srv.ExportSecretKeyLocal(m.Ctx(), accountID) 228 require.NoError(t, err) 229 require.Equal(t, bundle.AccountBundles[accountID].Signers[0], exported) 230 }) 231 232 a1, s1 := randomStellarKeypair() 233 argS1 := stellar1.ImportSecretKeyLocalArg{ 234 SecretKey: s1, 235 MakePrimary: false, 236 Name: "qq", 237 } 238 err = srv.ImportSecretKeyLocal(m.Ctx(), argS1) 239 require.NoError(t, err) 240 241 mustAskForPassphrase(func() { 242 accountID := bundle.Accounts[0].AccountID 243 exported, err := srv.ExportSecretKeyLocal(m.Ctx(), accountID) 244 require.NoError(t, err) 245 require.Equal(t, bundle.AccountBundles[accountID].Signers[0], exported) 246 }) 247 248 mustAskForPassphrase(func() { 249 exported, err := srv.ExportSecretKeyLocal(m.Ctx(), a1) 250 require.NoError(t, err) 251 require.Equal(t, s1, exported) 252 }) 253 254 withWrongPassphrase := func(f func()) { 255 ui := &libkb.TestSecretUI{Passphrase: "notquite" + tcs[0].Fu.Passphrase} 256 tcs[0].Srv.uiSource.(*testUISource).secretUI = ui 257 f() 258 require.True(t, ui.CalledGetPassphrase, "operation should ask for passphrase") 259 tcs[0].Srv.uiSource.(*testUISource).secretUI = nullSecretUI{} 260 } 261 262 withWrongPassphrase(func() { 263 _, err := srv.ExportSecretKeyLocal(m.Ctx(), a1) 264 require.Error(t, err) 265 require.IsType(t, libkb.PassphraseError{}, err) 266 }) 267 268 _, err = srv.ExportSecretKeyLocal(m.Ctx(), stellar1.AccountID(s1)) 269 require.Error(t, err, "export confusing secret and public") 270 271 err = srv.ImportSecretKeyLocal(m.Ctx(), argS1) 272 require.Error(t, err) 273 274 u0, err := tcs[1].G.LoadUserByUID(tcs[0].G.ActiveDevice.UID()) 275 require.NoError(t, err) 276 addr := u0.StellarAccountID() 277 require.False(t, a1.Eq(*addr)) 278 279 a2, s2 := randomStellarKeypair() 280 own, err := srv.OwnAccountLocal(m.Ctx(), a2) 281 require.NoError(t, err) 282 require.False(t, own) 283 284 argS2 := stellar1.ImportSecretKeyLocalArg{ 285 SecretKey: s2, 286 MakePrimary: true, 287 Name: "uu", 288 } 289 err = srv.ImportSecretKeyLocal(m.Ctx(), argS2) 290 require.NoError(t, err) 291 292 u0, err = tcs[1].G.LoadUserByUID(tcs[0].G.ActiveDevice.UID()) 293 require.NoError(t, err) 294 addr = u0.StellarAccountID() 295 require.False(t, a1.Eq(*addr)) 296 297 err = srv.ImportSecretKeyLocal(m.Ctx(), argS2) 298 require.Error(t, err) 299 300 own, err = srv.OwnAccountLocal(m.Ctx(), a1) 301 require.NoError(t, err) 302 require.True(t, own) 303 own, err = srv.OwnAccountLocal(m.Ctx(), a2) 304 require.NoError(t, err) 305 require.True(t, own) 306 307 bundle, err = remote.FetchSecretlessBundle(m) 308 require.NoError(t, err) 309 require.Len(t, bundle.Accounts, 3) 310 } 311 312 func TestBalances(t *testing.T) { 313 tcs, cleanup := setupNTests(t, 1) 314 defer cleanup() 315 316 accountID := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID()) 317 318 balances, err := tcs[0].Srv.BalancesLocal(context.Background(), accountID) 319 if err != nil { 320 t.Fatal(err) 321 } 322 323 require.Len(t, balances, 1) 324 require.Equal(t, balances[0].Asset.Type, "native") 325 require.Equal(t, balances[0].Amount, "10000") 326 } 327 328 func TestGetWalletAccountsCLILocal(t *testing.T) { 329 tcs, cleanup := setupNTests(t, 1) 330 defer cleanup() 331 332 acceptDisclaimer(tcs[0]) 333 334 tcs[0].Backend.ImportAccountsForUser(tcs[0]) 335 336 accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background()) 337 require.NoError(t, err) 338 339 require.Len(t, accs, 1) 340 account := accs[0] 341 require.Len(t, account.Balance, 1) 342 require.Equal(t, account.Balance[0].Asset.Type, "native") 343 require.Equal(t, account.Balance[0].Amount, "0") 344 require.True(t, account.IsPrimary) 345 require.NotNil(t, account.ExchangeRate) 346 require.EqualValues(t, stellar.DefaultCurrencySetting, account.ExchangeRate.Currency) 347 } 348 349 func TestSendLocalStellarAddress(t *testing.T) { 350 tcs, cleanup := setupNTests(t, 1) 351 defer cleanup() 352 353 acceptDisclaimer(tcs[0]) 354 355 srv := tcs[0].Srv 356 rm := tcs[0].Backend 357 accountIDSender := rm.AddAccount(tcs[0].Fu.GetUID()) 358 accountIDRecip := rm.AddAccount(tcs[0].Fu.GetUID()) 359 360 err := srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{ 361 SecretKey: rm.SecretKey(accountIDSender), 362 MakePrimary: true, 363 Name: "uu", 364 }) 365 require.NoError(t, err) 366 367 arg := stellar1.SendCLILocalArg{ 368 Recipient: accountIDRecip.String(), 369 Amount: "100", 370 Asset: stellar1.Asset{Type: "native"}, 371 } 372 _, err = srv.SendCLILocal(context.Background(), arg) 373 require.NoError(t, err) 374 375 balances, err := srv.BalancesLocal(context.Background(), accountIDSender) 376 if err != nil { 377 t.Fatal(err) 378 } 379 require.Equal(t, balances[0].Amount, "9899.9999900") 380 381 balances, err = srv.BalancesLocal(context.Background(), accountIDRecip) 382 if err != nil { 383 t.Fatal(err) 384 } 385 require.Equal(t, balances[0].Amount, "10100.0000000") 386 387 senderMsgs := kbtest.MockSentMessages(tcs[0].G, tcs[0].T) 388 require.Len(t, senderMsgs, 0) 389 } 390 391 func TestSendLocalKeybase(t *testing.T) { 392 tcs, cleanup := setupNTests(t, 2) 393 defer cleanup() 394 395 acceptDisclaimer(tcs[0]) 396 acceptDisclaimer(tcs[1]) 397 398 srvSender := tcs[0].Srv 399 rm := tcs[0].Backend 400 accountIDSender := rm.AddAccount(tcs[0].Fu.GetUID()) 401 accountIDRecip := rm.AddAccount(tcs[1].Fu.GetUID()) 402 403 srvRecip := tcs[1].Srv 404 405 argImport := stellar1.ImportSecretKeyLocalArg{ 406 SecretKey: rm.SecretKey(accountIDSender), 407 MakePrimary: true, 408 Name: "uu", 409 } 410 err := srvSender.ImportSecretKeyLocal(context.Background(), argImport) 411 require.NoError(t, err) 412 413 argImport.SecretKey = rm.SecretKey(accountIDRecip) 414 err = srvRecip.ImportSecretKeyLocal(context.Background(), argImport) 415 require.NoError(t, err) 416 417 arg := stellar1.SendCLILocalArg{ 418 Recipient: strings.ToUpper(tcs[1].Fu.Username), 419 Amount: "100", 420 Asset: stellar1.AssetNative(), 421 } 422 _, err = srvSender.SendCLILocal(context.Background(), arg) 423 require.NoError(t, err) 424 425 balances, err := srvSender.BalancesLocal(context.Background(), accountIDSender) 426 require.NoError(t, err) 427 require.Equal(t, "9899.9999900", balances[0].Amount) 428 429 err = srvRecip.walletState.RefreshAll(tcs[1].MetaContext(), "test") 430 require.NoError(t, err) 431 balances, err = srvRecip.BalancesLocal(context.Background(), accountIDRecip) 432 require.NoError(t, err) 433 require.Equal(t, "10100.0000000", balances[0].Amount) 434 435 senderMsgs := kbtest.MockSentMessages(tcs[0].G, tcs[0].T) 436 require.Len(t, senderMsgs, 1) 437 require.Equal(t, senderMsgs[0].MsgType, chat1.MessageType_SENDPAYMENT) 438 } 439 440 func TestRecentPaymentsLocal(t *testing.T) { 441 tcs, cleanup := setupNTests(t, 2) 442 defer cleanup() 443 444 acceptDisclaimer(tcs[0]) 445 acceptDisclaimer(tcs[1]) 446 447 srvSender := tcs[0].Srv 448 rm := tcs[0].Backend 449 accountIDSender := rm.AddAccount(tcs[0].Fu.GetUID()) 450 accountIDRecip := rm.AddAccount(tcs[1].Fu.GetUID()) 451 452 srvRecip := tcs[1].Srv 453 454 argImport := stellar1.ImportSecretKeyLocalArg{ 455 SecretKey: rm.SecretKey(accountIDSender), 456 MakePrimary: true, 457 Name: "uu", 458 } 459 err := srvSender.ImportSecretKeyLocal(context.Background(), argImport) 460 require.NoError(t, err) 461 462 argImport.SecretKey = rm.SecretKey(accountIDRecip) 463 err = srvRecip.ImportSecretKeyLocal(context.Background(), argImport) 464 require.NoError(t, err) 465 466 arg := stellar1.SendCLILocalArg{ 467 Recipient: tcs[1].Fu.Username, 468 Amount: "100", 469 Asset: stellar1.Asset{Type: "native"}, 470 } 471 _, err = srvSender.SendCLILocal(context.Background(), arg) 472 require.NoError(t, err) 473 474 checkPayment := func(p stellar1.PaymentCLILocal) { 475 require.Equal(t, accountIDSender, p.FromStellar) 476 require.Equal(t, accountIDRecip, *p.ToStellar) 477 require.NotNil(t, p.ToUsername) 478 require.Equal(t, tcs[1].Fu.Username, *(p.ToUsername)) 479 require.Equal(t, "100.0000000", p.Amount) 480 } 481 senderPayments, err := srvSender.RecentPaymentsCLILocal(context.Background(), nil) 482 require.NoError(t, err) 483 require.Len(t, senderPayments, 1) 484 require.NotNil(t, senderPayments[0].Payment, senderPayments[0].Err) 485 checkPayment(*senderPayments[0].Payment) 486 487 recipPayments, err := srvRecip.RecentPaymentsCLILocal(context.Background(), nil) 488 require.NoError(t, err) 489 require.Len(t, recipPayments, 1) 490 require.NotNil(t, recipPayments[0].Payment, recipPayments[0].Err) 491 checkPayment(*recipPayments[0].Payment) 492 493 payment, err := srvSender.PaymentDetailCLILocal(context.Background(), senderPayments[0].Payment.TxID.String()) 494 require.NoError(t, err) 495 checkPayment(payment) 496 payment, err = srvRecip.PaymentDetailCLILocal(context.Background(), recipPayments[0].Payment.TxID.String()) 497 require.NoError(t, err) 498 checkPayment(payment) 499 } 500 501 func TestRelayTransferInnards(t *testing.T) { 502 tcs, cleanup := setupNTests(t, 2) 503 defer cleanup() 504 505 acceptDisclaimer(tcs[0]) 506 stellarSender, senderAccountBundle, err := stellar.LookupSenderPrimary(tcs[0].MetaContext()) 507 require.NoError(t, err) 508 require.Equal(t, stellarSender.AccountID, senderAccountBundle.AccountID) 509 510 u1, err := libkb.LoadUser(libkb.NewLoadUserByNameArg(tcs[0].G, tcs[1].Fu.Username)) 511 require.NoError(t, err) 512 513 t.Logf("create relay transfer") 514 m := libkb.NewMetaContextBackground(tcs[0].G) 515 recipient, err := stellar.LookupRecipient(m, stellarcommon.RecipientInput(u1.GetNormalizedName()), false) 516 require.NoError(t, err) 517 appKey, teamID, err := relays.GetKey(m, recipient) 518 require.NoError(t, err) 519 sp, unlock := stellar.NewSeqnoProvider(libkb.NewMetaContextForTest(tcs[0].TestContext), tcs[0].Srv.walletState) 520 defer unlock() 521 out, err := relays.Create(relays.Input{ 522 From: senderAccountBundle.Signers[0], 523 AmountXLM: "10.0005", 524 Note: "hey", 525 EncryptFor: appKey, 526 SeqnoProvider: sp, 527 BaseFee: 100, 528 }) 529 require.NoError(t, err) 530 _, err = libkb.ParseStellarAccountID(out.RelayAccountID.String()) 531 require.NoError(t, err) 532 require.True(t, len(out.FundTx.Signed) > 100) 533 534 t.Logf("decrypt") 535 relaySecrets, err := relays.DecryptB64(tcs[0].MetaContext(), teamID, out.EncryptedB64) 536 require.NoError(t, err) 537 _, accountID, _, err := libkb.ParseStellarSecretKey(relaySecrets.Sk.SecureNoLogString()) 538 require.NoError(t, err) 539 require.Equal(t, out.RelayAccountID, accountID) 540 require.Len(t, relaySecrets.StellarID, 64) 541 require.Equal(t, "hey", relaySecrets.Note) 542 } 543 544 func TestRelaySBSClaim(t *testing.T) { 545 testRelaySBS(t, false) 546 } 547 548 func TestRelaySBSYank(t *testing.T) { 549 testRelaySBS(t, true) 550 } 551 552 func testRelaySBS(t *testing.T, yank bool) { 553 tcs, cleanup := setupTestsWithSettings(t, []usetting{usettingFull, usettingPukless}) 554 defer cleanup() 555 556 acceptDisclaimer(tcs[0]) 557 558 tcs[0].Backend.ImportAccountsForUser(tcs[0]) 559 tcs[0].Backend.Gift(getPrimaryAccountID(tcs[0]), "5") 560 sendRes, err := tcs[0].Srv.SendCLILocal(context.Background(), stellar1.SendCLILocalArg{ 561 Recipient: tcs[1].Fu.Username, 562 Amount: "3", 563 Asset: stellar1.AssetNative(), 564 }) 565 require.NoError(t, err) 566 567 details, err := tcs[0].Backend.PaymentDetailsGeneric(context.Background(), tcs[0], sendRes.KbTxID.String()) 568 require.NoError(t, err) 569 570 claimant := 0 571 if !yank { 572 claimant = 1 573 574 tcs[1].Tp.DisableUpgradePerUserKey = false 575 acceptDisclaimer(tcs[1]) 576 577 tcs[0].Backend.ImportAccountsForUser(tcs[claimant]) 578 579 // The implicit team has an invite for the claimant. Now the sender signs them into the team. 580 t.Logf("Sender keys recipient into implicit team") 581 teamID := details.Summary.Relay().TeamID 582 team, err := teams.Load(context.Background(), tcs[0].G, keybase1.LoadTeamArg{ID: teamID}) 583 require.NoError(t, err) 584 invite, _, found := team.FindActiveKeybaseInvite(tcs[claimant].Fu.GetUID()) 585 require.True(t, found) 586 err = teams.HandleSBSRequest(context.Background(), tcs[0].G, keybase1.TeamSBSMsg{ 587 TeamID: teamID, 588 Invitees: []keybase1.TeamInvitee{{ 589 InviteID: invite.Id, 590 Uid: tcs[claimant].Fu.GetUID(), 591 EldestSeqno: tcs[claimant].Fu.EldestSeqno, 592 Role: keybase1.TeamRole_ADMIN, 593 }}, 594 }) 595 require.NoError(t, err) 596 } 597 598 err = tcs[claimant].Srv.walletState.RefreshAll(tcs[claimant].MetaContext(), "test") 599 require.NoError(t, err) 600 601 history, err := tcs[claimant].Srv.RecentPaymentsCLILocal(context.Background(), nil) 602 require.NoError(t, err) 603 require.Len(t, history, 1) 604 require.Nil(t, history[0].Err) 605 require.NotNil(t, history[0].Payment) 606 require.Equal(t, "Claimable", history[0].Payment.Status) 607 txID := history[0].Payment.TxID 608 609 fhistory, err := tcs[claimant].Srv.GetPendingPaymentsLocal(context.Background(), stellar1.GetPendingPaymentsLocalArg{AccountID: getPrimaryAccountID(tcs[claimant])}) 610 require.NoError(t, err) 611 require.Len(t, fhistory, 1) 612 require.Nil(t, fhistory[0].Err) 613 require.NotNil(t, fhistory[0].Payment) 614 require.NotEmpty(t, fhistory[0].Payment.Id) 615 require.NotZero(t, fhistory[0].Payment.Time) 616 require.Equal(t, stellar1.PaymentStatus_CLAIMABLE, fhistory[0].Payment.StatusSimplified) 617 require.Equal(t, "claimable", fhistory[0].Payment.StatusDescription) 618 if yank { 619 require.Equal(t, "3 XLM", fhistory[0].Payment.AmountDescription) 620 require.Equal(t, stellar1.BalanceDelta_DECREASE, fhistory[0].Payment.Delta) 621 } else { 622 require.Equal(t, "3 XLM", fhistory[0].Payment.AmountDescription) 623 require.Equal(t, stellar1.BalanceDelta_INCREASE, fhistory[0].Payment.Delta) // assertion related to CORE-9322 624 } 625 626 tcs[0].Backend.AssertBalance(getPrimaryAccountID(tcs[0]), "1.9999900") 627 if !yank { 628 tcs[claimant].Backend.AssertBalance(getPrimaryAccountID(tcs[claimant]), "0") 629 } 630 631 res, err := tcs[claimant].Srv.ClaimCLILocal(context.Background(), stellar1.ClaimCLILocalArg{TxID: txID.String()}) 632 require.NoError(t, err) 633 require.NotEqual(t, "", res.ClaimStellarID) 634 635 if !yank { 636 tcs[0].Backend.AssertBalance(getPrimaryAccountID(tcs[0]), "1.9999900") 637 tcs[claimant].Backend.AssertBalance(getPrimaryAccountID(tcs[claimant]), "2.9999800") 638 } else { 639 tcs[claimant].Backend.AssertBalance(getPrimaryAccountID(tcs[claimant]), "4.9999800") 640 } 641 642 frontendExpStatusSimp := stellar1.PaymentStatus_COMPLETED 643 frontendExpToAssertion := tcs[1].Fu.Username 644 frontendExpOrigToAssertion := "" 645 if yank { 646 frontendExpStatusSimp = stellar1.PaymentStatus_CANCELED 647 frontendExpToAssertion, frontendExpOrigToAssertion = frontendExpOrigToAssertion, frontendExpToAssertion 648 } 649 frontendExpStatusDesc := strings.ToLower(frontendExpStatusSimp.String()) 650 checkStatusesAndAssertions := func(p *stellar1.PaymentLocal) { 651 require.Equal(t, frontendExpStatusSimp, p.StatusSimplified) 652 require.Equal(t, frontendExpStatusDesc, p.StatusDescription) 653 require.Equal(t, frontendExpToAssertion, p.ToAssertion) 654 require.Equal(t, frontendExpOrigToAssertion, p.OriginalToAssertion) 655 } 656 657 history, err = tcs[claimant].Srv.RecentPaymentsCLILocal(context.Background(), nil) 658 require.NoError(t, err) 659 require.Len(t, history, 1) 660 require.Nil(t, history[0].Err) 661 require.NotNil(t, history[0].Payment) 662 if !yank { 663 require.Equal(t, "Completed", history[0].Payment.Status) 664 } else { 665 require.Equal(t, "Canceled", history[0].Payment.Status) 666 } 667 668 fhistoryPage, err := tcs[claimant].Srv.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: getPrimaryAccountID(tcs[claimant])}) 669 require.NoError(t, err) 670 fhistory = fhistoryPage.Payments 671 require.Len(t, fhistory, 1) 672 require.Nil(t, fhistory[0].Err) 673 require.NotNil(t, fhistory[0].Payment) 674 checkStatusesAndAssertions(fhistory[0].Payment) 675 676 history, err = tcs[0].Srv.RecentPaymentsCLILocal(context.Background(), nil) 677 require.NoError(t, err) 678 require.Len(t, history, 1) 679 require.Nil(t, history[0].Err) 680 require.NotNil(t, history[0].Payment) 681 if !yank { 682 require.Equal(t, "Completed", history[0].Payment.Status) 683 } else { 684 require.Equal(t, "Canceled", history[0].Payment.Status) 685 } 686 687 err = tcs[0].Srv.walletState.RefreshAll(tcs[0].MetaContext(), "test") 688 require.NoError(t, err) 689 690 fhistoryPage, err = tcs[0].Srv.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: getPrimaryAccountID(tcs[0])}) 691 require.NoError(t, err) 692 fhistory = fhistoryPage.Payments 693 require.Len(t, fhistory, 1) 694 require.Nil(t, fhistory[0].Err) 695 require.NotNil(t, fhistory[0].Payment) 696 checkStatusesAndAssertions(fhistory[0].Payment) 697 698 t.Logf("try to claim again") 699 res, err = tcs[claimant].Srv.ClaimCLILocal(context.Background(), stellar1.ClaimCLILocalArg{TxID: txID.String()}) 700 require.Error(t, err) 701 require.Equal(t, "Payment already claimed by "+tcs[claimant].Fu.Username, err.Error()) 702 } 703 704 func TestRelayResetClaim(t *testing.T) { 705 testRelayReset(t, false) 706 } 707 708 func TestRelayResetYank(t *testing.T) { 709 testRelayReset(t, true) 710 } 711 712 func testRelayReset(t *testing.T, yank bool) { 713 tcs, cleanup := setupTestsWithSettings(t, []usetting{usettingFull, usettingFull}) 714 defer cleanup() 715 716 acceptDisclaimer(tcs[0]) 717 718 tcs[0].Backend.ImportAccountsForUser(tcs[0]) 719 tcs[0].Backend.Gift(getPrimaryAccountID(tcs[0]), "10") 720 721 sendRes, err := tcs[0].Srv.SendCLILocal(context.Background(), stellar1.SendCLILocalArg{ 722 Recipient: tcs[1].Fu.Username, 723 Amount: "4", 724 Asset: stellar1.AssetNative(), 725 }) 726 require.NoError(t, err) 727 728 details, err := tcs[0].Backend.PaymentDetailsGeneric(context.Background(), tcs[0], sendRes.KbTxID.String()) 729 require.NoError(t, err) 730 731 typ, err := details.Summary.Typ() 732 require.NoError(t, err) 733 require.Equal(t, stellar1.PaymentSummaryType_RELAY, typ) 734 735 // Reset and reprovision 736 kbtest.ResetAccount(tcs[1].TestContext, tcs[1].Fu) 737 require.NoError(t, tcs[1].Fu.Login(tcs[1].G)) 738 739 teamID := details.Summary.Relay().TeamID 740 t.Logf("Team ID is: %s", teamID) 741 742 var claimant int 743 if !yank { 744 // Admit back to the team. 745 err = teams.ReAddMemberAfterReset(context.Background(), tcs[0].G, teamID, tcs[1].Fu.Username) 746 require.NoError(t, err) 747 748 acceptDisclaimer(tcs[1]) 749 tcs[1].Backend.ImportAccountsForUser(tcs[1]) 750 751 claimant = 1 752 } else { 753 // User0 will try to claim the funds back without readding user1 to the 754 // impteam. Also do not accept disclaimer as user1. 755 claimant = 0 756 } 757 758 err = tcs[claimant].Srv.walletState.RefreshAll(tcs[claimant].MetaContext(), "test") 759 require.NoError(t, err) 760 tcs[claimant].Srv.walletState.DumpToLog(tcs[claimant].MetaContext()) 761 762 history, err := tcs[claimant].Srv.RecentPaymentsCLILocal(context.Background(), nil) 763 require.NoError(t, err) 764 require.Len(t, history, 1) 765 require.Nil(t, history[0].Err) 766 require.NotNil(t, history[0].Payment) 767 require.Equal(t, "Claimable", history[0].Payment.Status) 768 txID := history[0].Payment.TxID 769 770 t.Logf("claimant primary account id: %s", getPrimaryAccountID(tcs[claimant])) 771 772 fhistory, err := tcs[claimant].Srv.GetPendingPaymentsLocal(context.Background(), 773 stellar1.GetPendingPaymentsLocalArg{AccountID: getPrimaryAccountID(tcs[claimant])}) 774 require.NoError(t, err) 775 require.Len(t, fhistory, 1) 776 require.Nil(t, fhistory[0].Err) 777 require.NotNil(t, fhistory[0].Payment) 778 require.NotEmpty(t, fhistory[0].Payment.Id) 779 require.NotZero(t, fhistory[0].Payment.Time) 780 require.Equal(t, stellar1.PaymentStatus_CLAIMABLE, fhistory[0].Payment.StatusSimplified) 781 require.Equal(t, "claimable", fhistory[0].Payment.StatusDescription) 782 783 res, err := tcs[claimant].Srv.ClaimCLILocal(context.Background(), stellar1.ClaimCLILocalArg{TxID: txID.String()}) 784 require.NoError(t, err) 785 require.NotEqual(t, "", res.ClaimStellarID) 786 787 if !yank { 788 tcs[0].Backend.AssertBalance(getPrimaryAccountID(tcs[0]), "5.9999900") 789 tcs[1].Backend.AssertBalance(getPrimaryAccountID(tcs[1]), "3.9999800") 790 } else { 791 tcs[0].Backend.AssertBalance(getPrimaryAccountID(tcs[0]), "9.9999800") 792 } 793 } 794 795 func TestGetAvailableCurrencies(t *testing.T) { 796 tcs, cleanup := setupNTests(t, 1) 797 defer cleanup() 798 799 conf, err := tcs[0].G.GetStellar().GetServerDefinitions(context.Background()) 800 require.NoError(t, err) 801 require.Equal(t, conf.Currencies["USD"].Name, "US Dollar") 802 require.Equal(t, conf.Currencies["EUR"].Name, "Euro") 803 } 804 805 func TestDefaultCurrency(t *testing.T) { 806 // Initial account are created without display currency. When an account 807 // has no currency selected, default "USD" is used. Additional accounts 808 // display currencies should be set to primary account currency or NULL as 809 // well (and can later be changed by the user). 810 811 tcs, cleanup := setupNTests(t, 1) 812 defer cleanup() 813 814 acceptDisclaimer(tcs[0]) 815 tcs[0].Backend.ImportAccountsForUser(tcs[0]) 816 817 primary := getPrimaryAccountID(tcs[0]) 818 currency, err := remote.GetAccountDisplayCurrency(context.Background(), tcs[0].G, primary) 819 require.NoError(t, err) 820 require.EqualValues(t, "", currency) 821 822 // stellar.GetAccountDisplayCurrency also checks for NULLs and returns 823 // default currency code. 824 codeStr, err := stellar.GetAccountDisplayCurrency(tcs[0].MetaContext(), primary) 825 require.NoError(t, err) 826 require.Equal(t, "USD", codeStr) 827 828 err = tcs[0].Srv.SetDisplayCurrency(context.Background(), stellar1.SetDisplayCurrencyArg{ 829 AccountID: primary, 830 Currency: "EUR", 831 }) 832 require.NoError(t, err) 833 834 currency, err = remote.GetAccountDisplayCurrency(context.Background(), tcs[0].G, primary) 835 require.NoError(t, err) 836 require.EqualValues(t, "EUR", currency) 837 838 a1, s1 := randomStellarKeypair() 839 err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{ 840 SecretKey: s1, 841 MakePrimary: false, 842 Name: "uu", 843 }) 844 require.NoError(t, err) 845 846 // Should be "EUR" as well, inherited from primary account. Try to 847 // use RPC instead of remote endpoint directly this time. 848 currencyObj, err := tcs[0].Srv.GetDisplayCurrencyLocal(context.Background(), stellar1.GetDisplayCurrencyLocalArg{ 849 AccountID: &a1, 850 }) 851 require.NoError(t, err) 852 require.IsType(t, stellar1.CurrencyLocal{}, currencyObj) 853 require.Equal(t, stellar1.OutsideCurrencyCode("EUR"), currencyObj.Code) 854 } 855 856 func TestRequestPayment(t *testing.T) { 857 tcs, cleanup := setupNTests(t, 2) 858 defer cleanup() 859 860 acceptDisclaimer(tcs[0]) 861 acceptDisclaimer(tcs[1]) 862 xlm := stellar1.AssetNative() 863 reqID, err := tcs[0].Srv.MakeRequestCLILocal(context.Background(), stellar1.MakeRequestCLILocalArg{ 864 Recipient: tcs[1].Fu.Username, 865 Asset: &xlm, 866 Amount: "5.23", 867 Note: "hello world", 868 }) 869 require.NoError(t, err) 870 871 senderMsgs := kbtest.MockSentMessages(tcs[0].G, tcs[0].T) 872 require.Len(t, senderMsgs, 1) 873 require.Equal(t, senderMsgs[0].MsgType, chat1.MessageType_REQUESTPAYMENT) 874 875 err = tcs[0].Srv.CancelRequestLocal(context.Background(), stellar1.CancelRequestLocalArg{ 876 ReqID: reqID, 877 }) 878 require.NoError(t, err) 879 880 details, err := tcs[0].Srv.GetRequestDetailsLocal(context.Background(), stellar1.GetRequestDetailsLocalArg{ 881 ReqID: reqID, 882 }) 883 require.NoError(t, err) 884 require.Equal(t, stellar1.RequestStatus_CANCELED, details.Status) 885 require.Equal(t, "5.23", details.Amount) 886 require.Nil(t, details.Currency) 887 require.NotNil(t, details.Asset) 888 require.Equal(t, stellar1.AssetNative(), *details.Asset) 889 require.Equal(t, "5.23 XLM", details.AmountDescription) 890 } 891 892 func TestRequestPaymentOutsideCurrency(t *testing.T) { 893 tcs, cleanup := setupNTests(t, 2) 894 defer cleanup() 895 896 acceptDisclaimer(tcs[0]) 897 acceptDisclaimer(tcs[1]) 898 reqID, err := tcs[0].Srv.MakeRequestCLILocal(context.Background(), stellar1.MakeRequestCLILocalArg{ 899 Recipient: tcs[1].Fu.Username, 900 Currency: &usd, 901 Amount: "8.196", 902 Note: "got 10 bucks (minus tax)?", 903 }) 904 require.NoError(t, err) 905 details, err := tcs[0].Srv.GetRequestDetailsLocal(context.Background(), stellar1.GetRequestDetailsLocalArg{ 906 ReqID: reqID, 907 }) 908 require.NoError(t, err) 909 require.Equal(t, stellar1.RequestStatus_OK, details.Status) 910 require.Equal(t, "8.196", details.Amount) 911 require.Nil(t, details.Asset) 912 require.NotNil(t, details.Currency) 913 require.Equal(t, stellar1.OutsideCurrencyCode("USD"), *details.Currency) 914 require.Equal(t, "$8.20 USD", details.AmountDescription) 915 } 916 917 func TestBundleFlows(t *testing.T) { 918 tcs, cleanup := setupNTests(t, 1) 919 defer cleanup() 920 ctx := context.Background() 921 g := tcs[0].G 922 setupWithNewBundle(t, tcs[0]) 923 924 mctx := libkb.NewMetaContext(ctx, g) 925 bundle, err := remote.FetchSecretlessBundle(mctx) 926 require.NoError(t, err) 927 accounts := bundle.Accounts 928 secretsMap := bundle.AccountBundles 929 var accountIDs []stellar1.AccountID 930 for _, acct := range accounts { 931 signers := secretsMap[acct.AccountID].Signers 932 require.Equal(t, len(signers), 0) 933 accountIDs = append(accountIDs, acct.AccountID) 934 } 935 require.Equal(t, len(accountIDs), 1) 936 // add a new account non-primary account 937 a1, s1 := randomStellarKeypair() 938 err = tcs[0].Srv.ImportSecretKeyLocal(ctx, stellar1.ImportSecretKeyLocalArg{ 939 SecretKey: s1, 940 MakePrimary: false, 941 Name: "aa", 942 }) 943 require.NoError(t, err) 944 945 // add a new primary account 946 a2, s2 := randomStellarKeypair() 947 err = tcs[0].Srv.ImportSecretKeyLocal(ctx, stellar1.ImportSecretKeyLocalArg{ 948 SecretKey: s2, 949 MakePrimary: true, 950 Name: "bb", 951 }) 952 require.NoError(t, err) 953 954 assertFetchAccountBundles(t, tcs[0], a2) 955 956 // switch which account is primary 957 defaultChangeRes, err := tcs[0].Srv.SetWalletAccountAsDefaultLocal(ctx, stellar1.SetWalletAccountAsDefaultLocalArg{ 958 AccountID: a1, 959 }) 960 require.NoError(t, err) 961 require.Equal(t, a1, defaultChangeRes[0].AccountID) 962 require.True(t, defaultChangeRes[0].IsDefault) 963 assertFetchAccountBundles(t, tcs[0], a1) 964 965 fullBundle, err := fetchWholeBundleForTesting(mctx) 966 require.NoError(t, err) 967 err = fullBundle.CheckInvariants() 968 require.NoError(t, err) 969 require.Equal(t, 3, len(fullBundle.Accounts)) 970 for _, acc := range fullBundle.Accounts { 971 ab := fullBundle.AccountBundles[acc.AccountID] 972 require.Equal(t, 1, len(ab.Signers)) 973 _, parsedAccountID, _, err := libkb.ParseStellarSecretKey(string(ab.Signers[0])) 974 require.NoError(t, err) 975 require.Equal(t, parsedAccountID, acc.AccountID) 976 } 977 978 // ExportSecretKey 979 privKey, err := tcs[0].Srv.GetWalletAccountSecretKeyLocal(ctx, stellar1.GetWalletAccountSecretKeyLocalArg{ 980 AccountID: a2, 981 }) 982 require.NoError(t, err) 983 require.EqualValues(t, s2, privKey) 984 985 // ChangeAccountName 986 res, err := tcs[0].Srv.ChangeWalletAccountNameLocal(ctx, stellar1.ChangeWalletAccountNameLocalArg{ 987 AccountID: a2, 988 NewName: "rename", 989 }) 990 require.NoError(t, err) 991 require.Equal(t, "rename", res.Name) 992 bundle, err = remote.FetchAccountBundle(mctx, a2) 993 require.NoError(t, err) 994 for _, acc := range bundle.Accounts { 995 if acc.AccountID == a2 { 996 require.Equal(t, acc.Name, "rename") 997 accSigners := bundle.AccountBundles[a2].Signers 998 require.Equal(t, accSigners[0], s2) 999 } 1000 } 1001 1002 // DeleteAccount 1003 err = tcs[0].Srv.DeleteWalletAccountLocal(ctx, stellar1.DeleteWalletAccountLocalArg{ 1004 AccountID: a2, 1005 UserAcknowledged: "yes", 1006 }) 1007 require.NoError(t, err) 1008 // fetching this account explicitly should error 1009 _, err = remote.FetchAccountBundle(mctx, a2) 1010 require.Error(t, err) 1011 aerr, ok := err.(libkb.AppStatusError) 1012 if !ok { 1013 t.Fatalf("invalid error type %T", err) 1014 } 1015 require.Equal(t, libkb.SCStellarMissingAccount, aerr.Code) 1016 // fetching everything should yield a bundle that 1017 // does not include this account 1018 bundle, err = fetchWholeBundleForTesting(mctx) 1019 require.NoError(t, err) 1020 for _, acc := range bundle.Accounts { 1021 require.False(t, acc.AccountID == a2) 1022 } 1023 for accID := range bundle.AccountBundles { 1024 require.False(t, accID == a2) 1025 } 1026 1027 // CreateNewAccount 1028 accID, err := tcs[0].Srv.CreateWalletAccountLocal(ctx, stellar1.CreateWalletAccountLocalArg{ 1029 Name: "skittles", 1030 }) 1031 require.NoError(t, err) 1032 bundle, err = remote.FetchSecretlessBundle(mctx) 1033 require.NoError(t, err) 1034 found := false 1035 for _, acc := range bundle.Accounts { 1036 if acc.Name == "skittles" { 1037 require.False(t, found) 1038 require.Equal(t, accID, acc.AccountID) 1039 found = true 1040 } 1041 } 1042 require.True(t, found) 1043 } 1044 1045 func assertFetchAccountBundles(t *testing.T, tc *TestContext, primaryAccountID stellar1.AccountID) { 1046 // fetch a secretless bundle to get all of the accountIDs 1047 ctx := context.Background() 1048 g := tc.G 1049 mctx := libkb.NewMetaContext(ctx, g) 1050 secretlessBundle, err := remote.FetchSecretlessBundle(mctx) 1051 require.NoError(t, err) 1052 err = secretlessBundle.CheckInvariants() 1053 require.NoError(t, err) 1054 var accountIDs []stellar1.AccountID 1055 var foundPrimary bool 1056 for _, acct := range secretlessBundle.Accounts { 1057 accountIDs = append(accountIDs, acct.AccountID) 1058 if acct.AccountID == primaryAccountID { 1059 foundPrimary = true 1060 } 1061 } 1062 require.True(t, foundPrimary) 1063 1064 // fetch the account bundle for each account and validate that it looks correct 1065 // for each account in the bundle including the ones not explicitly fetched 1066 for _, accountID := range accountIDs { 1067 fetchedBundle, err := remote.FetchAccountBundle(mctx, accountID) 1068 require.NoError(t, err) 1069 err = fetchedBundle.CheckInvariants() 1070 require.NoError(t, err) 1071 ab := fetchedBundle.AccountBundles 1072 for _, acct := range fetchedBundle.Accounts { 1073 if acct.AccountID == primaryAccountID { 1074 require.True(t, acct.IsPrimary) 1075 } else { 1076 require.False(t, acct.IsPrimary) 1077 } 1078 if acct.AccountID == accountID { 1079 // this is the account we were explicitly fetching, so it should have signers 1080 signers := ab[accountID].Signers 1081 require.Equal(t, len(signers), 1) 1082 _, parsedAccountID, _, err := libkb.ParseStellarSecretKey(string(signers[0])) 1083 require.NoError(t, err) 1084 require.Equal(t, parsedAccountID, accountID) 1085 } else { 1086 // this is not an account we were explicitly fetching 1087 // so it should not be in the account bundle map 1088 _, accountInAccountBundle := ab[acct.AccountID] 1089 require.False(t, accountInAccountBundle) 1090 } 1091 } 1092 } 1093 } 1094 1095 func RequireAppStatusError(t *testing.T, code int, err error) { 1096 require.Error(t, err) 1097 require.IsType(t, err, libkb.AppStatusError{}) 1098 if aerr, ok := err.(libkb.AppStatusError); ok { 1099 require.Equal(t, code, aerr.Code) 1100 } 1101 } 1102 1103 // TestMakeAccountMobileOnlyOnDesktop imports a new secret stellar key, then makes it 1104 // mobile only from a desktop device. The subsequent fetch fails because it is 1105 // a desktop device. 1106 func TestMakeAccountMobileOnlyOnDesktop(t *testing.T) { 1107 tc, cleanup := setupDesktopTest(t) 1108 defer cleanup() 1109 ctx := context.Background() 1110 g := tc.G 1111 mctx := libkb.NewMetaContextBackground(g) 1112 setupWithNewBundle(t, tc) 1113 1114 a1, s1 := randomStellarKeypair() 1115 err := tc.Srv.ImportSecretKeyLocal(ctx, stellar1.ImportSecretKeyLocalArg{ 1116 SecretKey: s1, 1117 MakePrimary: false, 1118 Name: "vault", 1119 }) 1120 require.NoError(t, err) 1121 1122 rev2Bundle, err := remote.FetchAccountBundle(mctx, a1) 1123 require.NoError(t, err) 1124 require.Equal(t, stellar1.BundleRevision(2), rev2Bundle.Revision) 1125 // NOTE: we're using this rev2Bundle later... 1126 1127 // Mobile-only mode can only be set from mobile device. 1128 err = tc.Srv.SetAccountMobileOnlyLocal(ctx, stellar1.SetAccountMobileOnlyLocalArg{ 1129 AccountID: a1, 1130 }) 1131 RequireAppStatusError(t, libkb.SCStellarDeviceNotMobile, err) 1132 1133 // This will make the device older on the server. 1134 makeActiveDeviceOlder(t, g) 1135 1136 // This does not affect anything, it's still a desktop device. 1137 err = tc.Srv.SetAccountMobileOnlyLocal(ctx, stellar1.SetAccountMobileOnlyLocalArg{ 1138 AccountID: a1, 1139 }) 1140 RequireAppStatusError(t, libkb.SCStellarDeviceNotMobile, err) 1141 1142 // Provision a new mobile device, and then use the newly provisioned mobile 1143 // device to set mobile only. 1144 tc2, cleanup2 := provisionNewDeviceForTest(t, tc, keybase1.DeviceTypeV2_MOBILE) 1145 defer cleanup2() 1146 1147 err = tc2.Srv.SetAccountMobileOnlyLocal(context.TODO(), stellar1.SetAccountMobileOnlyLocalArg{ 1148 AccountID: a1, 1149 }) 1150 RequireAppStatusError(t, libkb.SCStellarMobileOnlyPurgatory, err) 1151 1152 // Make mobile older and try again, should work this time. 1153 makeActiveDeviceOlder(t, tc2.G) 1154 1155 err = tc2.Srv.SetAccountMobileOnlyLocal(context.TODO(), stellar1.SetAccountMobileOnlyLocalArg{ 1156 AccountID: a1, 1157 }) 1158 require.NoError(t, err) 1159 1160 // Once mobile only is ON, try some stuff from our desktop device. 1161 _, err = remote.FetchAccountBundle(mctx, a1) 1162 RequireAppStatusError(t, libkb.SCStellarDeviceNotMobile, err) 1163 1164 // Desktop can still get the secretless bundle 1165 primaryAcctName := fmt.Sprintf("%s's account", tc.Fu.Username) 1166 rev3Bundle, err := remote.FetchSecretlessBundle(mctx) 1167 require.NoError(t, err) 1168 require.Equal(t, stellar1.BundleRevision(3), rev3Bundle.Revision) 1169 accountID0 := rev3Bundle.Accounts[0].AccountID 1170 require.Equal(t, primaryAcctName, rev3Bundle.Accounts[0].Name) 1171 require.True(t, rev3Bundle.Accounts[0].IsPrimary) 1172 require.Len(t, rev3Bundle.AccountBundles[accountID0].Signers, 0) 1173 accountID1 := rev3Bundle.Accounts[1].AccountID 1174 require.Equal(t, stellar1.AccountMode_MOBILE, rev3Bundle.Accounts[1].Mode) 1175 require.False(t, rev3Bundle.Accounts[1].IsPrimary) 1176 require.Len(t, rev3Bundle.AccountBundles[accountID1].Signers, 0) 1177 require.Equal(t, "vault", rev3Bundle.Accounts[1].Name) 1178 1179 err = remote.Post(mctx, *rev2Bundle) 1180 RequireAppStatusError(t, libkb.SCStellarDeviceNotMobile, err) 1181 1182 // Tinker with it. 1183 rev2Bundle.Revision = 4 1184 err = remote.Post(mctx, *rev2Bundle) 1185 RequireAppStatusError(t, libkb.SCStellarDeviceNotMobile, err) 1186 1187 for i := 0; i < 10; i++ { 1188 // turn mobile only off 1189 err = tc2.Srv.SetAccountAllDevicesLocal(context.TODO(), stellar1.SetAccountAllDevicesLocalArg{ 1190 AccountID: a1, 1191 }) 1192 require.NoError(t, err) 1193 1194 // and on 1195 err = tc2.Srv.SetAccountMobileOnlyLocal(context.TODO(), stellar1.SetAccountMobileOnlyLocalArg{ 1196 AccountID: a1, 1197 }) 1198 require.NoError(t, err) 1199 } 1200 } 1201 1202 // TestMakeAccountMobileOnlyOnRecentMobile imports a new secret stellar key, then 1203 // makes it mobile only. The subsequent fetch fails because it is 1204 // a recently provisioned mobile device. After 7 days, the fetch works. 1205 func TestMakeAccountMobileOnlyOnRecentMobile(t *testing.T) { 1206 tc, cleanup := setupMobileTest(t) 1207 defer cleanup() 1208 ctx := context.Background() 1209 g := tc.G 1210 mctx := libkb.NewMetaContext(ctx, g) 1211 setupWithNewBundle(t, tc) 1212 1213 a1, s1 := randomStellarKeypair() 1214 err := tc.Srv.ImportSecretKeyLocal(ctx, stellar1.ImportSecretKeyLocalArg{ 1215 SecretKey: s1, 1216 MakePrimary: false, 1217 Name: "vault", 1218 }) 1219 require.NoError(t, err) 1220 1221 checker := newAcctBundleChecker(a1, s1) 1222 1223 bundle, err := remote.FetchAccountBundle(mctx, a1) 1224 require.NoError(t, err) 1225 t.Logf("bundle: %+v", bundle) 1226 checker.assertBundle(t, bundle, 2, 1, stellar1.AccountMode_USER) 1227 1228 // the mobile only device is too recent, so this would fail 1229 err = tc.Srv.SetAccountMobileOnlyLocal(ctx, stellar1.SetAccountMobileOnlyLocalArg{ 1230 AccountID: a1, 1231 }) 1232 RequireAppStatusError(t, libkb.SCStellarMobileOnlyPurgatory, err) 1233 1234 // this will make the device older on the server 1235 makeActiveDeviceOlder(t, g) 1236 // so now the set will work 1237 err = tc.Srv.SetAccountMobileOnlyLocal(ctx, stellar1.SetAccountMobileOnlyLocalArg{ 1238 AccountID: a1, 1239 }) 1240 require.NoError(t, err) 1241 1242 // and so will the fetch. 1243 bundle, err = remote.FetchAccountBundle(mctx, a1) 1244 require.NoError(t, err) 1245 checker.assertBundle(t, bundle, 3, 2, stellar1.AccountMode_MOBILE) 1246 1247 // this should not post a new bundle 1248 err = tc.Srv.SetAccountMobileOnlyLocal(ctx, stellar1.SetAccountMobileOnlyLocalArg{ 1249 AccountID: a1, 1250 }) 1251 require.NoError(t, err) 1252 bundle, err = remote.FetchAccountBundle(mctx, a1) 1253 require.NoError(t, err) 1254 checker.assertBundle(t, bundle, 3, 2, stellar1.AccountMode_MOBILE) 1255 1256 // Get a new mobile device that will be too recent to fetch 1257 // MOBILE ONLY bundle. 1258 tc2, cleanup2 := provisionNewDeviceForTest(t, tc, keybase1.DeviceTypeV2_MOBILE) 1259 defer cleanup2() 1260 1261 _, err = remote.FetchAccountBundle(libkb.NewMetaContext(context.Background(), tc2.G), a1) 1262 RequireAppStatusError(t, libkb.SCStellarMobileOnlyPurgatory, err) 1263 1264 // make it accessible on all devices 1265 err = tc.Srv.SetAccountAllDevicesLocal(ctx, stellar1.SetAccountAllDevicesLocalArg{ 1266 AccountID: a1, 1267 }) 1268 require.NoError(t, err) 1269 1270 bundle, err = remote.FetchAccountBundle(mctx, a1) 1271 require.NoError(t, err) 1272 checker.assertBundle(t, bundle, 4, 3, stellar1.AccountMode_USER) 1273 1274 // Now that it's AccountMode_USER, too recent mobile device can access it too. 1275 bundle, err = remote.FetchAccountBundle(libkb.NewMetaContext(context.Background(), tc2.G), a1) 1276 require.NoError(t, err) 1277 checker.assertBundle(t, bundle, 4, 3, stellar1.AccountMode_USER) 1278 } 1279 1280 type DummyMerkleStore struct { 1281 Entry keybase1.MerkleStoreEntry 1282 } 1283 1284 func (dm DummyMerkleStore) GetLatestEntry(m libkb.MetaContext) (e keybase1.MerkleStoreEntry, err error) { 1285 return dm.Entry, nil 1286 } 1287 func (dm DummyMerkleStore) GetLatestEntryWithKnown(m libkb.MetaContext, mskh *keybase1.MerkleStoreKitHash) (entry *keybase1.MerkleStoreEntry, err error) { 1288 return nil, nil 1289 } 1290 1291 var _ libkb.MerkleStore = (*DummyMerkleStore)(nil) 1292 1293 func TestGetPartnerUrlsLocal(t *testing.T) { 1294 // inject some fake data into the merkle store hanging off G 1295 // and then verify that the stellar exchange urls are extracted correctly 1296 // the only one of the three that should show up is the stellar_partners url which is 1297 // not marked as admin_only. 1298 tc, cleanup := setupMobileTest(t) 1299 defer cleanup() 1300 ctx := context.Background() 1301 g := tc.G 1302 firstPartnerURL := "billtop.com/%{accountID}" 1303 secondPartnerURL := "https://bitwat.com/keybase" 1304 jsonEntry := json.RawMessage(fmt.Sprintf(` 1305 {"external_urls":{ 1306 "stellar_partners":[ 1307 {"admin_only":false,"description":"buy from billtop","extra":"{\"superfun\":true}","icon_filename":"first.png","title":"BillTop","url":"%s"} 1308 , {"admin_only":true,"description":"buy from bitwat","extra":"{\"superfun\":false}","icon_filename":"second.png","title":"BitWat","url":"%s"} 1309 ], 1310 "something_unrelated":[ 1311 {"something":"else entirely","url":"dunno.pizza/txID"} 1312 ] 1313 }} 1314 `, firstPartnerURL, secondPartnerURL)) 1315 injectedEntry := keybase1.MerkleStoreEntry{ 1316 Hash: "000this-is-tested-elsewhere000", 1317 Entry: keybase1.MerkleStoreEntryString(jsonEntry), 1318 } 1319 injectedStore := DummyMerkleStore{injectedEntry} 1320 g.SetExternalURLStore(injectedStore) 1321 1322 res, err := tc.Srv.GetPartnerUrlsLocal(ctx, 0) 1323 require.NoError(t, err) 1324 require.Equal(t, len(res), 1) 1325 require.Equal(t, res[0].Url, firstPartnerURL) 1326 require.Equal(t, res[0].Extra, `{"superfun":true}`) 1327 require.False(t, res[0].AdminOnly) 1328 } 1329 1330 func TestAutoClaimLoop(t *testing.T) { 1331 tcs, cleanup := setupTestsWithSettings(t, []usetting{usettingFull, usettingFull}) 1332 defer cleanup() 1333 1334 acceptDisclaimer(tcs[0]) 1335 1336 tcs[0].Backend.ImportAccountsForUser(tcs[0]) 1337 tcs[0].Backend.Gift(getPrimaryAccountID(tcs[0]), "100") 1338 sendRes, err := tcs[0].Srv.SendCLILocal(context.Background(), stellar1.SendCLILocalArg{ 1339 Recipient: tcs[1].Fu.Username, 1340 Amount: "3", 1341 Asset: stellar1.AssetNative(), 1342 ForceRelay: true, 1343 }) 1344 require.NoError(t, err) 1345 1346 acceptDisclaimer(tcs[1]) 1347 tcs[1].Backend.ImportAccountsForUser(tcs[1]) 1348 tcs[1].Backend.EnableAutoclaimMock(tcs[1]) 1349 1350 tcs[1].G.GetStellar().KickAutoClaimRunner(tcs[1].MetaContext(), gregor1.MsgID{}) 1351 1352 var found bool 1353 for i := 0; i < 10; i++ { 1354 time.Sleep(100 * time.Millisecond * libkb.CITimeMultiplier(tcs[1].G)) 1355 payment := tcs[1].Backend.txLog.Find(sendRes.KbTxID.String()) 1356 claim := payment.Summary.Relay().Claim 1357 if claim != nil { 1358 require.Equal(t, stellar1.TransactionStatus_SUCCESS, claim.TxStatus) 1359 require.Equal(t, stellar1.RelayDirection_CLAIM, claim.Dir) 1360 found = true 1361 break 1362 } 1363 } 1364 1365 if !found { 1366 t.Fatal("Timed out waiting for auto claim") 1367 } 1368 1369 tcs[0].Backend.AssertBalance(getPrimaryAccountID(tcs[0]), "96.9999900") 1370 tcs[1].Backend.AssertBalance(getPrimaryAccountID(tcs[1]), "2.9999800") 1371 } 1372 1373 func TestShutdown(t *testing.T) { 1374 tcs, cleanup := setupNTests(t, 1) 1375 defer cleanup() 1376 1377 accountID := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID()) 1378 1379 tcs[0].Srv.walletState.SeqnoLock() 1380 _, err := tcs[0].Srv.walletState.AccountSeqnoAndBump(context.Background(), accountID) 1381 tcs[0].Srv.walletState.SeqnoUnlock() 1382 if err != nil { 1383 t.Fatal(err) 1384 } 1385 1386 balances, err := tcs[0].Srv.BalancesLocal(context.Background(), accountID) 1387 if err != nil { 1388 t.Fatal(err) 1389 } 1390 1391 require.Len(t, balances, 1) 1392 require.Equal(t, balances[0].Asset.Type, "native") 1393 require.Equal(t, balances[0].Amount, "10000") 1394 1395 var wg sync.WaitGroup 1396 for i := 0; i < 10; i++ { 1397 wg.Add(1) 1398 go func(index int) { 1399 time.Sleep(time.Duration(index*10) * time.Millisecond) 1400 _, err := tcs[0].Srv.BalancesLocal(context.Background(), accountID) 1401 if err != nil { 1402 t.Error(err) 1403 } 1404 wg.Done() 1405 }(i) 1406 } 1407 1408 wg.Add(1) 1409 go func() { 1410 if err := tcs[0].Srv.walletState.Shutdown(tcs[0].MetaContext()); err != nil { 1411 t.Logf("shutdown error: %s", err) 1412 } 1413 wg.Done() 1414 }() 1415 1416 wg.Wait() 1417 } 1418 1419 func TestSignTransactionXdr(t *testing.T) { 1420 tcs, cleanup := setupNTests(t, 2) 1421 defer cleanup() 1422 1423 acceptDisclaimer(tcs[0]) 1424 accounts := tcs[0].Backend.ImportAccountsForUser(tcs[0]) 1425 acceptDisclaimer(tcs[1]) 1426 accounts2 := tcs[1].Backend.ImportAccountsForUser(tcs[1]) 1427 1428 sp, unlock := stellar.NewSeqnoProvider(tcs[0].MetaContext(), tcs[0].Srv.walletState) 1429 defer unlock() 1430 tx := stellarnet.NewBaseTx(stellarnet.AddressStr(accounts[0].accountID), sp, 100) 1431 tx.AddPaymentOp(stellarnet.AddressStr(accounts[0].accountID), "100") 1432 tx.AddMemoText("test") 1433 signRes, err := tx.Sign(stellarnet.SeedStr(accounts[0].secretKey)) 1434 require.NoError(t, err) 1435 1436 // Unpack transaction, strip signatures. We will try to resign 1437 // that transaction using SignTransactionXdrLocal and are hoping 1438 // to see the same result. 1439 unpacked, _, err := unpackTx(signRes.Signed) 1440 require.NoError(t, err) 1441 1442 var buf bytes.Buffer 1443 unpacked.Signatures = []xdr.DecoratedSignature{} 1444 _, err = xdr.Marshal(&buf, unpacked) 1445 require.NoError(t, err) 1446 unsigned := base64.StdEncoding.EncodeToString(buf.Bytes()) 1447 1448 // Happy path. 1449 res, err := tcs[0].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1450 EnvelopeXdr: unsigned, 1451 }) 1452 require.NoError(t, err) 1453 require.Equal(t, accounts[0].accountID, res.AccountID) 1454 // Signed result should match stellarnet result from before we stipped signatures. 1455 require.Equal(t, signRes.Signed, res.SingedTx) 1456 // Wasn't trying to submit, so both SubmitTxID and SubmitErr should be nil. 1457 require.Nil(t, res.SubmitErr) 1458 require.Nil(t, res.SubmitTxID) 1459 1460 // Submitting - we are not mocking whole submit process, but we can test if 1461 // the flag does anything. 1462 res, err = tcs[0].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1463 EnvelopeXdr: unsigned, 1464 Submit: true, 1465 }) 1466 require.NoError(t, err) 1467 require.Equal(t, accounts[0].accountID, res.AccountID) 1468 require.Equal(t, signRes.Signed, res.SingedTx) 1469 require.NotNil(t, res.SubmitErr) 1470 require.Contains(t, *res.SubmitErr, "post any transaction is not mocked") 1471 require.Nil(t, res.SubmitTxID) 1472 1473 var emptyResult stellar1.SignXdrResult 1474 1475 // Try with invalid envelope. 1476 envelope := "AAAAAJHtRFG9qD+hsTVmp0/1ZPWkxQj/F4217ia3nVY+dPHjAAAAZAAAAAACd" 1477 res, err = tcs[0].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1478 EnvelopeXdr: envelope, 1479 }) 1480 require.Error(t, err) 1481 require.Equal(t, emptyResult, res) 1482 1483 // Try with account id user doesn't own. 1484 invalidAccID, _ := randomStellarKeypair() 1485 res, err = tcs[0].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1486 EnvelopeXdr: unsigned, 1487 AccountID: &invalidAccID, 1488 }) 1489 require.Equal(t, emptyResult, res) 1490 require.Error(t, err) 1491 1492 // Same, but the SourceAccount is one that user doesn't own. 1493 res, err = tcs[1].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1494 EnvelopeXdr: unsigned, 1495 }) 1496 require.Equal(t, emptyResult, res) 1497 require.Error(t, err) 1498 1499 // Can, however, sign with non-default account ID that user owns. 1500 accID2 := accounts2[0].accountID 1501 res, err = tcs[1].Srv.SignTransactionXdrLocal(context.Background(), stellar1.SignTransactionXdrLocalArg{ 1502 EnvelopeXdr: unsigned, 1503 AccountID: &accID2, 1504 }) 1505 require.NoError(t, err) 1506 require.Equal(t, res.AccountID, accID2) 1507 require.NotEmpty(t, res.SingedTx) 1508 } 1509 1510 func makeActiveDeviceOlder(t *testing.T, g *libkb.GlobalContext) { 1511 deviceID := g.ActiveDevice.DeviceID() 1512 apiArg := libkb.APIArg{ 1513 Endpoint: "test/agedevice", 1514 SessionType: libkb.APISessionTypeREQUIRED, 1515 Args: libkb.HTTPArgs{ 1516 "device_id": libkb.S{Val: deviceID.String()}, 1517 }, 1518 } 1519 mctx := libkb.NewMetaContextBackground(g) 1520 _, err := g.API.Post(mctx, apiArg) 1521 require.NoError(t, err) 1522 } 1523 1524 type acctBundleChecker struct { 1525 accountID stellar1.AccountID 1526 secretKey stellar1.SecretKey 1527 } 1528 1529 func newAcctBundleChecker(a stellar1.AccountID, s stellar1.SecretKey) *acctBundleChecker { 1530 return &acctBundleChecker{ 1531 accountID: a, 1532 secretKey: s, 1533 } 1534 } 1535 1536 func (a *acctBundleChecker) assertBundle(t *testing.T, b *stellar1.Bundle, revisionParent, revisionAccount stellar1.BundleRevision, mode stellar1.AccountMode) { 1537 require.NotNil(t, b) 1538 require.Equal(t, revisionParent, b.Revision) 1539 require.Len(t, b.AccountBundles, 1) 1540 secret, err := bundle.AccountWithSecret(b, a.accountID) 1541 require.NoError(t, err) 1542 require.NotNil(t, secret) 1543 require.Equal(t, mode, secret.Mode) 1544 require.Equal(t, a.accountID, secret.AccountID) 1545 require.Len(t, secret.Signers, 1) 1546 require.Equal(t, a.secretKey, secret.Signers[0]) 1547 require.Equal(t, revisionAccount, secret.Revision) 1548 require.NotEmpty(t, b.Prev) 1549 require.NotEmpty(t, b.OwnHash) 1550 } 1551 1552 type TestContext struct { 1553 libkb.TestContext 1554 Fu *kbtest.FakeUser 1555 Srv *Server 1556 Backend *BackendMock 1557 } 1558 1559 func (tc *TestContext) MetaContext() libkb.MetaContext { 1560 return libkb.NewMetaContextForTest(tc.TestContext) 1561 } 1562 1563 // Create n TestContexts with logged in users 1564 // Returns (FakeUsers, TestContexts, CleanupFunction) 1565 func setupNTests(t *testing.T, n int) ([]*TestContext, func()) { 1566 var settings []usetting 1567 for i := 0; i < n; i++ { 1568 settings = append(settings, usettingFull) 1569 } 1570 return setupTestsWithSettings(t, settings) 1571 } 1572 1573 // setupDesktopTest signs up the user on a desktop device. 1574 func setupDesktopTest(t *testing.T) (*TestContext, func()) { 1575 settings := []usetting{usettingFull} 1576 tcs, f := setupTestsWithSettings(t, settings) 1577 return tcs[0], f 1578 } 1579 1580 // setupMobileTest signs up the user on a mobile device. 1581 func setupMobileTest(t *testing.T) (*TestContext, func()) { 1582 settings := []usetting{usettingMobile} 1583 tcs, f := setupTestsWithSettings(t, settings) 1584 return tcs[0], f 1585 } 1586 1587 type usetting string 1588 1589 const ( 1590 usettingFull usetting = "full" 1591 usettingPukless usetting = "pukless" 1592 usettingMobile usetting = "mobile" 1593 ) 1594 1595 func setupTestsWithSettings(t *testing.T, settings []usetting) ([]*TestContext, func()) { 1596 require.True(t, len(settings) > 0, "must create at least 1 tc") 1597 var tcs []*TestContext 1598 bem := NewBackendMock(t) 1599 for i, setting := range settings { 1600 tc := SetupTest(t, "wall", 1) 1601 switch setting { 1602 case usettingFull: 1603 case usettingMobile: 1604 case usettingPukless: 1605 tc.Tp.DisableUpgradePerUserKey = true 1606 } 1607 var fu *kbtest.FakeUser 1608 var err error 1609 if setting == usettingMobile { 1610 fu, err = kbtest.CreateAndSignupFakeUserMobile("wall", tc.G) 1611 } else { 1612 fu, err = kbtest.CreateAndSignupFakeUser("wall", tc.G) 1613 } 1614 require.NoError(t, err) 1615 tc2 := &TestContext{ 1616 TestContext: tc, 1617 Fu: fu, 1618 // All TCs in a test share the same backend. 1619 Backend: bem, 1620 } 1621 t.Logf("setup user %v %v", i, tc2.Fu.Username) 1622 rcm := NewRemoteClientMock(tc2, bem) 1623 ws := stellar.NewWalletState(tc.G, rcm) 1624 tc2.Srv = New(tc.G, newTestUISource(), ws) 1625 stellar.ServiceInit(tc.G, ws, nil) 1626 tcs = append(tcs, tc2) 1627 } 1628 cleanup := func() { 1629 for _, tc := range tcs { 1630 tc.Cleanup() 1631 } 1632 } 1633 for i, tc := range tcs { 1634 t.Logf("U%d: %v %v", i, tc.Fu.Username, tc.Fu.GetUserVersion()) 1635 } 1636 return tcs, cleanup 1637 } 1638 1639 func provisionNewDeviceForTest(t *testing.T, tc *TestContext, newDeviceType keybase1.DeviceTypeV2) (outTc *TestContext, cleanup func()) { 1640 bem := tc.Backend 1641 tc2 := SetupTest(t, "wall_p", 1) 1642 kbtest.ProvisionNewDeviceKex(&tc.TestContext, &tc2, tc.Fu, newDeviceType) 1643 outTc = &TestContext{ 1644 TestContext: tc2, 1645 Fu: tc.Fu, 1646 } 1647 rcm := NewRemoteClientMock(outTc, bem) 1648 ws := stellar.NewWalletState(tc2.G, rcm) 1649 outTc.Srv = New(tc2.G, newTestUISource(), ws) 1650 stellar.ServiceInit(tc2.G, ws, nil) 1651 cleanup = func() { 1652 tc2.Cleanup() 1653 } 1654 return outTc, cleanup 1655 } 1656 1657 func randomStellarKeypair() (pub stellar1.AccountID, sec stellar1.SecretKey) { 1658 full, err := keypair.Random() 1659 if err != nil { 1660 panic(err) 1661 } 1662 return stellar1.AccountID(full.Address()), stellar1.SecretKey(full.Seed()) 1663 } 1664 1665 func getPrimaryAccountID(tc *TestContext) stellar1.AccountID { 1666 accounts, err := tc.Srv.GetWalletAccountsLocal(context.Background(), 0) 1667 require.NoError(tc.T, err) 1668 for _, a := range accounts { 1669 if a.IsDefault { 1670 return a.AccountID 1671 } 1672 } 1673 require.Fail(tc.T, "no primary account") 1674 return "" 1675 } 1676 1677 type nullSecretUI struct{} 1678 1679 func (nullSecretUI) GetPassphrase(keybase1.GUIEntryArg, *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) { 1680 return keybase1.GetPassphraseRes{}, fmt.Errorf("nullSecretUI.GetPassphrase") 1681 } 1682 1683 type testUISource struct { 1684 secretUI libkb.SecretUI 1685 identifyUI libkb.IdentifyUI 1686 stellarUI stellar1.UiInterface 1687 } 1688 1689 func newTestUISource() *testUISource { 1690 return &testUISource{ 1691 secretUI: nullSecretUI{}, 1692 identifyUI: &kbtest.FakeIdentifyUI{}, 1693 stellarUI: &mockStellarUI{}, 1694 } 1695 } 1696 1697 func (t *testUISource) SecretUI(g *libkb.GlobalContext, sessionID int) libkb.SecretUI { 1698 return t.secretUI 1699 } 1700 1701 func (t *testUISource) IdentifyUI(g *libkb.GlobalContext, sessionID int) libkb.IdentifyUI { 1702 return t.identifyUI 1703 } 1704 1705 func (t *testUISource) StellarUI() stellar1.UiInterface { 1706 return t.stellarUI 1707 } 1708 1709 type mockStellarUI struct { 1710 PaymentReviewedHandler func(context.Context, stellar1.PaymentReviewedArg) error 1711 } 1712 1713 func (ui *mockStellarUI) PaymentReviewed(ctx context.Context, arg stellar1.PaymentReviewedArg) error { 1714 if ui.PaymentReviewedHandler != nil { 1715 return ui.PaymentReviewedHandler(ctx, arg) 1716 } 1717 return fmt.Errorf("mockStellarUI.UiPaymentReview called with no handler") 1718 } 1719 1720 // fetchWholeBundleForTesting gets the secretless bundle and loops through the accountIDs 1721 // to get the signers for each of them and build a single, full bundle with all 1722 // of the information. This will error from any device that does not have access 1723 // to all of the accounts (e.g. a desktop after mobile-only). 1724 func fetchWholeBundleForTesting(mctx libkb.MetaContext) (bundle *stellar1.Bundle, err error) { 1725 if mctx.G().Env.GetRunMode() == libkb.ProductionRunMode { 1726 return nil, errors.New("fetchWholeBundleForTesting is only for test and dev") 1727 } 1728 bundle, err = remote.FetchSecretlessBundle(mctx) 1729 if err != nil { 1730 return nil, err 1731 } 1732 newAccBundles := make(map[stellar1.AccountID]stellar1.AccountBundle) 1733 for _, acct := range bundle.Accounts { 1734 singleBundle, err := remote.FetchAccountBundle(mctx, acct.AccountID) 1735 if err != nil { 1736 return nil, err 1737 } 1738 accBundle := singleBundle.AccountBundles[acct.AccountID] 1739 newAccBundles[acct.AccountID] = accBundle 1740 } 1741 bundle.AccountBundles = newAccBundles 1742 return bundle, nil 1743 }