github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }