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

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	lru "github.com/hashicorp/golang-lru"
    12  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    13  	jsonw "github.com/keybase/go-jsonw"
    14  )
    15  
    16  type CheckResult struct {
    17  	Contextified
    18  	Status       ProofError // Or nil if it was a success
    19  	VerifiedHint *SigHint   // client provided verified hint if any
    20  	Time         time.Time  // When the last check was
    21  	PvlHash      string     // Added after other fields. Some entries may not have this packed.
    22  }
    23  
    24  func (cr CheckResult) Pack() *jsonw.Wrapper {
    25  	p := jsonw.NewDictionary()
    26  	if cr.Status != nil {
    27  		s := jsonw.NewDictionary()
    28  		_ = s.SetKey("code", jsonw.NewInt(int(cr.Status.GetProofStatus())))
    29  		_ = s.SetKey("desc", jsonw.NewString(cr.Status.GetDesc()))
    30  		_ = p.SetKey("status", s)
    31  		if cr.VerifiedHint != nil {
    32  			_ = p.SetKey("verified_hint", cr.VerifiedHint.MarshalToJSON())
    33  		}
    34  	}
    35  	_ = p.SetKey("time", jsonw.NewInt64(cr.Time.Unix()))
    36  	_ = p.SetKey("pvlhash", jsonw.NewString(cr.PvlHash))
    37  	return p
    38  }
    39  
    40  func (cr CheckResult) Freshness() keybase1.CheckResultFreshness {
    41  	now := cr.G().Clock().Now()
    42  	age := now.Sub(cr.Time)
    43  	switch {
    44  	case cr.Status == nil:
    45  		switch {
    46  		case age < cr.G().Env.GetProofCacheMediumDur():
    47  			return keybase1.CheckResultFreshness_FRESH
    48  		case age < cr.G().Env.GetProofCacheLongDur():
    49  			return keybase1.CheckResultFreshness_AGED
    50  		}
    51  	case ProofErrorIsPvlBad(cr.Status):
    52  		// Don't use cache results for pvl problems.
    53  		// The hope is that they will soon be resolved server-side.
    54  		return keybase1.CheckResultFreshness_RANCID
    55  	case !ProofErrorIsSoft(cr.Status):
    56  		if age < cr.G().Env.GetProofCacheShortDur() {
    57  			return keybase1.CheckResultFreshness_FRESH
    58  		}
    59  	default:
    60  		// don't use cache results for "soft" errors (500s, timeouts)
    61  		// see issue #140
    62  	}
    63  	return keybase1.CheckResultFreshness_RANCID
    64  }
    65  
    66  func NewNowCheckResult(g *GlobalContext, pe ProofError) *CheckResult {
    67  	return &CheckResult{
    68  		Contextified: NewContextified(g),
    69  		Status:       pe,
    70  		Time:         g.Clock().Now(),
    71  	}
    72  }
    73  
    74  func NewCheckResult(g *GlobalContext, jw *jsonw.Wrapper) (res *CheckResult, err error) {
    75  	var ignoreErr error
    76  	var t int64
    77  	var code int
    78  	var desc string
    79  	var pvlHash string
    80  
    81  	jw.AtKey("time").GetInt64Void(&t, &err)
    82  	jw.AtKey("pvlhash").GetStringVoid(&pvlHash, &ignoreErr)
    83  	verifiedHint, err := NewSigHint(jw.AtKey("verified_hint"))
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	status := jw.AtKey("status")
    89  	var pe ProofError
    90  	if !status.IsNil() {
    91  		status.AtKey("desc").GetStringVoid(&desc, &err)
    92  		status.AtKey("code").GetIntVoid(&code, &err)
    93  		pe = NewProofError(keybase1.ProofStatus(code), desc)
    94  	}
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	res = &CheckResult{
    99  		Contextified: NewContextified(g),
   100  		Status:       pe,
   101  		VerifiedHint: verifiedHint,
   102  		Time:         time.Unix(t, 0),
   103  		PvlHash:      pvlHash,
   104  	}
   105  	return res, nil
   106  }
   107  
   108  type ProofCache struct {
   109  	Contextified
   110  	capac int
   111  	lru   *lru.Cache
   112  	sync.RWMutex
   113  	noDisk bool
   114  }
   115  
   116  func NewProofCache(g *GlobalContext, capac int) *ProofCache {
   117  	return &ProofCache{Contextified: NewContextified(g), capac: capac}
   118  }
   119  
   120  func (pc *ProofCache) DisableDisk() {
   121  	pc.Lock()
   122  	defer pc.Unlock()
   123  	pc.noDisk = true
   124  }
   125  
   126  func (pc *ProofCache) Reset() error {
   127  	pc.Lock()
   128  	defer pc.Unlock()
   129  	return pc.initCache()
   130  }
   131  
   132  func (pc *ProofCache) setup() error {
   133  	pc.Lock()
   134  	defer pc.Unlock()
   135  	if pc.lru != nil {
   136  		return nil
   137  	}
   138  	return pc.initCache()
   139  }
   140  
   141  func (pc *ProofCache) initCache() error {
   142  	lru, err := lru.New(pc.capac)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	pc.lru = lru
   147  	return nil
   148  }
   149  
   150  func (pc *ProofCache) memGet(sid keybase1.SigID) *CheckResult {
   151  	if err := pc.setup(); err != nil {
   152  		return nil
   153  	}
   154  
   155  	pc.RLock()
   156  	defer pc.RUnlock()
   157  
   158  	tmp, found := pc.lru.Get(sid)
   159  	if !found {
   160  		return nil
   161  	}
   162  	cr, ok := tmp.(CheckResult)
   163  	if !ok {
   164  		pc.G().Log.Errorf("Bad type assertion in ProofCache.Get")
   165  		return nil
   166  	}
   167  	if cr.Freshness() == keybase1.CheckResultFreshness_RANCID {
   168  		pc.lru.Remove(sid)
   169  		return nil
   170  	}
   171  	return &cr
   172  }
   173  
   174  func (pc *ProofCache) memPut(sid keybase1.SigID, cr CheckResult) {
   175  	if err := pc.setup(); err != nil {
   176  		return
   177  	}
   178  
   179  	pc.RLock()
   180  	defer pc.RUnlock()
   181  
   182  	pc.lru.Add(sid, cr)
   183  }
   184  
   185  func (pc *ProofCache) memDelete(sid keybase1.SigID) {
   186  	if err := pc.setup(); err != nil {
   187  		return
   188  	}
   189  	pc.RLock()
   190  	defer pc.RUnlock()
   191  	pc.lru.Remove(sid)
   192  }
   193  
   194  func (pc *ProofCache) Get(sid keybase1.SigID, pvlHash keybase1.MerkleStoreKitHash) *CheckResult {
   195  	if pc == nil {
   196  		return nil
   197  	}
   198  
   199  	cr := pc.memGet(sid)
   200  	if cr == nil {
   201  		cr = pc.dbGet(sid)
   202  	}
   203  	if cr == nil {
   204  		return nil
   205  	}
   206  
   207  	if cr.PvlHash == "" {
   208  		pc.G().Log.Debug("^ ProofCache ignoring entry with pvl-hash empty")
   209  		return nil
   210  	}
   211  	if cr.PvlHash != string(pvlHash) {
   212  		pc.G().Log.Debug("^ ProofCache ignoring entry with pvl-hash mismatch")
   213  		return nil
   214  	}
   215  
   216  	return cr
   217  }
   218  
   219  func (pc *ProofCache) dbKey(sid keybase1.SigID) (DbKey, string) {
   220  	sidstr := sid.String()
   221  	key := DbKey{Typ: DBProofCheck, Key: sidstr}
   222  	return key, sidstr
   223  }
   224  
   225  func (pc *ProofCache) dbGet(sid keybase1.SigID) (cr *CheckResult) {
   226  	dbkey, sidstr := pc.dbKey(sid)
   227  
   228  	pc.G().Log.Debug("+ ProofCache.dbGet(%s)", sidstr)
   229  	defer func() {
   230  		pc.G().Log.Debug("- ProofCache.dbGet(%s) -> %v", sidstr, (cr != nil))
   231  	}()
   232  
   233  	if pc.noDisk {
   234  		pc.G().Log.Debug("| disk proof cache disabled")
   235  		return nil
   236  	}
   237  
   238  	jw, err := pc.G().LocalDb.Get(dbkey)
   239  	if err != nil {
   240  		pc.G().Log.Errorf("Error lookup up proof check in DB: %s", err)
   241  		return nil
   242  	}
   243  	if jw == nil {
   244  		pc.G().Log.Debug("| Cached CheckResult for %s wasn't found ", sidstr)
   245  		return nil
   246  	}
   247  
   248  	cr, err = NewCheckResult(pc.G(), jw)
   249  	if err != nil {
   250  		pc.G().Log.Errorf("Bad cached CheckResult for %s", sidstr)
   251  		return nil
   252  	}
   253  
   254  	if cr.Freshness() == keybase1.CheckResultFreshness_RANCID {
   255  		if err := pc.G().LocalDb.Delete(dbkey); err != nil {
   256  			pc.G().Log.Errorf("Delete error: %s", err)
   257  		}
   258  		pc.G().Log.Debug("| Cached CheckResult for %s wasn't fresh", sidstr)
   259  		return nil
   260  	}
   261  
   262  	return cr
   263  }
   264  
   265  func (pc *ProofCache) dbPut(sid keybase1.SigID, cr CheckResult) error {
   266  	if pc.noDisk {
   267  		return nil
   268  	}
   269  
   270  	dbkey, _ := pc.dbKey(sid)
   271  	jw := cr.Pack()
   272  	return pc.G().LocalDb.Put(dbkey, []DbKey{}, jw)
   273  }
   274  
   275  func (pc *ProofCache) dbDelete(sid keybase1.SigID) error {
   276  	if pc.noDisk {
   277  		return nil
   278  	}
   279  	dbkey, _ := pc.dbKey(sid)
   280  	return pc.G().LocalDb.Delete(dbkey)
   281  }
   282  
   283  func (pc *ProofCache) Put(sid keybase1.SigID, lcr *LinkCheckResult, pvlHash keybase1.MerkleStoreKitHash) error {
   284  	if pc == nil {
   285  		return nil
   286  	}
   287  	cr := CheckResult{
   288  		Contextified: pc.Contextified,
   289  		Status:       lcr.err,
   290  		VerifiedHint: lcr.verifiedHint,
   291  		Time:         pc.G().Clock().Now(),
   292  		PvlHash:      string(pvlHash),
   293  	}
   294  	pc.memPut(sid, cr)
   295  	return pc.dbPut(sid, cr)
   296  }
   297  
   298  func (pc *ProofCache) Delete(sid keybase1.SigID) error {
   299  	if pc == nil {
   300  		return fmt.Errorf("nil ProofCache")
   301  	}
   302  	pc.memDelete(sid)
   303  	return pc.dbDelete(sid)
   304  }