github.com/bartle-stripe/trillian@v1.2.1/testonly/hammer/hammer.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package hammer
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"math/rand"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/golang/glog"
    28  	"github.com/golang/protobuf/proto"
    29  	"github.com/google/trillian"
    30  	"github.com/google/trillian/client"
    31  	"github.com/google/trillian/monitoring"
    32  	"github.com/google/trillian/testonly"
    33  	"github.com/google/trillian/types"
    34  )
    35  
    36  const (
    37  	defaultEmitSeconds = 10
    38  	// How many SMRs to hold on to.
    39  	smrCount = 30
    40  	// How far beyond current revision to request for invalid requests
    41  	invalidStretch = int64(10000)
    42  	// rev=-1 is used when requesting the latest revision
    43  	latestRevision = int64(-1)
    44  	// Format specifier for generating leaf values
    45  	valueFormat = "value-%09d"
    46  	minValueLen = len("value-") + 9 // prefix + 9 digits
    47  )
    48  
    49  var (
    50  	maxRetryDuration = 60 * time.Second
    51  )
    52  
    53  var (
    54  	// Metrics are all per-map (label "mapid"), and per-entrypoint (label "ep").
    55  	once        sync.Once
    56  	reqs        monitoring.Counter   // mapid, ep => value
    57  	errs        monitoring.Counter   // mapid, ep => value
    58  	rsps        monitoring.Counter   // mapid, ep => value
    59  	rspLatency  monitoring.Histogram // mapid, ep => distribution-of-values
    60  	invalidReqs monitoring.Counter   // mapid, ep => value
    61  )
    62  
    63  // setupMetrics initializes all the exported metrics.
    64  func setupMetrics(mf monitoring.MetricFactory) {
    65  	reqs = mf.NewCounter("reqs", "Number of valid requests sent", "mapid", "ep")
    66  	errs = mf.NewCounter("errs", "Number of error responses received for valid requests", "mapid", "ep")
    67  	rsps = mf.NewCounter("rsps", "Number of responses received for valid requests", "mapid", "ep")
    68  	rspLatency = mf.NewHistogram("rsp_latency", "Latency of responses received for valid requests in seconds", "mapid", "ep")
    69  	invalidReqs = mf.NewCounter("invalid_reqs", "Number of deliberately-invalid requests sent", "mapid", "ep")
    70  }
    71  
    72  // errSkip indicates that a test operation should be skipped.
    73  type errSkip struct{}
    74  
    75  func (e errSkip) Error() string {
    76  	return "test operation skipped"
    77  }
    78  
    79  // errInvariant indicates that an invariant check failed, with details in msg.
    80  type errInvariant struct {
    81  	msg string
    82  }
    83  
    84  func (e errInvariant) Error() string {
    85  	return fmt.Sprintf("Invariant check failed: %v", e.msg)
    86  }
    87  
    88  // MapEntrypointName identifies a Map RPC entrypoint
    89  type MapEntrypointName string
    90  
    91  // Constants for entrypoint names, as exposed in statistics/logging.
    92  const (
    93  	GetLeavesName    = MapEntrypointName("GetLeaves")
    94  	GetLeavesRevName = MapEntrypointName("GetLeavesRev")
    95  	SetLeavesName    = MapEntrypointName("SetLeaves")
    96  	GetSMRName       = MapEntrypointName("GetSMR")
    97  	GetSMRRevName    = MapEntrypointName("GetSMRRev")
    98  )
    99  
   100  var mapEntrypoints = []MapEntrypointName{GetLeavesName, GetLeavesRevName, SetLeavesName, GetSMRName, GetSMRRevName}
   101  
   102  // Choice is a readable representation of a choice about how to perform a hammering operation.
   103  type Choice string
   104  
   105  // Constants for both valid and invalid operation choices.
   106  const (
   107  	ExistingKey    = Choice("ExistingKey")
   108  	NonexistentKey = Choice("NonexistentKey")
   109  	MalformedKey   = Choice("MalformedKey")
   110  	DuplicateKey   = Choice("DuplicateKey")
   111  	RevTooBig      = Choice("RevTooBig")
   112  	RevIsNegative  = Choice("RevIsNegative")
   113  	CreateLeaf     = Choice("CreateLeaf")
   114  	UpdateLeaf     = Choice("UpdateLeaf")
   115  	DeleteLeaf     = Choice("DeleteLeaf")
   116  )
   117  
   118  // MapBias indicates the bias for selecting different map operations.
   119  type MapBias struct {
   120  	Bias  map[MapEntrypointName]int
   121  	total int
   122  	// InvalidChance gives the odds of performing an invalid operation, as the N in 1-in-N.
   123  	InvalidChance map[MapEntrypointName]int
   124  }
   125  
   126  // choose randomly picks an operation to perform according to the biases.
   127  func (hb *MapBias) choose(r *rand.Rand) MapEntrypointName {
   128  	if hb.total == 0 {
   129  		for _, ep := range mapEntrypoints {
   130  			hb.total += hb.Bias[ep]
   131  		}
   132  	}
   133  	which := r.Intn(hb.total)
   134  	for _, ep := range mapEntrypoints {
   135  		which -= hb.Bias[ep]
   136  		if which < 0 {
   137  			return ep
   138  		}
   139  	}
   140  	panic("random choice out of range")
   141  }
   142  
   143  // invalid randomly chooses whether an operation should be invalid.
   144  func (hb *MapBias) invalid(ep MapEntrypointName, r *rand.Rand) bool {
   145  	chance := hb.InvalidChance[ep]
   146  	if chance <= 0 {
   147  		return false
   148  	}
   149  	return (r.Intn(chance) == 0)
   150  }
   151  
   152  // MapConfig provides configuration for a stress/load test.
   153  type MapConfig struct {
   154  	MapID                int64
   155  	MetricFactory        monitoring.MetricFactory
   156  	Client               trillian.TrillianMapClient
   157  	Admin                trillian.TrillianAdminClient
   158  	RandSource           rand.Source
   159  	EPBias               MapBias
   160  	LeafSize, ExtraSize  uint
   161  	MinLeaves, MaxLeaves int
   162  	Operations           uint64
   163  	EmitInterval         time.Duration
   164  	IgnoreErrors         bool
   165  	// NumCheckers indicates how many separate inclusion checker goroutines
   166  	// to run.  Note that the behaviour of these checkers is not governed by
   167  	// RandSource.
   168  	NumCheckers int
   169  }
   170  
   171  // String conforms with Stringer for MapConfig.
   172  func (c MapConfig) String() string {
   173  	return fmt.Sprintf("mapID:%d biases:{%v} #operations:%d emit every:%v ignoreErrors? %t",
   174  		c.MapID, c.EPBias, c.Operations, c.EmitInterval, c.IgnoreErrors)
   175  }
   176  
   177  // HitMap performs load/stress operations according to given config.
   178  func HitMap(cfg MapConfig) error {
   179  	ctx := context.Background()
   180  
   181  	s, err := newHammerState(ctx, &cfg)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	ticker := time.NewTicker(cfg.EmitInterval)
   187  	go func(c <-chan time.Time) {
   188  		for range c {
   189  			glog.Info(s.String())
   190  		}
   191  	}(ticker.C)
   192  
   193  	var wg sync.WaitGroup
   194  	// Anything that arrives on errs terminates all processing (but there
   195  	// may be more errors queued up behind it).
   196  	errs := make(chan error, cfg.NumCheckers+1)
   197  	// The done channel is used to signal all of the goroutines to
   198  	// terminate.
   199  	done := make(chan struct{})
   200  	for i := 0; i < cfg.NumCheckers; i++ {
   201  		wg.Add(1)
   202  		go func(i int) {
   203  			defer wg.Done()
   204  			glog.Infof("%d: start checker %d", s.cfg.MapID, i)
   205  			err := s.readChecker(ctx, done, i)
   206  			if err != nil {
   207  				errs <- err
   208  			}
   209  			glog.Infof("%d: checker %d done with %v", s.cfg.MapID, i, err)
   210  		}(i)
   211  	}
   212  
   213  	wg.Add(1)
   214  	go func() {
   215  		defer wg.Done()
   216  		glog.Infof("%d: start main goroutine", s.cfg.MapID)
   217  		count, err := s.performOperations(ctx, done)
   218  		errs <- err // may be nil for the main goroutine completion
   219  		glog.Infof("%d: performed %d operations on map", cfg.MapID, count)
   220  	}()
   221  
   222  	// Wait for first error, completion (which shows up as a nil error) or
   223  	// external cancellation.
   224  	var firstErr error
   225  	select {
   226  	case <-ctx.Done():
   227  		glog.Infof("%d: context canceled", cfg.MapID)
   228  	case e := <-errs:
   229  		firstErr = e
   230  		if firstErr != nil {
   231  			glog.Infof("%d: first error encountered: %v", cfg.MapID, e)
   232  		}
   233  	}
   234  	close(done)
   235  
   236  	ticker.Stop()
   237  	wg.Wait()
   238  	close(errs)
   239  	for e := range errs {
   240  		if e != nil {
   241  			glog.Infof("%d: error encountered: %v", cfg.MapID, e)
   242  		}
   243  	}
   244  	return firstErr
   245  }
   246  
   247  // hammerState tracks the operations that have been performed during a test run.
   248  type hammerState struct {
   249  	cfg      *MapConfig
   250  	verifier *client.MapVerifier
   251  
   252  	// prng is not thread-safe and should only be used from the main hammer
   253  	// goroutine for reproducability.
   254  	prng *rand.Rand
   255  
   256  	// copies of earlier contents of the map
   257  	prevContents versionedMapContents
   258  
   259  	mu sync.RWMutex // Protects everything below
   260  
   261  	// SMRs are arranged from later to earlier (so [0] is the most recent), and the
   262  	// discovery of new SMRs will push older ones off the end.
   263  	smr [smrCount]*trillian.SignedMapRoot
   264  
   265  	// Counters for generating unique keys/values.
   266  	keyIdx   int
   267  	valueIdx int
   268  }
   269  
   270  func newHammerState(ctx context.Context, cfg *MapConfig) (*hammerState, error) {
   271  	tree, err := cfg.Admin.GetTree(ctx, &trillian.GetTreeRequest{TreeId: cfg.MapID})
   272  	if err != nil {
   273  		return nil, fmt.Errorf("failed to get tree information: %v", err)
   274  	}
   275  	glog.Infof("%d: hammering tree with configuration %+v", cfg.MapID, tree)
   276  	verifier, err := client.NewMapVerifierFromTree(tree)
   277  	if err != nil {
   278  		return nil, fmt.Errorf("failed to get tree verifier: %v", err)
   279  	}
   280  
   281  	mf := cfg.MetricFactory
   282  	if mf == nil {
   283  		mf = monitoring.InertMetricFactory{}
   284  	}
   285  	once.Do(func() { setupMetrics(mf) })
   286  	if cfg.EmitInterval == 0 {
   287  		cfg.EmitInterval = defaultEmitSeconds * time.Second
   288  	}
   289  	if cfg.MinLeaves < 0 {
   290  		return nil, fmt.Errorf("invalid MinLeaves %d", cfg.MinLeaves)
   291  	}
   292  	if cfg.MaxLeaves < cfg.MinLeaves {
   293  		return nil, fmt.Errorf("invalid MaxLeaves %d is less than MinLeaves %d", cfg.MaxLeaves, cfg.MinLeaves)
   294  	}
   295  	if int(cfg.LeafSize) < minValueLen {
   296  		return nil, fmt.Errorf("invalid LeafSize %d is smaller than min %d", cfg.LeafSize, minValueLen)
   297  	}
   298  
   299  	return &hammerState{
   300  		cfg:      cfg,
   301  		prng:     rand.New(cfg.RandSource),
   302  		verifier: verifier,
   303  	}, nil
   304  }
   305  
   306  func (s *hammerState) performOperations(ctx context.Context, done <-chan struct{}) (uint64, error) {
   307  	count := uint64(0)
   308  	for ; count < s.cfg.Operations; count++ {
   309  		select {
   310  		case <-done:
   311  			return count, nil
   312  		default:
   313  		}
   314  		if err := s.retryOneOp(ctx); err != nil {
   315  			return count, err
   316  		}
   317  	}
   318  	return count, nil
   319  }
   320  
   321  // readChecker loops performing (read-only) checking operations until the done
   322  // channel is closed.
   323  func (s *hammerState) readChecker(ctx context.Context, done <-chan struct{}, idx int) error {
   324  	// Use a separate rand.Source so the main goroutine stays predictable.
   325  	prng := rand.New(rand.NewSource(int64(idx)))
   326  	for {
   327  		select {
   328  		case <-done:
   329  			return nil
   330  		default:
   331  		}
   332  		if err := s.doGetLeaves(ctx, prng, false /* latest */); err != nil {
   333  			if _, ok := err.(errSkip); ok {
   334  				continue
   335  			}
   336  			return err
   337  		}
   338  	}
   339  }
   340  
   341  func (s *hammerState) nextKey() string {
   342  	s.mu.Lock()
   343  	defer s.mu.Unlock()
   344  	s.keyIdx++
   345  	return fmt.Sprintf("key-%08d", s.keyIdx)
   346  }
   347  
   348  func (s *hammerState) nextValue() []byte {
   349  	s.mu.Lock()
   350  	defer s.mu.Unlock()
   351  	s.valueIdx++
   352  	result := make([]byte, s.cfg.LeafSize)
   353  	copy(result, fmt.Sprintf(valueFormat, s.valueIdx))
   354  	return result
   355  }
   356  
   357  func extraDataForValue(value []byte, sz uint) []byte {
   358  	result := make([]byte, sz)
   359  	copy(result, "extra-"+string(value))
   360  	return result
   361  }
   362  
   363  func (s *hammerState) label() string {
   364  	return strconv.FormatInt(s.cfg.MapID, 10)
   365  }
   366  
   367  func (s *hammerState) String() string {
   368  	details := ""
   369  	totalReqs := 0
   370  	totalInvalidReqs := 0
   371  	totalErrs := 0
   372  	for _, ep := range mapEntrypoints {
   373  		reqCount := int(reqs.Value(s.label(), string(ep)))
   374  		totalReqs += reqCount
   375  		if s.cfg.EPBias.Bias[ep] > 0 {
   376  			details += fmt.Sprintf(" %s=%d/%d", ep, int(rsps.Value(s.label(), string(ep))), reqCount)
   377  		}
   378  		totalInvalidReqs += int(invalidReqs.Value(s.label(), string(ep)))
   379  		totalErrs += int(errs.Value(s.label(), string(ep)))
   380  	}
   381  	smr := s.previousSMR(0)
   382  	return fmt.Sprintf("%d: lastSMR.rev=%s ops: total=%d invalid=%d errs=%v%s", s.cfg.MapID, smrRev(smr), totalReqs, totalInvalidReqs, totalErrs, details)
   383  }
   384  
   385  func (s *hammerState) pushSMR(smr *trillian.SignedMapRoot) {
   386  	s.mu.Lock()
   387  	defer s.mu.Unlock()
   388  
   389  	// Shuffle earlier SMRs along.
   390  	for i := smrCount - 1; i > 0; i-- {
   391  		s.smr[i] = s.smr[i-1]
   392  	}
   393  
   394  	s.smr[0] = smr
   395  }
   396  
   397  func (s *hammerState) previousSMR(which int) *trillian.SignedMapRoot {
   398  	s.mu.RLock()
   399  	defer s.mu.RUnlock()
   400  	return s.smr[which]
   401  }
   402  
   403  func (s *hammerState) chooseOp() MapEntrypointName {
   404  	return s.cfg.EPBias.choose(s.prng)
   405  }
   406  
   407  func (s *hammerState) chooseInvalid(ep MapEntrypointName) bool {
   408  	return s.cfg.EPBias.invalid(ep, s.prng)
   409  }
   410  
   411  func (s *hammerState) chooseLeafCount(prng *rand.Rand) int {
   412  	delta := 1 + s.cfg.MaxLeaves - s.cfg.MinLeaves
   413  	return s.cfg.MinLeaves + prng.Intn(delta)
   414  }
   415  
   416  func (s *hammerState) retryOneOp(ctx context.Context) (err error) {
   417  	ep := s.chooseOp()
   418  	if s.chooseInvalid(ep) {
   419  		glog.V(3).Infof("%d: perform invalid %s operation", s.cfg.MapID, ep)
   420  		invalidReqs.Inc(s.label(), string(ep))
   421  		return s.performInvalidOp(ctx, ep)
   422  	}
   423  
   424  	glog.V(3).Infof("%d: perform %s operation", s.cfg.MapID, ep)
   425  	defer func(start time.Time) {
   426  		rspLatency.Observe(time.Since(start).Seconds(), s.label(), string(ep))
   427  	}(time.Now())
   428  
   429  	deadline := time.Now().Add(maxRetryDuration)
   430  	ctx, cancel := context.WithDeadline(ctx, deadline)
   431  	defer cancel()
   432  
   433  	done := false
   434  	for !done {
   435  		reqs.Inc(s.label(), string(ep))
   436  		err = s.performOp(ctx, ep)
   437  
   438  		switch err.(type) {
   439  		case nil:
   440  			rsps.Inc(s.label(), string(ep))
   441  			done = true
   442  		case errSkip:
   443  			err = nil
   444  			done = true
   445  		case errInvariant:
   446  			// Ensure invariant failures are not ignorable.  They indicate a design assumption
   447  			// being broken or incorrect, so must be seen.
   448  			done = true
   449  		default:
   450  			errs.Inc(s.label(), string(ep))
   451  			if s.cfg.IgnoreErrors {
   452  				glog.Warningf("%d: op %v failed (will retry): %v", s.cfg.MapID, ep, err)
   453  			} else {
   454  				done = true
   455  			}
   456  		}
   457  
   458  		if time.Now().After(deadline) {
   459  			glog.Warningf("%d: gave up retrying failed op %v after %v, returning last err %v", s.cfg.MapID, ep, maxRetryDuration, err)
   460  			done = true
   461  		}
   462  	}
   463  	return err
   464  }
   465  
   466  func (s *hammerState) performOp(ctx context.Context, ep MapEntrypointName) error {
   467  	switch ep {
   468  	case GetLeavesName:
   469  		return s.getLeaves(ctx)
   470  	case GetLeavesRevName:
   471  		return s.getLeavesRev(ctx)
   472  	case SetLeavesName:
   473  		return s.setLeaves(ctx)
   474  	case GetSMRName:
   475  		return s.getSMR(ctx)
   476  	case GetSMRRevName:
   477  		return s.getSMRRev(ctx)
   478  	default:
   479  		return fmt.Errorf("internal error: unknown entrypoint %s selected for valid request", ep)
   480  	}
   481  }
   482  
   483  func (s *hammerState) performInvalidOp(ctx context.Context, ep MapEntrypointName) error {
   484  	switch ep {
   485  	case GetLeavesName:
   486  		return s.getLeavesInvalid(ctx)
   487  	case GetLeavesRevName:
   488  		return s.getLeavesRevInvalid(ctx)
   489  	case SetLeavesName:
   490  		return s.setLeavesInvalid(ctx)
   491  	case GetSMRRevName:
   492  		return s.getSMRRevInvalid(ctx)
   493  	case GetSMRName:
   494  		return fmt.Errorf("no invalid request possible for entrypoint %s", ep)
   495  	default:
   496  		return fmt.Errorf("internal error: unknown entrypoint %s selected for invalid request", ep)
   497  	}
   498  }
   499  
   500  func (s *hammerState) getLeaves(ctx context.Context) error {
   501  	return s.doGetLeaves(ctx, s.prng, true /*latest*/)
   502  }
   503  
   504  func (s *hammerState) getLeavesRev(ctx context.Context) error {
   505  	return s.doGetLeaves(ctx, s.prng, false /*latest*/)
   506  }
   507  
   508  func (s *hammerState) doGetLeaves(ctx context.Context, prng *rand.Rand, latest bool) error {
   509  	choices := []Choice{ExistingKey, NonexistentKey}
   510  
   511  	if s.prevContents.empty() {
   512  		glog.V(3).Infof("%d: skipping get-leaves as no data yet", s.cfg.MapID)
   513  		return errSkip{}
   514  	}
   515  	var contents *mapContents
   516  	if latest {
   517  		contents = s.prevContents.lastCopy()
   518  	} else {
   519  		contents = s.prevContents.pickCopy(prng)
   520  	}
   521  
   522  	n := s.chooseLeafCount(prng) // can be zero
   523  	indices := make([][]byte, n)
   524  	for i := 0; i < n; i++ {
   525  		choice := choices[prng.Intn(len(choices))]
   526  		if contents.empty() {
   527  			choice = NonexistentKey
   528  		}
   529  		switch choice {
   530  		case ExistingKey:
   531  			// No duplicate removal, so we can end up asking for same key twice in the same request.
   532  			key := contents.pickKey(prng)
   533  			indices[i] = key
   534  		case NonexistentKey:
   535  			indices[i] = testonly.TransparentHash("non-existent-key")
   536  		}
   537  	}
   538  
   539  	var rsp *trillian.GetMapLeavesResponse
   540  	label := "get-leaves"
   541  	var err error
   542  	if latest {
   543  		req := &trillian.GetMapLeavesRequest{
   544  			MapId: s.cfg.MapID,
   545  			Index: indices,
   546  		}
   547  		rsp, err = s.cfg.Client.GetLeaves(ctx, req)
   548  		if err != nil {
   549  			return fmt.Errorf("failed to %s(%d leaves): %v", label, len(req.Index), err)
   550  		}
   551  	} else {
   552  		label += "-rev"
   553  		req := &trillian.GetMapLeavesByRevisionRequest{
   554  			MapId:    s.cfg.MapID,
   555  			Revision: int64(contents.rev),
   556  			Index:    indices,
   557  		}
   558  		rsp, err = s.cfg.Client.GetLeavesByRevision(ctx, req)
   559  		if err != nil {
   560  			return fmt.Errorf("failed to %s(%d leaves): %v", label, len(req.Index), err)
   561  		}
   562  	}
   563  
   564  	if glog.V(3) {
   565  		dumpRespKeyVals(rsp.MapLeafInclusion)
   566  	}
   567  
   568  	root, err := s.verifier.VerifySignedMapRoot(rsp.MapRoot)
   569  	if err != nil {
   570  		return err
   571  	}
   572  	for _, inc := range rsp.MapLeafInclusion {
   573  		if err := s.verifier.VerifyMapLeafInclusionHash(root.RootHash, inc); err != nil {
   574  			return err
   575  		}
   576  	}
   577  
   578  	if err := contents.checkContents(rsp.MapLeafInclusion, s.cfg.ExtraSize); err != nil {
   579  		return fmt.Errorf("incorrect contents of %s(): %v", label, err)
   580  	}
   581  	glog.V(2).Infof("%d: got %d leaves, with SMR(time=%q, rev=%d)", s.cfg.MapID, len(rsp.MapLeafInclusion), time.Unix(0, int64(root.TimestampNanos)), root.Revision)
   582  	return nil
   583  }
   584  
   585  func dumpRespKeyVals(incls []*trillian.MapLeafInclusion) {
   586  	fmt.Println("Rsp key-vals:")
   587  	for _, inc := range incls {
   588  		var key mapKey
   589  		copy(key[:], inc.Leaf.Index)
   590  		leafVal := inc.Leaf.LeafValue
   591  		fmt.Printf("k: %v -> v: %v\n", string(key[:]), string(leafVal))
   592  	}
   593  	fmt.Println("~~~~~~~~~~~~~")
   594  }
   595  
   596  func (s *hammerState) getLeavesInvalid(ctx context.Context) error {
   597  	key := testonly.TransparentHash("..invalid-size")
   598  	req := trillian.GetMapLeavesRequest{
   599  		MapId: s.cfg.MapID,
   600  		Index: [][]byte{key[2:]},
   601  	}
   602  	rsp, err := s.cfg.Client.GetLeaves(ctx, &req)
   603  	if err == nil {
   604  		return fmt.Errorf("unexpected success: get-leaves(MalformedKey: %+v): %+v", req, rsp.MapRoot)
   605  	}
   606  	glog.V(2).Infof("%d: expected failure: get-leaves(MalformedKey: %+v): %+v", s.cfg.MapID, req, rsp)
   607  	return nil
   608  }
   609  
   610  func (s *hammerState) getLeavesRevInvalid(ctx context.Context) error {
   611  	choices := []Choice{MalformedKey, RevTooBig, RevIsNegative}
   612  
   613  	req := trillian.GetMapLeavesByRevisionRequest{MapId: s.cfg.MapID}
   614  	contents := s.prevContents.lastCopy()
   615  	choice := choices[s.prng.Intn(len(choices))]
   616  
   617  	rev := int64(0)
   618  	var index []byte
   619  	if contents.empty() {
   620  		// No contents so we can't choose a key
   621  		choice = MalformedKey
   622  	} else {
   623  		rev = contents.rev
   624  		index = contents.pickKey(s.prng)
   625  	}
   626  	switch choice {
   627  	case MalformedKey:
   628  		key := testonly.TransparentHash("..invalid-size")
   629  		req.Index = [][]byte{key[2:]}
   630  		req.Revision = rev
   631  	case RevTooBig:
   632  		req.Index = [][]byte{index}
   633  		req.Revision = rev + invalidStretch
   634  	case RevIsNegative:
   635  		req.Index = [][]byte{index}
   636  		req.Revision = -rev - invalidStretch
   637  	}
   638  	rsp, err := s.cfg.Client.GetLeavesByRevision(ctx, &req)
   639  	if err == nil {
   640  		return fmt.Errorf("unexpected success: get-leaves-rev(%v: %+v): %+v", choice, req, rsp.MapRoot)
   641  	}
   642  	glog.V(2).Infof("%d: expected failure: get-leaves-rev(%v: %+v): %+v", s.cfg.MapID, choice, req, rsp)
   643  	return nil
   644  }
   645  
   646  func (s *hammerState) setLeaves(ctx context.Context) error {
   647  	choices := []Choice{CreateLeaf, UpdateLeaf, DeleteLeaf}
   648  
   649  	n := s.chooseLeafCount(s.prng)
   650  	if n == 0 {
   651  		n = 1
   652  	}
   653  	leaves := make([]*trillian.MapLeaf, 0, n)
   654  	contents := s.prevContents.lastCopy()
   655  	rev := int64(0)
   656  	if contents != nil {
   657  		rev = contents.rev
   658  	}
   659  leafloop:
   660  	for i := 0; i < n; i++ {
   661  		choice := choices[s.prng.Intn(len(choices))]
   662  		if contents.empty() {
   663  			choice = CreateLeaf
   664  		}
   665  		switch choice {
   666  		case CreateLeaf:
   667  			key := s.nextKey()
   668  			value := s.nextValue()
   669  			leaves = append(leaves, &trillian.MapLeaf{
   670  				Index:     testonly.TransparentHash(key),
   671  				LeafValue: value,
   672  				ExtraData: extraDataForValue(value, s.cfg.ExtraSize),
   673  			})
   674  			glog.V(3).Infof("%d: %v: data[%q]=%q", s.cfg.MapID, choice, key, string(value))
   675  		case UpdateLeaf, DeleteLeaf:
   676  			key := contents.pickKey(s.prng)
   677  			// Not allowed to have the same key more than once in the same request
   678  			for _, leaf := range leaves {
   679  				if bytes.Equal(leaf.Index, key) {
   680  					break leafloop
   681  				}
   682  			}
   683  			var value, extra []byte
   684  			if choice == UpdateLeaf {
   685  				value = s.nextValue()
   686  				extra = extraDataForValue(value, s.cfg.ExtraSize)
   687  			}
   688  			leaves = append(leaves, &trillian.MapLeaf{Index: key, LeafValue: value, ExtraData: extra})
   689  			glog.V(3).Infof("%d: %v: data[%q]=%q (extra=%q)", s.cfg.MapID, choice, dehash(key), string(value), string(extra))
   690  		}
   691  	}
   692  	req := trillian.SetMapLeavesRequest{
   693  		MapId:    s.cfg.MapID,
   694  		Leaves:   leaves,
   695  		Metadata: metadataForRev(uint64(rev + 1)),
   696  	}
   697  	rsp, err := s.cfg.Client.SetLeaves(ctx, &req)
   698  	if err != nil {
   699  		return fmt.Errorf("failed to set-leaves(count=%d): %v", len(req.Leaves), err)
   700  	}
   701  	root, err := s.verifier.VerifySignedMapRoot(rsp.MapRoot)
   702  	if err != nil {
   703  		return err
   704  	}
   705  
   706  	s.pushSMR(rsp.MapRoot)
   707  	if err := s.prevContents.updateContentsWith(root.Revision, leaves); err != nil {
   708  		return err
   709  	}
   710  	glog.V(2).Infof("%d: set %d leaves, new SMR(time=%q, rev=%d)", s.cfg.MapID, len(leaves), time.Unix(0, int64(root.TimestampNanos)), root.Revision)
   711  	return nil
   712  }
   713  
   714  func (s *hammerState) setLeavesInvalid(ctx context.Context) error {
   715  	choices := []Choice{MalformedKey, DuplicateKey}
   716  
   717  	var leaves []*trillian.MapLeaf
   718  	value := []byte("value-for-invalid-req")
   719  
   720  	choice := choices[s.prng.Intn(len(choices))]
   721  	contents := s.prevContents.lastCopy()
   722  	if contents.empty() {
   723  		choice = MalformedKey
   724  	}
   725  	switch choice {
   726  	case MalformedKey:
   727  		key := testonly.TransparentHash("..invalid-size")
   728  		leaves = append(leaves, &trillian.MapLeaf{Index: key[2:], LeafValue: value})
   729  	case DuplicateKey:
   730  		key := contents.pickKey(s.prng)
   731  		leaves = append(leaves, &trillian.MapLeaf{Index: key, LeafValue: value})
   732  		leaves = append(leaves, &trillian.MapLeaf{Index: key, LeafValue: value})
   733  	}
   734  	req := trillian.SetMapLeavesRequest{MapId: s.cfg.MapID, Leaves: leaves}
   735  	rsp, err := s.cfg.Client.SetLeaves(ctx, &req)
   736  	if err == nil {
   737  		return fmt.Errorf("unexpected success: set-leaves(%v: %+v): %+v", choice, req, rsp.MapRoot)
   738  	}
   739  	glog.V(2).Infof("%d: expected failure: set-leaves(%v: %+v): %+v", s.cfg.MapID, choice, req, rsp)
   740  	return nil
   741  }
   742  
   743  func (s *hammerState) getSMR(ctx context.Context) error {
   744  	req := trillian.GetSignedMapRootRequest{MapId: s.cfg.MapID}
   745  	rsp, err := s.cfg.Client.GetSignedMapRoot(ctx, &req)
   746  	if err != nil {
   747  		return fmt.Errorf("failed to get-smr: %v", err)
   748  	}
   749  	root, err := s.verifier.VerifySignedMapRoot(rsp.MapRoot)
   750  	if err != nil {
   751  		return err
   752  	}
   753  	if got, want := string(root.Metadata), string(metadataForRev(root.Revision)); got != want {
   754  		return fmt.Errorf("map metadata=%q; want %q", got, want)
   755  	}
   756  
   757  	s.pushSMR(rsp.MapRoot)
   758  	glog.V(2).Infof("%d: got SMR(time=%q, rev=%d)", s.cfg.MapID, time.Unix(0, int64(root.TimestampNanos)), root.Revision)
   759  	return nil
   760  }
   761  
   762  func (s *hammerState) getSMRRev(ctx context.Context) error {
   763  	which := s.prng.Intn(smrCount)
   764  	smr := s.previousSMR(which)
   765  	if smr == nil || len(smr.MapRoot) == 0 {
   766  		glog.V(3).Infof("%d: skipping get-smr-rev as no earlier SMR", s.cfg.MapID)
   767  		return errSkip{}
   768  	}
   769  	smrRoot, err := s.verifier.VerifySignedMapRoot(smr)
   770  	if err != nil {
   771  		return err
   772  	}
   773  	rev := int64(smrRoot.Revision)
   774  
   775  	rsp, err := s.cfg.Client.GetSignedMapRootByRevision(ctx,
   776  		&trillian.GetSignedMapRootByRevisionRequest{MapId: s.cfg.MapID, Revision: rev})
   777  	if err != nil {
   778  		return fmt.Errorf("failed to get-smr-rev(@%d): %v", rev, err)
   779  	}
   780  	root, err := s.verifier.VerifySignedMapRoot(rsp.MapRoot)
   781  	if err != nil {
   782  		return err
   783  	}
   784  	glog.V(2).Infof("%d: got SMR(time=%q, rev=%d)", s.cfg.MapID, time.Unix(0, int64(root.TimestampNanos)), root.Revision)
   785  
   786  	if !proto.Equal(rsp.MapRoot, smr) {
   787  		return fmt.Errorf("get-smr-rev(@%d)=%+v, want %+v", rev, rsp.MapRoot, smr)
   788  	}
   789  	return nil
   790  }
   791  
   792  func (s *hammerState) getSMRRevInvalid(ctx context.Context) error {
   793  	choices := []Choice{RevTooBig, RevIsNegative}
   794  
   795  	rev := latestRevision
   796  	contents := s.prevContents.lastCopy()
   797  	if contents != nil {
   798  		rev = contents.rev
   799  	}
   800  
   801  	choice := choices[s.prng.Intn(len(choices))]
   802  
   803  	switch choice {
   804  	case RevTooBig:
   805  		rev += invalidStretch
   806  	case RevIsNegative:
   807  		rev = -invalidStretch
   808  	}
   809  	req := trillian.GetSignedMapRootByRevisionRequest{MapId: s.cfg.MapID, Revision: rev}
   810  	rsp, err := s.cfg.Client.GetSignedMapRootByRevision(ctx, &req)
   811  	if err == nil {
   812  		return fmt.Errorf("unexpected success: get-smr-rev(%v: @%d): %+v", choice, rev, rsp.MapRoot)
   813  	}
   814  	glog.V(2).Infof("%d: expected failure: get-smr-rev(%v: @%d): %+v", s.cfg.MapID, choice, rev, rsp)
   815  	return nil
   816  }
   817  
   818  func smrRev(smr *trillian.SignedMapRoot) string {
   819  	if smr == nil {
   820  		return "n/a"
   821  	}
   822  	var root types.MapRootV1
   823  	if err := root.UnmarshalBinary(smr.MapRoot); err != nil {
   824  		return "unknown"
   825  	}
   826  	return fmt.Sprintf("%d", root.Revision)
   827  }
   828  
   829  func dehash(index []byte) string {
   830  	return strings.TrimRight(string(index), "\x00")
   831  }
   832  
   833  // metadataForRev returns the metadata value that the maphammer always uses for
   834  // a specific revision.
   835  func metadataForRev(rev uint64) []byte {
   836  	if rev == 0 {
   837  		return []byte{}
   838  	}
   839  	return []byte(fmt.Sprintf("Metadata-%d", rev))
   840  }