github.com/bartle-stripe/trillian@v1.2.1/integration/maptest/map.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 maptest 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/hex" 21 "fmt" 22 "testing" 23 24 "github.com/golang/glog" 25 "github.com/kylelemons/godebug/pretty" 26 27 "github.com/google/trillian" 28 "github.com/google/trillian/client" 29 "github.com/google/trillian/examples/ct/ctmapper/ctmapperpb" 30 "github.com/google/trillian/testonly" 31 "github.com/google/trillian/types" 32 33 stestonly "github.com/google/trillian/storage/testonly" 34 ) 35 36 // NamedTestFn is a binding between a readable test name (used for a Go subtest) and a function 37 // that performs the test, given a Trillian Admin and Map client. 38 type NamedTestFn struct { 39 Name string 40 Fn func(context.Context, *testing.T, trillian.TrillianAdminClient, trillian.TrillianMapClient) 41 } 42 43 // TestTable is a collection of NamedTestFns. 44 type TestTable []NamedTestFn 45 46 // AllTests is the TestTable containing all the trillian Map integration tests. 47 // Be sure to extend this when additional tests are added. 48 // This is done so that tests can be run in different environments in a portable way. 49 var AllTests = TestTable{ 50 {"MapRevisionZero", RunMapRevisionZero}, 51 {"MapRevisionInvalid", RunMapRevisionInvalid}, 52 {"LeafHistory", RunLeafHistory}, 53 {"Inclusion", RunInclusion}, 54 {"InclusionBatch", RunInclusionBatch}, 55 } 56 57 var h2b = testonly.MustHexDecode 58 59 // createBatchLeaves produces n unique map leaves. 60 func createBatchLeaves(batch, n int) []*trillian.MapLeaf { 61 leaves := make([]*trillian.MapLeaf, 0, n) 62 for i := 0; i < n; i++ { 63 leaves = append(leaves, &trillian.MapLeaf{ 64 Index: testonly.TransparentHash(fmt.Sprintf("batch-%d-key-%d", batch, i)), 65 LeafValue: []byte(fmt.Sprintf("batch-%d-value-%d", batch, i)), 66 }) 67 } 68 return leaves 69 } 70 71 func isEmptyMap(ctx context.Context, tmap trillian.TrillianMapClient, tree *trillian.Tree) error { 72 r, err := tmap.GetSignedMapRoot(ctx, &trillian.GetSignedMapRootRequest{MapId: tree.TreeId}) 73 if err != nil { 74 return fmt.Errorf("failed to get empty map head: %v", err) 75 } 76 77 var mapRoot types.MapRootV1 78 if err := mapRoot.UnmarshalBinary(r.GetMapRoot().GetMapRoot()); err != nil { 79 return err 80 } 81 82 if got, want := mapRoot.Revision, uint64(0); got != want { 83 return fmt.Errorf("got SMR with revision %d, want %d", got, want) 84 } 85 return nil 86 } 87 88 func verifyGetSignedMapRootResponse(mapVerifier *client.MapVerifier, mapRoot *trillian.SignedMapRoot, wantRevision int64) error { 89 root, err := mapVerifier.VerifySignedMapRoot(mapRoot) 90 if err != nil { 91 return err 92 } 93 if got, want := int64(root.Revision), wantRevision; got != want { 94 return fmt.Errorf("got SMR with revision %d, want %d", got, want) 95 } 96 return nil 97 } 98 99 func verifyGetMapLeavesResponse(mapVerifier *client.MapVerifier, getResp *trillian.GetMapLeavesResponse, indexes [][]byte, 100 wantRevision int64) error { 101 if got, want := len(getResp.GetMapLeafInclusion()), len(indexes); got != want { 102 return fmt.Errorf("got %d values, want %d", got, want) 103 } 104 if err := verifyGetSignedMapRootResponse(mapVerifier, getResp.GetMapRoot(), wantRevision); err != nil { 105 return err 106 } 107 for _, incl := range getResp.GetMapLeafInclusion() { 108 leaf := incl.GetLeaf().GetLeafValue() 109 index := incl.GetLeaf().GetIndex() 110 leafHash := incl.GetLeaf().GetLeafHash() 111 112 wantLeafHash, err := mapVerifier.Hasher.HashLeaf(mapVerifier.MapID, index, leaf) 113 if err != nil { 114 return err 115 } 116 if got, want := leafHash, wantLeafHash; !bytes.Equal(got, want) { 117 return fmt.Errorf("HashLeaf(%s): %x, want %x", leaf, got, want) 118 } 119 if err := mapVerifier.VerifyMapLeafInclusion(getResp.GetMapRoot(), incl); err != nil { 120 return fmt.Errorf("VerifyMapLeafInclusion(%x): %v", index, err) 121 } 122 } 123 return nil 124 } 125 126 // newTreeWithHasher is a test setup helper for creating new trees with a given hasher. 127 func newTreeWithHasher(ctx context.Context, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient, hashStrategy trillian.HashStrategy) (*trillian.Tree, error) { 128 treeParams := stestonly.MapTree 129 treeParams.HashStrategy = hashStrategy 130 tree, err := tadmin.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: treeParams}) 131 if err != nil { 132 return nil, err 133 } 134 135 if err := client.InitMap(ctx, tree, tmap); err != nil { 136 return nil, err 137 } 138 139 return tree, nil 140 } 141 142 type hashStrategyAndRoot struct { 143 hashStrategy trillian.HashStrategy 144 wantRoot []byte 145 } 146 147 // RunMapRevisionZero performs checks on Trillian Map behavior for new, empty maps. 148 func RunMapRevisionZero(ctx context.Context, t *testing.T, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient) { 149 for _, tc := range []struct { 150 desc string 151 hashStrategy []hashStrategyAndRoot 152 wantRev int64 153 }{ 154 { 155 desc: "empty map has SMR at rev 0 but not rev 1", 156 hashStrategy: []hashStrategyAndRoot{ 157 {trillian.HashStrategy_TEST_MAP_HASHER, testonly.MustDecodeBase64("xmifEIEqCYCXbZUz2Dh1KCFmFZVn7DUVVxbBQTr1PWo=")}, 158 {trillian.HashStrategy_CONIKS_SHA512_256, nil /* TODO: need to fix the treeID to have a known answer */}, 159 }, 160 wantRev: 0, 161 }, 162 } { 163 for _, hsr := range tc.hashStrategy { 164 t.Run(fmt.Sprintf("%v/%v", tc.desc, hsr.hashStrategy), func(t *testing.T) { 165 tree, err := newTreeWithHasher(ctx, tadmin, tmap, hsr.hashStrategy) 166 if err != nil { 167 t.Fatalf("newTreeWithHasher(%v): %v", hsr.hashStrategy, err) 168 } 169 mapVerifier, err := client.NewMapVerifierFromTree(tree) 170 if err != nil { 171 t.Fatalf("NewMapVerifierFromTree(): %v", err) 172 } 173 174 getSmrResp, err := tmap.GetSignedMapRoot(ctx, &trillian.GetSignedMapRootRequest{MapId: tree.TreeId}) 175 if err != nil { 176 t.Fatalf("GetSignedMapRoot(): %v", err) 177 } 178 if err := verifyGetSignedMapRootResponse(mapVerifier, getSmrResp.GetMapRoot(), tc.wantRev); err != nil { 179 t.Errorf("verifyGetSignedMapRootResponse(rev %v): %v", tc.wantRev, err) 180 } 181 182 getSmrByRevResp, err := tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{ 183 MapId: tree.TreeId, 184 Revision: 0, 185 }) 186 if err != nil { 187 t.Errorf("GetSignedMapRootByRevision(): %v", err) 188 } 189 if err := verifyGetSignedMapRootResponse(mapVerifier, getSmrByRevResp.GetMapRoot(), tc.wantRev); err != nil { 190 t.Errorf("verifyGetSignedMapRootResponse(rev %v): %v", tc.wantRev, err) 191 } 192 193 got, want := getSmrByRevResp.GetMapRoot(), getSmrResp.GetMapRoot() 194 if diff := pretty.Compare(got, want); diff != "" { 195 t.Errorf("GetSignedMapRootByRevision() != GetSignedMapRoot(); diff (-got +want):\n%v", diff) 196 } 197 198 if _, err = tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{ 199 MapId: tree.TreeId, 200 Revision: 1, 201 }); err == nil { 202 t.Errorf("GetSignedMapRootByRevision(rev: 1) err? false want? true") 203 } 204 // TODO(phad): ideally we'd inspect err's type and check it contains a NOT_FOUND Code (5), but I don't want 205 // a dependency on gRPC here. 206 }) 207 } 208 } 209 } 210 211 // RunMapRevisionInvalid performs checks on Map APIs where revision takes illegal values. 212 func RunMapRevisionInvalid(ctx context.Context, t *testing.T, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient) { 213 const indexHex = "0000000000000000000000000000000000000000000000000000000000000001" 214 for _, tc := range []struct { 215 desc string 216 HashStrategy []trillian.HashStrategy 217 set [][]*trillian.MapLeaf 218 get []struct { 219 index []byte 220 revision int64 221 wantErr bool 222 } 223 }{ 224 { 225 desc: "single leaf update", 226 HashStrategy: []trillian.HashStrategy{trillian.HashStrategy_TEST_MAP_HASHER, trillian.HashStrategy_CONIKS_SHA512_256}, 227 set: [][]*trillian.MapLeaf{ 228 {}, // Advance revision without changing anything. 229 {{Index: h2b(indexHex), LeafValue: []byte("A")}}, 230 }, 231 get: []struct { 232 index []byte 233 revision int64 234 wantErr bool 235 }{ 236 {index: h2b(indexHex), revision: -1, wantErr: true}, 237 {index: h2b(indexHex), revision: 0, wantErr: false}, 238 }, 239 }, 240 } { 241 for _, hashStrategy := range tc.HashStrategy { 242 t.Run(fmt.Sprintf("%v/%v", tc.desc, hashStrategy), func(t *testing.T) { 243 tree, err := newTreeWithHasher(ctx, tadmin, tmap, hashStrategy) 244 if err != nil { 245 t.Fatalf("newTreeWithHasher(%v): %v", hashStrategy, err) 246 } 247 for _, batch := range tc.set { 248 if _, err := tmap.SetLeaves(ctx, &trillian.SetMapLeavesRequest{ 249 MapId: tree.TreeId, 250 Leaves: batch, 251 }); err != nil { 252 t.Fatalf("SetLeaves(): %v", err) 253 } 254 } 255 256 for _, batch := range tc.get { 257 _, err := tmap.GetLeavesByRevision(ctx, &trillian.GetMapLeavesByRevisionRequest{ 258 MapId: tree.TreeId, 259 Index: [][]byte{batch.index}, 260 Revision: batch.revision, 261 }) 262 if gotErr := err != nil; gotErr != batch.wantErr { 263 t.Errorf("GetLeavesByRevision(rev: %d)=_, err? %t want? %t (err=%v)", batch.revision, gotErr, batch.wantErr, err) 264 } 265 } 266 }) 267 } 268 } 269 } 270 271 // RunLeafHistory performs checks on Trillian Map leaf updates under a variety of Hash Strategies. 272 func RunLeafHistory(ctx context.Context, t *testing.T, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient) { 273 for _, tc := range []struct { 274 desc string 275 HashStrategy []trillian.HashStrategy 276 set [][]*trillian.MapLeaf 277 get []struct { 278 revision int64 279 Index []byte 280 LeafValue []byte 281 } 282 }{ 283 { 284 desc: "single leaf update", 285 HashStrategy: []trillian.HashStrategy{trillian.HashStrategy_TEST_MAP_HASHER, trillian.HashStrategy_CONIKS_SHA512_256}, 286 set: [][]*trillian.MapLeaf{ 287 {}, // Advance revision without changing anything. 288 { 289 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("A")}, 290 }, 291 {}, // Advance revision without changing anything. 292 { 293 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("B")}, 294 }, 295 { 296 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("C")}, 297 }, 298 }, 299 get: []struct { 300 revision int64 301 Index []byte 302 LeafValue []byte 303 }{ 304 {revision: 1, Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: nil}, // Empty to empty root. 305 {revision: 2, Index: []byte("doesnotexist...................."), LeafValue: nil}, // Empty to first root, through empty branch. 306 {revision: 2, Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("A")}, // Value to first root. 307 {revision: 3, Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("A")}, 308 {revision: 4, Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("B")}, 309 {revision: 5, Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("C")}, 310 }, 311 }, 312 } { 313 for _, hashStrategy := range tc.HashStrategy { 314 t.Run(fmt.Sprintf("%v/%v", tc.desc, hashStrategy), func(t *testing.T) { 315 tree, err := newTreeWithHasher(ctx, tadmin, tmap, hashStrategy) 316 if err != nil { 317 t.Fatalf("newTreeWithHasher(%v): %v", hashStrategy, err) 318 } 319 mapVerifier, err := client.NewMapVerifierFromTree(tree) 320 if err != nil { 321 t.Fatalf("NewMapVerifierFromTree(): %v", err) 322 } 323 324 for _, batch := range tc.set { 325 _, err := tmap.SetLeaves(ctx, &trillian.SetMapLeavesRequest{ 326 MapId: tree.TreeId, 327 Leaves: batch, 328 }) 329 if err != nil { 330 t.Fatalf("SetLeaves(): %v", err) 331 } 332 } 333 334 for _, batch := range tc.get { 335 indexes := [][]byte{batch.Index} 336 getResp, err := tmap.GetLeavesByRevision(ctx, &trillian.GetMapLeavesByRevisionRequest{ 337 MapId: tree.TreeId, 338 Index: indexes, 339 Revision: batch.revision, 340 }) 341 if err != nil { 342 t.Errorf("GetLeavesByRevision(rev: %d)=_, err %v want nil", batch.revision, err) 343 continue 344 } 345 346 if got, want := len(getResp.GetMapLeafInclusion()), 1; got < want { 347 t.Errorf("GetLeavesByRevision(rev: %v).len: %v, want >= %v", batch.revision, got, want) 348 } 349 if got, want := getResp.GetMapLeafInclusion()[0].GetLeaf().GetLeafValue(), batch.LeafValue; !bytes.Equal(got, want) { 350 t.Errorf("GetLeavesByRevision(rev: %v).LeafValue: %s, want %s", batch.revision, got, want) 351 } 352 353 if err := verifyGetMapLeavesResponse(mapVerifier, getResp, indexes, int64(batch.revision)); err != nil { 354 t.Errorf("verifyGetMapLeavesResponse(rev %v): %v", batch.revision, err) 355 } 356 } 357 }) 358 } 359 } 360 } 361 362 // RunInclusion performs checks on Trillian Map inclusion proofs after setting and getting leafs, 363 // for a variety of hash strategies. 364 func RunInclusion(ctx context.Context, t *testing.T, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient) { 365 for _, tc := range []struct { 366 desc string 367 HashStrategy []trillian.HashStrategy 368 leaves []*trillian.MapLeaf 369 }{ 370 { 371 desc: "single", 372 HashStrategy: []trillian.HashStrategy{trillian.HashStrategy_TEST_MAP_HASHER, trillian.HashStrategy_CONIKS_SHA512_256}, 373 leaves: []*trillian.MapLeaf{ 374 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("A")}, 375 }, 376 }, 377 { 378 desc: "multi", 379 HashStrategy: []trillian.HashStrategy{trillian.HashStrategy_TEST_MAP_HASHER, trillian.HashStrategy_CONIKS_SHA512_256}, 380 leaves: []*trillian.MapLeaf{ 381 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000000"), LeafValue: []byte("A")}, 382 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000001"), LeafValue: []byte("B")}, 383 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000002"), LeafValue: []byte("C")}, 384 {Index: h2b("0000000000000000000000000000000000000000000000000000000000000003"), LeafValue: nil}, 385 }, 386 }, 387 { 388 desc: "across subtrees", 389 HashStrategy: []trillian.HashStrategy{trillian.HashStrategy_TEST_MAP_HASHER, trillian.HashStrategy_CONIKS_SHA512_256}, 390 leaves: []*trillian.MapLeaf{ 391 {Index: h2b("0000000000000180000000000000000000000000000000000000000000000000"), LeafValue: []byte("Z")}, 392 }, 393 }, 394 } { 395 for _, hashStrategy := range tc.HashStrategy { 396 t.Run(fmt.Sprintf("%v/%v", tc.desc, hashStrategy), func(t *testing.T) { 397 tree, err := newTreeWithHasher(ctx, tadmin, tmap, hashStrategy) 398 if err != nil { 399 t.Fatalf("newTreeWithHasher(%v): %v", hashStrategy, err) 400 } 401 mapVerifier, err := client.NewMapVerifierFromTree(tree) 402 if err != nil { 403 t.Fatalf("NewMapVerifierFromTree(): %v", err) 404 } 405 406 if _, err := tmap.SetLeaves(ctx, &trillian.SetMapLeavesRequest{ 407 MapId: tree.TreeId, 408 Leaves: tc.leaves, 409 Metadata: testonly.MustMarshalAnyNoT(&ctmapperpb.MapperMetadata{ 410 HighestFullyCompletedSeq: 0xcafe, 411 }), 412 }); err != nil { 413 t.Fatalf("SetLeaves(): %v", err) 414 } 415 416 indexes := [][]byte{} 417 for _, l := range tc.leaves { 418 indexes = append(indexes, l.Index) 419 } 420 getResp, err := tmap.GetLeaves(ctx, &trillian.GetMapLeavesRequest{ 421 MapId: tree.TreeId, 422 Index: indexes, 423 }) 424 if err != nil { 425 t.Fatalf("GetLeaves(): %v", err) 426 } 427 428 if err := verifyGetMapLeavesResponse(mapVerifier, getResp, indexes, 1); err != nil { 429 t.Errorf("verifyGetMapLeavesResponse(): %v", err) 430 } 431 }) 432 } 433 } 434 } 435 436 // RunInclusionBatch performs checks on Trillian Map inclusion proofs, after setting and getting leafs in 437 // larger batches, checking also the SignedMapRoot revisions along the way, for a variety of hash strategies. 438 func RunInclusionBatch(ctx context.Context, t *testing.T, tadmin trillian.TrillianAdminClient, tmap trillian.TrillianMapClient) { 439 for _, tc := range []struct { 440 desc string 441 HashStrategy trillian.HashStrategy 442 batchSize, numBatches int 443 large bool 444 }{ 445 446 { 447 desc: "maphasher short batch", 448 HashStrategy: trillian.HashStrategy_TEST_MAP_HASHER, 449 batchSize: 10, numBatches: 10, 450 large: false, 451 }, 452 { 453 desc: "maphasher batch", 454 HashStrategy: trillian.HashStrategy_TEST_MAP_HASHER, 455 batchSize: 64, numBatches: 32, 456 large: true, 457 }, 458 // TODO(gdbelvin): investigate batches of size > 150. 459 // We are currently getting DB connection starvation: Too many connections. 460 } { 461 if testing.Short() && tc.large { 462 glog.Infof("testing.Short() is true. Skipping %v", tc.desc) 463 continue 464 } 465 tree, err := newTreeWithHasher(ctx, tadmin, tmap, tc.HashStrategy) 466 if err != nil { 467 t.Fatalf("%v: newTreeWithHasher(%v): %v", tc.desc, tc.HashStrategy, err) 468 } 469 470 if err := runMapBatchTest(ctx, t, tc.desc, tmap, tree, tc.batchSize, tc.numBatches); err != nil { 471 t.Errorf("BatchSize: %v, Batches: %v: %v", tc.batchSize, tc.numBatches, err) 472 } 473 } 474 } 475 476 // runMapBatchTest is a helper for RunInclusionBatch. 477 func runMapBatchTest(ctx context.Context, t *testing.T, desc string, tmap trillian.TrillianMapClient, tree *trillian.Tree, batchSize, numBatches int) error { 478 t.Helper() 479 480 mapVerifier, err := client.NewMapVerifierFromTree(tree) 481 if err != nil { 482 t.Fatalf("NewMapVerifierFromTree(): %v", err) 483 } 484 485 // Ensure we're starting with an empty map 486 if err := isEmptyMap(ctx, tmap, tree); err != nil { 487 t.Fatalf("%s: isEmptyMap() err=%v want nil", desc, err) 488 } 489 490 // Generate leaves. 491 leafBatch := make([][]*trillian.MapLeaf, numBatches) 492 leafMap := make(map[string]*trillian.MapLeaf) 493 for i := range leafBatch { 494 leafBatch[i] = createBatchLeaves(i, batchSize) 495 for _, l := range leafBatch[i] { 496 leafMap[hex.EncodeToString(l.Index)] = l 497 } 498 } 499 500 // Write some data in batches 501 for _, b := range leafBatch { 502 if _, err := tmap.SetLeaves(ctx, &trillian.SetMapLeavesRequest{ 503 MapId: tree.TreeId, 504 Leaves: b, 505 }); err != nil { 506 t.Fatalf("%s: SetLeaves(): %v", desc, err) 507 } 508 } 509 510 // Check your head 511 r, err := tmap.GetSignedMapRoot(ctx, &trillian.GetSignedMapRootRequest{MapId: tree.TreeId}) 512 if err != nil || r.MapRoot == nil { 513 t.Fatalf("%s: failed to get map head: %v", desc, err) 514 } 515 516 if err := verifyGetSignedMapRootResponse(mapVerifier, r.GetMapRoot(), int64(numBatches)); err != nil { 517 t.Fatalf("%s: %v", desc, err) 518 } 519 520 // Shuffle the indexes. Map access is randomized. 521 indexBatch := make([][][]byte, 0, numBatches) 522 i := 0 523 for _, v := range leafMap { 524 if i%batchSize == 0 { 525 indexBatch = append(indexBatch, make([][]byte, 0, batchSize)) 526 } 527 batchIndex := i / batchSize 528 indexBatch[batchIndex] = append(indexBatch[batchIndex], v.Index) 529 i++ 530 } 531 532 for i, indexes := range indexBatch { 533 getResp, err := tmap.GetLeaves(ctx, &trillian.GetMapLeavesRequest{ 534 MapId: tree.TreeId, 535 Index: indexes, 536 }) 537 if err != nil { 538 t.Errorf("%s: GetLeaves(): %v", desc, err) 539 continue 540 } 541 542 if err := verifyGetMapLeavesResponse(mapVerifier, getResp, indexes, int64(numBatches)); err != nil { 543 t.Errorf("%s: batch %v: verifyGetMapLeavesResponse(): %v", desc, i, err) 544 continue 545 } 546 547 // Verify leaf contents 548 for _, incl := range getResp.MapLeafInclusion { 549 index := incl.GetLeaf().GetIndex() 550 leaf := incl.GetLeaf().GetLeafValue() 551 ev, ok := leafMap[hex.EncodeToString(index)] 552 if !ok { 553 t.Errorf("%s: unexpected key returned: %s", desc, index) 554 } 555 if got, want := leaf, ev.LeafValue; !bytes.Equal(got, want) { 556 t.Errorf("%s: got value %s, want %s", desc, got, want) 557 } 558 } 559 } 560 return nil 561 }