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  }