github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/integration/log.go (about)

     1  // Copyright 2016 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 integration
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"math/rand"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/golang/glog"
    28  	"github.com/google/trillian"
    29  	"github.com/google/trillian/client/backoff"
    30  	"github.com/google/trillian/merkle"
    31  	"github.com/google/trillian/merkle/rfc6962"
    32  	"github.com/google/trillian/types"
    33  )
    34  
    35  // TestParameters bundles up all the settings for a test run
    36  type TestParameters struct {
    37  	treeID              int64
    38  	checkLogEmpty       bool
    39  	queueLeaves         bool
    40  	awaitSequencing     bool
    41  	startLeaf           int64
    42  	leafCount           int64
    43  	uniqueLeaves        int64
    44  	queueBatchSize      int
    45  	sequencerBatchSize  int
    46  	readBatchSize       int64
    47  	sequencingWaitTotal time.Duration
    48  	sequencingPollWait  time.Duration
    49  	rpcRequestDeadline  time.Duration
    50  	customLeafPrefix    string
    51  }
    52  
    53  // DefaultTestParameters builds a TestParameters object for a normal
    54  // test of the given log.
    55  func DefaultTestParameters(treeID int64) TestParameters {
    56  	return TestParameters{
    57  		treeID:              treeID,
    58  		checkLogEmpty:       true,
    59  		queueLeaves:         true,
    60  		awaitSequencing:     true,
    61  		startLeaf:           0,
    62  		leafCount:           1000,
    63  		uniqueLeaves:        1000,
    64  		queueBatchSize:      50,
    65  		sequencerBatchSize:  100,
    66  		readBatchSize:       50,
    67  		sequencingWaitTotal: 10 * time.Second * 60,
    68  		sequencingPollWait:  time.Second * 5,
    69  		rpcRequestDeadline:  time.Second * 30,
    70  		customLeafPrefix:    "",
    71  	}
    72  }
    73  
    74  type consistencyProofParams struct {
    75  	size1 int64
    76  	size2 int64
    77  }
    78  
    79  // inclusionProofTestIndices are the 0 based leaf indices to probe inclusion proofs at.
    80  var inclusionProofTestIndices = []int64{5, 27, 31, 80, 91}
    81  
    82  // consistencyProofTestParams are the intervals
    83  // to test proofs at
    84  var consistencyProofTestParams = []consistencyProofParams{{1, 2}, {2, 3}, {1, 3}, {2, 4}}
    85  
    86  // consistencyProofBadTestParams are the intervals to probe for consistency proofs, none of
    87  // these should succeed. Zero is not a valid tree size, nor is -1. 10000000 is outside the
    88  // range we'll reasonably queue (multiple of batch size).
    89  var consistencyProofBadTestParams = []consistencyProofParams{{0, 0}, {-1, 0}, {10000000, 10000000}}
    90  
    91  // RunLogIntegration runs a log integration test using the given client and test
    92  // parameters.
    93  func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters) error {
    94  	// Step 1 - Optionally check log starts empty then optionally queue leaves on server
    95  	if params.checkLogEmpty {
    96  		glog.Infof("Checking log is empty before starting test")
    97  		resp, err := getLatestSignedLogRoot(client, params)
    98  		if err != nil {
    99  			return fmt.Errorf("failed to get latest log root: %v %v", resp, err)
   100  		}
   101  
   102  		// TODO(gbelvin): Replace with VerifySignedLogRoot
   103  		var root types.LogRootV1
   104  		if err := root.UnmarshalBinary(resp.SignedLogRoot.GetLogRoot()); err != nil {
   105  			return fmt.Errorf("could not read current log root: %v", err)
   106  		}
   107  
   108  		if root.TreeSize > 0 {
   109  			return fmt.Errorf("expected an empty log but got tree head response: %v", resp)
   110  		}
   111  	}
   112  
   113  	var leafCounts map[string]int
   114  	var err error
   115  	if params.queueLeaves {
   116  		glog.Infof("Queueing %d leaves to log server ...", params.leafCount)
   117  		if leafCounts, err = queueLeaves(client, params); err != nil {
   118  			return fmt.Errorf("failed to queue leaves: %v", err)
   119  		}
   120  	}
   121  
   122  	// Step 2 - Wait for queue to drain when server sequences, give up if it doesn't happen (optional)
   123  	if params.awaitSequencing {
   124  		glog.Infof("Waiting for log to sequence ...")
   125  		if err := waitForSequencing(params.treeID, client, params); err != nil {
   126  			return fmt.Errorf("leaves were not sequenced: %v", err)
   127  		}
   128  	}
   129  
   130  	// Step 3 - Use get entries to read back what was written, check leaves are correct
   131  	glog.Infof("Reading back leaves from log ...")
   132  	leafMap, err := readbackLogEntries(params.treeID, client, params, leafCounts)
   133  
   134  	if err != nil {
   135  		return fmt.Errorf("could not read back log entries: %v", err)
   136  	}
   137  
   138  	// Step 4 - Cross validation between log and memory tree root hashes
   139  	glog.Infof("Checking log STH with our constructed in-memory tree ...")
   140  	tree, err := buildMemoryMerkleTree(leafMap, params)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	if err := checkLogRootHashMatches(tree, client, params); err != nil {
   145  		return fmt.Errorf("log consistency check failed: %v", err)
   146  	}
   147  
   148  	// Now that the basic tree has passed validation we can start testing proofs
   149  
   150  	// Step 5 - Test some inclusion proofs
   151  	glog.Info("Testing inclusion proofs")
   152  
   153  	// Ensure log doesn't serve a proof for a leaf index outside the tree size
   154  	if err := checkInclusionProofLeafOutOfRange(params.treeID, client, params); err != nil {
   155  		return fmt.Errorf("log served out of range proof (index): %v", err)
   156  	}
   157  
   158  	// Ensure that log doesn't serve a proof for a valid index at a size outside the tree
   159  	if err := checkInclusionProofTreeSizeOutOfRange(params.treeID, client, params); err != nil {
   160  		return fmt.Errorf("log served out of range proof (tree size): %v", err)
   161  	}
   162  
   163  	// Probe the log at several leaf indices each with a range of tree sizes
   164  	for _, testIndex := range inclusionProofTestIndices {
   165  		if err := checkInclusionProofsAtIndex(testIndex, params.treeID, tree, client, params); err != nil {
   166  			return fmt.Errorf("log inclusion index: %d proof checks failed: %v", testIndex, err)
   167  		}
   168  	}
   169  
   170  	// TODO(al): test some inclusion proofs by Merkle hash too.
   171  
   172  	// Step 6 - Test some consistency proofs
   173  	glog.Info("Testing consistency proofs")
   174  
   175  	// Make some consistency proof requests that we know should not succeed
   176  	for _, consistParams := range consistencyProofBadTestParams {
   177  		if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize)); err == nil {
   178  			return fmt.Errorf("log consistency for %v: unexpected proof returned", consistParams)
   179  		}
   180  	}
   181  
   182  	// Probe the log between some tree sizes we know are included and check the results against
   183  	// the in memory tree. Request proofs at both STH and non STH sizes unless batch size is one,
   184  	// when these would be equivalent requests.
   185  	for _, consistParams := range consistencyProofTestParams {
   186  		if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize)); err != nil {
   187  			return fmt.Errorf("log consistency for %v: proof checks failed: %v", consistParams, err)
   188  		}
   189  
   190  		// Only do this if the batch size changes when halved
   191  		if params.queueBatchSize > 1 {
   192  			if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize/2)); err != nil {
   193  				return fmt.Errorf("log consistency for %v: proof checks failed (Non STH size): %v", consistParams, err)
   194  			}
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  func queueLeaves(client trillian.TrillianLogClient, params TestParameters) (map[string]int, error) {
   201  	if params.uniqueLeaves == 0 {
   202  		params.uniqueLeaves = params.leafCount
   203  	}
   204  
   205  	leaves := []*trillian.LogLeaf{}
   206  
   207  	uniqueLeaves := make([]*trillian.LogLeaf, 0, params.uniqueLeaves)
   208  	for i := int64(0); i < params.uniqueLeaves; i++ {
   209  		leafNumber := params.startLeaf + i
   210  		data := []byte(fmt.Sprintf("%sLeaf %d", params.customLeafPrefix, leafNumber))
   211  		leaf := &trillian.LogLeaf{
   212  			LeafValue: data,
   213  			ExtraData: []byte(fmt.Sprintf("%sExtra %d", params.customLeafPrefix, leafNumber)),
   214  		}
   215  		uniqueLeaves = append(uniqueLeaves, leaf)
   216  	}
   217  
   218  	// We'll shuffle the sent leaves around a bit to see if that breaks things,
   219  	// but record and log the seed we use so we can reproduce failures.
   220  	seed := time.Now().UnixNano()
   221  	rand.Seed(seed)
   222  	perm := rand.Perm(int(params.leafCount))
   223  	glog.Infof("Queueing %d leaves total, built from %d unique leaves, using permutation seed %d", params.leafCount, len(uniqueLeaves), seed)
   224  
   225  	counts := make(map[string]int)
   226  	for l := int64(0); l < params.leafCount; l++ {
   227  		leaf := uniqueLeaves[int64(perm[l])%params.uniqueLeaves]
   228  		leaves = append(leaves, leaf)
   229  		counts[string(leaf.LeafValue)]++
   230  
   231  		if len(leaves) >= params.queueBatchSize || (l+1) == params.leafCount {
   232  			glog.Infof("Queueing %d leaves...", len(leaves))
   233  
   234  			ctx, cancel := getRPCDeadlineContext(params)
   235  			b := &backoff.Backoff{
   236  				Min:    100 * time.Millisecond,
   237  				Max:    10 * time.Second,
   238  				Factor: 2,
   239  				Jitter: true,
   240  			}
   241  
   242  			err := b.Retry(ctx, func() error {
   243  				_, err := client.QueueLeaves(ctx, &trillian.QueueLeavesRequest{
   244  					LogId:  params.treeID,
   245  					Leaves: leaves,
   246  				})
   247  				return err
   248  			})
   249  			cancel()
   250  
   251  			if err != nil {
   252  				return nil, err
   253  			}
   254  			leaves = leaves[:0] // starting new batch
   255  		}
   256  	}
   257  
   258  	return counts, nil
   259  }
   260  
   261  func waitForSequencing(treeID int64, client trillian.TrillianLogClient, params TestParameters) error {
   262  	endTime := time.Now().Add(params.sequencingWaitTotal)
   263  
   264  	glog.Infof("Waiting for sequencing until: %v", endTime)
   265  
   266  	for endTime.After(time.Now()) {
   267  		req := trillian.GetSequencedLeafCountRequest{LogId: treeID}
   268  		ctx, cancel := getRPCDeadlineContext(params)
   269  		sequencedLeaves, err := client.GetSequencedLeafCount(ctx, &req)
   270  		cancel()
   271  
   272  		if err != nil {
   273  			return err
   274  		}
   275  
   276  		glog.Infof("Leaf count: %d", sequencedLeaves.LeafCount)
   277  
   278  		if sequencedLeaves.LeafCount >= params.leafCount+params.startLeaf {
   279  			return nil
   280  		}
   281  
   282  		glog.Infof("Leaves sequenced: %d. Still waiting ...", sequencedLeaves.LeafCount)
   283  
   284  		time.Sleep(params.sequencingPollWait)
   285  	}
   286  
   287  	return errors.New("wait time expired")
   288  }
   289  
   290  func readbackLogEntries(logID int64, client trillian.TrillianLogClient, params TestParameters, expect map[string]int) (map[int64]*trillian.LogLeaf, error) {
   291  	// Take a copy of the expect map, since we'll be modifying it:
   292  	expect = func(m map[string]int) map[string]int {
   293  		r := make(map[string]int)
   294  		for k, v := range m {
   295  			r[k] = v
   296  		}
   297  		return r
   298  	}(expect)
   299  
   300  	currentLeaf := int64(0)
   301  	leafMap := make(map[int64]*trillian.LogLeaf)
   302  	glog.Infof("Expecting %d unique leaves", len(expect))
   303  
   304  	for currentLeaf < params.leafCount {
   305  		// We have to allow for the last batch potentially being a short one
   306  		numLeaves := params.leafCount - currentLeaf
   307  
   308  		if numLeaves > params.readBatchSize {
   309  			numLeaves = params.readBatchSize
   310  		}
   311  
   312  		glog.Infof("Reading %d leaves from %d ...", numLeaves, currentLeaf+params.startLeaf)
   313  		req := makeGetLeavesByIndexRequest(logID, currentLeaf+params.startLeaf, numLeaves)
   314  		ctx, cancel := getRPCDeadlineContext(params)
   315  		response, err := client.GetLeavesByIndex(ctx, req)
   316  		cancel()
   317  
   318  		if err != nil {
   319  			return nil, err
   320  		}
   321  
   322  		// Check we got the right leaf count
   323  		if len(response.Leaves) == 0 {
   324  			return nil, fmt.Errorf("expected %d leaves log returned none", numLeaves)
   325  		}
   326  
   327  		// Check the leaf contents make sense. Can't rely on exact ordering as queue timestamps will be
   328  		// close between batches and identical within batches.
   329  		for l := 0; l < len(response.Leaves); l++ {
   330  			// Check for duplicate leaf index in response data - should not happen
   331  			leaf := response.Leaves[l]
   332  
   333  			lk := string(leaf.LeafValue)
   334  			expect[lk]--
   335  
   336  			if expect[lk] == 0 {
   337  				delete(expect, lk)
   338  			}
   339  			leafMap[leaf.LeafIndex] = leaf
   340  
   341  			hash, err := rfc6962.DefaultHasher.HashLeaf(leaf.LeafValue)
   342  			if err != nil {
   343  				return nil, fmt.Errorf("HashLeaf(%v): %v", leaf.LeafValue, err)
   344  			}
   345  
   346  			if got, want := hex.EncodeToString(hash), hex.EncodeToString(leaf.MerkleLeafHash); got != want {
   347  				return nil, fmt.Errorf("leaf %d hash mismatch expected got: %s want: %s", leaf.LeafIndex, got, want)
   348  			}
   349  
   350  			// Ensure that the ExtraData in the leaf made it through the roundtrip. This was set up when
   351  			// we queued the leaves.
   352  			if got, want := hex.EncodeToString(leaf.ExtraData), hex.EncodeToString([]byte(strings.Replace(string(leaf.LeafValue), "Leaf", "Extra", 1))); got != want {
   353  				return nil, fmt.Errorf("leaf %d extra data got: %s, want:%s (%v)", leaf.LeafIndex, got, want, leaf)
   354  			}
   355  		}
   356  
   357  		currentLeaf += int64(len(response.Leaves))
   358  	}
   359  
   360  	// By this point we expect to have seen all the leaves so there should be nothing in the map
   361  	if len(expect) != 0 {
   362  		return nil, fmt.Errorf("incorrect leaves read back (+missing, -extra): %v", expect)
   363  	}
   364  
   365  	return leafMap, nil
   366  }
   367  
   368  func checkLogRootHashMatches(tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters) error {
   369  	// Check the STH against the hash we got from our tree
   370  	resp, err := getLatestSignedLogRoot(client, params)
   371  	if err != nil {
   372  		return err
   373  	}
   374  	var root types.LogRootV1
   375  	if err := root.UnmarshalBinary(resp.SignedLogRoot.GetLogRoot()); err != nil {
   376  		return err
   377  	}
   378  
   379  	// Hash must not be empty and must match the one we built ourselves
   380  	if got, want := root.RootHash, tree.CurrentRoot().Hash(); !bytes.Equal(got, want) {
   381  		return fmt.Errorf("root hash mismatch expected got: %x want: %x", got, want)
   382  	}
   383  
   384  	return nil
   385  }
   386  
   387  // checkInclusionProofLeafOutOfRange requests an inclusion proof beyond the current tree size. This
   388  // should fail
   389  func checkInclusionProofLeafOutOfRange(logID int64, client trillian.TrillianLogClient, params TestParameters) error {
   390  	// Test is a leaf index bigger than the current tree size
   391  	ctx, cancel := getRPCDeadlineContext(params)
   392  	proof, err := client.GetInclusionProof(ctx, &trillian.GetInclusionProofRequest{
   393  		LogId:     logID,
   394  		LeafIndex: params.leafCount + 1,
   395  		TreeSize:  int64(params.leafCount),
   396  	})
   397  	cancel()
   398  
   399  	if err == nil {
   400  		return fmt.Errorf("log returned proof for leaf index outside tree: %d v %d: %v", params.leafCount+1, params.leafCount, proof)
   401  	}
   402  
   403  	return nil
   404  }
   405  
   406  // checkInclusionProofTreeSizeOutOfRange requests an inclusion proof for a leaf within the tree size at
   407  // a tree size larger than the current tree size. This should succeed but with an STH for the current
   408  // tree and an empty proof, because it is a result of skew.
   409  func checkInclusionProofTreeSizeOutOfRange(logID int64, client trillian.TrillianLogClient, params TestParameters) error {
   410  	// Test is an in range leaf index for a tree size that doesn't exist
   411  	ctx, cancel := getRPCDeadlineContext(params)
   412  	req := &trillian.GetInclusionProofRequest{
   413  		LogId:     logID,
   414  		LeafIndex: int64(params.sequencerBatchSize),
   415  		TreeSize:  params.leafCount + int64(params.sequencerBatchSize),
   416  	}
   417  	proof, err := client.GetInclusionProof(ctx, req)
   418  	cancel()
   419  	if err != nil {
   420  		return fmt.Errorf("log returned error for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, err)
   421  	}
   422  
   423  	var root types.LogRootV1
   424  	if err := root.UnmarshalBinary(proof.SignedLogRoot.LogRoot); err != nil {
   425  		return fmt.Errorf("could not read current log root: %v", err)
   426  	}
   427  
   428  	if proof.Proof != nil {
   429  		return fmt.Errorf("log returned proof for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, proof)
   430  	}
   431  	if int64(root.TreeSize) >= req.TreeSize {
   432  		return fmt.Errorf("log returned bad root for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, proof)
   433  	}
   434  
   435  	return nil
   436  }
   437  
   438  // checkInclusionProofsAtIndex obtains and checks proofs at tree sizes from zero up to 2 x the sequencing
   439  // batch size (or number of leaves queued if less). The log should only serve proofs for indices in a tree
   440  // at least as big as the index where STHs where the index is a multiple of the sequencer batch size. All
   441  // proofs returned should match ones computed by the alternate Merkle Tree implementation, which differs
   442  // from what the log uses.
   443  func checkInclusionProofsAtIndex(index int64, logID int64, tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters) error {
   444  	for treeSize := int64(0); treeSize < min(params.leafCount, int64(2*params.sequencerBatchSize)); treeSize++ {
   445  		ctx, cancel := getRPCDeadlineContext(params)
   446  		resp, err := client.GetInclusionProof(ctx, &trillian.GetInclusionProofRequest{
   447  			LogId:     logID,
   448  			LeafIndex: index,
   449  			TreeSize:  int64(treeSize),
   450  		})
   451  		cancel()
   452  
   453  		// If the index is larger than the tree size we cannot have a valid proof
   454  		shouldHaveProof := index < treeSize
   455  		if got, want := err == nil, shouldHaveProof; got != want {
   456  			return fmt.Errorf("GetInclusionProof(index: %d, treeSize %d): %v, want nil: %v", index, treeSize, err, want)
   457  		}
   458  		if !shouldHaveProof {
   459  			continue
   460  		}
   461  
   462  		// Verify inclusion proof.
   463  		root := tree.RootAtSnapshot(treeSize).Hash()
   464  		verifier := merkle.NewLogVerifier(rfc6962.DefaultHasher)
   465  		// Offset by 1 to make up for C++ / Go implementation differences.
   466  		merkleLeafHash := tree.LeafHash(index + 1)
   467  		if err := verifier.VerifyInclusionProof(index, treeSize, resp.Proof.Hashes, root, merkleLeafHash); err != nil {
   468  			return err
   469  		}
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func checkConsistencyProof(consistParams consistencyProofParams, treeID int64, tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters, batchSize int64) error {
   476  	// We expect the proof request to succeed
   477  	ctx, cancel := getRPCDeadlineContext(params)
   478  	req := &trillian.GetConsistencyProofRequest{
   479  		LogId:          treeID,
   480  		FirstTreeSize:  consistParams.size1 * int64(batchSize),
   481  		SecondTreeSize: consistParams.size2 * int64(batchSize),
   482  	}
   483  	resp, err := client.GetConsistencyProof(ctx, req)
   484  	cancel()
   485  	if err != nil {
   486  		return fmt.Errorf("GetConsistencyProof(%v) = %v %v", consistParams, err, resp)
   487  	}
   488  
   489  	if resp.SignedLogRoot == nil || resp.SignedLogRoot.LogRoot == nil {
   490  		return fmt.Errorf("received invalid response: %v", resp)
   491  	}
   492  	var root types.LogRootV1
   493  	if err := root.UnmarshalBinary(resp.SignedLogRoot.LogRoot); err != nil {
   494  		return fmt.Errorf("could not read current log root: %v", err)
   495  	}
   496  
   497  	if req.SecondTreeSize > int64(root.TreeSize) {
   498  		return fmt.Errorf("requested tree size %d > available tree size %d", req.SecondTreeSize, root.TreeSize)
   499  	}
   500  
   501  	verifier := merkle.NewLogVerifier(rfc6962.DefaultHasher)
   502  	root1 := tree.RootAtSnapshot(req.FirstTreeSize).Hash()
   503  	root2 := tree.RootAtSnapshot(req.SecondTreeSize).Hash()
   504  	return verifier.VerifyConsistencyProof(req.FirstTreeSize, req.SecondTreeSize,
   505  		root1, root2, resp.Proof.Hashes)
   506  }
   507  
   508  func makeGetLeavesByIndexRequest(logID int64, startLeaf, numLeaves int64) *trillian.GetLeavesByIndexRequest {
   509  	leafIndices := make([]int64, 0, numLeaves)
   510  
   511  	for l := int64(0); l < numLeaves; l++ {
   512  		leafIndices = append(leafIndices, l+startLeaf)
   513  	}
   514  
   515  	return &trillian.GetLeavesByIndexRequest{LogId: logID, LeafIndex: leafIndices}
   516  }
   517  
   518  func buildMemoryMerkleTree(leafMap map[int64]*trillian.LogLeaf, params TestParameters) (*merkle.InMemoryMerkleTree, error) {
   519  	// Build the same tree with two different Merkle implementations as an additional check. We don't
   520  	// just rely on the compact tree as the server uses the same code so bugs could be masked
   521  	compactTree := merkle.NewCompactMerkleTree(rfc6962.DefaultHasher)
   522  	merkleTree := merkle.NewInMemoryMerkleTree(rfc6962.DefaultHasher)
   523  
   524  	// We use the leafMap as we need to use the same order for the memory tree to get the same hash.
   525  	for l := params.startLeaf; l < params.leafCount; l++ {
   526  		compactTree.AddLeaf(leafMap[l].LeafValue, func(int, int64, []byte) error {
   527  			return nil
   528  		})
   529  		if _, _, err := merkleTree.AddLeaf(leafMap[l].LeafValue); err != nil {
   530  			return nil, err
   531  		}
   532  	}
   533  
   534  	// If the two reference results disagree there's no point in continuing the checks. This is a
   535  	// "can't happen" situation.
   536  	if !bytes.Equal(compactTree.CurrentRoot(), merkleTree.CurrentRoot().Hash()) {
   537  		return nil, fmt.Errorf("different root hash results from merkle tree building: %v and %v", compactTree.CurrentRoot(), merkleTree.CurrentRoot())
   538  	}
   539  
   540  	return merkleTree, nil
   541  }
   542  
   543  func getLatestSignedLogRoot(client trillian.TrillianLogClient, params TestParameters) (*trillian.GetLatestSignedLogRootResponse, error) {
   544  	req := trillian.GetLatestSignedLogRootRequest{LogId: params.treeID}
   545  	ctx, cancel := getRPCDeadlineContext(params)
   546  	resp, err := client.GetLatestSignedLogRoot(ctx, &req)
   547  	cancel()
   548  
   549  	return resp, err
   550  }
   551  
   552  // getRPCDeadlineTime calculates the future time an RPC should expire based on our config
   553  func getRPCDeadlineContext(params TestParameters) (context.Context, context.CancelFunc) {
   554  	return context.WithDeadline(context.Background(), time.Now().Add(params.rpcRequestDeadline))
   555  }
   556  
   557  func min(a, b int64) int64 {
   558  	if a < b {
   559  		return a
   560  	}
   561  
   562  	return b
   563  }