github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/roachpb/batch.go (about)

     1  // Copyright 2014 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package roachpb
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/concurrency/lock"
    20  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  //go:generate go run -tags gen-batch gen_batch.go
    26  
    27  // SetActiveTimestamp sets the correct timestamp at which the request is to be
    28  // carried out. For transactional requests, ba.Timestamp must be zero initially
    29  // and it will be set to txn.ReadTimestamp (note though this mostly impacts
    30  // reads; writes use txn.Timestamp). For non-transactional requests, if no
    31  // timestamp is specified, nowFn is used to create and set one.
    32  func (ba *BatchRequest) SetActiveTimestamp(nowFn func() hlc.Timestamp) error {
    33  	if txn := ba.Txn; txn != nil {
    34  		if ba.Timestamp != (hlc.Timestamp{}) {
    35  			return errors.New("transactional request must not set batch timestamp")
    36  		}
    37  
    38  		// The batch timestamp is the timestamp at which reads are performed. We set
    39  		// this to the txn's read timestamp, even if the txn's provisional
    40  		// commit timestamp has been forwarded, so that all reads within a txn
    41  		// observe the same snapshot of the database regardless of how the
    42  		// provisional commit timestamp evolves.
    43  		//
    44  		// Note that writes will be performed at the provisional commit timestamp,
    45  		// txn.Timestamp, regardless of the batch timestamp.
    46  		ba.Timestamp = txn.ReadTimestamp
    47  	} else {
    48  		// When not transactional, allow empty timestamp and use nowFn instead
    49  		if ba.Timestamp == (hlc.Timestamp{}) {
    50  			ba.Timestamp = nowFn()
    51  		}
    52  	}
    53  	return nil
    54  }
    55  
    56  // UpdateTxn updates the batch transaction from the supplied one in
    57  // a copy-on-write fashion, i.e. without mutating an existing
    58  // Transaction struct.
    59  func (ba *BatchRequest) UpdateTxn(o *Transaction) {
    60  	if o == nil {
    61  		return
    62  	}
    63  	o.AssertInitialized(context.TODO())
    64  	if ba.Txn == nil {
    65  		ba.Txn = o
    66  		return
    67  	}
    68  	clonedTxn := ba.Txn.Clone()
    69  	clonedTxn.Update(o)
    70  	ba.Txn = clonedTxn
    71  }
    72  
    73  // IsLeaseRequest returns whether the batch consists of a single RequestLease
    74  // request. Note that TransferLease requests return false.
    75  // RequestLease requests are special because they're the only type of requests a
    76  // non-lease-holder can propose.
    77  func (ba *BatchRequest) IsLeaseRequest() bool {
    78  	if !ba.IsSingleRequest() {
    79  		return false
    80  	}
    81  	_, ok := ba.GetArg(RequestLease)
    82  	return ok
    83  }
    84  
    85  // IsAdmin returns true iff the BatchRequest contains an admin request.
    86  func (ba *BatchRequest) IsAdmin() bool {
    87  	return ba.hasFlag(isAdmin)
    88  }
    89  
    90  // IsWrite returns true iff the BatchRequest contains a write.
    91  func (ba *BatchRequest) IsWrite() bool {
    92  	return ba.hasFlag(isWrite)
    93  }
    94  
    95  // IsReadOnly returns true if all requests within are read-only.
    96  func (ba *BatchRequest) IsReadOnly() bool {
    97  	return len(ba.Requests) > 0 && !ba.hasFlag(isWrite|isAdmin)
    98  }
    99  
   100  // RequiresLeaseHolder returns true if the request can only be served by the
   101  // leaseholders of the ranges it addresses.
   102  func (ba *BatchRequest) RequiresLeaseHolder() bool {
   103  	return ba.IsLocking() || ba.Header.ReadConsistency.RequiresReadLease()
   104  }
   105  
   106  // IsReverse returns true iff the BatchRequest contains a reverse request.
   107  func (ba *BatchRequest) IsReverse() bool {
   108  	return ba.hasFlag(isReverse)
   109  }
   110  
   111  // IsTransactional returns true iff the BatchRequest contains requests that can
   112  // be part of a transaction.
   113  func (ba *BatchRequest) IsTransactional() bool {
   114  	return ba.hasFlag(isTxn)
   115  }
   116  
   117  // IsAllTransactional returns true iff the BatchRequest contains only requests
   118  // that can be part of a transaction.
   119  func (ba *BatchRequest) IsAllTransactional() bool {
   120  	return ba.hasFlagForAll(isTxn)
   121  }
   122  
   123  // IsLocking returns true iff the BatchRequest intends to acquire locks.
   124  func (ba *BatchRequest) IsLocking() bool {
   125  	return ba.hasFlag(isLocking)
   126  }
   127  
   128  // IsIntentWrite returns true iff the BatchRequest contains an intent write.
   129  func (ba *BatchRequest) IsIntentWrite() bool {
   130  	return ba.hasFlag(isIntentWrite)
   131  }
   132  
   133  // IsUnsplittable returns true iff the BatchRequest an un-splittable request.
   134  func (ba *BatchRequest) IsUnsplittable() bool {
   135  	return ba.hasFlag(isUnsplittable)
   136  }
   137  
   138  // IsSingleRequest returns true iff the BatchRequest contains a single request.
   139  func (ba *BatchRequest) IsSingleRequest() bool {
   140  	return len(ba.Requests) == 1
   141  }
   142  
   143  // IsSingleSkipLeaseCheckRequest returns true iff the batch contains a single
   144  // request, and that request has the skipLeaseCheck flag set.
   145  func (ba *BatchRequest) IsSingleSkipLeaseCheckRequest() bool {
   146  	return ba.IsSingleRequest() && ba.hasFlag(skipLeaseCheck)
   147  }
   148  
   149  // IsSinglePushTxnRequest returns true iff the batch contains a single
   150  // request, and that request is for a PushTxn.
   151  func (ba *BatchRequest) IsSinglePushTxnRequest() bool {
   152  	if ba.IsSingleRequest() {
   153  		_, ok := ba.Requests[0].GetInner().(*PushTxnRequest)
   154  		return ok
   155  	}
   156  	return false
   157  }
   158  
   159  // IsSingleHeartbeatTxnRequest returns true iff the batch contains a single
   160  // request, and that request is a HeartbeatTxn.
   161  func (ba *BatchRequest) IsSingleHeartbeatTxnRequest() bool {
   162  	if ba.IsSingleRequest() {
   163  		_, ok := ba.Requests[0].GetInner().(*HeartbeatTxnRequest)
   164  		return ok
   165  	}
   166  	return false
   167  }
   168  
   169  // IsSingleEndTxnRequest returns true iff the batch contains a single request,
   170  // and that request is an EndTxnRequest.
   171  func (ba *BatchRequest) IsSingleEndTxnRequest() bool {
   172  	if ba.IsSingleRequest() {
   173  		_, ok := ba.Requests[0].GetInner().(*EndTxnRequest)
   174  		return ok
   175  	}
   176  	return false
   177  }
   178  
   179  // IsSingleAbortTxnRequest returns true iff the batch contains a single request,
   180  // and that request is an EndTxnRequest(commit=false).
   181  func (ba *BatchRequest) IsSingleAbortTxnRequest() bool {
   182  	if ba.IsSingleRequest() {
   183  		if et, ok := ba.Requests[0].GetInner().(*EndTxnRequest); ok {
   184  			return !et.Commit
   185  		}
   186  	}
   187  	return false
   188  }
   189  
   190  // IsSingleSubsumeRequest returns true iff the batch contains a single request,
   191  // and that request is an SubsumeRequest.
   192  func (ba *BatchRequest) IsSingleSubsumeRequest() bool {
   193  	if ba.IsSingleRequest() {
   194  		_, ok := ba.Requests[0].GetInner().(*SubsumeRequest)
   195  		return ok
   196  	}
   197  	return false
   198  }
   199  
   200  // IsSingleComputeChecksumRequest returns true iff the batch contains a single
   201  // request, and that request is a ComputeChecksumRequest.
   202  func (ba *BatchRequest) IsSingleComputeChecksumRequest() bool {
   203  	if ba.IsSingleRequest() {
   204  		_, ok := ba.Requests[0].GetInner().(*ComputeChecksumRequest)
   205  		return ok
   206  	}
   207  	return false
   208  }
   209  
   210  // IsSingleCheckConsistencyRequest returns true iff the batch contains a single
   211  // request, and that request is a CheckConsistencyRequest.
   212  func (ba *BatchRequest) IsSingleCheckConsistencyRequest() bool {
   213  	if ba.IsSingleRequest() {
   214  		_, ok := ba.Requests[0].GetInner().(*CheckConsistencyRequest)
   215  		return ok
   216  	}
   217  	return false
   218  }
   219  
   220  // IsSingleAddSSTableRequest returns true iff the batch contains a single
   221  // request, and that request is an AddSSTableRequest that will ingest as an SST,
   222  // (i.e. does not have IngestAsWrites set)
   223  func (ba *BatchRequest) IsSingleAddSSTableRequest() bool {
   224  	if ba.IsSingleRequest() {
   225  		req, ok := ba.Requests[0].GetInner().(*AddSSTableRequest)
   226  		return ok && !req.IngestAsWrites
   227  	}
   228  	return false
   229  }
   230  
   231  // IsCompleteTransaction determines whether a batch contains every write in a
   232  // transactions.
   233  func (ba *BatchRequest) IsCompleteTransaction() bool {
   234  	et, hasET := ba.GetArg(EndTxn)
   235  	if !hasET || !et.(*EndTxnRequest).Commit {
   236  		return false
   237  	}
   238  	maxSeq := et.Header().Sequence
   239  	switch maxSeq {
   240  	case 0:
   241  		// If the batch isn't using sequence numbers,
   242  		// assume that it is not a complete transaction.
   243  		return false
   244  	case 1:
   245  		// The transaction performed no writes.
   246  		return true
   247  	}
   248  	if int(maxSeq) > len(ba.Requests) {
   249  		// Fast-path.
   250  		return false
   251  	}
   252  	// Check whether any sequence numbers were skipped between 1 and the
   253  	// EndTxn's sequence number. A Batch is only a complete transaction
   254  	// if it contains every write that the transaction performed.
   255  	nextSeq := enginepb.TxnSeq(1)
   256  	for _, args := range ba.Requests {
   257  		req := args.GetInner()
   258  		seq := req.Header().Sequence
   259  		if seq > nextSeq {
   260  			return false
   261  		}
   262  		if seq == nextSeq {
   263  			if !IsIntentWrite(req) {
   264  				return false
   265  			}
   266  			nextSeq++
   267  			if nextSeq == maxSeq {
   268  				return true
   269  			}
   270  		}
   271  	}
   272  	panic("unreachable")
   273  }
   274  
   275  // GetPrevLeaseForLeaseRequest returns the previous lease, at the time
   276  // of proposal, for a request lease or transfer lease request. If the
   277  // batch does not contain a single lease request, this method will panic.
   278  func (ba *BatchRequest) GetPrevLeaseForLeaseRequest() Lease {
   279  	return ba.Requests[0].GetInner().(leaseRequestor).prevLease()
   280  }
   281  
   282  // hasFlag returns true iff one of the requests within the batch contains the
   283  // specified flag.
   284  func (ba *BatchRequest) hasFlag(flag int) bool {
   285  	for _, union := range ba.Requests {
   286  		if (union.GetInner().flags() & flag) != 0 {
   287  			return true
   288  		}
   289  	}
   290  	return false
   291  }
   292  
   293  // hasFlagForAll returns true iff all of the requests within the batch contains
   294  // the specified flag.
   295  func (ba *BatchRequest) hasFlagForAll(flag int) bool {
   296  	if len(ba.Requests) == 0 {
   297  		return false
   298  	}
   299  	for _, union := range ba.Requests {
   300  		if (union.GetInner().flags() & flag) == 0 {
   301  			return false
   302  		}
   303  	}
   304  	return true
   305  }
   306  
   307  // GetArg returns a request of the given type if one is contained in the
   308  // Batch. The request returned is the first of its kind, with the exception
   309  // of EndTxn, where it examines the very last request only.
   310  func (ba *BatchRequest) GetArg(method Method) (Request, bool) {
   311  	// when looking for EndTxn, just look at the last entry.
   312  	if method == EndTxn {
   313  		if length := len(ba.Requests); length > 0 {
   314  			if req := ba.Requests[length-1].GetInner(); req.Method() == EndTxn {
   315  				return req, true
   316  			}
   317  		}
   318  		return nil, false
   319  	}
   320  
   321  	for _, arg := range ba.Requests {
   322  		if req := arg.GetInner(); req.Method() == method {
   323  			return req, true
   324  		}
   325  	}
   326  	return nil, false
   327  }
   328  
   329  func (br *BatchResponse) String() string {
   330  	var str []string
   331  	str = append(str, fmt.Sprintf("(err: %v)", br.Error))
   332  	for count, union := range br.Responses {
   333  		// Limit the strings to provide just a summary. Without this limit a log
   334  		// message with a BatchResponse can be very long.
   335  		if count >= 20 && count < len(br.Responses)-5 {
   336  			if count == 20 {
   337  				str = append(str, fmt.Sprintf("... %d skipped ...", len(br.Responses)-25))
   338  			}
   339  			continue
   340  		}
   341  		str = append(str, fmt.Sprintf("%T", union.GetInner()))
   342  	}
   343  	return strings.Join(str, ", ")
   344  }
   345  
   346  // LockSpanIterate calls the passed method with the key ranges of the
   347  // transactional locks contained in the batch. Usually the key spans
   348  // contained in the requests are used, but when a response contains a
   349  // ResumeSpan the ResumeSpan is subtracted from the request span to
   350  // provide a more minimal span of keys affected by the request.
   351  func (ba *BatchRequest) LockSpanIterate(br *BatchResponse, fn func(Span, lock.Durability)) {
   352  	for i, arg := range ba.Requests {
   353  		req := arg.GetInner()
   354  		if !IsLocking(req) {
   355  			continue
   356  		}
   357  		var resp Response
   358  		if br != nil {
   359  			resp = br.Responses[i].GetInner()
   360  		}
   361  		if span, ok := ActualSpan(req, resp); ok {
   362  			fn(span, LockingDurability(req))
   363  		}
   364  	}
   365  }
   366  
   367  // RefreshSpanIterate calls the passed function with the key spans of
   368  // requests in the batch which need to be refreshed. These requests
   369  // must be checked via Refresh/RefreshRange to avoid having to restart
   370  // a SERIALIZABLE transaction. Usually the key spans contained in the
   371  // requests are used, but when a response contains a ResumeSpan the
   372  // ResumeSpan is subtracted from the request span to provide a more
   373  // minimal span of keys affected by the request. The supplied function
   374  // is called with each span.
   375  func (ba *BatchRequest) RefreshSpanIterate(br *BatchResponse, fn func(Span)) {
   376  	for i, arg := range ba.Requests {
   377  		req := arg.GetInner()
   378  		if !NeedsRefresh(req) {
   379  			continue
   380  		}
   381  		var resp Response
   382  		if br != nil {
   383  			resp = br.Responses[i].GetInner()
   384  		}
   385  		if span, ok := ActualSpan(req, resp); ok {
   386  			fn(span)
   387  		}
   388  	}
   389  }
   390  
   391  // ActualSpan returns the actual request span which was operated on,
   392  // according to the existence of a resume span in the response. If
   393  // nothing was operated on, returns false.
   394  func ActualSpan(req Request, resp Response) (Span, bool) {
   395  	h := req.Header()
   396  	if resp != nil {
   397  		resumeSpan := resp.Header().ResumeSpan
   398  		// If a resume span exists we need to cull the span.
   399  		if resumeSpan != nil {
   400  			// Handle the reverse case first.
   401  			if bytes.Equal(resumeSpan.Key, h.Key) {
   402  				if bytes.Equal(resumeSpan.EndKey, h.EndKey) {
   403  					return Span{}, false
   404  				}
   405  				return Span{Key: resumeSpan.EndKey, EndKey: h.EndKey}, true
   406  			}
   407  			// The forward case.
   408  			return Span{Key: h.Key, EndKey: resumeSpan.Key}, true
   409  		}
   410  	}
   411  	return h.Span(), true
   412  }
   413  
   414  // Combine combines each slot of the given request into the corresponding slot
   415  // of the base response. The number of slots must be equal and the respective
   416  // slots must be combinable.
   417  // On error, the receiver BatchResponse is in an invalid state. In either case,
   418  // the supplied BatchResponse must not be used any more.
   419  // It is an error to call Combine on responses with errors in them. The
   420  // DistSender strips the errors from any responses that it combines.
   421  func (br *BatchResponse) Combine(otherBatch *BatchResponse, positions []int) error {
   422  	if err := br.BatchResponse_Header.combine(otherBatch.BatchResponse_Header); err != nil {
   423  		return err
   424  	}
   425  	for i := range otherBatch.Responses {
   426  		pos := positions[i]
   427  		if br.Responses[pos] == (ResponseUnion{}) {
   428  			br.Responses[pos] = otherBatch.Responses[i]
   429  			continue
   430  		}
   431  		valLeft := br.Responses[pos].GetInner()
   432  		valRight := otherBatch.Responses[i].GetInner()
   433  		if err := CombineResponses(valLeft, valRight); err != nil {
   434  			return err
   435  		}
   436  	}
   437  	return nil
   438  }
   439  
   440  // Add adds a request to the batch request. It's a convenience method;
   441  // requests may also be added directly into the slice.
   442  func (ba *BatchRequest) Add(requests ...Request) {
   443  	for _, args := range requests {
   444  		ba.Requests = append(ba.Requests, RequestUnion{})
   445  		ba.Requests[len(ba.Requests)-1].MustSetInner(args)
   446  	}
   447  }
   448  
   449  // Add adds a response to the batch response. It's a convenience method;
   450  // responses may also be added directly.
   451  func (br *BatchResponse) Add(reply Response) {
   452  	br.Responses = append(br.Responses, ResponseUnion{})
   453  	br.Responses[len(br.Responses)-1].MustSetInner(reply)
   454  }
   455  
   456  // Methods returns a slice of the contained methods.
   457  func (ba *BatchRequest) Methods() []Method {
   458  	var res []Method
   459  	for _, arg := range ba.Requests {
   460  		res = append(res, arg.GetInner().Method())
   461  	}
   462  	return res
   463  }
   464  
   465  // Split separates the requests contained in a batch so that each subset of
   466  // requests can be executed by a Store (without changing order). In particular,
   467  // Admin requests are always singled out and mutating requests separated from
   468  // reads. The boolean parameter indicates whether EndTxn should be
   469  // special-cased: If false, an EndTxn request will never be split into a new
   470  // chunk (otherwise, it is treated according to its flags). This allows sending
   471  // a whole transaction in a single Batch when addressing a single range.
   472  //
   473  // NOTE: One reason for splitting reads from writes is that write-only batches
   474  // can sometimes have their read timestamp bumped on the server, which doesn't
   475  // work for read requests due to how the timestamp-aware latching works (i.e. a
   476  // read that acquired a latch @ ts10 can't simply be bumped to ts 20 because
   477  // there might have been overlapping writes in the 10..20 window).
   478  func (ba BatchRequest) Split(canSplitET bool) [][]RequestUnion {
   479  	compatible := func(exFlags, newFlags int) bool {
   480  		// isAlone requests are never compatible.
   481  		if (exFlags&isAlone) != 0 || (newFlags&isAlone) != 0 {
   482  			return false
   483  		}
   484  		// If the current or new flags are empty and neither include isAlone,
   485  		// everything goes.
   486  		if exFlags == 0 || newFlags == 0 {
   487  			return true
   488  		}
   489  		// Otherwise, the flags below must remain the same with the new
   490  		// request added.
   491  		//
   492  		// Note that we're not checking isRead: The invariants we're
   493  		// enforcing are that a batch can't mix non-writes with writes.
   494  		// Checking isRead would cause ConditionalPut and Put to conflict,
   495  		// which is not what we want.
   496  		const mask = isWrite | isAdmin | isReverse
   497  		return (mask & exFlags) == (mask & newFlags)
   498  	}
   499  	var parts [][]RequestUnion
   500  	for len(ba.Requests) > 0 {
   501  		part := ba.Requests
   502  		var gFlags, hFlags = -1, -1
   503  		for i, union := range ba.Requests {
   504  			args := union.GetInner()
   505  			flags := args.flags()
   506  			method := args.Method()
   507  			if (flags & isPrefix) != 0 {
   508  				// Requests with the isPrefix flag want to be grouped with the
   509  				// next non-header request in a batch. Scan forward and find
   510  				// first non-header request. Naively, this would result in
   511  				// quadratic behavior for repeat isPrefix requests. We avoid
   512  				// this by caching first non-header request's flags in hFlags.
   513  				if hFlags == -1 {
   514  					for _, nUnion := range ba.Requests[i+1:] {
   515  						nArgs := nUnion.GetInner()
   516  						nFlags := nArgs.flags()
   517  						nMethod := nArgs.Method()
   518  						if !canSplitET && nMethod == EndTxn {
   519  							nFlags = 0 // always compatible
   520  						}
   521  						if (nFlags & isPrefix) == 0 {
   522  							hFlags = nFlags
   523  							break
   524  						}
   525  					}
   526  				}
   527  				if hFlags != -1 && (hFlags&isAlone) == 0 {
   528  					flags = hFlags
   529  				}
   530  			} else {
   531  				hFlags = -1 // reset
   532  			}
   533  			cmpFlags := flags
   534  			if !canSplitET && method == EndTxn {
   535  				cmpFlags = 0 // always compatible
   536  			}
   537  			if gFlags == -1 {
   538  				// If no flags are set so far, everything goes.
   539  				gFlags = flags
   540  			} else {
   541  				if !compatible(gFlags, cmpFlags) {
   542  					part = ba.Requests[:i]
   543  					break
   544  				}
   545  				gFlags |= flags
   546  			}
   547  		}
   548  		parts = append(parts, part)
   549  		ba.Requests = ba.Requests[len(part):]
   550  	}
   551  	return parts
   552  }
   553  
   554  // String gives a brief summary of the contained requests and keys in the batch.
   555  // TODO(tschottdorf): the key range is useful information, but requires `keys`.
   556  // See #2198.
   557  func (ba BatchRequest) String() string {
   558  	var str []string
   559  	if ba.Txn != nil {
   560  		str = append(str, fmt.Sprintf("[txn: %s]", ba.Txn.Short()))
   561  	}
   562  	for count, arg := range ba.Requests {
   563  		// Limit the strings to provide just a summary. Without this limit
   564  		// a log message with a BatchRequest can be very long.
   565  		if count >= 20 && count < len(ba.Requests)-5 {
   566  			if count == 20 {
   567  				str = append(str, fmt.Sprintf("... %d skipped ...", len(ba.Requests)-25))
   568  			}
   569  			continue
   570  		}
   571  		req := arg.GetInner()
   572  		if et, ok := req.(*EndTxnRequest); ok {
   573  			h := req.Header()
   574  			str = append(str, fmt.Sprintf("%s(commit:%t tsflex:%t) [%s] ",
   575  				req.Method(), et.Commit, et.CanCommitAtHigherTimestamp, h.Key))
   576  		} else {
   577  			h := req.Header()
   578  			var s string
   579  			if req.Method() == PushTxn {
   580  				pushReq := req.(*PushTxnRequest)
   581  				s = fmt.Sprintf("PushTxn(%s->%s)", pushReq.PusherTxn.Short(), pushReq.PusheeTxn.Short())
   582  			} else {
   583  				s = req.Method().String()
   584  			}
   585  			str = append(str, fmt.Sprintf("%s [%s,%s)", s, h.Key, h.EndKey))
   586  		}
   587  	}
   588  	return strings.Join(str, ", ")
   589  }
   590  
   591  // ValidateForEvaluation performs sanity checks on the batch when it's received
   592  // by the "server" for evaluation.
   593  func (ba BatchRequest) ValidateForEvaluation() error {
   594  	if ba.RangeID == 0 {
   595  		return errors.AssertionFailedf("batch request missing range ID")
   596  	} else if ba.Replica.StoreID == 0 {
   597  		return errors.AssertionFailedf("batch request missing store ID")
   598  	}
   599  	if _, ok := ba.GetArg(EndTxn); ok && ba.Txn == nil {
   600  		return errors.AssertionFailedf("EndTxn request without transaction")
   601  	}
   602  	if ba.Txn != nil {
   603  		if ba.Txn.WriteTooOld && (ba.Txn.ReadTimestamp.Equal(ba.Txn.WriteTimestamp)) {
   604  			return errors.AssertionFailedf("WriteTooOld set but no offset in timestamps. txn: %s", ba.Txn)
   605  		}
   606  	}
   607  	return nil
   608  }