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 }