github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/global.go (about) 1 package stellar 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/keybase/client/go/badges" 13 "github.com/keybase/client/go/gregor" 14 "github.com/keybase/client/go/libkb" 15 "github.com/keybase/client/go/protocol/chat1" 16 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/keybase/client/go/protocol/stellar1" 18 "github.com/keybase/client/go/slotctx" 19 "github.com/keybase/client/go/stellar/remote" 20 "github.com/keybase/stellarnet" 21 "github.com/stellar/go/build" 22 "github.com/stellar/go/clients/federation" 23 24 // nolint 25 "github.com/stellar/go/clients/horizon" 26 ) 27 28 func ServiceInit(g *libkb.GlobalContext, walletState *WalletState, badger *badges.Badger) { 29 if g.Env.GetRunMode() != libkb.ProductionRunMode { 30 stellarnet.SetClientAndNetwork(horizon.DefaultTestNetClient, build.TestNetwork) 31 } 32 s := NewStellar(g, walletState, badger) 33 g.SetStellar(s) 34 g.AddLogoutHook(s, "stellar") 35 g.AddDbNukeHook(s, "stellar") 36 g.PushShutdownHook(s.Shutdown) 37 } 38 39 type Stellar struct { 40 libkb.Contextified 41 remoter remote.Remoter 42 walletState *WalletState 43 44 serverConfLock sync.Mutex 45 cachedServerConf stellar1.StellarServerDefinitions 46 47 autoClaimRunnerLock sync.Mutex 48 autoClaimRunner *AutoClaimRunner // often nil 49 50 hasWalletCacheLock sync.Mutex 51 hasWalletCache map[keybase1.UserVersion]bool 52 53 federationClient federation.ClientInterface 54 55 bidLock sync.Mutex 56 bids []*buildPaymentEntry 57 58 bpcLock sync.Mutex 59 bpc BuildPaymentCache 60 61 disclaimerLock sync.Mutex 62 disclaimerAccepted *keybase1.UserVersion // A UV who has accepted the disclaimer. 63 64 accountsLock sync.Mutex 65 accounts *AccountsCache 66 67 // Slot for build payments that do not use BuildPaymentID. 68 buildPaymentSlot *slotctx.PrioritySlot 69 70 reconnectSlot *slotctx.Slot 71 72 badger *badges.Badger 73 } 74 75 var _ libkb.Stellar = (*Stellar)(nil) 76 77 func NewStellar(g *libkb.GlobalContext, walletState *WalletState, badger *badges.Badger) *Stellar { 78 return &Stellar{ 79 Contextified: libkb.NewContextified(g), 80 remoter: walletState, 81 walletState: walletState, 82 hasWalletCache: make(map[keybase1.UserVersion]bool), 83 federationClient: getFederationClient(g), 84 buildPaymentSlot: slotctx.NewPriority(), 85 reconnectSlot: slotctx.New(), 86 badger: badger, 87 } 88 } 89 90 type AccountsCache struct { 91 Stored time.Time 92 Revision stellar1.BundleRevision 93 Accounts []stellar1.BundleEntry 94 } 95 96 func (s *Stellar) CreateWalletSoft(ctx context.Context) { 97 CreateWalletSoft(libkb.NewMetaContext(ctx, s.G())) 98 } 99 100 func (s *Stellar) Upkeep(ctx context.Context) error { 101 return Upkeep(libkb.NewMetaContext(ctx, s.G())) 102 } 103 104 func (s *Stellar) OnLogout(mctx libkb.MetaContext) error { 105 s.Clear(mctx) 106 return nil 107 } 108 109 func (s *Stellar) OnDbNuke(mctx libkb.MetaContext) error { 110 s.Clear(mctx) 111 return nil 112 } 113 114 func (s *Stellar) Shutdown(mctx libkb.MetaContext) error { 115 s.Clear(mctx) 116 return nil 117 } 118 119 func (s *Stellar) Clear(mctx libkb.MetaContext) { 120 s.shutdownAutoClaimRunner() 121 s.deleteBpc() 122 s.deleteDisclaimer() 123 s.clearBids() 124 s.clearAccounts() 125 } 126 127 func (s *Stellar) shutdownAutoClaimRunner() { 128 s.autoClaimRunnerLock.Lock() 129 defer s.autoClaimRunnerLock.Unlock() 130 // Shutdown and delete the ACR. 131 if acr := s.autoClaimRunner; acr != nil { 132 acr.Shutdown(libkb.NewMetaContextBackground(s.G())) 133 } 134 s.autoClaimRunner = nil 135 } 136 137 func (s *Stellar) deleteBpc() { 138 s.bpcLock.Lock() 139 defer s.bpcLock.Unlock() 140 s.bpc = nil 141 } 142 143 func (s *Stellar) deleteDisclaimer() { 144 s.disclaimerLock.Lock() 145 defer s.disclaimerLock.Unlock() 146 s.disclaimerAccepted = nil 147 } 148 149 func (s *Stellar) clearBids() { 150 s.buildPaymentSlot.Stop() 151 s.bidLock.Lock() 152 defer s.bidLock.Unlock() 153 for _, bid := range s.bids { 154 bid.Slot.Stop() 155 } 156 s.bids = nil 157 } 158 159 func (s *Stellar) clearAccounts() { 160 s.accountsLock.Lock() 161 defer s.accountsLock.Unlock() 162 s.accounts = nil 163 } 164 165 func (s *Stellar) GetServerDefinitions(ctx context.Context) (ret stellar1.StellarServerDefinitions, err error) { 166 s.serverConfLock.Lock() 167 defer s.serverConfLock.Unlock() 168 if s.cachedServerConf.Revision == 0 { 169 // check if still 0, we might have waited for other thread 170 // to finish fetching. 171 if ret, err = remote.FetchServerConfig(ctx, s.G()); err != nil { 172 return ret, err 173 } 174 175 s.cachedServerConf = ret 176 } 177 178 return s.cachedServerConf, nil 179 } 180 181 func (s *Stellar) KnownCurrencyCodeInstant(ctx context.Context, code string) (known, ok bool) { 182 code = strings.ToUpper(code) 183 if code == "XLM" { 184 return true, true 185 } 186 s.serverConfLock.Lock() 187 defer s.serverConfLock.Unlock() 188 if s.cachedServerConf.Revision == 0 { 189 return false, false 190 } 191 _, known = s.cachedServerConf.Currencies[stellar1.OutsideCurrencyCode(code)] 192 return known, true 193 } 194 195 // `trigger` is optional, and is of the gregor message that caused the kick. 196 func (s *Stellar) KickAutoClaimRunner(mctx libkb.MetaContext, trigger gregor.MsgID) { 197 // Create the ACR if one does not exist. 198 mctx.Debug("KickAutoClaimRunner(trigger:%v)", trigger) 199 s.autoClaimRunnerLock.Lock() 200 defer s.autoClaimRunnerLock.Unlock() 201 if s.autoClaimRunner == nil { 202 s.autoClaimRunner = NewAutoClaimRunner(s.walletState) 203 } 204 s.autoClaimRunner.Kick(mctx, trigger) 205 } 206 207 func (s *Stellar) InformHasWallet(ctx context.Context, uv keybase1.UserVersion) { 208 if uv.Uid.IsNil() { 209 s.G().Log.CErrorf(ctx, "Stellar.InformHasWallet called with nil UID") 210 return 211 } 212 if uv.EldestSeqno <= 0 { 213 // It is not possible for such a user to have a wallet. 214 s.G().Log.CErrorf(ctx, "Stellar.InformHasWallet called with %v EldestSeqno", uv.EldestSeqno) 215 return 216 } 217 s.hasWalletCacheLock.Lock() 218 defer s.hasWalletCacheLock.Unlock() 219 s.G().Log.CDebugf(ctx, "Stellar.InformHasWallet(%v)", uv) 220 s.hasWalletCache[uv] = true 221 } 222 223 func (s *Stellar) CachedHasWallet(ctx context.Context, uv keybase1.UserVersion) bool { 224 s.hasWalletCacheLock.Lock() 225 defer s.hasWalletCacheLock.Unlock() 226 has := s.hasWalletCache[uv] 227 s.G().Log.CDebugf(ctx, "Stellar.CachedHasWallet(%v) -> %v", uv, has) 228 return has 229 } 230 231 func (s *Stellar) SetFederationClientForTest(cli federation.ClientInterface) { 232 s.federationClient = cli 233 } 234 235 func (s *Stellar) getBuildPaymentCache() BuildPaymentCache { 236 s.bpcLock.Lock() 237 defer s.bpcLock.Unlock() 238 if s.bpc == nil { 239 s.bpc = newBuildPaymentCache(s.remoter) 240 } 241 return s.bpc 242 } 243 244 // UpdateUnreadCount will take the unread count for an account id and 245 // update the badger. 246 func (s *Stellar) UpdateUnreadCount(ctx context.Context, accountID stellar1.AccountID, unread int) error { 247 if s.badger == nil { 248 s.G().Log.CDebugf(ctx, "Stellar Global has no badger") 249 return nil 250 } 251 252 s.badger.SetWalletAccountUnreadCount(ctx, accountID, unread) 253 return nil 254 } 255 256 // SendMiniChatPayments sends multiple payments from one sender to multiple 257 // different recipients as fast as it can. These come from chat messages 258 // like "+1XLM@alice +2XLM@charlie". 259 func (s *Stellar) SendMiniChatPayments(mctx libkb.MetaContext, convID chat1.ConversationID, payments []libkb.MiniChatPayment) ([]libkb.MiniChatPaymentResult, error) { 260 return SendMiniChatPayments(mctx, s.walletState, convID, payments) 261 } 262 263 // SpecMiniChatPayments creates a summary of the amounts that a list of MiniChatPayments will 264 // result in. 265 func (s *Stellar) SpecMiniChatPayments(mctx libkb.MetaContext, payments []libkb.MiniChatPayment) (*libkb.MiniChatPaymentSummary, error) { 266 return SpecMiniChatPayments(mctx, s.walletState, payments) 267 } 268 269 // HandleOobm will handle any out of band gregor messages for stellar. 270 func (s *Stellar) HandleOobm(ctx context.Context, obm gregor.OutOfBandMessage) (bool, error) { 271 if obm.System() == nil { 272 return false, errors.New("nil system in out of band message") 273 } 274 275 // make a new background context for the handlers 276 mctx := libkb.NewMetaContextBackground(s.G()).WithLogTag("WAOOBM") 277 278 // all of these handlers should be in goroutines so they don't block the 279 // oobm handler thread. 280 281 switch obm.System().String() { 282 case "internal.reconnect": 283 go s.handleReconnect(mctx) 284 // returning false, nil here so that others can handle this one too 285 return false, nil 286 case stellar1.PushPaymentStatus: 287 go s.handlePaymentStatus(mctx, obm) 288 return true, nil 289 case stellar1.PushPaymentNotification: 290 go s.handlePaymentNotification(mctx, obm) 291 return true, nil 292 case stellar1.PushRequestStatus: 293 go s.handleRequestStatus(mctx, obm) 294 return true, nil 295 } 296 297 return false, nil 298 } 299 300 func (s *Stellar) handleReconnect(mctx libkb.MetaContext) { 301 defer mctx.Trace("Stellar.handleReconnect", nil)() 302 mctx.Debug("stellar received reconnect msg, doing delayed wallet refresh") 303 mctx = mctx.WithCtx(s.reconnectSlot.Use(mctx.Ctx())) 304 mctx, cancel := cancelOnMobileBackground(mctx) 305 defer cancel() 306 if err := libkb.Sleep(mctx.Ctx(), libkb.RandomJitter(4*time.Second)); err != nil { 307 mctx.Debug("Stellar.handleReconnect canceled") 308 return 309 } 310 if libkb.IsMobilePlatform() { 311 // sleep some more on mobile 312 if err := libkb.Sleep(mctx.Ctx(), libkb.RandomJitter(4*time.Second)); err != nil { 313 mctx.Debug("Stellar.handleReconnect canceled") 314 return 315 } 316 } 317 mctx.Debug("Stellar.handleReconnect delay complete, refreshing wallet state") 318 319 if err := s.walletState.RefreshAll(mctx, "reconnect"); err != nil { 320 mctx.Debug("Stellar.handleReconnect RefreshAll error: %s", err) 321 } 322 } 323 324 func (s *Stellar) handlePaymentStatus(mctx libkb.MetaContext, obm gregor.OutOfBandMessage) { 325 var err error 326 defer mctx.Trace("Stellar.handlePaymentStatus", &err)() 327 328 var msg stellar1.PaymentStatusMsg 329 if err = json.Unmarshal(obm.Body().Bytes(), &msg); err != nil { 330 mctx.Debug("error unmarshaling obm PaymentStatusMsg: %s", err) 331 return 332 } 333 334 paymentID := stellar1.NewPaymentID(msg.TxID) 335 notifiedAccountID, err := s.refreshPaymentFromNotification(mctx, msg.AccountID, paymentID) 336 if err != nil { 337 mctx.Debug("refreshPaymentFromNotification error: %s", err) 338 return 339 } 340 341 s.G().NotifyRouter.HandleWalletPaymentStatusNotification(mctx.Ctx(), notifiedAccountID, paymentID) 342 } 343 344 func (s *Stellar) handlePaymentNotification(mctx libkb.MetaContext, obm gregor.OutOfBandMessage) { 345 var err error 346 defer mctx.Trace("Stellar.handlePaymentNotification", &err)() 347 var msg stellar1.PaymentNotificationMsg 348 if err = json.Unmarshal(obm.Body().Bytes(), &msg); err != nil { 349 mctx.Debug("error unmarshaling obm PaymentNotificationMsg: %s", err) 350 return 351 } 352 353 notifiedAccountID, err := s.refreshPaymentFromNotification(mctx, msg.AccountID, msg.PaymentID) 354 if err != nil { 355 mctx.Debug("refreshPaymentFromNotification error: %s", err) 356 return 357 } 358 s.G().NotifyRouter.HandleWalletPaymentNotification(mctx.Ctx(), notifiedAccountID, msg.PaymentID) 359 } 360 361 func (s *Stellar) findAccountFromPayment(mctx libkb.MetaContext, accountID stellar1.AccountID, payment *stellar1.PaymentLocal) (stellar1.AccountID, error) { 362 var emptyAccountID stellar1.AccountID 363 364 // double-check that the accountID from the notification matches one of the accountIDs 365 // in the payment 366 if accountID == payment.FromAccountID || (payment.ToAccountID != nil && accountID == *payment.ToAccountID) { 367 ok, _, err := getGlobal(mctx.G()).OwnAccountCached(mctx, accountID) 368 if err != nil { 369 return emptyAccountID, err 370 } 371 if ok { 372 // the user owns the accountID in the notification 373 return accountID, nil 374 } 375 } 376 377 // check if the user owns either from or to accountID: 378 379 ok, _, err := getGlobal(mctx.G()).OwnAccountCached(mctx, payment.FromAccountID) 380 if err != nil { 381 return emptyAccountID, err 382 } 383 if ok { 384 // the running user is the sender of this payment 385 return payment.FromAccountID, nil 386 } 387 if payment.ToAccountID == nil { 388 return emptyAccountID, ErrAccountNotFound 389 } 390 ok, _, err = getGlobal(mctx.G()).OwnAccountCached(mctx, *payment.ToAccountID) 391 if err != nil { 392 return emptyAccountID, err 393 } 394 if ok { 395 // the running user is the recipient of this payment 396 return *payment.ToAccountID, nil 397 } 398 return emptyAccountID, ErrAccountNotFound 399 } 400 401 func (s *Stellar) refreshPaymentFromNotification(mctx libkb.MetaContext, accountID stellar1.AccountID, paymentID stellar1.PaymentID) (notifiedAccountID stellar1.AccountID, err error) { 402 var emptyAccountID stellar1.AccountID 403 404 // load the payment 405 loader := DefaultLoader(s.G()) 406 loader.LoadPaymentSync(mctx.Ctx(), paymentID) 407 payment, ok := loader.GetPaymentLocal(mctx.Ctx(), paymentID) 408 if !ok { 409 return emptyAccountID, fmt.Errorf("couldn't find the payment immediately after loading it %v", paymentID) 410 } 411 412 // find the accountID for the running user in the payment (could be sender, recipient, neither) 413 notifiedAccountID, err = s.findAccountFromPayment(mctx, accountID, payment) 414 if err != nil { 415 return emptyAccountID, err 416 } 417 // refresh the wallet state for this accountID 418 if err := s.walletState.Refresh(mctx, notifiedAccountID, "notification received"); err != nil { 419 return notifiedAccountID, err 420 } 421 return notifiedAccountID, nil 422 } 423 424 func (s *Stellar) handleRequestStatus(mctx libkb.MetaContext, obm gregor.OutOfBandMessage) { 425 var err error 426 defer mctx.Trace("Stellar.handleRequestStatus", &err)() 427 var msg stellar1.RequestStatusMsg 428 if err = json.Unmarshal(obm.Body().Bytes(), &msg); err != nil { 429 mctx.Debug("error unmarshaling obm RequestStatusMsg: %s", err) 430 return 431 } 432 433 mctx.G().NotifyRouter.HandleWalletRequestStatusNotification(mctx.Ctx(), msg.ReqID) 434 DefaultLoader(mctx.G()).UpdateRequest(mctx.Ctx(), msg.ReqID) 435 } 436 437 type hasAcceptedDisclaimerDBEntry struct { 438 Version int // 1 439 Accepted bool 440 } 441 442 // For a UV, accepted starts out false and transitions to true. It never becomes false again. 443 // A cached true is returned, but a false always hits the server. 444 func (s *Stellar) hasAcceptedDisclaimer(ctx context.Context) (bool, error) { 445 log := func(format string, args ...interface{}) { 446 s.G().Log.CDebugf(ctx, "Stellar.hasAcceptedDisclaimer "+format, args...) 447 } 448 uv, err := s.G().GetMeUV(ctx) 449 if err != nil { 450 return false, err 451 } 452 s.disclaimerLock.Lock() 453 defer s.disclaimerLock.Unlock() 454 // Check memory 455 memAccepted := s.disclaimerAccepted != nil && s.disclaimerAccepted.Eq(uv) 456 log("mem -> %v", memAccepted) 457 if memAccepted { 458 return true, nil 459 } 460 // Check disk 461 dbKey := libkb.DbKey{ 462 Typ: libkb.DBStellarDisclaimer, 463 Key: uv.String(), 464 } 465 var dbEntry hasAcceptedDisclaimerDBEntry 466 found, err := s.G().LocalDb.GetInto(&dbEntry, dbKey) 467 log("disk -> [found:%v err:(%v) v:%v accepted:%v]", found, err, dbEntry.Version, dbEntry.Accepted) 468 if err == nil && found && dbEntry.Version == 1 && dbEntry.Accepted { 469 err = s.informAcceptedDisclaimerLocked(ctx) 470 if err != nil { 471 log("store -> err:(%v)", err) 472 } 473 return true, nil 474 } 475 // Check remote 476 accepted, err := remote.GetAcceptedDisclaimer(ctx, s.G()) 477 log("remote -> [err:(%v) accepted:%v]", err, accepted) 478 if err != nil { 479 return false, err 480 } 481 if accepted { 482 err = s.informAcceptedDisclaimerLocked(ctx) 483 if err != nil { 484 log("store -> err:(%v)", err) 485 } 486 } 487 return accepted, nil 488 } 489 490 func (s *Stellar) informAcceptedDisclaimer(ctx context.Context) { 491 s.disclaimerLock.Lock() 492 defer s.disclaimerLock.Unlock() 493 _ = s.informAcceptedDisclaimerLocked(ctx) 494 } 495 496 func (s *Stellar) informAcceptedDisclaimerLocked(ctx context.Context) (err error) { 497 defer s.G().CTrace(ctx, "Stellar.informAcceptedDisclaimer", &err)() 498 uv, err := s.G().GetMeUV(ctx) 499 if err != nil { 500 return err 501 } 502 // Store memory 503 s.disclaimerAccepted = &uv 504 // Store disk 505 return s.G().LocalDb.PutObj(libkb.DbKey{ 506 Typ: libkb.DBStellarDisclaimer, 507 Key: uv.String(), 508 }, nil, hasAcceptedDisclaimerDBEntry{ 509 Version: 1, 510 Accepted: true, 511 }) 512 } 513 514 func (s *Stellar) startBuildPayment(mctx libkb.MetaContext) (bid stellar1.BuildPaymentID, err error) { 515 defer func() { 516 x := bid.String() 517 if err != nil { 518 x = fmt.Sprintf("ERR(%v)", err.Error()) 519 } 520 mctx.Debug("Stellar.startBuildPayment -> %v", x) 521 }() 522 bid, err = RandomBuildPaymentID() 523 if err != nil { 524 return "", err 525 } 526 s.bidLock.Lock() 527 defer s.bidLock.Unlock() 528 s.bids = append(s.bids, newBuildPaymentEntry(bid)) 529 const maxConcurrentBuilds = 20 530 if len(s.bids) > maxConcurrentBuilds { 531 // Too many open payment builds. Drop the oldest ones at the beginning of the list. 532 // Leave the newest ones at the end of the list. 533 for i := 0; i < len(s.bids)-maxConcurrentBuilds; i++ { 534 entry := s.bids[i] 535 entry.Slot.Shutdown() 536 } 537 s.bids = s.bids[len(s.bids)-maxConcurrentBuilds:] 538 } 539 return bid, nil 540 } 541 542 // stopBuildPayment stops a bid forever. 543 func (s *Stellar) stopBuildPayment(mctx libkb.MetaContext, bid stellar1.BuildPaymentID) { 544 mctx.Debug("Stellar.stopBuildPayment(%v)", bid) 545 if bid.IsNil() { 546 s.buildPaymentSlot.Stop() 547 return 548 } 549 s.bidLock.Lock() 550 defer s.bidLock.Unlock() 551 for _, entry := range s.bids { 552 if entry.Bid.Eq(bid) { 553 if entry.Stopped { 554 mctx.Debug("payment already stopped") 555 return 556 } 557 entry.Slot.Shutdown() 558 entry.Stopped = true 559 mctx.Debug("payment shutdown") 560 return 561 } 562 } 563 mctx.Debug("payment not found to stop") 564 } 565 566 // acquireBuildPayment takes ownership of a payment build. 567 // Returns a new `mctx` that the caller should switch to. Because it runs within the slot. 568 // When err=nil the caller owns `data` and must release it with `release` when finished. 569 // When err!=nil data is nil. 570 // `release` can be called even when err!=nil. 571 // `mctx` can also be used if err!=nil. 572 // Callers should `release` soon after their context is canceled. 573 func (s *Stellar) acquireBuildPayment(mctx1 libkb.MetaContext, bid stellar1.BuildPaymentID, sessionID int) ( 574 mctx libkb.MetaContext, data *buildPaymentData, release func(), err error) { 575 mctx = mctx1 576 mctx.Debug("Stellar.acquireBuildPayment(%v)", bid) 577 release = func() {} 578 s.bidLock.Lock() 579 defer s.bidLock.Unlock() 580 for _, entry := range s.bids { 581 entry := entry 582 if !entry.Bid.Eq(bid) { 583 continue 584 } 585 if entry.Stopped { 586 return mctx, nil, release, fmt.Errorf("This payment might have already been sent. Check your recent payments before trying again.") 587 } 588 mctx = mctx.WithCtx(entry.Slot.Use(mctx.Ctx(), sessionID)) 589 if err = mctx.Ctx().Err(); err != nil { 590 return mctx, nil, release, err 591 } 592 err = libkb.AcquireWithContextAndTimeout(mctx.Ctx(), &entry.DataLock, 5*time.Second) 593 if err != nil { 594 mctx.Debug("error while attempting to acquire data lock: %v", err) 595 return mctx, nil, release, err 596 } 597 release = libkb.Once(func() { 598 entry.DataLock.Unlock() 599 }) 600 return mctx, &entry.Data, release, nil 601 } 602 return mctx, nil, release, fmt.Errorf("payment build not found") 603 } 604 605 // finalizeBuildPayment stops a bid forever and returns its data. 606 func (s *Stellar) finalizeBuildPayment(mctx libkb.MetaContext, bid stellar1.BuildPaymentID) (res *buildPaymentData, err error) { 607 mctx.Debug("Stellar.finalizeBuildPayment(%v)", bid) 608 s.bidLock.Lock() 609 defer s.bidLock.Unlock() 610 for _, entry := range s.bids { 611 entry := entry 612 if !entry.Bid.Eq(bid) { 613 continue 614 } 615 if entry.Stopped { 616 return nil, fmt.Errorf("This payment might have already been sent. Check your recent payments before trying again.") 617 } 618 entry.Slot.Shutdown() 619 entry.Stopped = true 620 err = libkb.AcquireWithContextAndTimeout(mctx.Ctx(), &entry.DataLock, 5*time.Second) 621 if err != nil { 622 // This likely means something in the Slot is not yielding to its context or forgot to release the lock. 623 mctx.Debug("error while attempting to acquire data lock: %v", err) 624 return nil, err 625 } 626 res = &entry.Data 627 entry.DataLock.Unlock() 628 return res, nil 629 } 630 return nil, fmt.Errorf("payment build not found") 631 } 632 633 func (s *Stellar) WalletStateForTest() *WalletState { 634 return s.walletState 635 } 636 637 func (s *Stellar) RemovePendingTx(mctx libkb.MetaContext, accountID stellar1.AccountID, txID stellar1.TransactionID) error { 638 return s.walletState.RemovePendingTx(mctx.Ctx(), accountID, txID) 639 } 640 641 // BaseFee returns the server-suggested base fee per operation. 642 func (s *Stellar) BaseFee(mctx libkb.MetaContext) uint64 { 643 return s.walletState.BaseFee(mctx) 644 } 645 646 func (s *Stellar) InformBundle(mctx libkb.MetaContext, rev stellar1.BundleRevision, accounts []stellar1.BundleEntry) { 647 go func() { 648 err := libkb.AcquireWithContextAndTimeout(mctx.Ctx(), &s.accountsLock, 5*time.Second) 649 if err != nil { 650 mctx.Debug("InformBundle: error acquiring lock") 651 return 652 } 653 defer s.accountsLock.Unlock() 654 if s.accounts != nil && rev < s.accounts.Revision { 655 return 656 } 657 s.accounts = &AccountsCache{ 658 Stored: mctx.G().Clock().Now(), 659 Revision: rev, 660 Accounts: accounts, 661 } 662 }() 663 } 664 665 func (s *Stellar) InformDefaultCurrencyChange(mctx libkb.MetaContext) { 666 go func() { 667 s.getBuildPaymentCache().InformDefaultCurrencyChange(mctx) 668 }() 669 } 670 671 func (s *Stellar) OwnAccountCached(mctx libkb.MetaContext, accountID stellar1.AccountID) (own, isPrimary bool, err error) { 672 own, isPrimary, _, err = s.OwnAccountPlusNameCached(mctx, accountID) 673 return own, isPrimary, err 674 } 675 676 func (s *Stellar) OwnAccountPlusNameCached(mctx libkb.MetaContext, accountID stellar1.AccountID) (own, isPrimary bool, accountName string, err error) { 677 err = libkb.AcquireWithContextAndTimeout(mctx.Ctx(), &s.accountsLock, 5*time.Second) 678 if err != nil { 679 mctx.Debug("OwnAccountPlusNameCached: error acquiring lock") 680 return 681 } 682 if s.accounts != nil && mctx.G().Clock().Now().Sub(s.accounts.Stored.Round(0)) < 2*time.Minute { 683 for _, acc := range s.accounts.Accounts { 684 if acc.AccountID.Eq(accountID) { 685 s.accountsLock.Unlock() 686 return true, acc.IsPrimary, acc.Name, nil 687 } 688 } 689 s.accountsLock.Unlock() 690 return false, false, "", nil 691 } 692 s.accountsLock.Unlock() 693 return OwnAccountPlusName(mctx, accountID) 694 } 695 696 func (s *Stellar) Refresh(mctx libkb.MetaContext, reason string) { 697 if err := s.walletState.RefreshAll(mctx, reason); err != nil { 698 mctx.Debug("Stellar.Refresh(%s) ws.RefreshAll error: %s", reason, err) 699 } 700 } 701 702 // getFederationClient is a helper function used during 703 // initialization. 704 func getFederationClient(g *libkb.GlobalContext) federation.ClientInterface { 705 if g.Env.GetRunMode() != libkb.ProductionRunMode { 706 return federation.DefaultTestNetClient 707 } 708 return federation.DefaultPublicNetClient 709 } 710 711 // getGlobal gets the libkb.Stellar off of G and asserts it into a stellar.Stellar 712 func getGlobal(g *libkb.GlobalContext) *Stellar { 713 return g.GetStellar().(*Stellar) 714 }