github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/ftl.go (about)

     1  package teams
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/keybase/client/go/gregor"
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/keybase/client/go/sig3"
    13  	"github.com/keybase/client/go/teams/hidden"
    14  	storage "github.com/keybase/client/go/teams/storage"
    15  )
    16  
    17  //
    18  // ftl.go
    19  //
    20  // Fast Team chain Loader
    21  //
    22  // Routines for fast-team loading. In fast team loading, we ignore most signatures
    23  // and use the Merkle Tree as a source of truth. This is good enough for getting
    24  // directly into a chat, where it's not necessary to see a list of team members.
    25  //
    26  
    27  // FastTeamChainLoader loads teams using the "fast approach." It doesn't compute
    28  // membership or check any signatures. It just checks for consistency against the merkle
    29  // tree, and audits that the merkle tree is being faithfully constructed.
    30  type FastTeamChainLoader struct {
    31  
    32  	// context for loading things from the outside world.
    33  	world LoaderContext
    34  
    35  	// single-flight lock on TeamID
    36  	locktab *libkb.LockTable
    37  
    38  	// Hold onto FastTeamLoad by-products as long as we have room, and store
    39  	// them persistently to disk.
    40  	storage *storage.FTLStorage
    41  
    42  	// We can get pushed by the server into "force repoll" mode, in which we're
    43  	// not getting cache invalidations. An example: when Coyne or Nojima revokes
    44  	// a device. We want to cut down on notification spam. So instead, all attempts
    45  	// to load a team result in a preliminary poll for freshness, which this state is enabled.
    46  	forceRepollMutex sync.RWMutex
    47  	forceRepollUntil gregor.TimeOrOffset
    48  }
    49  
    50  const FTLVersion = 1
    51  
    52  // NewFastLoader makes a new fast loader and initializes it.
    53  func NewFastTeamLoader(g *libkb.GlobalContext) *FastTeamChainLoader {
    54  	ret := &FastTeamChainLoader{
    55  		world:   NewLoaderContextFromG(g),
    56  		locktab: libkb.NewLockTable(),
    57  	}
    58  	ret.storage = storage.NewFTLStorage(g, ret.upgradeStoredState)
    59  	return ret
    60  }
    61  
    62  // NewFastTeamLoaderAndInstall creates a new loader and installs it into G.
    63  func NewFastTeamLoaderAndInstall(g *libkb.GlobalContext) *FastTeamChainLoader {
    64  	l := NewFastTeamLoader(g)
    65  	g.SetFastTeamLoader(l)
    66  	g.AddLogoutHook(l, "fastTeamLoader")
    67  	g.AddDbNukeHook(l, "fastTeamLoader")
    68  	return l
    69  }
    70  
    71  var _ libkb.FastTeamLoader = (*FastTeamChainLoader)(nil)
    72  
    73  func ftlLogTag(m libkb.MetaContext) libkb.MetaContext {
    74  	return m.WithLogTag("FTL")
    75  }
    76  
    77  func FTL(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) {
    78  	return m.G().GetFastTeamLoader().Load(m, arg)
    79  }
    80  
    81  type ftlCombinedData struct {
    82  	visible *keybase1.FastTeamData
    83  	hidden  *keybase1.HiddenTeamChain
    84  }
    85  
    86  func newFTLCombinedData(v *keybase1.FastTeamData, h *keybase1.HiddenTeamChain) ftlCombinedData {
    87  	return ftlCombinedData{visible: v, hidden: h}
    88  }
    89  
    90  func (f ftlCombinedData) latestKeyGeneration() keybase1.PerTeamKeyGeneration {
    91  	ret := f.visible.LatestKeyGeneration
    92  	g := f.hidden.MaxReaderPerTeamKeyGeneration()
    93  	if ret < g {
    94  		ret = g
    95  	}
    96  	return ret
    97  }
    98  
    99  func (f ftlCombinedData) perTeamKey(g keybase1.PerTeamKeyGeneration) *keybase1.PerTeamKey {
   100  	ret, ok := f.visible.Chain.PerTeamKeys[g]
   101  	if ok {
   102  		return &ret
   103  	}
   104  	ret, ok = f.hidden.GetReaderPerTeamKeyAtGeneration(g)
   105  	if ok {
   106  		return &ret
   107  	}
   108  	return nil
   109  }
   110  
   111  // Load fast-loads the given team. Provide some hints as to how to load it. You can specify an application
   112  // and key generations needed, if you are entering chat. Those links will be returned unstubbed
   113  // from the server, and then the keys can be output in the result.
   114  func (f *FastTeamChainLoader) Load(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) {
   115  	m = ftlLogTag(m)
   116  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#Load(%+v)", arg), &err)()
   117  	originalArg := arg.DeepCopy()
   118  
   119  	res, err = f.loadOneAttempt(m, arg)
   120  	if err != nil {
   121  		return res, err
   122  	}
   123  
   124  	if arg.AssertTeamName != nil && !arg.AssertTeamName.Eq(res.Name) {
   125  		m.Debug("Did not get expected subteam name; will reattempt with forceRefresh (%s != %s)", arg.AssertTeamName.String(), res.Name.String())
   126  		arg.ForceRefresh = true
   127  		res, err = f.loadOneAttempt(m, arg)
   128  		if err != nil {
   129  			return res, err
   130  		}
   131  		if !arg.AssertTeamName.Eq(res.Name) {
   132  			return res, NewBadNameError(fmt.Sprintf("After force-refresh, still bad team name: wanted %s, but got %s", arg.AssertTeamName.String(), res.Name.String()))
   133  		}
   134  	}
   135  
   136  	if ShouldRunBoxAudit(m) {
   137  		newM, shouldReload := VerifyBoxAudit(m, res.Name.ToTeamID(arg.Public))
   138  		if shouldReload {
   139  			return f.Load(newM, originalArg)
   140  		}
   141  	} else {
   142  		m.Debug("Box auditor feature flagged off; not checking jail during ftl team load...")
   143  	}
   144  
   145  	return res, nil
   146  }
   147  
   148  // VerifyTeamName verifies that the given ID aligns with the given name, using the Merkle tree only
   149  // (and not verifying sigs along the way).
   150  func (f *FastTeamChainLoader) VerifyTeamName(m libkb.MetaContext, id keybase1.TeamID, name keybase1.TeamName, forceRefresh bool) (err error) {
   151  	m = m.WithLogTag("FTL")
   152  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#VerifyTeamName(%v,%s)", id, name.String()), &err)()
   153  	_, err = f.Load(m, keybase1.FastTeamLoadArg{
   154  		ID:                    id,
   155  		Public:                id.IsPublic(),
   156  		AssertTeamName:        &name,
   157  		ForceRefresh:          forceRefresh,
   158  		HiddenChainIsOptional: true,
   159  	})
   160  	return err
   161  }
   162  
   163  func (f *FastTeamChainLoader) loadOneAttempt(m libkb.MetaContext, arg keybase1.FastTeamLoadArg) (res keybase1.FastTeamLoadRes, err error) {
   164  
   165  	if arg.ID.IsPublic() != arg.Public {
   166  		return res, NewBadPublicError(arg.ID, arg.Public)
   167  	}
   168  
   169  	flr, err := f.load(m, fastLoadArg{FastTeamLoadArg: arg})
   170  	if err != nil {
   171  		return res, err
   172  	}
   173  
   174  	res.ApplicationKeys = flr.applicationKeys
   175  	res.Name, err = f.verifyTeamNameViaParentLoad(m, arg.ID, arg.Public, flr.unverifiedName, flr.upPointer, arg.ID, arg.ForceRefresh)
   176  	if err != nil {
   177  		return res, err
   178  	}
   179  
   180  	return res, nil
   181  }
   182  
   183  // verifyTeamNameViaParentLoad takes a team ID, and a pointer to a parent team's sigchain, and computes
   184  // the full resolved team name. If the pointer is null, we'll assume this is a root team and do the
   185  // verification via hash-comparison.
   186  func (f *FastTeamChainLoader) verifyTeamNameViaParentLoad(m libkb.MetaContext, id keybase1.TeamID, isPublic bool, unverifiedName keybase1.TeamName, parent *keybase1.UpPointer, bottomSubteam keybase1.TeamID, forceRefresh bool) (res keybase1.TeamName, err error) {
   187  
   188  	if parent == nil {
   189  		if !unverifiedName.IsRootTeam() {
   190  			return res, NewBadNameError("expected a root team")
   191  		}
   192  		if !unverifiedName.ToTeamID(isPublic).Eq(id) {
   193  			return res, NewBadNameError("root team v. team ID mismatch")
   194  		}
   195  		return unverifiedName, nil
   196  	}
   197  
   198  	if parent.ParentID.IsPublic() != isPublic {
   199  		return res, NewBadPublicError(parent.ParentID, isPublic)
   200  	}
   201  
   202  	parentRes, err := f.load(m, fastLoadArg{
   203  		FastTeamLoadArg: keybase1.FastTeamLoadArg{
   204  			ID:                    parent.ParentID,
   205  			Public:                isPublic,
   206  			ForceRefresh:          forceRefresh,
   207  			HiddenChainIsOptional: true, // we do not need to see the hidden chain for the parent
   208  		},
   209  		downPointersNeeded: []keybase1.Seqno{parent.ParentSeqno},
   210  		needLatestName:     true,
   211  		readSubteamID:      bottomSubteam,
   212  	})
   213  	if err != nil {
   214  		return res, err
   215  	}
   216  	downPointer, ok := parentRes.downPointers[parent.ParentSeqno]
   217  	if !ok {
   218  		return res, NewBadNameError("down pointer not found in parent")
   219  	}
   220  	suffix := downPointer.NameComponent
   221  
   222  	parentName, err := f.verifyTeamNameViaParentLoad(m, parent.ParentID, isPublic, parentRes.unverifiedName, parentRes.upPointer, bottomSubteam, forceRefresh)
   223  	if err != nil {
   224  		return res, err
   225  	}
   226  
   227  	return parentName.Append(suffix)
   228  }
   229  
   230  // fastLoadRes is used internally to convey the results of the #load() call.
   231  type fastLoadRes struct {
   232  	applicationKeys []keybase1.TeamApplicationKey
   233  	unverifiedName  keybase1.TeamName
   234  	downPointers    map[keybase1.Seqno]keybase1.DownPointer
   235  	upPointer       *keybase1.UpPointer
   236  }
   237  
   238  // fastLoadArg is used internally to pass arguments to the #load() call. It is a small wrapper
   239  // around the keybase1.FastTeamLoadArg that's passed through to the public #Load() call.
   240  type fastLoadArg struct {
   241  	keybase1.FastTeamLoadArg
   242  	downPointersNeeded []keybase1.Seqno
   243  	readSubteamID      keybase1.TeamID
   244  	needLatestName     bool
   245  	forceReset         bool
   246  }
   247  
   248  // needChainTail returns true if the argument mandates that we need a reasonably up-to-date chain tail,
   249  // let's say to figure out what this team is currently named, or to figure out the most recent
   250  // encryption key to encrypt new messages for.
   251  func (a fastLoadArg) needChainTail() bool {
   252  	return a.needLatestName || a.NeedLatestKey
   253  }
   254  
   255  // load acquires a lock by team ID, and the runs loadLockedWithRetries.
   256  func (f *FastTeamChainLoader) load(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) {
   257  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#load(%+v)", arg), &err)()
   258  
   259  	// Single-flight lock by team ID.
   260  	lock := f.locktab.AcquireOnName(m.Ctx(), m.G(), arg.ID.String())
   261  	defer lock.Release(m.Ctx())
   262  
   263  	res, err = f.loadLockedWithRetries(m, arg)
   264  	if hidden.ShouldClearSupportFlagOnError(err) {
   265  		m.Debug("Clearing support hidden chain flag for team %s because of error %v in FTL", arg.ID, err)
   266  		m.G().GetHiddenTeamChainManager().ClearSupportFlagIfFalse(m, arg.ID)
   267  	}
   268  	return res, err
   269  }
   270  
   271  // loadLockedWithRetries attempts two loads of the team. If the first iteration returns an FTLMissingSeedError,
   272  // we'll blast through the cache and attempt a full reload a second time. Then that's for all the marbles.
   273  func (f *FastTeamChainLoader) loadLockedWithRetries(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) {
   274  
   275  	for i := 0; i < 2; i++ {
   276  		res, err = f.loadLocked(m, arg)
   277  		if err == nil {
   278  			return res, err
   279  		}
   280  		if _, ok := err.(FTLMissingSeedError); !ok {
   281  			return nil, err
   282  		}
   283  		m.Debug("Got retriable error %s; will force reset", err)
   284  		arg.forceReset = true
   285  	}
   286  	return res, err
   287  }
   288  
   289  // dervieSeedAtGeneration either goes to cache or rederives the PTK private seed
   290  // for the given generation gen.
   291  func (f *FastTeamChainLoader) deriveSeedAtGeneration(m libkb.MetaContext, gen keybase1.PerTeamKeyGeneration, dat ftlCombinedData) (seed keybase1.PerTeamKeySeed, err error) {
   292  
   293  	state := dat.visible
   294  
   295  	seed, ok := state.Chain.PerTeamKeySeedsVerified[gen]
   296  	if ok {
   297  		return seed, nil
   298  	}
   299  
   300  	var tmp keybase1.PerTeamKeySeed
   301  	tmp, ok = state.PerTeamKeySeedsUnverified[gen]
   302  	if !ok {
   303  		// See CORE-9207. We can hit this case if we previously loaded a parent team for verifying a subteam
   304  		// name before we were members of the team, and then later get added to the team, and then try to
   305  		// reload the team. We didn't have boxes from the first time around, so just force a full reload.
   306  		// It's inefficient but it's a very rare case.
   307  		return seed, NewFTLMissingSeedError(gen)
   308  	}
   309  
   310  	ptkChain := dat.perTeamKey(gen)
   311  	if ptkChain == nil {
   312  		return seed, NewFastLoadError(fmt.Sprintf("no per team key public halves at generation %d", gen))
   313  	}
   314  
   315  	check, ok := state.SeedChecks[gen]
   316  	if !ok {
   317  		return seed, NewFastLoadError(fmt.Sprintf("no per team key seed check at %d", gen))
   318  	}
   319  
   320  	km, err := NewTeamKeyManagerWithSecret(state.ID(), tmp, gen, &check)
   321  	if err != nil {
   322  		return seed, err
   323  	}
   324  
   325  	sigKey, err := km.SigningKey()
   326  	if err != nil {
   327  		return seed, err
   328  	}
   329  
   330  	if !ptkChain.SigKID.SecureEqual(sigKey.GetKID()) {
   331  		m.Debug("sig KID gen:%v (local) %v != %v (chain)", gen, sigKey.GetKID(), ptkChain.SigKID)
   332  		return seed, NewFastLoadError(fmt.Sprintf("wrong team key (sig) found at generation %v", gen))
   333  	}
   334  
   335  	encKey, err := km.EncryptionKey()
   336  	if err != nil {
   337  		return seed, err
   338  	}
   339  
   340  	if !ptkChain.EncKID.SecureEqual(encKey.GetKID()) {
   341  		m.Debug("enc KID gen:%v (local) %v != %v (chain)", gen, encKey.GetKID(), ptkChain.EncKID)
   342  		return seed, NewFastLoadError(fmt.Sprintf("wrong team key (enc) found at generation %v", gen))
   343  	}
   344  
   345  	// write back to cache
   346  	seed = tmp
   347  	state.Chain.PerTeamKeySeedsVerified[gen] = seed
   348  	return seed, err
   349  }
   350  
   351  // deriveKeyForApplicationAtGeneration pulls from cache or generates the PTK for the
   352  // given application at the given generation.
   353  func (f *FastTeamChainLoader) deriveKeyForApplicationAtGeneration(m libkb.MetaContext, app keybase1.TeamApplication, gen keybase1.PerTeamKeyGeneration, dat ftlCombinedData) (key keybase1.TeamApplicationKey, err error) {
   354  
   355  	seed, err := f.deriveSeedAtGeneration(m, gen, dat)
   356  	if err != nil {
   357  		return key, err
   358  	}
   359  
   360  	var mask *keybase1.MaskB64
   361  	if m := dat.visible.ReaderKeyMasks[app]; m != nil {
   362  		tmp, ok := m[gen]
   363  		if ok {
   364  			mask = &tmp
   365  		}
   366  	}
   367  	if mask == nil {
   368  		m.Debug("Could not get reader key mask for <%s,%d>", app, gen)
   369  		if dat.visible.ID().IsSubTeam() {
   370  			m.Debug("guessing lack of RKM is due to not being an explicit member of the subteam")
   371  			return key, NewNotExplicitMemberOfSubteamError()
   372  		}
   373  		return key, NewFastLoadError("Could not load application keys")
   374  	}
   375  
   376  	rkm := keybase1.ReaderKeyMask{
   377  		Application: app,
   378  		Generation:  gen,
   379  		Mask:        *mask,
   380  	}
   381  	return applicationKeyForMask(rkm, seed)
   382  }
   383  
   384  // deriveKeysForApplication pulls from cache or generates several geneartions of PTKs
   385  // for the given application.
   386  func (f *FastTeamChainLoader) deriveKeysForApplication(m libkb.MetaContext, app keybase1.TeamApplication, arg fastLoadArg, dat ftlCombinedData) (keys []keybase1.TeamApplicationKey, err error) {
   387  
   388  	latestGen := dat.latestKeyGeneration()
   389  
   390  	var didLatest bool
   391  	doKey := func(gen keybase1.PerTeamKeyGeneration) error {
   392  		var key keybase1.TeamApplicationKey
   393  		key, err = f.deriveKeyForApplicationAtGeneration(m, app, gen, dat)
   394  		if err != nil {
   395  			return err
   396  		}
   397  		keys = append(keys, key)
   398  		if gen == latestGen {
   399  			didLatest = true
   400  		}
   401  		return nil
   402  	}
   403  
   404  	if arg.NeedLatestKey {
   405  		// This debug is useful to have since it will spell out which version is the latest in the log
   406  		// if the caller asked for latest.
   407  		m.Debug("FastTeamChainLoader#deriveKeysForApplication: sending back latest at key generation %d", latestGen)
   408  	}
   409  
   410  	for _, gen := range arg.KeyGenerationsNeeded {
   411  		if err = doKey(gen); err != nil {
   412  			return nil, err
   413  		}
   414  	}
   415  	if !didLatest && arg.NeedLatestKey {
   416  		if err = doKey(latestGen); err != nil {
   417  			return nil, err
   418  		}
   419  	}
   420  	return keys, nil
   421  }
   422  
   423  // deriveKeys pulls from cache or generates PTKs for an set of (application X generations)
   424  // pairs, for all in the cartesian product.
   425  func (f *FastTeamChainLoader) deriveKeys(m libkb.MetaContext, arg fastLoadArg, dat ftlCombinedData) (keys []keybase1.TeamApplicationKey, err error) {
   426  	for _, app := range arg.Applications {
   427  		var tmp []keybase1.TeamApplicationKey
   428  		tmp, err = f.deriveKeysForApplication(m, app, arg, dat)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  		keys = append(keys, tmp...)
   433  	}
   434  	return keys, nil
   435  }
   436  
   437  // toResult turns the current fast state into a fastLoadRes.
   438  func (f *FastTeamChainLoader) toResult(m libkb.MetaContext, arg fastLoadArg, dat ftlCombinedData) (res *fastLoadRes, err error) {
   439  	res = &fastLoadRes{
   440  		unverifiedName: dat.visible.Name,
   441  		downPointers:   dat.visible.Chain.DownPointers,
   442  		upPointer:      dat.visible.Chain.LastUpPointer,
   443  	}
   444  	res.applicationKeys, err = f.deriveKeys(m, arg, dat)
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	return res, nil
   449  }
   450  
   451  // findState in cache finds the team ID's state in an in-memory cache.
   452  func (f *FastTeamChainLoader) findStateInCache(m libkb.MetaContext, id keybase1.TeamID) (data *keybase1.FastTeamData, frozen bool, tombstoned bool) {
   453  	return f.storage.Get(m, id, id.IsPublic())
   454  }
   455  
   456  // stateHasKeySeed returns true/false if the state has the seed material for the given
   457  // generation. Either the fully verified PTK seed, or the public portion and
   458  // unverified PTK seed.
   459  func stateHasKeySeed(m libkb.MetaContext, gen keybase1.PerTeamKeyGeneration, state *keybase1.FastTeamData) bool {
   460  	_, foundVerified := state.Chain.PerTeamKeySeedsVerified[gen]
   461  	if foundVerified {
   462  		return true
   463  	}
   464  	_, foundUnverifiedSeed := state.PerTeamKeySeedsUnverified[gen]
   465  	if !foundUnverifiedSeed {
   466  		return false
   467  	}
   468  	_, foundPerTeamKey := state.Chain.PerTeamKeys[gen]
   469  	return foundPerTeamKey
   470  }
   471  
   472  // stateHasKeys checks to see if the given state has the keys specified in the shopping list. If not, it will
   473  // modify the shopping list and return false. If yes, it will leave the shopping list unchanged and return
   474  // true.
   475  func stateHasKeys(m libkb.MetaContext, shoppingList *shoppingList, arg fastLoadArg, data ftlCombinedData) (fresh bool) {
   476  	gens := make(map[keybase1.PerTeamKeyGeneration]struct{})
   477  	state := data.visible
   478  
   479  	fresh = true
   480  
   481  	if arg.NeedLatestKey && !state.LoadedLatest {
   482  		m.Debug("latest was never loaded, we need to load it")
   483  		shoppingList.needMerkleRefresh = true
   484  		shoppingList.needLatestKey = true
   485  		fresh = false
   486  	}
   487  
   488  	// The key generations needed are the ones passed in, and also, potentially, our cached
   489  	// LatestKeyGeneration from the state. It could be that when we go to the server, this is no
   490  	// longer the LatestKeyGeneration, but it might be. It depends. But in either case, we should
   491  	// pull down the mask, since it's a bug to not have it if it turns out the server refresh
   492  	// didn't budge the latest key generation.
   493  	kgn := append([]keybase1.PerTeamKeyGeneration{}, arg.KeyGenerationsNeeded...)
   494  	latestKeyGeneration := data.latestKeyGeneration()
   495  	if arg.NeedLatestKey && state.LoadedLatest && latestKeyGeneration > 0 {
   496  		kgn = append(kgn, latestKeyGeneration)
   497  	}
   498  
   499  	for _, app := range arg.Applications {
   500  		for _, gen := range kgn {
   501  			add := false
   502  			if state.ReaderKeyMasks[app] == nil || state.ReaderKeyMasks[app][gen] == nil {
   503  				m.Debug("state doesn't have mask for <%d,%d>", app, gen)
   504  				add = true
   505  			}
   506  			if !stateHasKeySeed(m, gen, state) {
   507  				m.Debug("state doesn't have key seed for gen=%d", gen)
   508  				add = true
   509  			}
   510  			if add {
   511  				gens[gen] = struct{}{}
   512  				fresh = false
   513  			}
   514  		}
   515  	}
   516  
   517  	shoppingList.applications = append([]keybase1.TeamApplication{}, arg.Applications...)
   518  
   519  	if !fresh {
   520  		for gen := range gens {
   521  			shoppingList.generations = append(shoppingList.generations, gen)
   522  		}
   523  	}
   524  
   525  	// Let's just get all keys from the past, so figure out the minimal seed value that we have.
   526  	shoppingList.seedLow = computeSeedLow(state)
   527  
   528  	return fresh
   529  }
   530  
   531  // stateHasDownPointers checks to see if the given state has the down pointers specified in the shopping list.
   532  // If not, it will change the shopping list to have the down pointers and return false. If yes, it will
   533  // leave the shopping list unchanged and return true.
   534  func stateHasDownPointers(m libkb.MetaContext, shoppingList *shoppingList, arg fastLoadArg, state *keybase1.FastTeamData) (ret bool) {
   535  	ret = true
   536  
   537  	for _, seqno := range arg.downPointersNeeded {
   538  		if _, ok := state.Chain.DownPointers[seqno]; !ok {
   539  			m.Debug("Down pointer at seqno=%d wasn't found", seqno)
   540  			shoppingList.addDownPointer(seqno)
   541  			ret = false
   542  		}
   543  	}
   544  	return ret
   545  }
   546  
   547  // computeSeedLow computes the value for ftl_seed_low that we're going to send up to the server for fetches.
   548  func computeSeedLow(state *keybase1.FastTeamData) keybase1.PerTeamKeyGeneration {
   549  	if state.MaxContinuousPTKGeneration > 0 {
   550  		return state.MaxContinuousPTKGeneration
   551  	}
   552  	var ret keybase1.PerTeamKeyGeneration
   553  	for i := keybase1.PerTeamKeyGeneration(1); i <= state.LatestKeyGeneration; i++ {
   554  		_, found := state.PerTeamKeySeedsUnverified[i]
   555  		if !found {
   556  			break
   557  		}
   558  		ret = i
   559  	}
   560  	state.MaxContinuousPTKGeneration = ret
   561  	return ret
   562  }
   563  
   564  // shoppingList is a list of what we need from the server.
   565  type shoppingList struct {
   566  	needMerkleRefresh bool // if we need to refresh the Merkle path for this team
   567  	needLatestKey     bool // true if we never loaded the latest mask, and need to do it
   568  
   569  	// links *and* PTKs newer than the given seqno. And RKMs for
   570  	// the given apps.
   571  	linksSince   keybase1.Seqno
   572  	downPointers []keybase1.Seqno
   573  
   574  	// The applications we care about.
   575  	applications []keybase1.TeamApplication
   576  
   577  	// The generations we care about. We'll always get back the most recent RKMs
   578  	// if we send a needMerkleRefresh.
   579  	seedLow     keybase1.PerTeamKeyGeneration
   580  	generations []keybase1.PerTeamKeyGeneration
   581  
   582  	// the last hidden link we got, in case we need to download more
   583  	hiddenLinksSince keybase1.Seqno
   584  }
   585  
   586  // groceries are what we get back from the server.
   587  type groceries struct {
   588  	newLinks          []*ChainLinkUnpacked
   589  	rkms              []keybase1.ReaderKeyMask
   590  	latestKeyGen      keybase1.PerTeamKeyGeneration
   591  	seeds             []keybase1.PerTeamKeySeed
   592  	newHiddenLinks    []sig3.ExportJSON
   593  	expMaxHiddenSeqno keybase1.Seqno
   594  	lastMerkleRoot    *libkb.MerkleRoot
   595  }
   596  
   597  // isEmpty returns true if our shopping list is empty. In this case, we have no need to go to the
   598  // server (store), and can just return with what's in our cache.
   599  func (s shoppingList) isEmpty() bool {
   600  	return !s.needMerkleRefresh && len(s.generations) == 0 && len(s.downPointers) == 0
   601  }
   602  
   603  // onlyNeedsRefresh will be true if we only are going to the server for a refresh,
   604  // say when encrypting for the latest key version. If the merkle tree says we're up to date,
   605  // we can skip the team/get call.
   606  func (s shoppingList) onlyNeedsRefresh() bool {
   607  	return s.needMerkleRefresh && !s.needLatestKey && len(s.generations) == 0 && len(s.downPointers) == 0
   608  }
   609  
   610  // addDownPointer adds a down pointer to our shopping list. If we need to read naming information
   611  // out of a parent team, we'll add the corresponding sequence number here. The we expect the
   612  // payload JSON for the corrsponding seqno -- that we already have the wrapper chainlink v2
   613  // that contains the hash of this payload JSON.
   614  func (s *shoppingList) addDownPointer(seqno keybase1.Seqno) {
   615  	s.downPointers = append(s.downPointers, seqno)
   616  }
   617  
   618  // computeWithPreviousState looks into the given load arg, and also our current cached state, to figure
   619  // what to get from the server. The results are compiled into a "shopping list" that we'll later
   620  // use when we concoct our server request.
   621  func (f *FastTeamChainLoader) computeWithPreviousState(m libkb.MetaContext, s *shoppingList, arg fastLoadArg, data ftlCombinedData) {
   622  	state := data.visible
   623  	cachedAt := state.CachedAt.Time()
   624  	s.linksSince = state.Chain.Last.Seqno
   625  
   626  	if arg.forceReset {
   627  		s.linksSince = keybase1.Seqno(0)
   628  		m.Debug("forceReset specified, so reloading from low=0")
   629  	}
   630  
   631  	if arg.needChainTail() && m.G().Clock().Now().Sub(cachedAt) > time.Hour {
   632  		m.Debug("cached value is more than an hour old (cached at %s)", cachedAt)
   633  		s.needMerkleRefresh = true
   634  	}
   635  	if arg.needChainTail() && state.LatestSeqnoHint > state.Chain.Last.Seqno {
   636  		m.Debug("cached value is stale: seqno %d > %d", state.LatestSeqnoHint, state.Chain.Last.Seqno)
   637  		s.needMerkleRefresh = true
   638  	}
   639  	if arg.needChainTail() && data.hidden.IsStale() {
   640  		m.Debug("HiddenTeamChain was stale, forcing refresh")
   641  		s.needMerkleRefresh = true
   642  	}
   643  	if arg.ForceRefresh {
   644  		m.Debug("refresh forced via flag")
   645  		s.needMerkleRefresh = true
   646  	}
   647  	if !s.needMerkleRefresh && f.InForceRepollMode(m) {
   648  		m.Debug("must repoll since in force mode")
   649  		s.needMerkleRefresh = true
   650  	}
   651  	if !stateHasKeys(m, s, arg, data) {
   652  		m.Debug("state was missing needed encryption keys, or we need the freshest")
   653  	}
   654  	if !stateHasDownPointers(m, s, arg, state) {
   655  		m.Debug("state was missing unstubbed links")
   656  	}
   657  }
   658  
   659  // computeFreshLoad computes a shopping list from a fresh load of the state.
   660  func (s *shoppingList) computeFreshLoad(m libkb.MetaContext, arg fastLoadArg) {
   661  	s.needMerkleRefresh = true
   662  	s.applications = append([]keybase1.TeamApplication{}, arg.Applications...)
   663  	s.downPointers = append([]keybase1.Seqno{}, arg.downPointersNeeded...)
   664  	s.generations = append([]keybase1.PerTeamKeyGeneration{}, arg.KeyGenerationsNeeded...)
   665  }
   666  
   667  func (s *shoppingList) addHiddenLow(hp *hidden.LoaderPackage) {
   668  	s.hiddenLinksSince = hp.LastSeqno()
   669  }
   670  
   671  // applicationsToString converts the list of applications to a comma-separated string.
   672  func applicationsToString(applications []keybase1.TeamApplication) string {
   673  	var tmp []string
   674  	for _, k := range applications {
   675  		tmp = append(tmp, fmt.Sprintf("%d", int(k)))
   676  	}
   677  	return strings.Join(tmp, ",")
   678  }
   679  
   680  // generationsToString converts the list of generations to a comma-separated string.
   681  func generationsToString(generations []keybase1.PerTeamKeyGeneration) string {
   682  	var tmp []string
   683  	for _, k := range generations {
   684  		tmp = append(tmp, fmt.Sprintf("%d", int(k)))
   685  	}
   686  	return strings.Join(tmp, ",")
   687  }
   688  
   689  // toHTTPArgs turns our shopping list into what we need from the server. Here is what we need:
   690  // all stubs since `low`, which might be 0, in which case all `stubs`. The first link we
   691  // get back must be unstubbed. The last "up pointer" must be unstubbed. Any link in `seqnos`
   692  // must be returned unstubbed, and might be in the sequence *before* `low`. We specify
   693  // key generations and applications, and need reader key masks for all applications
   694  // in the (apps X gens) cartesian product.
   695  func (a fastLoadArg) toHTTPArgs(m libkb.MetaContext, s shoppingList, hp *hidden.LoaderPackage) libkb.HTTPArgs {
   696  	ret := libkb.HTTPArgs{
   697  		"id":                  libkb.S{Val: a.ID.String()},
   698  		"public":              libkb.B{Val: a.Public},
   699  		"ftl":                 libkb.B{Val: true},
   700  		"ftl_low":             libkb.I{Val: int(s.linksSince)},
   701  		"ftl_seqnos":          libkb.S{Val: seqnosToString(s.downPointers)},
   702  		"ftl_key_generations": libkb.S{Val: generationsToString(s.generations)},
   703  		"ftl_version":         libkb.I{Val: FTLVersion},
   704  		"ftl_seed_low":        libkb.I{Val: int(s.seedLow)},
   705  	}
   706  	if len(s.applications) > 0 {
   707  		ret["ftl_include_applications"] = libkb.S{Val: applicationsToString(s.applications)}
   708  	}
   709  	if a.NeedLatestKey {
   710  		ret["ftl_n_newest_key_generations"] = libkb.I{Val: int(3)}
   711  	}
   712  	if !a.readSubteamID.IsNil() {
   713  		ret["read_subteam_id"] = libkb.S{Val: a.readSubteamID.String()}
   714  	}
   715  
   716  	if hp.HiddenChainDataEnabled() {
   717  		ret["ftl_hidden_low"] = libkb.I{Val: int(s.hiddenLinksSince)}
   718  	}
   719  	return ret
   720  }
   721  
   722  // loadFromServerWithRetries loads the leaf in the merkle tree and then fetches from team/get.json the links
   723  // needed for the team chain. There is a race possible, when a link is added between the two. In that
   724  // case, refetch in a loop until we match up. It will retry in the case of GreenLinkErrors. If
   725  // the given state was fresh already, then we'll return a nil groceries.
   726  func (f *FastTeamChainLoader) loadFromServerWithRetries(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (groceries *groceries, err error) {
   727  
   728  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#loadFromServerWithRetries(%s,%v)", arg.ID, arg.Public), &err)()
   729  
   730  	const nRetries = 3
   731  	for i := 0; i < nRetries; i++ {
   732  		groceries, err = f.loadFromServerOnce(m, arg, state, shoppingList, hp)
   733  		switch err.(type) {
   734  		case nil:
   735  			return groceries, nil
   736  		case GreenLinkError:
   737  			m.Debug("FastTeamChainLoader retrying after green link")
   738  			continue
   739  		default:
   740  			return nil, err
   741  		}
   742  	}
   743  	return nil, err
   744  }
   745  
   746  // makeHTTPRequest hits the HTTP GET endpoint for the team data.
   747  func (f *FastTeamChainLoader) makeHTTPRequest(m libkb.MetaContext, args libkb.HTTPArgs, isPublic bool) (t rawTeam, err error) {
   748  	apiArg := libkb.NewAPIArg("team/get")
   749  	apiArg.Args = args
   750  	if isPublic {
   751  		apiArg.SessionType = libkb.APISessionTypeOPTIONAL
   752  	} else {
   753  		apiArg.SessionType = libkb.APISessionTypeREQUIRED
   754  	}
   755  	err = m.G().API.GetDecode(m, apiArg, &t)
   756  	if err != nil {
   757  		return t, err
   758  	}
   759  	return t, nil
   760  }
   761  
   762  func (f *FastTeamChainLoader) checkHiddenResp(m libkb.MetaContext, arg fastLoadArg, hiddenResp *libkb.MerkleHiddenResponse, hp *hidden.LoaderPackage) (hiddenIsFresh bool, err error) {
   763  	if !arg.HiddenChainIsOptional && hiddenResp.RespType == libkb.MerkleHiddenResponseTypeNONE {
   764  		return hiddenIsFresh, libkb.NewHiddenChainDataMissingError("the server did not return the necessary hidden chain data")
   765  	}
   766  
   767  	if hiddenResp.RespType != libkb.MerkleHiddenResponseTypeFLAGOFF && hiddenResp.RespType != libkb.MerkleHiddenResponseTypeNONE {
   768  		hiddenIsFresh, err = hp.CheckHiddenMerklePathResponseAndAddRatchets(m, hiddenResp)
   769  		if err != nil {
   770  			return hiddenIsFresh, err
   771  		}
   772  	} else {
   773  		hiddenIsFresh = true
   774  	}
   775  
   776  	return hiddenIsFresh, nil
   777  }
   778  
   779  // loadFromServerOnce turns the giving "shoppingList" into requests for the server, and then makes
   780  // an HTTP GET to fetch the corresponding "groceries." Once retrieved, we unpack links, and
   781  // check for "green" links --- those that might have been added to the team after the merkle update
   782  // we previously read. If we find a green link, we retry in our caller. Otherwise, we also do the
   783  // key decryption here, decrypting the most recent generation, and all prevs we haven't previously
   784  // decrypted.
   785  func (f *FastTeamChainLoader) loadFromServerOnce(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (ret *groceries, err error) {
   786  
   787  	defer m.Trace("FastTeamChainLoader#loadFromServerOnce", &err)()
   788  
   789  	var teamUpdate rawTeam
   790  	var links []*ChainLinkUnpacked
   791  	var lastSecretGen keybase1.PerTeamKeyGeneration
   792  	var seeds []keybase1.PerTeamKeySeed
   793  
   794  	lastSeqno, lastLinkID, hiddenResp, lastMerkleRoot, err := f.world.merkleLookupWithHidden(m.Ctx(), arg.ID, arg.Public)
   795  	if err != nil {
   796  		return nil, err
   797  	}
   798  
   799  	hiddenIsFresh, err := f.checkHiddenResp(m, arg, hiddenResp, hp)
   800  	if err != nil {
   801  		return nil, err
   802  	}
   803  
   804  	if shoppingList.onlyNeedsRefresh() && state != nil && state.Chain.Last != nil && state.Chain.Last.Seqno == lastSeqno && hiddenIsFresh {
   805  		if !lastLinkID.Eq(state.Chain.Last.LinkID) {
   806  			m.Debug("link ID mismatch at tail seqno %d: wanted %s but got %s", state.Chain.Last.LinkID, lastLinkID)
   807  			return nil, NewFastLoadError("cached last link at seqno=%d did not match current merke tree", lastSeqno)
   808  		}
   809  		m.Debug("according to merkle tree, previously loaded chain at %d is current, and shopping list was empty", lastSeqno)
   810  		return nil, nil
   811  	}
   812  
   813  	teamUpdate, err = f.makeHTTPRequest(m, arg.toHTTPArgs(m, shoppingList, hp), arg.Public)
   814  	if err != nil {
   815  		return nil, err
   816  	}
   817  
   818  	if !teamUpdate.ID.Eq(arg.ID) {
   819  		return nil, NewFastLoadError("server returned wrong id: %v != %v", teamUpdate.ID, arg.ID)
   820  	}
   821  	links, err = teamUpdate.unpackLinks(m)
   822  	if err != nil {
   823  		return nil, err
   824  	}
   825  
   826  	numStubbed := 0
   827  
   828  	for _, link := range links {
   829  		if link.Seqno() > lastSeqno {
   830  			m.Debug("TeamLoader found green link seqno:%v", link.Seqno())
   831  			return nil, NewGreenLinkError(link.Seqno())
   832  		}
   833  		if link.Seqno() == lastSeqno && !lastLinkID.Eq(link.LinkID().Export()) {
   834  			m.Debug("Merkle tail mismatch at link %d: %v != %v", lastSeqno, lastLinkID, link.LinkID().Export())
   835  			return nil, NewInvalidLink(link, "last link did not match merkle tree")
   836  		}
   837  		if link.isStubbed() {
   838  			numStubbed++
   839  		}
   840  	}
   841  
   842  	if teamUpdate.Box != nil {
   843  		lastSecretGen, seeds, err = unboxPerTeamSecrets(m, f.world, teamUpdate.Box, teamUpdate.Prevs)
   844  		if err != nil {
   845  			return nil, err
   846  		}
   847  	}
   848  
   849  	hp.SetRatchetBlindingKeySet(teamUpdate.RatchetBlindingKeySet)
   850  
   851  	m.Debug("loadFromServerOnce: got back %d new links; %d stubbed; %d RKMs; %d prevs; box=%v; lastSecretGen=%d; %d hidden chainlinks", len(links), numStubbed, len(teamUpdate.ReaderKeyMasks), len(teamUpdate.Prevs), teamUpdate.Box != nil, lastSecretGen, len(teamUpdate.HiddenChain))
   852  
   853  	return &groceries{
   854  		newLinks:          links,
   855  		latestKeyGen:      lastSecretGen,
   856  		rkms:              teamUpdate.ReaderKeyMasks,
   857  		seeds:             seeds,
   858  		newHiddenLinks:    teamUpdate.HiddenChain,
   859  		expMaxHiddenSeqno: hiddenResp.UncommittedSeqno,
   860  		lastMerkleRoot:    lastMerkleRoot,
   861  	}, nil
   862  }
   863  
   864  // checkStubs makes sure that new links sent down from the server have the right stubbing/unstubbing
   865  // pattern. The rules are: the most recent "up pointer" should be unstubbed. The first link should be
   866  // unstubbed. The last key rotation should be unstubbed (though we can't really check this now).
   867  // And any links we ask for should be unstubbed too.
   868  func (f *FastTeamChainLoader) checkStubs(m libkb.MetaContext, shoppingList shoppingList, newLinks []*ChainLinkUnpacked, canReadTeam bool) (err error) {
   869  
   870  	if len(newLinks) == 0 {
   871  		return nil
   872  	}
   873  
   874  	isUpPointer := func(t libkb.SigchainV2Type) bool {
   875  		return (t == libkb.SigchainV2TypeTeamRenameUpPointer) || (t == libkb.SigchainV2TypeTeamDeleteUpPointer)
   876  	}
   877  
   878  	isKeyRotation := func(link *ChainLinkUnpacked) bool {
   879  		return (link.LinkType() == libkb.SigchainV2TypeTeamRotateKey) || (!link.isStubbed() && link.inner != nil && link.inner.Body.Key != nil)
   880  	}
   881  
   882  	// these are the links that we explicitly asked for from the server.
   883  	neededSeqnos := make(map[keybase1.Seqno]bool)
   884  	for _, s := range shoppingList.downPointers {
   885  		neededSeqnos[s] = true
   886  	}
   887  
   888  	foundUpPointer := false
   889  	foundKeyRotation := false
   890  	for i := len(newLinks) - 1; i >= 0; i-- {
   891  		link := newLinks[i]
   892  
   893  		// Check that the most recent up pointer is unstubbed
   894  		if !foundUpPointer && isUpPointer(link.LinkType()) {
   895  			if link.isStubbed() {
   896  				return NewInvalidLink(link, "expected last 'UP' pointer to be unstubbed")
   897  			}
   898  			foundUpPointer = true
   899  		}
   900  
   901  		// This check is approximate, since the server can hide key rotations, since they can be
   902  		// included in membership changes.
   903  		if !foundKeyRotation && isKeyRotation(link) {
   904  			if link.isStubbed() {
   905  				return NewInvalidLink(link, "we expected the last key rotation to be unstubbed")
   906  			}
   907  			foundKeyRotation = true
   908  		}
   909  
   910  		if neededSeqnos[link.Seqno()] && link.isStubbed() {
   911  			return NewInvalidLink(link, "server sent back stubbed link, but we asked for unstubbed")
   912  		}
   913  	}
   914  
   915  	if newLinks[0].isStubbed() && newLinks[0].Seqno() == keybase1.Seqno(1) {
   916  		return NewInvalidLink(newLinks[0], "expected head link to be unstubbed")
   917  	}
   918  
   919  	return nil
   920  }
   921  
   922  func checkSeqType(m libkb.MetaContext, arg fastLoadArg, link *ChainLinkUnpacked) error {
   923  	if link.SeqType() != keybase1.SeqType_NONE && ((arg.Public && link.SeqType() != keybase1.SeqType_PUBLIC) || (!arg.Public && link.SeqType() != keybase1.SeqType_SEMIPRIVATE)) {
   924  		m.Debug("Bad seqtype at %v/%d: %d", arg.ID, link.Seqno(), link.SeqType())
   925  		return NewInvalidLink(link, "bad seqtype")
   926  	}
   927  	return nil
   928  }
   929  
   930  // checkPrevs checks the previous pointers on the new links that came down from the server. It
   931  // only checks prevs for links that are newer than the last link gotten in this chain.
   932  // We assume the rest are expanding hashes for links we've previously downloaded.
   933  func (f *FastTeamChainLoader) checkPrevs(m libkb.MetaContext, arg fastLoadArg, last *keybase1.LinkTriple, newLinks []*ChainLinkUnpacked) (err error) {
   934  	if len(newLinks) == 0 {
   935  		return nil
   936  	}
   937  
   938  	var prev keybase1.LinkTriple
   939  	if last != nil {
   940  		prev = *last
   941  	}
   942  
   943  	cmpHash := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) {
   944  
   945  		// not ideal to have to export here, but it simplifies the code.
   946  		prevex := link.Prev().Export()
   947  
   948  		if prev.LinkID.IsNil() && prevex.IsNil() {
   949  			return nil
   950  		}
   951  		if prev.LinkID.IsNil() || prevex.IsNil() {
   952  			m.Debug("Bad prev nil/non-nil pointer check at seqno %d: (prev=%v vs curr=%v)", link.Seqno(), prev.LinkID.IsNil(), prevex.IsNil())
   953  			return NewInvalidLink(link, "bad nil/non-nil prev pointer comparison")
   954  		}
   955  		if !prev.LinkID.Eq(prevex) {
   956  			m.Debug("Bad prev comparison at seqno %d: %s != %s", prev.LinkID, prevex)
   957  			return NewInvalidLink(link, "bad prev pointer")
   958  		}
   959  		return nil
   960  	}
   961  
   962  	cmpSeqnos := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) {
   963  		if prev.Seqno+1 != link.Seqno() {
   964  			m.Debug("Bad sequence violation: %d+1 != %d", prev.Seqno, link.Seqno())
   965  			return NewInvalidLink(link, "seqno violation")
   966  		}
   967  		return checkSeqType(m, arg, link)
   968  	}
   969  
   970  	cmp := func(prev keybase1.LinkTriple, link *ChainLinkUnpacked) (err error) {
   971  		err = cmpHash(prev, link)
   972  		if err != nil {
   973  			return err
   974  		}
   975  		return cmpSeqnos(prev, link)
   976  	}
   977  
   978  	for _, link := range newLinks {
   979  		// We might have gotten some links from the past just for the purposes of expanding
   980  		// previous links that were stubbed. We don't need to check prevs on them, since
   981  		// we previously did.
   982  		if last != nil && last.Seqno >= link.Seqno() {
   983  			continue
   984  		}
   985  		err := cmp(prev, link)
   986  		if err != nil {
   987  			return err
   988  		}
   989  		prev = link.LinkTriple()
   990  	}
   991  	return nil
   992  }
   993  
   994  // audit runs probabilistic merkle tree audit on the new links, to make sure that the server isn't
   995  // running odd-even-style attacks against members in a group.
   996  func (f *FastTeamChainLoader) audit(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, hiddenChain *keybase1.HiddenTeamChain, lastMerkleRoot *libkb.MerkleRoot) (err error) {
   997  	head, ok := state.Chain.MerkleInfo[1]
   998  	if !ok {
   999  		return NewAuditError("cannot run audit without merkle info for head")
  1000  	}
  1001  	last := state.Chain.Last
  1002  	if last == nil {
  1003  		return NewAuditError("cannot run audit, no last chain data")
  1004  	}
  1005  	auditMode := keybase1.AuditMode_STANDARD
  1006  	if arg.HiddenChainIsOptional {
  1007  		auditMode = keybase1.AuditMode_STANDARD_NO_HIDDEN
  1008  	}
  1009  	return m.G().GetTeamAuditor().AuditTeam(m, arg.ID, arg.Public, head.Seqno, state.Chain.LinkIDs, hiddenChain.GetOuter(), last.Seqno, hiddenChain.GetLastCommittedSeqno(), lastMerkleRoot, auditMode)
  1010  }
  1011  
  1012  // readDownPointer reads a down pointer out of a given link, if it's unstubbed. Down pointers
  1013  // are (1) new_subteams; (2) subteam rename down pointers; and (3) subteam delete down pointers.
  1014  // Will return (nil, non-nil) if there is an error.
  1015  func readDownPointer(m libkb.MetaContext, link *ChainLinkUnpacked) (*keybase1.DownPointer, error) {
  1016  	if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.Subteam == nil {
  1017  		return nil, nil
  1018  	}
  1019  	subteam := link.inner.Body.Team.Subteam
  1020  	typ := link.LinkType()
  1021  	if typ != libkb.SigchainV2TypeTeamNewSubteam && typ != libkb.SigchainV2TypeTeamRenameSubteam && typ != libkb.SigchainV2TypeTeamDeleteSubteam {
  1022  		return nil, nil
  1023  	}
  1024  	del := (typ == libkb.SigchainV2TypeTeamDeleteSubteam)
  1025  	if len(subteam.Name) == 0 && len(subteam.ID) == 0 {
  1026  		return nil, nil
  1027  	}
  1028  	lastPart, err := subteam.Name.LastPart()
  1029  	if err != nil {
  1030  		return nil, err
  1031  	}
  1032  	xid, err := subteam.ID.ToTeamID()
  1033  	if err != nil {
  1034  		return nil, err
  1035  	}
  1036  	return &keybase1.DownPointer{
  1037  		Id:            xid,
  1038  		NameComponent: lastPart,
  1039  		IsDeleted:     del,
  1040  	}, nil
  1041  }
  1042  
  1043  // readMerkleRoot reads the merkle root out of the link if this link is unstubbed.
  1044  func readMerkleRoot(m libkb.MetaContext, link *ChainLinkUnpacked) (*keybase1.MerkleRootV2, error) {
  1045  	if link.inner == nil {
  1046  		return nil, nil
  1047  	}
  1048  	ret := link.inner.Body.MerkleRoot.ToMerkleRootV2()
  1049  	return &ret, nil
  1050  }
  1051  
  1052  // readUpPointer reads an up pointer out the given link, if it's unstubbed. Up pointers are
  1053  // (1) subteam heads; (2) subteam rename up pointers; and (3) subteam delete up pointers.
  1054  // Will return (nil, non-nil) if we hit any error condition.
  1055  func readUpPointer(m libkb.MetaContext, arg fastLoadArg, link *ChainLinkUnpacked) (*keybase1.UpPointer, error) {
  1056  	if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.Parent == nil {
  1057  		return nil, nil
  1058  	}
  1059  	parent := link.inner.Body.Team.Parent
  1060  	typ := link.LinkType()
  1061  	if typ != libkb.SigchainV2TypeTeamSubteamHead && typ != libkb.SigchainV2TypeTeamRenameUpPointer && typ != libkb.SigchainV2TypeTeamDeleteUpPointer {
  1062  		return nil, nil
  1063  	}
  1064  	xid, err := parent.ID.ToTeamID()
  1065  	if err != nil {
  1066  		return nil, err
  1067  	}
  1068  
  1069  	err = checkSeqType(m, arg, link)
  1070  	if err != nil {
  1071  		return nil, err
  1072  	}
  1073  	return &keybase1.UpPointer{
  1074  		OurSeqno:    link.Seqno(),
  1075  		ParentID:    xid,
  1076  		ParentSeqno: parent.Seqno,
  1077  		Deletion:    (typ == libkb.SigchainV2TypeTeamDeleteUpPointer),
  1078  	}, nil
  1079  }
  1080  
  1081  // putName takes the name out of the team (or subteam) head and stores it to state.
  1082  // In the case of a subteam, this name has not been verified, and we should
  1083  // verify it ourselves against the merkle tree.
  1084  func (f *FastTeamChainLoader) putName(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, newLinks []*ChainLinkUnpacked) (err error) {
  1085  	if len(newLinks) == 0 || newLinks[0].Seqno() != keybase1.Seqno(1) {
  1086  		return nil
  1087  	}
  1088  	head := newLinks[0]
  1089  	if head.isStubbed() {
  1090  		return NewInvalidLink(head, "head should never be stubbed")
  1091  	}
  1092  	if head.inner.Body.Team == nil || head.inner.Body.Team.Name == nil {
  1093  		return NewInvalidLink(head, "head name should never be nil")
  1094  	}
  1095  	nm := *head.inner.Body.Team.Name
  1096  	xname, err := keybase1.TeamNameFromString(string(nm))
  1097  	if err != nil {
  1098  		return err
  1099  	}
  1100  	if !state.Name.IsNil() && !state.Name.Eq(xname) {
  1101  		return NewInvalidLink(head, "wrong name for team")
  1102  	}
  1103  	state.Name = xname
  1104  	return nil
  1105  }
  1106  
  1107  // readPerTeamKey reads a PerTeamKey section, if it exists, out of the given unpacked chainlink.
  1108  func readPerTeamKey(m libkb.MetaContext, link *ChainLinkUnpacked) (ret *keybase1.PerTeamKey, err error) {
  1109  
  1110  	if link.inner == nil || link.inner.Body.Team == nil || link.inner.Body.Team.PerTeamKey == nil {
  1111  		return nil, nil
  1112  	}
  1113  	ptk := link.inner.Body.Team.PerTeamKey
  1114  	return &keybase1.PerTeamKey{
  1115  		Gen:    ptk.Generation,
  1116  		Seqno:  link.Seqno(),
  1117  		SigKID: ptk.SigKID,
  1118  		EncKID: ptk.EncKID,
  1119  	}, nil
  1120  }
  1121  
  1122  // putLinks takes the links we just downloaded from the server, and stores them to the state.
  1123  // It also fills in unstubbed fields for those links that have come back with payloads that
  1124  // were previously stubbed. There are several error cases that can come up, when reading down
  1125  // or up pointers from the reply.
  1126  func (f *FastTeamChainLoader) putLinks(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, newLinks []*ChainLinkUnpacked) (err error) {
  1127  	if len(newLinks) == 0 {
  1128  		return nil
  1129  	}
  1130  
  1131  	for _, link := range newLinks {
  1132  		existing, ok := state.Chain.LinkIDs[link.Seqno()]
  1133  		linkID := link.LinkID().Export()
  1134  		if ok {
  1135  			// NOTE! This is a crucial check, since we might have checked prev's on this link
  1136  			// in a previous run on the chain. We have to make sure an unstubbed link is
  1137  			// consistent with that previous check. See checkPrevs for when we skip
  1138  			// checking prevs in such a case, and need to check here for linkID equality.
  1139  			if !linkID.Eq(existing) {
  1140  				return NewInvalidLink(link, "list doesn't match previously cached link")
  1141  			}
  1142  		} else {
  1143  			state.Chain.LinkIDs[link.Seqno()] = linkID
  1144  		}
  1145  		dp, err := readDownPointer(m, link)
  1146  		if err != nil {
  1147  			return err
  1148  		}
  1149  		if dp != nil {
  1150  			state.Chain.DownPointers[link.Seqno()] = *dp
  1151  		}
  1152  		up, err := readUpPointer(m, arg, link)
  1153  		if err != nil {
  1154  			return err
  1155  		}
  1156  		if up != nil && (state.Chain.LastUpPointer == nil || state.Chain.LastUpPointer.OurSeqno < up.OurSeqno) {
  1157  			state.Chain.LastUpPointer = up
  1158  		}
  1159  		ptk, err := readPerTeamKey(m, link)
  1160  		if err != nil {
  1161  			return err
  1162  		}
  1163  		if ptk != nil {
  1164  			state.Chain.PerTeamKeys[ptk.Gen] = *ptk
  1165  		}
  1166  		merkleRoot, err := readMerkleRoot(m, link)
  1167  		if err != nil {
  1168  			return err
  1169  		}
  1170  		if merkleRoot != nil {
  1171  			state.Chain.MerkleInfo[link.Seqno()] = *merkleRoot
  1172  		}
  1173  	}
  1174  	newLast := newLinks[len(newLinks)-1]
  1175  	if state.Chain.Last == nil || state.Chain.Last.Seqno < newLast.Seqno() {
  1176  		tmp := newLast.LinkTriple()
  1177  		state.Chain.Last = &tmp
  1178  	}
  1179  	return nil
  1180  }
  1181  
  1182  // putRKMs stores the new reader key masks loaded from the server to the state structure.
  1183  func (f *FastTeamChainLoader) putRKMs(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, rkms []keybase1.ReaderKeyMask) (err error) {
  1184  	for _, rkm := range rkms {
  1185  		if _, ok := state.ReaderKeyMasks[rkm.Application]; !ok {
  1186  			state.ReaderKeyMasks[rkm.Application] = make(map[keybase1.PerTeamKeyGeneration]keybase1.MaskB64)
  1187  		}
  1188  		state.ReaderKeyMasks[rkm.Application][rkm.Generation] = rkm.Mask
  1189  	}
  1190  	return nil
  1191  }
  1192  
  1193  // putSeeds stores the crypto seeds to the PeterTeamKeySeedsUnverified slot of the state. It returns
  1194  // the last n seeds, counting backwards. We exploit this fact to infer the seed generations from their
  1195  // order.
  1196  func (f *FastTeamChainLoader) putSeeds(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, latestKeyGen keybase1.PerTeamKeyGeneration, seeds []keybase1.PerTeamKeySeed) (err error) {
  1197  	for i, seed := range seeds {
  1198  		state.PerTeamKeySeedsUnverified[latestKeyGen-keybase1.PerTeamKeyGeneration(len(seeds)-i-1)] = seed
  1199  	}
  1200  
  1201  	// We might have gotten back 0 seeds from the server, so don't overwrite a valid LatestKeyGeneration
  1202  	// with 0 in that case.
  1203  	if latestKeyGen > state.LatestKeyGeneration {
  1204  		state.LatestKeyGeneration = latestKeyGen
  1205  	}
  1206  	return nil
  1207  }
  1208  
  1209  func (f *FastTeamChainLoader) putSeedChecks(m libkb.MetaContext, state *keybase1.FastTeamData) (err error) {
  1210  	latestChainGen := keybase1.PerTeamKeyGeneration(len(state.PerTeamKeySeedsUnverified))
  1211  	if state.SeedChecks == nil {
  1212  		state.SeedChecks = make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamSeedCheck)
  1213  	}
  1214  	return computeSeedChecks(
  1215  		m.Ctx(),
  1216  		state.ID(),
  1217  		latestChainGen,
  1218  		func(g keybase1.PerTeamKeyGeneration) (check *keybase1.PerTeamSeedCheck, seed keybase1.PerTeamKeySeed, err error) {
  1219  			seed, ok := state.PerTeamKeySeedsUnverified[g]
  1220  			if !ok {
  1221  				return nil, keybase1.PerTeamKeySeed{}, fmt.Errorf("unexpected nil PerTeamKeySeedsUnverified at %d", g)
  1222  			}
  1223  			tmp, ok := state.SeedChecks[g]
  1224  			if ok {
  1225  				check = &tmp
  1226  			}
  1227  			return check, seed, nil
  1228  		},
  1229  		func(g keybase1.PerTeamKeyGeneration, check keybase1.PerTeamSeedCheck) {
  1230  			state.SeedChecks[g] = check
  1231  		},
  1232  	)
  1233  }
  1234  
  1235  func setCachedAtToNow(m libkb.MetaContext, state *keybase1.FastTeamData) {
  1236  	state.CachedAt = keybase1.ToTime(m.G().Clock().Now())
  1237  }
  1238  
  1239  func (f *FastTeamChainLoader) putMetadata(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData) error {
  1240  	setCachedAtToNow(m, state)
  1241  	if arg.NeedLatestKey {
  1242  		state.LoadedLatest = true
  1243  	}
  1244  	return nil
  1245  }
  1246  
  1247  // mutateState takes the groceries fetched from the server and applies them to our current state.
  1248  func (f *FastTeamChainLoader) mutateState(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, groceries *groceries) (err error) {
  1249  
  1250  	err = f.putName(m, arg, state, groceries.newLinks)
  1251  	if err != nil {
  1252  		return err
  1253  	}
  1254  	err = f.putLinks(m, arg, state, groceries.newLinks)
  1255  	if err != nil {
  1256  		return err
  1257  	}
  1258  	err = f.putRKMs(m, arg, state, groceries.rkms)
  1259  	if err != nil {
  1260  		return err
  1261  	}
  1262  	err = f.putSeeds(m, arg, state, groceries.latestKeyGen, groceries.seeds)
  1263  	if err != nil {
  1264  		return err
  1265  	}
  1266  	err = f.putSeedChecks(m, state)
  1267  	if err != nil {
  1268  		return err
  1269  	}
  1270  	err = f.putMetadata(m, arg, state)
  1271  	if err != nil {
  1272  		return err
  1273  	}
  1274  	return nil
  1275  }
  1276  
  1277  // makeState does a clone on a non-nil state, or makes a new state if nil.
  1278  func makeState(arg fastLoadArg, s *keybase1.FastTeamData) *keybase1.FastTeamData {
  1279  	if s != nil {
  1280  		tmp := s.DeepCopy()
  1281  		return &tmp
  1282  	}
  1283  	return &keybase1.FastTeamData{
  1284  		Subversion:                1,
  1285  		PerTeamKeySeedsUnverified: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKeySeed),
  1286  		SeedChecks:                make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamSeedCheck),
  1287  		ReaderKeyMasks:            make(map[keybase1.TeamApplication](map[keybase1.PerTeamKeyGeneration]keybase1.MaskB64)),
  1288  		Chain: keybase1.FastTeamSigChainState{
  1289  			ID:                      arg.ID,
  1290  			Public:                  arg.Public,
  1291  			PerTeamKeys:             make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKey),
  1292  			PerTeamKeySeedsVerified: make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKeySeed),
  1293  			DownPointers:            make(map[keybase1.Seqno]keybase1.DownPointer),
  1294  			LinkIDs:                 make(map[keybase1.Seqno]keybase1.LinkID),
  1295  			MerkleInfo:              make(map[keybase1.Seqno]keybase1.MerkleRootV2),
  1296  		},
  1297  	}
  1298  }
  1299  
  1300  func (f *FastTeamChainLoader) hiddenPackage(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData) (hp *hidden.LoaderPackage, err error) {
  1301  	hp, err = hidden.NewLoaderPackage(m, arg.ID,
  1302  		func() (encKID keybase1.KID, gen keybase1.PerTeamKeyGeneration, role keybase1.TeamRole, err error) {
  1303  			// Always return TeamRole_NONE since ftl does not have access to
  1304  			// member roles. The hidden chain uses the role to skip checks bot
  1305  			// members are not able to perform. Bot members should never FTL,
  1306  			// however since they don't have key access.
  1307  			if state == nil || len(state.Chain.PerTeamKeys) == 0 {
  1308  				return encKID, gen, keybase1.TeamRole_NONE, nil
  1309  			}
  1310  			var ptk keybase1.PerTeamKey
  1311  			for _, tmp := range state.Chain.PerTeamKeys {
  1312  				ptk = tmp
  1313  				break
  1314  			}
  1315  			return ptk.EncKID, ptk.Gen, keybase1.TeamRole_NONE, nil
  1316  		})
  1317  	if err != nil {
  1318  		return nil, err
  1319  	}
  1320  	if !arg.readSubteamID.IsNil() {
  1321  		m.Debug("hiddenPackage: disabling checks since we a subteam reader looking for parent chain")
  1322  		hp.DisableHiddenChainData()
  1323  	}
  1324  	if tmp := hidden.CheckFeatureGateForSupport(m, arg.ID); tmp != nil {
  1325  		m.Debug("hiddenPackage: disabling checks since we are feature-flagged off")
  1326  		hp.DisableHiddenChainData()
  1327  	}
  1328  	return hp, nil
  1329  }
  1330  
  1331  func (f *FastTeamChainLoader) consumeRatchets(m libkb.MetaContext, newLinks []*ChainLinkUnpacked, hp *hidden.LoaderPackage) (err error) {
  1332  	for _, link := range newLinks {
  1333  		if err := consumeRatchets(m, hp, link); err != nil {
  1334  			return err
  1335  		}
  1336  	}
  1337  	return nil
  1338  }
  1339  
  1340  func (f *FastTeamChainLoader) processHidden(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, groceries *groceries, hp *hidden.LoaderPackage) (err error) {
  1341  
  1342  	err = f.consumeRatchets(m, groceries.newLinks, hp)
  1343  	if err != nil {
  1344  		return err
  1345  	}
  1346  
  1347  	err = hp.Update(m, groceries.newHiddenLinks, groceries.expMaxHiddenSeqno)
  1348  	if err != nil {
  1349  		return err
  1350  	}
  1351  	err = hp.CheckUpdatesAgainstSeeds(m, func(g keybase1.PerTeamKeyGeneration) *keybase1.PerTeamSeedCheck {
  1352  		chk, ok := state.SeedChecks[g]
  1353  		if !ok {
  1354  			return nil
  1355  		}
  1356  		return &chk
  1357  	})
  1358  	if err != nil {
  1359  		return err
  1360  	}
  1361  	err = hp.CheckParentPointersOnFastLoad(m, state)
  1362  	if err != nil {
  1363  		return err
  1364  	}
  1365  
  1366  	err = hp.Commit(m)
  1367  	if err != nil {
  1368  		return err
  1369  	}
  1370  	return nil
  1371  }
  1372  
  1373  // refresh the team's state, but loading with the server. It will download new stubbed chainlinks,
  1374  // fill in unstubbed chainlinks, make sure that prev pointers match, make sure that the merkle
  1375  // tree agrees with the chain tail, and then run the audit mechanism. If the state is already
  1376  // fresh, we will return (nil, nil) and short-circuit.
  1377  func (f *FastTeamChainLoader) refresh(m libkb.MetaContext, arg fastLoadArg, state *keybase1.FastTeamData, shoppingList shoppingList, hp *hidden.LoaderPackage) (res *keybase1.FastTeamData, err error) {
  1378  
  1379  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#refresh(%+v)", arg), &err)()
  1380  
  1381  	groceries, err := f.loadFromServerWithRetries(m, arg, state, shoppingList, hp)
  1382  	if err != nil {
  1383  		return nil, err
  1384  	}
  1385  
  1386  	if groceries == nil {
  1387  		m.Debug("FastTeamChainLoader#refresh: our state was fresh according to the Merkle tree")
  1388  
  1389  		// Even if the state is fresh (no new chain links are necessary), we
  1390  		// might still have learned about old hidden links being committed to
  1391  		// the blind tree. This information needs to be persisted.
  1392  		err = hp.Update(m, []sig3.ExportJSON{}, 0)
  1393  		if err != nil {
  1394  			return nil, err
  1395  		}
  1396  		err = hp.Commit(m)
  1397  		if err != nil {
  1398  			return nil, err
  1399  		}
  1400  
  1401  		return nil, nil
  1402  	}
  1403  
  1404  	// Either makes a new state, or deepcopies the existing state, so that in the case
  1405  	// of an error, we haven't corrupted what's in cache. Thus, from here on out,
  1406  	// we are playing with our own (unshared) copy of the state.
  1407  	state = makeState(arg, state)
  1408  
  1409  	// check that all chain links sent down form a valid hash chain, and point
  1410  	// to what we already in had in cache.
  1411  	err = f.checkPrevs(m, arg, state.Chain.Last, groceries.newLinks)
  1412  	if err != nil {
  1413  		return nil, err
  1414  	}
  1415  
  1416  	// check that the server stubbed properly.
  1417  	err = f.checkStubs(m, shoppingList, groceries.newLinks, arg.readSubteamID.IsNil() /* canReadTeam */)
  1418  	if err != nil {
  1419  		return nil, err
  1420  	}
  1421  
  1422  	err = f.mutateState(m, arg, state, groceries)
  1423  	if err != nil {
  1424  		return nil, err
  1425  	}
  1426  
  1427  	err = f.processHidden(m, arg, state, groceries, hp)
  1428  	if err != nil {
  1429  		return nil, err
  1430  	}
  1431  
  1432  	// peform a probabilistic audit on the new links
  1433  	err = f.audit(m, arg, state, hp.ChainData(), groceries.lastMerkleRoot)
  1434  	if err != nil {
  1435  		return nil, err
  1436  	}
  1437  
  1438  	return state, nil
  1439  }
  1440  
  1441  // updateCache puts the new version of the state into the cache on the team's ID.
  1442  func (f *FastTeamChainLoader) updateCache(m libkb.MetaContext, state *keybase1.FastTeamData) {
  1443  	f.storage.Put(m, state)
  1444  }
  1445  
  1446  func (f *FastTeamChainLoader) upgradeStoredState(mctx libkb.MetaContext, state *keybase1.FastTeamData) (changed bool, err error) {
  1447  	if state == nil {
  1448  		return false, nil
  1449  	}
  1450  
  1451  	changed = false
  1452  	if state.Subversion == 0 {
  1453  		err = f.putSeedChecks(mctx, state)
  1454  		if err != nil {
  1455  			mctx.Debug("failed in upgrade of subversion 0->1: %s", err)
  1456  			return false, err
  1457  		}
  1458  		mctx.Debug("Upgrade to subversion 1")
  1459  		state.Subversion = 1
  1460  		changed = true
  1461  	}
  1462  
  1463  	return changed, nil
  1464  }
  1465  
  1466  // loadLocked is the inner loop for loading team. Should be called when holding the lock
  1467  // this teamID.
  1468  func (f *FastTeamChainLoader) loadLocked(m libkb.MetaContext, arg fastLoadArg) (res *fastLoadRes, err error) {
  1469  
  1470  	frozenState, frozen, tombstoned := f.findStateInCache(m, arg.ID)
  1471  	var state *keybase1.FastTeamData
  1472  	var hp *hidden.LoaderPackage
  1473  	if tombstoned {
  1474  		return nil, NewTeamTombstonedError()
  1475  	}
  1476  	if !frozen {
  1477  		state = frozenState
  1478  	}
  1479  
  1480  	hp, err = f.hiddenPackage(m, arg, state)
  1481  	if err != nil {
  1482  		return nil, err
  1483  	}
  1484  
  1485  	var shoppingList shoppingList
  1486  	if state != nil {
  1487  		combinedData := newFTLCombinedData(state, hp.ChainData())
  1488  		f.computeWithPreviousState(m, &shoppingList, arg, combinedData)
  1489  		if shoppingList.isEmpty() {
  1490  			return f.toResult(m, arg, combinedData)
  1491  		}
  1492  	} else {
  1493  		shoppingList.computeFreshLoad(m, arg)
  1494  	}
  1495  	shoppingList.addHiddenLow(hp)
  1496  
  1497  	m.Debug("FastTeamChainLoader#loadLocked: computed shopping list: %+v", shoppingList)
  1498  
  1499  	var newState *keybase1.FastTeamData
  1500  	newState, err = f.refresh(m, arg, state, shoppingList, hp)
  1501  	if err != nil {
  1502  		return nil, err
  1503  	}
  1504  
  1505  	// If newState == nil, that means that no updates were required, and the old state
  1506  	// is fine, we just need to update the cachedAt time. If newState is non-nil,
  1507  	// then we use if for our state going forward.
  1508  	if newState == nil {
  1509  		setCachedAtToNow(m, state)
  1510  	} else {
  1511  		state = newState
  1512  	}
  1513  	// Always update the cache, even if we're just bumping the cachedAt time.
  1514  	f.updateCache(m, state)
  1515  
  1516  	if frozen && frozenState != nil {
  1517  		frozenLast := frozenState.Chain.Last
  1518  		linkID := state.Chain.LinkIDs[frozenLast.Seqno]
  1519  		if !linkID.Eq(frozenLast.LinkID) {
  1520  			return nil, fmt.Errorf("FastTeamChainLoader#loadLocked: got wrong sigchain link ID for seqno %d: expected %v from previous cache entry (frozen=%t); got %v in new chain", frozenLast.Seqno, frozenLast.LinkID, frozen, linkID)
  1521  		}
  1522  	}
  1523  
  1524  	return f.toResult(m, arg, newFTLCombinedData(state, hp.ChainData()))
  1525  }
  1526  
  1527  // OnLogout is called when the user logs out, which purges the LRU.
  1528  func (f *FastTeamChainLoader) OnLogout(mctx libkb.MetaContext) error {
  1529  	f.storage.ClearMem()
  1530  	return nil
  1531  }
  1532  
  1533  // OnDbNuke is called when the disk cache is cleared, which purges the LRU.
  1534  func (f *FastTeamChainLoader) OnDbNuke(mctx libkb.MetaContext) error {
  1535  	f.storage.ClearMem()
  1536  	return nil
  1537  }
  1538  
  1539  func (f *FastTeamChainLoader) HintLatestSeqno(m libkb.MetaContext, id keybase1.TeamID, seqno keybase1.Seqno) (err error) {
  1540  	m = ftlLogTag(m)
  1541  
  1542  	defer m.Trace(fmt.Sprintf("FastTeamChainLoader#HintLatestSeqno(%v->%d)", id, seqno), &err)()
  1543  
  1544  	// Single-flight lock by team ID.
  1545  	lock := f.locktab.AcquireOnName(m.Ctx(), m.G(), id.String())
  1546  	defer lock.Release(m.Ctx())
  1547  
  1548  	if state, frozen, tombstoned := f.findStateInCache(m, id); state != nil && !frozen && !tombstoned {
  1549  		m.Debug("Found state in cache; updating")
  1550  		state.LatestSeqnoHint = seqno
  1551  		f.updateCache(m, state)
  1552  	}
  1553  
  1554  	return nil
  1555  }
  1556  
  1557  func (f *FastTeamChainLoader) ForceRepollUntil(m libkb.MetaContext, dtime gregor.TimeOrOffset) error {
  1558  	m.Debug("FastTeamChainLoader#ForceRepollUntil(%+v)", dtime)
  1559  	f.forceRepollMutex.Lock()
  1560  	defer f.forceRepollMutex.Unlock()
  1561  	f.forceRepollUntil = dtime
  1562  	return nil
  1563  }
  1564  
  1565  func (f *FastTeamChainLoader) InForceRepollMode(m libkb.MetaContext) bool {
  1566  	f.forceRepollMutex.Lock()
  1567  	defer f.forceRepollMutex.Unlock()
  1568  	if f.forceRepollUntil == nil {
  1569  		return false
  1570  	}
  1571  	if !f.forceRepollUntil.Before(m.G().Clock().Now()) {
  1572  		m.Debug("FastTeamChainLoader#InForceRepollMode: returning true")
  1573  		return true
  1574  	}
  1575  	f.forceRepollUntil = nil
  1576  	return false
  1577  }
  1578  
  1579  func newFrozenFastChain(chain *keybase1.FastTeamSigChainState) keybase1.FastTeamSigChainState {
  1580  	return keybase1.FastTeamSigChainState{
  1581  		ID:     chain.ID,
  1582  		Public: chain.Public,
  1583  		Last:   chain.Last,
  1584  	}
  1585  }
  1586  
  1587  func (f *FastTeamChainLoader) Freeze(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) {
  1588  	defer mctx.Trace(fmt.Sprintf("FastTeamChainLoader#Freeze(%s)", teamID), &err)()
  1589  
  1590  	// Single-flight lock by team ID.
  1591  	lock := f.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), teamID.String())
  1592  	defer lock.Release(mctx.Ctx())
  1593  
  1594  	td, frozen, tombstoned := f.storage.Get(mctx, teamID, teamID.IsPublic())
  1595  	if frozen || td == nil {
  1596  		return nil
  1597  	}
  1598  	newTD := &keybase1.FastTeamData{
  1599  		Frozen:     true,
  1600  		Tombstoned: tombstoned,
  1601  		Chain:      newFrozenFastChain(&td.Chain),
  1602  	}
  1603  	f.storage.Put(mctx, newTD)
  1604  	return nil
  1605  }
  1606  
  1607  func (f *FastTeamChainLoader) Tombstone(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) {
  1608  	defer mctx.Trace(fmt.Sprintf("FastTeamChainLoader#Tombstone(%s)", teamID), &err)()
  1609  
  1610  	// Single-flight lock by team ID.
  1611  	lock := f.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), teamID.String())
  1612  	defer lock.Release(mctx.Ctx())
  1613  
  1614  	td, frozen, tombstoned := f.storage.Get(mctx, teamID, teamID.IsPublic())
  1615  	if tombstoned || td == nil {
  1616  		return nil
  1617  	}
  1618  	newTD := &keybase1.FastTeamData{
  1619  		Frozen:     frozen,
  1620  		Tombstoned: true,
  1621  		Chain:      newFrozenFastChain(&td.Chain),
  1622  	}
  1623  	f.storage.Put(mctx, newTD)
  1624  	return nil
  1625  }