func fetchNodesAndBuildProof(ctx context.Context, nr nodeReader, th hashers.LogHasher, leafIndex int64, proofNodeFetches []merkle.NodeFetch) (*trillian.Proof, error) {
	ctx, spanEnd := spanFor(ctx, "fetchNodesAndBuildProof")
	defer spanEnd()
	proofNodes, err := fetchNodes(ctx, nr, proofNodeFetches)
	if err != nil {
		return nil, err
	}
	h := make([][]byte, len(proofNodes))
	for i, node := range proofNodes {
		h[i] = node.Hash
	}
	proof, err := merkle.Rehash(h, proofNodeFetches, th.HashChildren)
	if err != nil {
		return nil, err
	}
	return &trillian.Proof{
		LeafIndex: leafIndex,
		Hashes:    proof,
	}, nil
}
fetchNodesAndBuildProof is used by both inclusion and consistency proofs. It fetches the nodes
from storage and converts them into the proof proto that will be returned to the client.
This includes rehashing where necessary to serve proofs for tree sizes between stored tree
revisions. This code only relies on the nodeReader interface so can be tested without
a complete storage implementation.
package server
import (
	"context"
	"fmt"
	"github.com/google/trillian"
	"github.com/google/trillian/merkle"
	"github.com/google/trillian/merkle/compact"
	"github.com/google/trillian/merkle/hashers"
	"github.com/google/trillian/storage/tree"
)
type nodeReader interface {
	GetMerkleNodes(ctx context.Context, ids []compact.NodeID) ([]tree.Node, error)
}