github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/engine/identify2_with_uid.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 engine 5 6 import ( 7 "errors" 8 "fmt" 9 "sync" 10 "time" 11 12 gregor "github.com/keybase/client/go/gregor" 13 libkb "github.com/keybase/client/go/libkb" 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15 jsonw "github.com/keybase/go-jsonw" 16 ) 17 18 type Identify2TestStats struct { 19 untrackedFastPaths int 20 } 21 22 type Identify2WithUIDTestArgs struct { 23 noMe bool // don't load ME 24 tcl *libkb.TrackChainLink // the track chainlink to use 25 selfLoad bool // on if this is a self load 26 noCache bool // on if we shouldn't use the cache 27 cache libkb.Identify2Cacher 28 clock func() time.Time 29 forceRemoteCheck bool // on if we should force remote checks (like busting caches) 30 allowUntrackedFastPath bool // on if we can allow untracked fast path in test 31 stats Identify2TestStats 32 } 33 34 type identify2TrackType int 35 36 const ( 37 identify2NoTrack identify2TrackType = iota 38 identify2TrackOK 39 identify2TrackBroke 40 ) 41 42 type identifyUser struct { 43 arg libkb.LoadUserArg 44 full *libkb.User 45 thin *keybase1.UserPlusKeysV2AllIncarnations 46 isDeleted bool 47 } 48 49 func (i *identifyUser) GetUID() keybase1.UID { 50 if i.thin != nil { 51 return i.thin.GetUID() 52 } 53 if i.full != nil { 54 return i.full.GetUID() 55 } 56 panic("null user") 57 } 58 59 func (i *identifyUser) GetName() string { 60 if i.thin != nil { 61 return i.thin.GetName() 62 } 63 if i.full != nil { 64 return i.full.GetName() 65 } 66 panic("null user") 67 } 68 69 func (i *identifyUser) GetStatus() keybase1.StatusCode { 70 if i.thin != nil { 71 return i.thin.GetStatus() 72 } 73 if i.full != nil { 74 return i.full.GetStatus() 75 } 76 panic("null user") 77 } 78 79 func (i *identifyUser) GetNormalizedName() libkb.NormalizedUsername { 80 return libkb.NewNormalizedUsername(i.GetName()) 81 } 82 83 func (i *identifyUser) BaseProofSet() *libkb.ProofSet { 84 if i.thin != nil { 85 return libkb.BaseProofSet(i.thin) 86 } 87 if i.full != nil { 88 return i.full.BaseProofSet() 89 } 90 panic("null user") 91 } 92 93 func (i *identifyUser) User(cache libkb.Identify2Cacher) (*libkb.User, error) { 94 if i.full != nil { 95 return i.full, nil 96 } 97 if cache != nil { 98 cache.DidFullUserLoad(i.GetUID()) 99 } 100 var err error 101 i.full, err = libkb.LoadUser(i.arg) 102 return i.full, err 103 } 104 105 func (i *identifyUser) Export() *keybase1.User { 106 if i.thin != nil { 107 tmp := i.thin.ExportToSimpleUser() 108 return &tmp 109 } 110 if i.full != nil { 111 return i.full.Export() 112 } 113 panic("null user") 114 } 115 116 func (i *identifyUser) ExportToUserPlusKeysV2AllIncarnations() (*keybase1.UserPlusKeysV2AllIncarnations, error) { 117 if i.thin != nil { 118 return i.thin, nil 119 } 120 if i.full != nil { 121 return i.full.ExportToUPKV2AllIncarnations() 122 } 123 return nil, errors.New("null user in identify2: ExportToUserPlusKeysV2AllIncarnations") 124 } 125 126 func (i *identifyUser) IsCachedIdentifyFresh(upk *keybase1.UserPlusKeysV2AllIncarnations) bool { 127 if i.thin != nil { 128 ret := i.thin.Uvv.Equal(upk.Uvv) 129 return ret 130 } 131 if i.full != nil { 132 return i.full.IsCachedIdentifyFresh(upk) 133 } 134 panic("null user") 135 } 136 137 func (i *identifyUser) Equal(i2 *identifyUser) bool { 138 return i.GetUID().Equal(i2.GetUID()) 139 } 140 141 func (i *identifyUser) load(g *libkb.GlobalContext) (err error) { 142 i.thin, i.full, err = g.GetUPAKLoader().LoadV2(i.arg) 143 return err 144 } 145 146 func (i *identifyUser) forceFullLoad(m libkb.MetaContext) (err error) { 147 arg := i.arg.WithForceReload() 148 i.thin, i.full, err = m.G().GetUPAKLoader().LoadV2(arg) 149 return err 150 } 151 152 func (i *identifyUser) isNil() bool { 153 return i.thin == nil && i.full == nil 154 } 155 156 func (i *identifyUser) Full() *libkb.User { 157 return i.full 158 } 159 160 func loadIdentifyUser(m libkb.MetaContext, arg libkb.LoadUserArg, cache libkb.Identify2Cacher) (*identifyUser, error) { 161 ret := &identifyUser{arg: arg} 162 err := ret.load(m.G()) 163 if ret.isNil() { 164 ret = nil 165 } else if ret.full != nil && cache != nil { 166 cache.DidFullUserLoad(ret.GetUID()) 167 } 168 return ret, err 169 } 170 171 func (i *identifyUser) trackChainLinkFor(m libkb.MetaContext, name libkb.NormalizedUsername, uid keybase1.UID) (ret *libkb.TrackChainLink, err error) { 172 defer m.Trace(fmt.Sprintf("identifyUser#trackChainLinkFor(%s)", name), &err)() 173 174 if i.full != nil { 175 m.Debug("| Using full user object") 176 return i.full.TrackChainLinkFor(m, name, uid) 177 } 178 179 if i.thin != nil { 180 181 m.Debug("| Using thin user object") 182 183 // In the common case, we look at the thin UPAK and get the chain link 184 // ID of the track chain link for tracking the given user. We'll then 185 // go ahead and load that chain link from local level DB, and it's almost 186 // always going to be there, since it was written as a side effect of 187 // fetching the full user. There's a corner case, see just below... 188 ret, err = libkb.TrackChainLinkFromUPK2AI(m, i.thin, name, uid) 189 if _, inconsistent := err.(libkb.InconsistentCacheStateError); !inconsistent { 190 m.Debug("| returning in common case -> (found=%v, err=%v)", (ret != nil), err) 191 return ret, err 192 } 193 194 m.Debug("| fell through to forceFullLoad corner case") 195 196 // 197 // NOTE(max) 2016-12-31 198 // 199 // There's a corner case here -- the track chain link does exist, but 200 // it wasn't found on disk. This is probably because the db cache was nuked. 201 // Thus, in this case we force a full user reload, and we're sure to get 202 // the tracking information then. 203 // 204 // See Jira ticket CORE-4310 205 // 206 err = i.forceFullLoad(m) 207 if err != nil { 208 return nil, err 209 } 210 return i.full.TrackChainLinkFor(m, name, uid) 211 } 212 213 // No user loaded, so no track chain link. 214 m.Debug("| fell through the empty default case") 215 return nil, nil 216 } 217 218 // 219 // TODOs: 220 // - think harder about what we're caching in failure cases; right now we're only 221 // caching full successes. 222 // - Better error typing for various failures. 223 // - Work back in the identify card 224 // 225 226 // Identify2WithUID is the Identify engine used in KBFS, chat, and as a 227 // subroutine of command-line crypto. 228 type Identify2WithUID struct { 229 libkb.Contextified 230 231 arg *keybase1.Identify2Arg 232 testArgs *Identify2WithUIDTestArgs 233 trackToken keybase1.TrackToken 234 confirmResult keybase1.ConfirmResult 235 cachedRes *keybase1.Identify2ResUPK2 236 237 metaContext libkb.MetaContext 238 239 // If we just resolved a user, then we can plumb this through to loadUser() 240 ResolveBody *jsonw.Wrapper 241 242 me *identifyUser 243 them *identifyUser 244 245 themAssertion libkb.AssertionExpression 246 remoteAssertion libkb.AssertionAnd 247 localAssertion libkb.AssertionAnd 248 249 state libkb.IdentifyState 250 useTracking bool 251 identifyKeys []keybase1.IdentifyKey 252 253 resultCh chan<- error 254 255 // For eagerly checking remote Assertions as they come in, these 256 // member variables maintain state, protected by the remotesMutex. 257 remotesMutex sync.Mutex 258 remotesReceived *libkb.ProofSet 259 remotesError error 260 remotesCompleted bool 261 262 responsibleGregorItem gregor.Item 263 264 // When tracking is being performed, the identify engine is used with a tracking ui. 265 // These options are sent to the ui based on command line options. 266 // For normal identify, safe to leave these in their default zero state. 267 trackOptions keybase1.TrackOptions 268 269 // When called from chat, we should just collect breaking tracking failures, but 270 // not fail track. This is where we collect them 271 trackBreaks *keybase1.IdentifyTrackBreaks 272 } 273 274 var _ (Engine2) = (*Identify2WithUID)(nil) 275 var _ (libkb.CheckCompletedListener) = (*Identify2WithUID)(nil) 276 277 // Name is the unique engine name. 278 func (e *Identify2WithUID) Name() string { 279 return "Identify2WithUID" 280 } 281 282 func NewIdentify2WithUID(g *libkb.GlobalContext, arg *keybase1.Identify2Arg) *Identify2WithUID { 283 return &Identify2WithUID{ 284 Contextified: libkb.NewContextified(g), 285 arg: arg, 286 } 287 } 288 289 // GetPrereqs returns the engine prereqs. 290 func (e *Identify2WithUID) Prereqs() Prereqs { 291 return Prereqs{} 292 } 293 294 func (e *Identify2WithUID) WantDelegate(k libkb.UIKind) bool { 295 return k == libkb.IdentifyUIKind && e.arg.UseDelegateUI 296 } 297 298 func (e *Identify2WithUID) resetError(m libkb.MetaContext, inErr error) (outErr error) { 299 300 defer m.Trace(fmt.Sprintf("Identify2WithUID#resetError(%s)", libkb.ErrToOk(inErr)), &outErr)() 301 302 if inErr == nil { 303 return nil 304 } 305 306 // Check to see if this is an identify failure, and if not just return. If it is, we want 307 // to check what identify mode we are in here before returning an error. 308 if !libkb.IsIdentifyProofError(inErr) { 309 return inErr 310 } 311 312 if e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks() { 313 m.Debug("| Reset err from %v -> nil since caller is '%s' %d", inErr, e.arg.IdentifyBehavior, e.arg.IdentifyBehavior) 314 return nil 315 } 316 317 return inErr 318 } 319 320 // Run then engine 321 func (e *Identify2WithUID) Run(m libkb.MetaContext) (err error) { 322 323 m = m.WithLogTag("ID2") 324 325 n := fmt.Sprintf("Identify2WithUID#Run(UID=%v, Assertion=%s)", e.arg.Uid, e.arg.UserAssertion) 326 defer m.Trace(n, &err)() 327 m.Debug("| Full Arg: %+v", e.arg) 328 329 if e.arg.Uid.IsNil() { 330 return libkb.NoUIDError{} 331 } 332 333 // Only the first send matters, but we don't want to block the subsequent no-op 334 // sends. This code will break when we have more than 100 unblocking opportunities. 335 ch := make(chan error, 100) 336 337 e.resultCh = ch 338 go e.run(m) 339 err = <-ch 340 341 // Potentially reset the error based on the error and the calling context. 342 err = e.resetError(m, err) 343 return err 344 } 345 346 func (e *Identify2WithUID) notifyChat(m libkb.MetaContext, inErr error) error { 347 m.Debug("Identify2WithUID.run: notifyChat") 348 if !e.arg.IdentifyBehavior.ShouldRefreshChatView() { 349 return nil 350 } 351 if e.them == nil { 352 return fmt.Errorf("nil user") 353 } 354 nun := e.them.GetNormalizedName() 355 if nun.IsNil() { 356 return fmt.Errorf("no username for user") 357 } 358 // Don't notify if there's a non-identity proof error. 359 if inErr != nil && !libkb.IsIdentifyProofError(inErr) { 360 return nil 361 } 362 363 res, err := e.Result(m) 364 if err != nil { 365 return err 366 } 367 368 update := keybase1.CanonicalTLFNameAndIDWithBreaks{ 369 CanonicalName: keybase1.CanonicalTlfName(nun), 370 } 371 if libkb.IsIdentifyProofError(inErr) { 372 update.Breaks = keybase1.TLFBreak{Breaks: []keybase1.TLFIdentifyFailure{ 373 { 374 User: *e.them.Export(), 375 Breaks: res.TrackBreaks, 376 }, 377 }} 378 } 379 m.Debug("Identify2WithUID.run: running HandleChatIdentifyUpdate: %#v", update) 380 m.G().NotifyRouter.HandleChatIdentifyUpdate(m.Ctx(), update) 381 382 return nil 383 } 384 385 func (e *Identify2WithUID) run(m libkb.MetaContext) { 386 err := e.runReturnError(m) 387 388 if notifyChatErr := e.notifyChat(m, err); notifyChatErr != nil { 389 m.Warning("failed to notify chat of identify result: %s", notifyChatErr) 390 } 391 392 // always cancel IdentifyUI to allow clients to clean up. 393 // If no identifyUI was specified (because running the background) 394 // then don't do anything. 395 if m.UIs().IdentifyUI != nil { 396 err := m.UIs().IdentifyUI.Cancel(m) 397 if err != nil { 398 m.Debug("| error during cancel: %+v", err) 399 } 400 } 401 e.unblock(m, true /* isFinal */, err) 402 } 403 404 func (e *Identify2WithUID) hitFastCache(m libkb.MetaContext) bool { 405 406 if !e.allowCaching() { 407 m.Debug("| missed fast cache: no caching allowed") 408 return false 409 } 410 if e.useAnyAssertions() { 411 m.Debug("| missed fast cache: has assertions") 412 return false 413 } 414 if !e.allowEarlyOuts() { 415 m.Debug("| missed fast cache: we don't allow early outs") 416 return false 417 } 418 if !e.checkFastCacheHit(m) { 419 m.Debug("| missed fast cache: didn't hit") 420 return false 421 } 422 return true 423 } 424 425 func (e *Identify2WithUID) untrackedFastPath(m libkb.MetaContext) (ret bool) { 426 427 defer m.Trace("Identify2WithUID#untrackedFastPath", nil)() 428 429 if !e.arg.IdentifyBehavior.CanUseUntrackedFastPath() { 430 m.Debug("| Can't use untracked fast path due to identify behavior %v", e.arg.IdentifyBehavior) 431 return false 432 } 433 434 statInc := func() { 435 if e.testArgs != nil { 436 e.testArgs.stats.untrackedFastPaths++ 437 } 438 } 439 440 if e.testArgs != nil && !e.testArgs.allowUntrackedFastPath { 441 m.Debug("| Can't use untracked fast path since disallowed in test") 442 return false 443 } 444 445 if e.them == nil { 446 m.Debug("| Can't use untracked fast path since failed to load them users") 447 return false 448 } 449 450 nun := e.them.GetNormalizedName() 451 452 // check if there's a tcl in the testArgs 453 if e.testArgs != nil && e.testArgs.tcl != nil { 454 trackedUsername, err := e.testArgs.tcl.GetTrackedUsername() 455 if err == nil && trackedUsername == nun { 456 m.Debug("| Test track link found for %s", nun.String()) 457 return false 458 } 459 } 460 461 if e.me == nil { 462 m.Debug("| Can use untracked fastpath since there is no logged in user") 463 statInc() 464 return true 465 } 466 467 tcl, err := e.me.trackChainLinkFor(m, nun, e.them.GetUID()) 468 if err != nil { 469 m.Debug("| Error getting track chain link: %s", err) 470 return false 471 } 472 473 if tcl != nil { 474 m.Debug("| Track found for %s", nun.String()) 475 return false 476 } 477 478 statInc() 479 return true 480 } 481 482 func (e *Identify2WithUID) runReturnError(m libkb.MetaContext) (err error) { 483 484 m.Debug("+ acquire singleflight lock for %s", e.arg.Uid) 485 lock, err := m.G().IDLocktab.AcquireOnNameWithContext(m.Ctx(), m.G(), e.arg.Uid.String()) 486 if err != nil { 487 m.Debug("| error acquiring singleflight lock for %s: %v", e.arg.Uid, err) 488 return err 489 } 490 m.Debug("- acquired singleflight lock") 491 492 defer func() { 493 m.Debug("+ Releasing singleflight lock for %s", e.arg.Uid) 494 lock.Release(m.Ctx()) 495 m.Debug("- Released singleflight lock") 496 }() 497 498 if err = e.loadAssertion(m); err != nil { 499 return err 500 } 501 502 if e.hitFastCache(m) { 503 m.Debug("| hit fast cache") 504 e.maybeNotify(m, "hit fast cache") 505 return nil 506 } 507 508 m.Debug("| Identify2WithUID.loadUsers") 509 if err = e.loadUsers(m); err != nil { 510 return err 511 } 512 513 if err = e.checkLocalAssertions(); err != nil { 514 return err 515 } 516 517 if e.isSelfLoad() && !e.arg.NoSkipSelf && !e.useRemoteAssertions() { 518 m.Debug("| was a self load, short-circuiting") 519 e.maybeCacheSelf(m) 520 return nil 521 } 522 523 // If we are rekeying or reclaiming quota from KBFS, then let's 524 // skip the external checks. 525 if e.arg.IdentifyBehavior.SkipExternalChecks() { 526 m.Debug("| skip external checks specified, short-circuiting") 527 return nil 528 } 529 530 if !e.useRemoteAssertions() && e.allowEarlyOuts() { 531 532 if e.untrackedFastPath(m) { 533 m.Debug("| used untracked fast path") 534 e.maybeNotify(m, "untracked fast path") 535 return nil 536 } 537 538 if e.checkSlowCacheHit(m) { 539 m.Debug("| hit slow cache, first check") 540 e.maybeNotify(m, "slow cache, first check") 541 return nil 542 } 543 } 544 545 m.Debug("| Identify2WithUID.createIdentifyState") 546 if err = e.createIdentifyState(m); err != nil { 547 return err 548 } 549 550 if err = e.runIdentifyPrecomputation(); err != nil { 551 return err 552 } 553 554 // First we check that all remote assertions as present for the user, 555 // whether or not the remote check actually succeeds (hence the 556 // ProofState_NONE check). 557 okStates := []keybase1.ProofState{keybase1.ProofState_NONE, keybase1.ProofState_OK} 558 if err = e.checkRemoteAssertions(okStates); err != nil { 559 m.Debug("| Early fail due to missing remote assertions") 560 return err 561 } 562 563 if e.useRemoteAssertions() && e.allowEarlyOuts() && e.checkSlowCacheHit(m) { 564 m.Debug("| hit slow cache, second check") 565 e.maybeNotify(m, "slow cache, second check") 566 return nil 567 } 568 569 // If we're not using tracking and we're not using remote assertions, 570 // we can unblock the RPC caller here, and perform the identifyUI operations 571 // in the background. NOTE: we need to copy out our background context, 572 // since it will the foreground context will disappear after we unblock. 573 m = m.BackgroundWithLogTags() 574 575 if (!e.useTracking && !e.useRemoteAssertions() && e.allowEarlyOuts()) || e.arg.IdentifyBehavior.UnblockThenForceIDTable() { 576 e.unblock(m, false /* isFinal */, nil /* err */) 577 } 578 579 return e.runIdentifyUI(m) 580 } 581 582 func (e *Identify2WithUID) allowEarlyOuts() bool { 583 return !e.arg.NeedProofSet && !e.arg.IdentifyBehavior.UnblockThenForceIDTable() 584 } 585 586 func (e *Identify2WithUID) getNow(m libkb.MetaContext) time.Time { 587 if e.testArgs != nil && e.testArgs.clock != nil { 588 return e.testArgs.clock() 589 } 590 return m.G().Clock().Now() 591 } 592 593 func (e *Identify2WithUID) unblock(m libkb.MetaContext, isFinal bool, err error) { 594 m.Debug("| unblocking...") 595 if e.arg.AlwaysBlock && !isFinal { 596 m.Debug("| skipping unblock; isFinal=%v; AlwaysBlock=%v...", isFinal, e.arg.AlwaysBlock) 597 } else { 598 e.resultCh <- err 599 m.Debug("| unblock sent...") 600 } 601 } 602 603 func (e *Identify2WithUID) maybeCacheSelf(m libkb.MetaContext) { 604 if e.getCache() != nil { 605 v, err := e.exportToResult(m) 606 if v != nil && err == nil { 607 err := e.getCache().Insert(v) 608 if err != nil { 609 m.Debug("| error inserting: %+v", err) 610 } 611 } 612 } 613 } 614 615 // exportToResult either returns (non-nil, nil) on success, or (nil, non-nil) on error. 616 func (e *Identify2WithUID) exportToResult(m libkb.MetaContext) (*keybase1.Identify2ResUPK2, error) { 617 if e.them == nil { 618 // this should never happen 619 return nil, libkb.UserNotFoundError{Msg: "failed to get a them user in Identify2WithUID#exportToResult"} 620 } 621 upk, err := e.toUserPlusKeysv2AllIncarnations() 622 if err != nil { 623 return nil, err 624 } 625 if upk == nil { 626 // this should never happen 627 return nil, libkb.UserNotFoundError{Msg: "failed export a them user in Identify2WithUID#exportToResult"} 628 } 629 return &keybase1.Identify2ResUPK2{ 630 Upk: *upk, 631 TrackBreaks: e.trackBreaks, 632 IdentifiedAt: keybase1.ToTime(e.getNow(m)), 633 }, nil 634 } 635 636 func (e *Identify2WithUID) maybeCacheResult(m libkb.MetaContext) { 637 638 isOK := e.state.Result().IsOK() 639 canCacheFailures := e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks() 640 641 m.Debug("+ maybeCacheResult (ok=%v; canCacheFailures=%v)", isOK, canCacheFailures) 642 defer m.Debug("- maybeCacheResult") 643 644 if e.getCache() == nil { 645 m.Debug("| cache is disabled, so nothing to do") 646 return 647 } 648 649 // If we hit an identify failure, and we're not allowed to cache failures, 650 // then at least bust out the cache. 651 if !isOK && !canCacheFailures { 652 m.Debug("| clearing cache due to failure") 653 uid := e.them.GetUID() 654 err := e.getCache().Delete(uid) 655 if err != nil { 656 m.Debug("| Error deleting uid: %+v", err) 657 } 658 if err := e.removeSlowCacheFromDB(m); err != nil { 659 m.Debug("| Error in removing slow cache from db: %s", err) 660 } 661 return 662 } 663 664 // Common case --- (isOK || canCacheFailures) 665 v, err := e.exportToResult(m) 666 if err != nil { 667 m.Debug("| not caching: error exporting: %s", err) 668 return 669 } 670 if v == nil { 671 m.Debug("| not caching; nil result") 672 return 673 } 674 err = e.getCache().Insert(v) 675 if err != nil { 676 m.Debug("| error inserting: %+v", err) 677 } 678 679 m.VLogf(libkb.VLog1, "| insert %+v", v) 680 681 // Don't write failures to the disk cache 682 if isOK { 683 if err := e.storeSlowCacheToDB(m); err != nil { 684 m.Debug("| Error in storing slow cache to db: %s", err) 685 } 686 } 687 } 688 689 func (e *Identify2WithUID) insertTrackToken(m libkb.MetaContext, outcome *libkb.IdentifyOutcome) (err error) { 690 defer m.Trace("Identify2WithUID#insertTrackToken", &err)() 691 e.trackToken, err = m.G().TrackCache().Insert(outcome) 692 if err != nil { 693 return err 694 } 695 return m.UIs().IdentifyUI.ReportTrackToken(m, e.trackToken) 696 } 697 698 // CCLCheckCompleted is triggered whenever a remote proof check completes. 699 // We get these calls as a result of being a "CheckCompletedListener". 700 // When each result comes in, we check against our pool of needed remote 701 // assertions. If the set is complete, or if one that we need errors, 702 // we can unblock the caller. 703 func (e *Identify2WithUID) CCLCheckCompleted(lcr *libkb.LinkCheckResult) { 704 e.remotesMutex.Lock() 705 defer e.remotesMutex.Unlock() 706 m := e.metaContext 707 708 m.Debug("+ CheckCompleted for %s", lcr.GetLink().ToIDString()) 709 defer m.Debug("- CheckCompleted") 710 711 // Always add to remotesReceived list, so that we have a full ProofSet. 712 pf := libkb.RemoteProofChainLinkToProof(lcr.GetLink()) 713 e.remotesReceived.Add(pf) 714 715 if !e.useRemoteAssertions() || e.useTracking { 716 m.Debug("| Not using remote assertions or is tracking") 717 return 718 } 719 720 if !e.remoteAssertion.HasFactor(pf) { 721 m.Debug("| Proof isn't needed in our remote-assertion early-out check: %v", pf) 722 return 723 } 724 725 if err := lcr.GetError(); err != nil { 726 m.Debug("| got error -> %v", err) 727 e.remotesError = err 728 } 729 730 // note(maxtaco): this is a little ugly in that it's O(n^2) where n is the number 731 // of identities in the assertion. But I can't imagine n > 3, so this is fine 732 // for now. 733 matched := e.remoteAssertion.MatchSet(*e.remotesReceived) 734 m.Debug("| matched -> %v", matched) 735 if matched { 736 e.remotesCompleted = true 737 } 738 739 if e.remotesError != nil || e.remotesCompleted { 740 m.Debug("| unblocking, with err = %v", e.remotesError) 741 e.unblock(m, false, e.remotesError) 742 } 743 } 744 745 func (e *Identify2WithUID) checkLocalAssertions() error { 746 if !e.localAssertion.MatchSet(*e.them.BaseProofSet()) { 747 return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: false} 748 } 749 return nil 750 } 751 752 func (e *Identify2WithUID) checkRemoteAssertions(okStates []keybase1.ProofState) error { 753 if e.them.isDeleted { 754 if e.G().Env.GetReadDeletedSigChain() { 755 return nil 756 } 757 return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: true} 758 } 759 ps := libkb.NewProofSet(nil) 760 e.state.Result().AddProofsToSet(ps, okStates) 761 if !e.remoteAssertion.MatchSet(*ps) { 762 return libkb.UnmetAssertionError{User: e.them.GetName(), Remote: true} 763 } 764 return nil 765 } 766 767 func (e *Identify2WithUID) loadAssertion(mctx libkb.MetaContext) (err error) { 768 if len(e.arg.UserAssertion) == 0 { 769 return nil 770 } 771 e.themAssertion, err = libkb.AssertionParseAndOnly(e.G().MakeAssertionContext(mctx), e.arg.UserAssertion) 772 if err == nil { 773 e.remoteAssertion, e.localAssertion = libkb.CollectAssertions(e.themAssertion) 774 } 775 return err 776 } 777 778 func (e *Identify2WithUID) useAnyAssertions() bool { 779 return e.useLocalAssertions() || e.useRemoteAssertions() 780 } 781 782 func (e *Identify2WithUID) allowCaching() bool { 783 return e.arg.IdentifyBehavior.AllowCaching() 784 } 785 786 func (e *Identify2WithUID) useLocalAssertions() bool { 787 return e.localAssertion.Len() > 0 788 } 789 790 // If we need a ProofSet, it's as if we need remote assertions. 791 func (e *Identify2WithUID) useRemoteAssertions() bool { 792 return (e.remoteAssertion.Len() > 0) 793 } 794 795 func (e *Identify2WithUID) runIdentifyPrecomputation() (err error) { 796 797 keyDiffDisplayHook := func(k keybase1.IdentifyKey) error { 798 e.identifyKeys = append(e.identifyKeys, k) 799 return nil 800 } 801 revokedKeyHook := func(id libkb.TrackIDComponent, diff libkb.TrackDiff) { 802 if diff == nil { 803 return 804 } 805 ipb := keybase1.IdentifyProofBreak{ 806 RemoteProof: libkb.ExportTrackIDComponentToRevokedProof(id).Proof, 807 Lcr: keybase1.LinkCheckResult{ 808 Diff: libkb.ExportTrackDiff(diff), 809 BreaksTracking: true, 810 }, 811 } 812 if e.trackBreaks == nil { 813 e.trackBreaks = &keybase1.IdentifyTrackBreaks{} 814 } 815 e.trackBreaks.Proofs = append(e.trackBreaks.Proofs, ipb) 816 } 817 e.state.Precompute(keyDiffDisplayHook, revokedKeyHook) 818 return nil 819 } 820 821 func (e *Identify2WithUID) displayUserCardAsync(m libkb.MetaContext) <-chan error { 822 // Skip showing the userCard if we are allowing deleted users since this 823 // will error out. 824 if e.arg.IdentifyBehavior.SkipUserCard() || e.G().Env.GetReadDeletedSigChain() { 825 return nil 826 } 827 return libkb.DisplayUserCardAsync(m, e.them.GetUID(), (e.me != nil)) 828 } 829 830 func (e *Identify2WithUID) setupIdentifyUI(m libkb.MetaContext) libkb.MetaContext { 831 if e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() { 832 m.Debug("| using the loopback identify UI") 833 iui := NewLoopbackIdentifyUI(m.G(), &e.trackBreaks) 834 m = m.WithIdentifyUI(iui) 835 } else if e.useTracking && e.arg.CanSuppressUI && !e.arg.ForceDisplay { 836 iui := newBufferedIdentifyUI(m.G(), m.UIs().IdentifyUI, keybase1.ConfirmResult{ 837 IdentityConfirmed: true, 838 }) 839 m = m.WithIdentifyUI(iui) 840 } 841 return m 842 } 843 844 func (e *Identify2WithUID) runIdentifyUI(m libkb.MetaContext) (err error) { 845 n := fmt.Sprintf("+ runIdentifyUI(%s)", e.them.GetName()) 846 defer m.Trace(n, &err)() 847 848 // RemoteReceived, start with the baseProofSet that has PGP 849 // fingerprints and the user's UID and username. 850 e.remotesReceived = e.them.BaseProofSet() 851 852 m = e.setupIdentifyUI(m) 853 iui := m.UIs().IdentifyUI 854 855 m.Debug("| IdentifyUI.Start(%s)", e.them.GetName()) 856 if err = iui.Start(m, e.them.GetName(), e.arg.Reason, e.arg.ForceDisplay); err != nil { 857 return err 858 } 859 for _, k := range e.identifyKeys { 860 if err = iui.DisplayKey(m, k); err != nil { 861 return err 862 } 863 } 864 m.Debug("| IdentifyUI.ReportLastTrack(%s)", e.them.GetName()) 865 if err = iui.ReportLastTrack(m, libkb.ExportTrackSummary(e.state.TrackLookup(), e.them.GetName())); err != nil { 866 return err 867 } 868 m.Debug("| IdentifyUI.LaunchNetworkChecks(%s)", e.them.GetName()) 869 if err = iui.LaunchNetworkChecks(m, e.state.ExportToUncheckedIdentity(m), e.them.Export()); err != nil { 870 return err 871 } 872 873 waiter := e.displayUserCardAsync(m) 874 875 m.Debug("| IdentifyUI.Identify(%s)", e.them.GetName()) 876 var them *libkb.User 877 them, err = e.them.User(e.getCache()) 878 if err != nil { 879 return err 880 } 881 882 identifyTableMode := libkb.IdentifyTableModeActive 883 if e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() { 884 identifyTableMode = libkb.IdentifyTableModePassive 885 } 886 887 // When we get a callback from IDTable().Identify, we don't get to thread our metacontext 888 // through (for now), so stash it in the this. 889 e.metaContext = m 890 if them.IDTable() == nil { 891 m.Debug("| No IDTable for user") 892 } else if err = them.IDTable().Identify(m, e.state, e.forceRemoteCheck(), iui, e, identifyTableMode); err != nil { 893 m.Debug("| Failure in running IDTable") 894 return err 895 } 896 897 if waiter != nil { 898 m.Debug("+ Waiting for UserCard") 899 if err = <-waiter; err != nil { 900 m.Debug("| Failure in showing UserCard") 901 return err 902 } 903 m.Debug("- Waited for UserCard") 904 } 905 906 // use Confirm to display the IdentifyOutcome 907 outcome := e.state.Result() 908 outcome.TrackOptions = e.trackOptions 909 e.confirmResult, err = iui.Confirm(m, outcome.Export(e.G())) 910 if err != nil { 911 m.Debug("| Failure in iui.Confirm") 912 return err 913 } 914 915 err = e.insertTrackToken(m, outcome) 916 if err != nil { 917 m.Debug("| Error inserting track token: %+v", err) 918 } 919 920 if err = iui.Finish(m); err != nil { 921 m.Debug("| Failure in iui.Finish") 922 return err 923 } 924 m.Debug("| IdentifyUI.Finished(%s)", e.them.GetName()) 925 926 err = e.checkRemoteAssertions([]keybase1.ProofState{keybase1.ProofState_OK}) 927 e.maybeCacheResult(m) 928 929 m.Debug("| IdentifyUI: checked remote assertions (%s)", e.them.GetName()) 930 931 if err == nil && !e.arg.NoErrorOnTrackFailure { 932 // We only care about tracking errors in this case; hence GetErrorLax 933 _, err = e.state.Result().GetErrorLax() 934 } 935 936 if outcome.IsOK() { 937 e.maybeNotify(m, "runIdentifyUI complete IsOk") 938 } 939 940 return err 941 } 942 943 func (e *Identify2WithUID) forceRemoteCheck() bool { 944 return e.arg.ForceRemoteCheck || (e.testArgs != nil && e.testArgs.forceRemoteCheck) 945 } 946 947 func (e *Identify2WithUID) createIdentifyState(m libkb.MetaContext) (err error) { 948 defer m.Trace("createIdentifyState", &err)() 949 var them *libkb.User 950 them, err = e.them.User(e.getCache()) 951 if err != nil { 952 return err 953 } 954 955 e.state = libkb.NewIdentifyStateWithGregorItem(m.G(), e.responsibleGregorItem, them) 956 957 if e.testArgs != nil && e.testArgs.tcl != nil { 958 m.Debug("| using test track") 959 e.useTracking = true 960 e.state.SetTrackLookup(e.testArgs.tcl) 961 return nil 962 } 963 964 if e.me == nil { 965 m.Debug("| null me") 966 return nil 967 } 968 969 tcl, err := e.me.trackChainLinkFor(m, them.GetNormalizedName(), them.GetUID()) 970 if tcl != nil { 971 m.Debug("| using track token %s", tcl.LinkID()) 972 e.useTracking = true 973 e.state.SetTrackLookup(tcl) 974 if ttcl, _ := libkb.TmpTrackChainLinkFor(m, e.me.GetUID(), them.GetUID()); ttcl != nil { 975 m.Debug("| also have temporary track") 976 e.state.SetTmpTrackLookup(ttcl) 977 } 978 } 979 980 return nil 981 } 982 983 // RequiredUIs returns the required UIs. 984 func (e *Identify2WithUID) RequiredUIs() []libkb.UIKind { 985 ret := []libkb.UIKind{} 986 if e.arg == nil || !e.arg.IdentifyBehavior.ShouldSuppressTrackerPopups() { 987 ret = append(ret, libkb.IdentifyUIKind) 988 } 989 return ret 990 } 991 992 // SubConsumers returns the other UI consumers for this engine. 993 func (e *Identify2WithUID) SubConsumers() []libkb.UIConsumer { 994 return nil 995 } 996 997 func (e *Identify2WithUID) isSelfLoad() bool { 998 if e.testArgs != nil && e.testArgs.selfLoad { 999 return true 1000 } 1001 return e.me != nil && e.them != nil && e.me.Equal(e.them) 1002 } 1003 1004 func (e *Identify2WithUID) loadUserOpts(arg libkb.LoadUserArg) libkb.LoadUserArg { 1005 if !e.allowCaching() { 1006 arg = arg.WithForcePoll(true) 1007 } 1008 return arg 1009 } 1010 1011 func (e *Identify2WithUID) loadMe(m libkb.MetaContext, uid keybase1.UID) (err error) { 1012 1013 // Short circuit loadMe for testing 1014 if e.testArgs != nil && e.testArgs.noMe { 1015 return nil 1016 } 1017 arg := libkb.NewLoadUserArgWithMetaContext(m).WithUID(uid).WithSelf(true).WithStubMode(libkb.StubModeUnstubbed) 1018 e.me, err = loadIdentifyUser(m, e.loadUserOpts(arg), e.getCache()) 1019 return err 1020 } 1021 1022 func (e *Identify2WithUID) loadThem(m libkb.MetaContext) (err error) { 1023 arg := e.loadUserOpts(libkb.NewLoadUserArgWithMetaContext(m).WithUID(e.arg.Uid).WithResolveBody(e.ResolveBody).WithPublicKeyOptional()) 1024 e.them, err = loadIdentifyUser(m, arg, e.getCache()) 1025 if err != nil { 1026 switch err.(type) { 1027 case libkb.NoKeyError: 1028 // convert this error to NoSigChainError 1029 return libkb.NoSigChainError{} 1030 case libkb.NotFoundError: 1031 return libkb.UserNotFoundError{UID: e.arg.Uid, Msg: "in Identify2WithUID"} 1032 default: // including libkb.UserDeletedError 1033 return err 1034 } 1035 } 1036 if e.them == nil { 1037 return libkb.UserNotFoundError{UID: e.arg.Uid, Msg: "in Identify2WithUID"} 1038 } 1039 err = libkb.UserErrorFromStatus(e.them.GetStatus()) 1040 if _, ok := err.(libkb.UserDeletedError); ok && e.arg.IdentifyBehavior.AllowDeletedUsers() || e.G().Env.GetReadDeletedSigChain() { 1041 e.them.isDeleted = true 1042 return nil 1043 } 1044 return err 1045 } 1046 1047 func (e *Identify2WithUID) loadUsers(m libkb.MetaContext) (err error) { 1048 var loadMeErr, loadThemErr error 1049 1050 var selfLoad bool 1051 var wg sync.WaitGroup 1052 1053 if !e.arg.ActLoggedOut { 1054 loggedIn, myUID := isLoggedIn(m) 1055 if loggedIn { 1056 selfLoad = myUID.Equal(e.arg.Uid) 1057 wg.Add(1) 1058 go func() { 1059 loadMeErr = e.loadMe(m, myUID) 1060 wg.Done() 1061 }() 1062 } 1063 } 1064 1065 if !selfLoad { 1066 wg.Add(1) 1067 go func() { 1068 loadThemErr = e.loadThem(m) 1069 wg.Done() 1070 }() 1071 } 1072 wg.Wait() 1073 1074 if loadMeErr != nil { 1075 return loadMeErr 1076 } 1077 if loadThemErr != nil { 1078 return loadThemErr 1079 } 1080 1081 if selfLoad { 1082 e.them = e.me 1083 } 1084 1085 return nil 1086 } 1087 1088 func (e *Identify2WithUID) checkFastCacheHit(m libkb.MetaContext) (hit bool) { 1089 prfx := fmt.Sprintf("Identify2WithUID#checkFastCacheHit(%s)", e.arg.Uid) 1090 defer m.Trace(prfx, nil)() 1091 if e.getCache() == nil { 1092 return false 1093 } 1094 1095 fn := func(u keybase1.Identify2ResUPK2) keybase1.Time { return u.Upk.Uvv.CachedAt } 1096 dfn := func(u keybase1.Identify2ResUPK2) time.Duration { 1097 return libkb.Identify2CacheShortTimeout 1098 } 1099 u, err := e.getCache().Get(e.arg.Uid, fn, dfn, e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks()) 1100 1101 if err != nil { 1102 m.Debug("| fast cache error for %s: %s", e.arg.Uid, err) 1103 } 1104 if u == nil { 1105 m.Debug("| fast cache returning false on nil output") 1106 return false 1107 } 1108 e.cachedRes = u 1109 return true 1110 } 1111 1112 func (e *Identify2WithUID) dbKey(them keybase1.UID) libkb.DbKey { 1113 return libkb.DbKey{ 1114 Typ: libkb.DBIdentify, 1115 Key: fmt.Sprintf("%s-%s", e.me.GetUID(), them), 1116 } 1117 } 1118 1119 func (e *Identify2WithUID) loadSlowCacheFromDB(m libkb.MetaContext) (ret *keybase1.Identify2ResUPK2) { 1120 defer m.Trace("Identify2WithUID#loadSlowCacheFromDB", nil)() 1121 1122 if e.getCache() != nil && !e.getCache().UseDiskCache() { 1123 m.Debug("| Disk cached disabled") 1124 return nil 1125 } 1126 1127 var ktm keybase1.Time 1128 key := e.dbKey(e.them.GetUID()) 1129 found, err := e.G().LocalDb.GetInto(&ktm, key) 1130 if err != nil { 1131 m.Debug("| Error loading key %+v from cache: %s", key, err) 1132 return nil 1133 } 1134 if !found { 1135 m.Debug("| Key wasn't found: %+v", key) 1136 return nil 1137 } 1138 tm := ktm.Time() 1139 now := e.getNow(m) 1140 diff := now.Sub(tm) 1141 if diff > libkb.Identify2CacheLongTimeout { 1142 m.Debug("| Object timed out %s ago", diff) 1143 return nil 1144 } 1145 var tmp keybase1.Identify2ResUPK2 1146 upk2ai, err := e.them.ExportToUserPlusKeysV2AllIncarnations() 1147 if err != nil { 1148 m.Warning("| Failed to export: %s", err) 1149 return nil 1150 } 1151 tmp.Upk = *upk2ai 1152 tmp.IdentifiedAt = ktm 1153 ret = &tmp 1154 return ret 1155 } 1156 1157 // Store (meUID, themUID) -> SuccessfulIDTime as we cache users to the slow cache. 1158 // Thus, after a cold boot, we don't start up with a cold identify cache. 1159 func (e *Identify2WithUID) storeSlowCacheToDB(m libkb.MetaContext) (err error) { 1160 prfx := fmt.Sprintf("Identify2WithUID#storeSlowCacheToDB(%s)", e.them.GetUID()) 1161 defer e.G().Trace(prfx, &err)() 1162 if e.me == nil { 1163 m.Debug("not storing to persistent slow cache since no me user") 1164 return nil 1165 } 1166 1167 key := e.dbKey(e.them.GetUID()) 1168 now := keybase1.ToTime(e.getNow(m)) 1169 err = e.G().LocalDb.PutObj(key, nil, now) 1170 return err 1171 } 1172 1173 // Remove (themUID) from the identify cache, if they're there. 1174 func (e *Identify2WithUID) removeSlowCacheFromDB(m libkb.MetaContext) (err error) { 1175 prfx := fmt.Sprintf("Identify2WithUID#removeSlowCacheFromDB(%s)", e.them.GetUID()) 1176 defer e.G().Trace(prfx, &err)() 1177 if e.me == nil { 1178 m.Debug("not removing from persistent slow cache since no me user") 1179 return nil 1180 } 1181 key := e.dbKey(e.them.GetUID()) 1182 err = e.G().LocalDb.Delete(key) 1183 return err 1184 } 1185 1186 func (e *Identify2WithUID) checkSlowCacheHit(m libkb.MetaContext) (ret bool) { 1187 prfx := fmt.Sprintf("Identify2WithUID#checkSlowCacheHit(%s)", e.them.GetUID()) 1188 defer m.Trace(prfx, nil)() 1189 1190 if e.getCache() == nil { 1191 return false 1192 } 1193 1194 if !e.allowCaching() { 1195 m.Debug("| missed fast cache: no caching allowed") 1196 return false 1197 } 1198 1199 timeFn := func(u keybase1.Identify2ResUPK2) keybase1.Time { return u.IdentifiedAt } 1200 durationFn := func(u keybase1.Identify2ResUPK2) time.Duration { 1201 if u.TrackBreaks != nil { 1202 return libkb.Identify2CacheBrokenTimeout 1203 } 1204 return libkb.Identify2CacheLongTimeout 1205 } 1206 u, err := e.getCache().Get(e.them.GetUID(), timeFn, durationFn, e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks()) 1207 1208 trackBrokenError := false 1209 if err != nil { 1210 m.Debug("| slow cache error for %s: %s", e.them.GetUID(), err) 1211 if _, ok := err.(libkb.TrackBrokenError); ok { 1212 trackBrokenError = true 1213 } 1214 } 1215 1216 if u == nil && e.me != nil && !trackBrokenError { 1217 u = e.loadSlowCacheFromDB(m) 1218 } 1219 1220 if u == nil { 1221 m.Debug("| %s: identify missed cache", prfx) 1222 return false 1223 } 1224 1225 if !e.them.IsCachedIdentifyFresh(&u.Upk) { 1226 m.Debug("| %s: cached identify was stale", prfx) 1227 return false 1228 } 1229 1230 e.cachedRes = u 1231 1232 // Update so that it hits the fast cache the next time 1233 u.Upk.Uvv.CachedAt = keybase1.ToTime(e.getNow(m)) 1234 err = e.getCache().Insert(u) 1235 if err != nil { 1236 m.Debug("| error on insert: %+v", err) 1237 } 1238 return true 1239 } 1240 1241 // Result will return (non-nil,nil) on success, and (nil,non-nil) on failure. 1242 func (e *Identify2WithUID) Result(m libkb.MetaContext) (*keybase1.Identify2ResUPK2, error) { 1243 if e.cachedRes != nil { 1244 return e.cachedRes, nil 1245 } 1246 res, err := e.exportToResult(m) 1247 if err != nil { 1248 return nil, err 1249 } 1250 if res == nil { 1251 return nil, libkb.UserNotFoundError{Msg: "identify2 unexpectly returned an empty user"} 1252 } 1253 return res, nil 1254 } 1255 1256 func (e *Identify2WithUID) GetProofSet() *libkb.ProofSet { 1257 return e.remotesReceived 1258 } 1259 1260 func (e *Identify2WithUID) GetIdentifyOutcome() *libkb.IdentifyOutcome { 1261 return e.state.Result() 1262 } 1263 1264 func (e *Identify2WithUID) toUserPlusKeysv2AllIncarnations() (*keybase1.UserPlusKeysV2AllIncarnations, error) { 1265 return e.them.ExportToUserPlusKeysV2AllIncarnations() 1266 } 1267 1268 func (e *Identify2WithUID) getCache() libkb.Identify2Cacher { 1269 if e.testArgs != nil && e.testArgs.cache != nil { 1270 return e.testArgs.cache 1271 } 1272 if e.testArgs != nil && e.testArgs.noCache { 1273 return nil 1274 } 1275 return e.G().Identify2Cache() 1276 } 1277 1278 func (e *Identify2WithUID) getTrackType() identify2TrackType { 1279 switch { 1280 case !e.useTracking || e.state.Result() == nil: 1281 return identify2NoTrack 1282 case e.state.Result().IsOK(): 1283 return identify2TrackOK 1284 default: 1285 return identify2TrackBroke 1286 } 1287 } 1288 1289 func (e *Identify2WithUID) SetResponsibleGregorItem(item gregor.Item) { 1290 e.responsibleGregorItem = item 1291 } 1292 1293 func (e *Identify2WithUID) TrackToken() keybase1.TrackToken { 1294 return e.trackToken 1295 } 1296 1297 func (e *Identify2WithUID) ConfirmResult() keybase1.ConfirmResult { 1298 return e.confirmResult 1299 } 1300 1301 func (e *Identify2WithUID) FullMeUser() *libkb.User { 1302 if e.me == nil { 1303 return nil 1304 } 1305 return e.me.Full() 1306 } 1307 1308 func (e *Identify2WithUID) FullThemUser() *libkb.User { 1309 if e.them == nil { 1310 return nil 1311 } 1312 return e.them.Full() 1313 } 1314 1315 func (e *Identify2WithUID) maybeNotify(mctx libkb.MetaContext, explanation string) { 1316 target := e.arg.Uid 1317 if e.them != nil { 1318 target = e.them.GetUID() 1319 } 1320 if e.me == nil { 1321 // This check is needed because ActLoggedOut causes the untracked fast path 1322 // to succeed even when the true active user is tracking the identifyee. 1323 mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope missing ME", target, explanation) 1324 return 1325 } 1326 if target.IsNil() { 1327 mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope missing UID", target, explanation) 1328 return 1329 } 1330 if e.arg.IdentifyBehavior.WarningInsteadOfErrorOnBrokenTracks() { 1331 mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) nope WarningInsteadOfErrorOnBrokenTracks", target, explanation) 1332 return 1333 } 1334 mctx.Debug("Identify2WithUID.maybeNotify(%v, %v) -> sending", target, explanation) 1335 go mctx.G().IdentifyDispatch.NotifyTrackingSuccess(mctx, target) 1336 }