github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/journal_md_ops.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/kbfsblock"
    13  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    14  	"github.com/keybase/client/go/kbfs/kbfsmd"
    15  	"github.com/keybase/client/go/kbfs/tlf"
    16  	"github.com/keybase/client/go/kbfs/tlfhandle"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  	"github.com/pkg/errors"
    19  	"golang.org/x/net/context"
    20  )
    21  
    22  // journalMDOps is an implementation of MDOps that delegates to a
    23  // TLF's mdJournal, if one exists. Specifically, it intercepts put
    24  // calls to write to the journal instead of the MDServer, where
    25  // something else is presumably flushing the journal to the MDServer.
    26  //
    27  // It then intercepts get calls to provide a combined view of the MDs
    28  // from the journal and the server when the journal is
    29  // non-empty. Specifically, if rev is the earliest revision in the
    30  // journal, and BID is the branch ID of the journal (which can only
    31  // have one), then any requests for revisions >= rev on BID will be
    32  // served from the journal instead of the server. If BID is empty,
    33  // i.e. the journal is holding merged revisions, then this means that
    34  // all merged revisions on the server from rev are hidden.
    35  //
    36  // TODO: This makes server updates meaningless for revisions >=
    37  // rev. Fix this.
    38  type journalMDOps struct {
    39  	MDOps
    40  	jManager *JournalManager
    41  }
    42  
    43  var _ MDOps = journalMDOps{}
    44  
    45  // convertImmutableBareRMDToIRMD decrypts the bare MD into a
    46  // full-fledged RMD.  The MD is assumed to have been read from the
    47  // journal.
    48  func (j journalMDOps) convertImmutableBareRMDToIRMD(ctx context.Context,
    49  	ibrmd ImmutableBareRootMetadata, handle *tlfhandle.Handle,
    50  	uid keybase1.UID, key kbfscrypto.VerifyingKey) (
    51  	ImmutableRootMetadata, error) {
    52  	// TODO: Avoid having to do this type assertion.
    53  	brmd, ok := ibrmd.RootMetadata.(kbfsmd.MutableRootMetadata)
    54  	if !ok {
    55  		return ImmutableRootMetadata{}, kbfsmd.MutableRootMetadataNoImplError{}
    56  	}
    57  
    58  	rmd := makeRootMetadata(brmd, ibrmd.extra, handle)
    59  
    60  	config := j.jManager.config
    61  	pmd, err := decryptMDPrivateData(ctx, config.Codec(), config.Crypto(),
    62  		config.BlockCache(), config.BlockOps(), config.KeyManager(),
    63  		config.KBPKI(), config, config.Mode(), uid,
    64  		rmd.GetSerializedPrivateMetadata(), rmd, rmd, j.jManager.log)
    65  	if err != nil {
    66  		return ImmutableRootMetadata{}, err
    67  	}
    68  
    69  	rmd.data = pmd
    70  	irmd := MakeImmutableRootMetadata(
    71  		rmd, key, ibrmd.mdID, ibrmd.localTimestamp, false)
    72  	return irmd, nil
    73  }
    74  
    75  // getHeadFromJournal returns the head RootMetadata for the TLF with
    76  // the given ID stored in the journal, assuming it exists and matches
    77  // the given branch ID and merge status. As a special case, if bid is
    78  // kbfsmd.NullBranchID and mStatus is Unmerged, the branch ID check is
    79  // skipped.
    80  func (j journalMDOps) getHeadFromJournal(
    81  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus,
    82  	handle *tlfhandle.Handle) (
    83  	ImmutableRootMetadata, error) {
    84  	tlfJournal, ok := j.jManager.getTLFJournal(id, handle)
    85  	if !ok {
    86  		return ImmutableRootMetadata{}, nil
    87  	}
    88  
    89  	if mStatus == kbfsmd.Unmerged && bid == kbfsmd.NullBranchID {
    90  		// We need to look up the branch ID because the caller didn't
    91  		// know it.
    92  		var err error
    93  		bid, err = tlfJournal.getBranchID()
    94  		if err != nil {
    95  			return ImmutableRootMetadata{}, err
    96  		}
    97  	}
    98  
    99  	head, err := tlfJournal.getMDHead(ctx, bid)
   100  	switch errors.Cause(err).(type) {
   101  	case nil:
   102  		break
   103  	case errTLFJournalDisabled:
   104  		return ImmutableRootMetadata{}, nil
   105  	default:
   106  		return ImmutableRootMetadata{}, err
   107  	}
   108  
   109  	if head == (ImmutableBareRootMetadata{}) {
   110  		return ImmutableRootMetadata{}, nil
   111  	}
   112  
   113  	if head.MergedStatus() != mStatus {
   114  		return ImmutableRootMetadata{}, nil
   115  	}
   116  
   117  	if mStatus == kbfsmd.Unmerged && bid != kbfsmd.NullBranchID && bid != head.BID() {
   118  		// The given branch ID doesn't match the one in the
   119  		// journal, which can only be an error.
   120  		return ImmutableRootMetadata{},
   121  			fmt.Errorf("Expected branch ID %s, got %s",
   122  				bid, head.BID())
   123  	}
   124  
   125  	headBareHandle, err := head.MakeBareTlfHandleWithExtra()
   126  	if err != nil {
   127  		return ImmutableRootMetadata{}, err
   128  	}
   129  
   130  	if handle == nil {
   131  		handle, err = tlfhandle.MakeHandleWithTlfID(
   132  			ctx, headBareHandle, id.Type(), j.jManager.config.KBPKI(),
   133  			j.jManager.config.KBPKI(), id,
   134  			j.jManager.config.OfflineAvailabilityForID(id))
   135  		if err != nil {
   136  			return ImmutableRootMetadata{}, err
   137  		}
   138  		handle.SetTlfID(id)
   139  	} else {
   140  		// Check for mutual handle resolution.
   141  		headHandle, err := tlfhandle.MakeHandleWithTlfID(
   142  			ctx, headBareHandle, id.Type(), j.jManager.config.KBPKI(),
   143  			j.jManager.config.KBPKI(), id,
   144  			j.jManager.config.OfflineAvailabilityForID(id))
   145  		if err != nil {
   146  			return ImmutableRootMetadata{}, err
   147  		}
   148  
   149  		if err := headHandle.MutuallyResolvesTo(ctx, j.jManager.config.Codec(),
   150  			j.jManager.config.KBPKI(), j.jManager.config.MDOps(),
   151  			j.jManager.config, *handle, head.RevisionNumber(), head.TlfID(),
   152  			j.jManager.log); err != nil {
   153  			return ImmutableRootMetadata{}, err
   154  		}
   155  	}
   156  
   157  	irmd, err := j.convertImmutableBareRMDToIRMD(
   158  		ctx, head, handle, tlfJournal.uid, tlfJournal.key)
   159  	if err != nil {
   160  		return ImmutableRootMetadata{}, err
   161  	}
   162  
   163  	return irmd, nil
   164  }
   165  
   166  func (j journalMDOps) getRangeFromJournal(
   167  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus,
   168  	start, stop kbfsmd.Revision) (
   169  	[]ImmutableRootMetadata, error) {
   170  	tlfJournal, ok := j.jManager.getTLFJournal(id, nil)
   171  	if !ok {
   172  		return nil, nil
   173  	}
   174  
   175  	ibrmds, err := tlfJournal.getMDRange(ctx, bid, start, stop)
   176  	switch errors.Cause(err).(type) {
   177  	case nil:
   178  		break
   179  	case errTLFJournalDisabled:
   180  		return nil, nil
   181  	default:
   182  		return nil, err
   183  	}
   184  
   185  	if len(ibrmds) == 0 {
   186  		return nil, nil
   187  	}
   188  
   189  	headIndex := len(ibrmds) - 1
   190  	head := ibrmds[headIndex]
   191  	if head.MergedStatus() != mStatus {
   192  		return nil, nil
   193  	}
   194  
   195  	if mStatus == kbfsmd.Unmerged && bid != kbfsmd.NullBranchID && bid != head.BID() {
   196  		// The given branch ID doesn't match the one in the
   197  		// journal, which can only be an error.
   198  		return nil, fmt.Errorf("Expected branch ID %s, got %s",
   199  			bid, head.BID())
   200  	}
   201  
   202  	bareHandle, err := head.MakeBareTlfHandleWithExtra()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	handle, err := tlfhandle.MakeHandleWithTlfID(
   207  		ctx, bareHandle, id.Type(), j.jManager.config.KBPKI(),
   208  		j.jManager.config.KBPKI(), id,
   209  		j.jManager.config.OfflineAvailabilityForID(id))
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	handle.SetTlfID(id)
   214  
   215  	irmds := make([]ImmutableRootMetadata, 0, len(ibrmds))
   216  
   217  	for _, ibrmd := range ibrmds {
   218  		irmd, err := j.convertImmutableBareRMDToIRMD(
   219  			ctx, ibrmd, handle, tlfJournal.uid, tlfJournal.key)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  
   224  		irmds = append(irmds, irmd)
   225  	}
   226  
   227  	// It would be nice to cache the irmds here, but we can't because
   228  	// the underlying journal might have been converted to a branch
   229  	// since we fetched them, and we can't risk putting them in the
   230  	// cache with the wrong branch ID.  TODO: convert them to
   231  	// ImmutableRootMetadata and cache them under the tlfJournal lock?
   232  
   233  	return irmds, nil
   234  }
   235  
   236  // GetIDForHandle implements the MDOps interface for journalMDOps.
   237  func (j journalMDOps) GetIDForHandle(
   238  	ctx context.Context, handle *tlfhandle.Handle) (id tlf.ID, err error) {
   239  	id = handle.TlfID()
   240  	if id == tlf.NullID {
   241  		id, err = j.MDOps.GetIDForHandle(ctx, handle)
   242  		if err != nil || id == tlf.NullID {
   243  			return tlf.NullID, err
   244  		}
   245  	}
   246  
   247  	// If this handle is for a local conflict, use the fake TLF ID
   248  	// that was assigned to it instead.
   249  	newID, ok := j.jManager.getConflictIDForHandle(id, handle)
   250  	if ok {
   251  		id = newID
   252  		handle.SetTlfID(id)
   253  	} else {
   254  		ci := handle.ConflictInfo()
   255  		if ci != nil && ci.Type == tlf.HandleExtensionLocalConflict {
   256  			return tlf.NullID, errors.Errorf(
   257  				"Couldn't find local conflict handle for %s",
   258  				handle.GetCanonicalPath())
   259  		}
   260  	}
   261  
   262  	// Create the journal if needed, while we have access to `handle`.
   263  	_, _ = j.jManager.getTLFJournal(id, handle)
   264  	return id, nil
   265  }
   266  
   267  // TODO: Combine the two GetForTLF functions in MDOps to avoid the
   268  // need for this helper function.
   269  func (j journalMDOps) getForTLF(ctx context.Context, id tlf.ID, bid kbfsmd.BranchID,
   270  	mStatus kbfsmd.MergeStatus, lockBeforeGet *keybase1.LockID,
   271  	delegateFn func(context.Context, tlf.ID, *keybase1.LockID) (
   272  		ImmutableRootMetadata, error)) (ImmutableRootMetadata, error) {
   273  	// If the journal has a head, use that.
   274  	irmd, err := j.getHeadFromJournal(ctx, id, bid, mStatus, nil)
   275  	if err != nil {
   276  		return ImmutableRootMetadata{}, err
   277  	}
   278  	if irmd != (ImmutableRootMetadata{}) {
   279  		return irmd, nil
   280  	}
   281  
   282  	if mStatus == kbfsmd.Unmerged {
   283  		// Journal users always store their unmerged heads locally, so
   284  		// no need to check with the server.
   285  		return ImmutableRootMetadata{}, nil
   286  	}
   287  
   288  	// Otherwise, consult the server instead.
   289  	return delegateFn(ctx, id, lockBeforeGet)
   290  }
   291  
   292  func (j journalMDOps) GetForTLF(
   293  	ctx context.Context, id tlf.ID, lockBeforeGet *keybase1.LockID) (
   294  	irmd ImmutableRootMetadata, err error) {
   295  	j.jManager.log.LazyTrace(ctx, "jMDOps: GetForTLF %s", id)
   296  	defer func() {
   297  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetForTLF %s done (err=%v)", id, err)
   298  	}()
   299  
   300  	return j.getForTLF(
   301  		ctx, id, kbfsmd.NullBranchID, kbfsmd.Merged, lockBeforeGet, j.MDOps.GetForTLF)
   302  }
   303  
   304  func (j journalMDOps) GetForTLFByTime(
   305  	ctx context.Context, id tlf.ID, serverTime time.Time) (
   306  	ImmutableRootMetadata, error) {
   307  	// For now, we don't bother looking up MDs from the journal by
   308  	// time -- that could be confusing, since the "server time" could
   309  	// change once the MD is actually flushed.
   310  	return j.MDOps.GetForTLFByTime(ctx, id, serverTime)
   311  }
   312  
   313  func (j journalMDOps) GetUnmergedForTLF(
   314  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID) (
   315  	irmd ImmutableRootMetadata, err error) {
   316  	j.jManager.log.LazyTrace(ctx, "jMDOps: GetUnmergedForTLF %s %s", id, bid)
   317  	defer func() {
   318  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetForTLF %s %s done (err=%v)", id, bid, err)
   319  	}()
   320  
   321  	delegateFn := func(ctx context.Context, id tlf.ID, _ *keybase1.LockID) (
   322  		ImmutableRootMetadata, error) {
   323  		return j.MDOps.GetUnmergedForTLF(ctx, id, bid)
   324  	}
   325  	return j.getForTLF(ctx, id, bid, kbfsmd.Unmerged, nil, delegateFn)
   326  }
   327  
   328  // TODO: Combine the two GetRange functions in MDOps to avoid the need
   329  // for this helper function.
   330  func (j journalMDOps) getRange(
   331  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus,
   332  	start, stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID,
   333  	delegateFn func(ctx context.Context, id tlf.ID,
   334  		start, stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID) (
   335  		[]ImmutableRootMetadata, error)) (
   336  	[]ImmutableRootMetadata, error) {
   337  	// Grab the range from the journal first.
   338  	jirmds, err := j.getRangeFromJournal(ctx, id, bid, mStatus, start, stop)
   339  	switch errors.Cause(err).(type) {
   340  	case nil:
   341  		break
   342  	case errTLFJournalDisabled:
   343  		// Fall back to the server.
   344  		return delegateFn(ctx, id, start, stop, lockBeforeGet)
   345  	default:
   346  		return nil, err
   347  	}
   348  
   349  	if len(jirmds) != 0 && lockBeforeGet != nil {
   350  		// We need to grab locks, so we have to hit the server. But it's
   351  		// dangerous to bypass journal if we have revisions in this range from
   352  		// the journal. For now, we just return error here.
   353  		// NOTE: In the future if we ever need locking in places other than
   354  		// SyncFromServer, we can use the naked Lock RPC to grab the lock if
   355  		// everything we need is in the journal already.
   356  		return nil, errors.New(
   357  			"cannot lock when getting revisions that exist in journal")
   358  	}
   359  
   360  	// If it's empty, fall back to the server if this isn't a local
   361  	// squash branch.  TODO: we should be able to avoid server access
   362  	// for regular conflict branches when the journal is enabled, as
   363  	// well, once we're confident that all old server-based branches
   364  	// have been resolved.
   365  	if len(jirmds) == 0 {
   366  		if bid == kbfsmd.PendingLocalSquashBranchID {
   367  			return jirmds, nil
   368  		}
   369  		return delegateFn(ctx, id, start, stop, lockBeforeGet)
   370  	}
   371  
   372  	// If the first revision from the journal is the first revision we
   373  	// asked for (or this is a local squash that doesn't require
   374  	// server access), then just return the range from the journal.
   375  	// TODO: we should be able to avoid server access for regular
   376  	// conflict branches, as well.
   377  	if jirmds[0].Revision() == start || bid == kbfsmd.PendingLocalSquashBranchID {
   378  		return jirmds, nil
   379  	}
   380  
   381  	// Otherwise, fetch the rest from the server and prepend them.
   382  	serverStop := jirmds[0].Revision() - 1
   383  	irmds, err := delegateFn(ctx, id, start, serverStop, lockBeforeGet)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  
   388  	if len(irmds) == 0 {
   389  		return jirmds, nil
   390  	}
   391  
   392  	lastRev := irmds[len(irmds)-1].Revision()
   393  	if lastRev != serverStop {
   394  		return nil, fmt.Errorf(
   395  			"Expected last server rev %d, got %d",
   396  			serverStop, lastRev)
   397  	}
   398  
   399  	return append(irmds, jirmds...), nil
   400  }
   401  
   402  func (j journalMDOps) GetRange(ctx context.Context, id tlf.ID, start,
   403  	stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID) (
   404  	irmds []ImmutableRootMetadata, err error) {
   405  	j.jManager.log.LazyTrace(ctx, "jMDOps: GetRange %s %d-%d", id, start, stop)
   406  	defer func() {
   407  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetRange %s %d-%d done (err=%v)", id, start, stop, err)
   408  	}()
   409  
   410  	return j.getRange(ctx, id, kbfsmd.NullBranchID, kbfsmd.Merged, start, stop, lockBeforeGet,
   411  		j.MDOps.GetRange)
   412  }
   413  
   414  func (j journalMDOps) GetUnmergedRange(
   415  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID,
   416  	start, stop kbfsmd.Revision) (irmd []ImmutableRootMetadata, err error) {
   417  	j.jManager.log.LazyTrace(ctx, "jMDOps: GetUnmergedRange %s %d-%d", id, start, stop)
   418  	defer func() {
   419  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetUnmergedRange %s %d-%d done (err=%v)", id, start, stop, err)
   420  	}()
   421  
   422  	delegateFn := func(ctx context.Context, id tlf.ID,
   423  		start, stop kbfsmd.Revision, _ *keybase1.LockID) (
   424  		[]ImmutableRootMetadata, error) {
   425  		return j.MDOps.GetUnmergedRange(ctx, id, bid, start, stop)
   426  	}
   427  	return j.getRange(ctx, id, bid, kbfsmd.Unmerged, start, stop, nil,
   428  		delegateFn)
   429  }
   430  
   431  func (j journalMDOps) Put(ctx context.Context, rmd *RootMetadata,
   432  	verifyingKey kbfscrypto.VerifyingKey,
   433  	lc *keybase1.LockContext, priority keybase1.MDPriority,
   434  	bps data.BlockPutState) (
   435  	irmd ImmutableRootMetadata, err error) {
   436  	j.jManager.log.LazyTrace(ctx, "jMDOps: Put %s %d", rmd.TlfID(), rmd.Revision())
   437  	defer func() {
   438  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: Put %s %d done (err=%v)", rmd.TlfID(), rmd.Revision(), err)
   439  	}()
   440  
   441  	if tlfJournal, ok := j.jManager.getTLFJournal(
   442  		rmd.TlfID(), rmd.GetTlfHandle()); ok {
   443  		if lc != nil {
   444  			return ImmutableRootMetadata{}, errors.New(
   445  				"journal Put doesn't support LockContext " +
   446  					"yet. Use FinishSingleOp to require locks on MD write.")
   447  		}
   448  		if priority != keybase1.MDPriorityNormal {
   449  			return ImmutableRootMetadata{}, errors.New(
   450  				"journal Put doesn't support priority other than " +
   451  					"MDPriorityNormal yet. Use FinishSingleOp to specify " +
   452  					"priority on MD write.")
   453  		}
   454  		// Just route to the journal.
   455  		irmd, err := tlfJournal.putMD(ctx, rmd, verifyingKey, bps)
   456  		switch errors.Cause(err).(type) {
   457  		case nil:
   458  			return irmd, nil
   459  		case errTLFJournalDisabled:
   460  			break
   461  		default:
   462  			return ImmutableRootMetadata{}, err
   463  		}
   464  	}
   465  
   466  	return j.MDOps.Put(ctx, rmd, verifyingKey, lc, priority, bps)
   467  }
   468  
   469  func (j journalMDOps) PutUnmerged(
   470  	ctx context.Context, rmd *RootMetadata,
   471  	verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) (
   472  	irmd ImmutableRootMetadata, err error) {
   473  	j.jManager.log.LazyTrace(ctx, "jMDOps: PutUnmerged %s %d", rmd.TlfID(), rmd.Revision())
   474  	defer func() {
   475  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: PutUnmerged %s %d done (err=%v)", rmd.TlfID(), rmd.Revision(), err)
   476  	}()
   477  
   478  	if tlfJournal, ok := j.jManager.getTLFJournal(
   479  		rmd.TlfID(), rmd.GetTlfHandle()); ok {
   480  		rmd.SetUnmerged()
   481  		irmd, err := tlfJournal.putMD(ctx, rmd, verifyingKey, bps)
   482  		switch errors.Cause(err).(type) {
   483  		case nil:
   484  			return irmd, nil
   485  		case errTLFJournalDisabled:
   486  			break
   487  		default:
   488  			return ImmutableRootMetadata{}, err
   489  		}
   490  	}
   491  
   492  	return j.MDOps.PutUnmerged(ctx, rmd, verifyingKey, bps)
   493  }
   494  
   495  func (j journalMDOps) PruneBranch(
   496  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID) (err error) {
   497  	j.jManager.log.LazyTrace(ctx, "jMDOps: PruneBranch %s %s", id, bid)
   498  	defer func() {
   499  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: PruneBranch %s %s (err=%v)", id, bid, err)
   500  	}()
   501  
   502  	if tlfJournal, ok := j.jManager.getTLFJournal(id, nil); ok {
   503  		// Prune the journal, too.
   504  		err := tlfJournal.clearMDs(ctx, bid)
   505  		switch errors.Cause(err).(type) {
   506  		case nil:
   507  			break
   508  		case errTLFJournalDisabled:
   509  			break
   510  		default:
   511  			return err
   512  		}
   513  	}
   514  
   515  	return j.MDOps.PruneBranch(ctx, id, bid)
   516  }
   517  
   518  func (j journalMDOps) ResolveBranch(
   519  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID,
   520  	blocksToDelete []kbfsblock.ID, rmd *RootMetadata,
   521  	verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) (
   522  	irmd ImmutableRootMetadata, err error) {
   523  	j.jManager.log.LazyTrace(ctx, "jMDOps: ResolveBranch %s %s", id, bid)
   524  	defer func() {
   525  		j.jManager.deferLog.LazyTrace(ctx, "jMDOps: ResolveBranch %s %s (err=%v)", id, bid, err)
   526  	}()
   527  
   528  	if tlfJournal, ok := j.jManager.getTLFJournal(id, rmd.GetTlfHandle()); ok {
   529  		irmd, err := tlfJournal.resolveBranch(
   530  			ctx, bid, blocksToDelete, rmd, verifyingKey, bps)
   531  		switch errors.Cause(err).(type) {
   532  		case nil:
   533  			return irmd, nil
   534  		case errTLFJournalDisabled:
   535  			break
   536  		default:
   537  			return ImmutableRootMetadata{}, err
   538  		}
   539  	}
   540  
   541  	return j.MDOps.ResolveBranch(
   542  		ctx, id, bid, blocksToDelete, rmd, verifyingKey, bps)
   543  }