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

     1  package stellar
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/protocol/stellar1"
    10  	"github.com/keybase/client/go/stellar/remote"
    11  	"github.com/keybase/client/go/stellar/stellarcommon"
    12  )
    13  
    14  // BuildPaymentCache has helpers for getting information quickly when building a payment.
    15  // Methods should err on the side of performance rather at the cost of serialization.
    16  // CORE-8119: But they don't yet.
    17  type BuildPaymentCache interface {
    18  	PrimaryAccount(libkb.MetaContext) (stellar1.AccountID, error)
    19  	// AccountSeqno should be cached _but_ it should also be busted asap.
    20  	// Because it is used to prevent users from sending payments twice in a row.
    21  	AccountSeqno(libkb.MetaContext, stellar1.AccountID) (string, error)
    22  	IsAccountFunded(libkb.MetaContext, stellar1.AccountID, stellar1.BuildPaymentID) (bool, error)
    23  	LookupRecipient(libkb.MetaContext, stellarcommon.RecipientInput) (stellarcommon.Recipient, error)
    24  	GetOutsideExchangeRate(libkb.MetaContext, stellar1.OutsideCurrencyCode) (stellar1.OutsideExchangeRate, error)
    25  	AvailableXLMToSend(libkb.MetaContext, stellar1.AccountID) (string, error)
    26  	GetOutsideCurrencyPreference(libkb.MetaContext, stellar1.AccountID, stellar1.BuildPaymentID) (stellar1.OutsideCurrencyCode, error)
    27  	ShouldOfferAdvancedSend(mctx libkb.MetaContext, from, to stellar1.AccountID) (stellar1.AdvancedBanner, error)
    28  	InformDefaultCurrencyChange(mctx libkb.MetaContext)
    29  }
    30  
    31  // Each instance is tied to a UV login. Must be discarded when switching users.
    32  // Threadsafe.
    33  // CORE-8119: Make all of these methods hit caches when called repeatedly.
    34  type buildPaymentCache struct {
    35  	sync.Mutex
    36  	remoter remote.Remoter
    37  
    38  	accountFundedCache             *TimeCache
    39  	lookupRecipientCache           *TimeCache
    40  	shouldOfferAdvancedSendCache   *TimeCache
    41  	currencyPreferenceCache        *TimeCache
    42  	currencyPreferenceForeverCache *TimeCache
    43  }
    44  
    45  func newBuildPaymentCache(remoter remote.Remoter) *buildPaymentCache {
    46  	return &buildPaymentCache{
    47  		remoter:                        remoter,
    48  		accountFundedCache:             NewTimeCache("accountFundedCache", 20, 0 /*forever*/),
    49  		lookupRecipientCache:           NewTimeCache("lookupRecipient", 20, time.Minute),
    50  		shouldOfferAdvancedSendCache:   NewTimeCache("shouldOfferAdvancedSend", 20, time.Minute),
    51  		currencyPreferenceCache:        NewTimeCache("currencyPreference", 20, 5*time.Minute),
    52  		currencyPreferenceForeverCache: NewTimeCache("currencyPreferenceForever", 20, 0 /*forever*/),
    53  	}
    54  }
    55  
    56  func (c *buildPaymentCache) PrimaryAccount(mctx libkb.MetaContext) (stellar1.AccountID, error) {
    57  	return GetOwnPrimaryAccountID(mctx)
    58  }
    59  
    60  func (c *buildPaymentCache) AccountSeqno(mctx libkb.MetaContext,
    61  	accountID stellar1.AccountID) (string, error) {
    62  	seqno, err := c.remoter.AccountSeqno(mctx.Ctx(), accountID)
    63  	return fmt.Sprintf("%v", seqno), err
    64  }
    65  
    66  func (c *buildPaymentCache) IsAccountFunded(mctx libkb.MetaContext,
    67  	accountID stellar1.AccountID, bid stellar1.BuildPaymentID) (res bool, err error) {
    68  	fill := func() (interface{}, error) {
    69  		funded, err := isAccountFunded(mctx.Ctx(), c.remoter, accountID)
    70  		res = funded
    71  		return funded, err
    72  	}
    73  	if !bid.IsNil() {
    74  		key := fmt.Sprintf("%v:%v", accountID, bid)
    75  		err = c.accountFundedCache.GetWithFill(mctx, key, &res, fill)
    76  		return res, err
    77  	}
    78  	_, err = fill()
    79  	return res, err
    80  }
    81  
    82  func (c *buildPaymentCache) LookupRecipient(mctx libkb.MetaContext,
    83  	to stellarcommon.RecipientInput) (res stellarcommon.Recipient, err error) {
    84  	fill := func() (interface{}, error) {
    85  		return LookupRecipient(mctx, to, false /* isCLI */)
    86  	}
    87  	err = c.lookupRecipientCache.GetWithFill(mctx, string(to), &res, fill)
    88  	return res, err
    89  }
    90  
    91  func (c *buildPaymentCache) ShouldOfferAdvancedSend(mctx libkb.MetaContext, from, to stellar1.AccountID) (res stellar1.AdvancedBanner, err error) {
    92  	key := from.String() + ":" + to.String()
    93  	fill := func() (interface{}, error) {
    94  		return ShouldOfferAdvancedSend(mctx, c.remoter, from, to)
    95  	}
    96  	err = c.shouldOfferAdvancedSendCache.GetWithFill(mctx, key, &res, fill)
    97  	return res, err
    98  }
    99  
   100  func (c *buildPaymentCache) GetOutsideExchangeRate(mctx libkb.MetaContext,
   101  	currency stellar1.OutsideCurrencyCode) (rate stellar1.OutsideExchangeRate, err error) {
   102  	return c.remoter.ExchangeRate(mctx.Ctx(), string(currency))
   103  }
   104  
   105  func (c *buildPaymentCache) AvailableXLMToSend(mctx libkb.MetaContext,
   106  	accountID stellar1.AccountID) (string, error) {
   107  	details, err := c.remoter.Details(mctx.Ctx(), accountID)
   108  	if err != nil {
   109  		return "", err
   110  	}
   111  	if details.Available == "" {
   112  		return "0", nil
   113  	}
   114  	return details.Available, nil
   115  }
   116  
   117  func (c *buildPaymentCache) GetOutsideCurrencyPreference(mctx libkb.MetaContext,
   118  	accountID stellar1.AccountID, bid stellar1.BuildPaymentID) (res stellar1.OutsideCurrencyCode, err error) {
   119  	fillInner := func() (interface{}, error) {
   120  		cr, err := GetCurrencySetting(mctx, accountID)
   121  		return cr.Code, err
   122  	}
   123  	fillOuter := func() (interface{}, error) {
   124  		err := c.currencyPreferenceCache.GetWithFill(mctx, accountID.String(), &res, fillInner)
   125  		return res, err
   126  	}
   127  	if !bid.IsNil() {
   128  		foreverKey := fmt.Sprintf("%v:%v", accountID, bid)
   129  		err = c.currencyPreferenceForeverCache.GetWithFill(mctx, foreverKey, &res, fillOuter)
   130  		return res, err
   131  	}
   132  	_, err = fillOuter()
   133  	return res, err
   134  }
   135  
   136  func (c *buildPaymentCache) InformDefaultCurrencyChange(mctx libkb.MetaContext) {
   137  	c.currencyPreferenceCache.Clear()
   138  	c.currencyPreferenceForeverCache.Clear()
   139  }