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 }