github.com/true-sqn/fabric@v2.1.1+incompatible/discovery/client/client_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package discovery
     8  
     9  import (
    10  	"context"
    11  	"crypto/tls"
    12  	"crypto/x509"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"net"
    16  	"path/filepath"
    17  	"strconv"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/golang/protobuf/proto"
    22  	"github.com/hyperledger/fabric-protos-go/common"
    23  	"github.com/hyperledger/fabric-protos-go/discovery"
    24  	"github.com/hyperledger/fabric-protos-go/gossip"
    25  	"github.com/hyperledger/fabric-protos-go/msp"
    26  	"github.com/hyperledger/fabric-protos-go/peer"
    27  	"github.com/hyperledger/fabric/common/chaincode"
    28  	"github.com/hyperledger/fabric/common/policies"
    29  	"github.com/hyperledger/fabric/common/policydsl"
    30  	"github.com/hyperledger/fabric/common/util"
    31  	fabricdisc "github.com/hyperledger/fabric/discovery"
    32  	"github.com/hyperledger/fabric/discovery/endorsement"
    33  	"github.com/hyperledger/fabric/gossip/api"
    34  	gossipcommon "github.com/hyperledger/fabric/gossip/common"
    35  	gdisc "github.com/hyperledger/fabric/gossip/discovery"
    36  	"github.com/hyperledger/fabric/gossip/protoext"
    37  	"github.com/hyperledger/fabric/internal/pkg/comm"
    38  	"github.com/hyperledger/fabric/protoutil"
    39  	"github.com/pkg/errors"
    40  	"github.com/stretchr/testify/assert"
    41  	"github.com/stretchr/testify/mock"
    42  	"google.golang.org/grpc"
    43  	"google.golang.org/grpc/credentials"
    44  )
    45  
    46  const (
    47  	signerCacheSize uint = 1
    48  )
    49  
    50  var (
    51  	ctx = context.Background()
    52  
    53  	orgCombinationsThatSatisfyPolicy = [][]string{
    54  		{"A", "B"}, {"C"}, {"A", "D"},
    55  	}
    56  
    57  	orgCombinationsThatSatisfyPolicy2 = [][]string{
    58  		{"B", "D"},
    59  	}
    60  
    61  	expectedOrgCombinations = []map[string]struct{}{
    62  		{
    63  			"A": {},
    64  			"B": {},
    65  		},
    66  		{
    67  			"C": {},
    68  		},
    69  		{
    70  			"A": {},
    71  			"D": {},
    72  		},
    73  	}
    74  
    75  	expectedOrgCombinations2 = []map[string]struct{}{
    76  		{
    77  			"B": {},
    78  			"C": {},
    79  			"D": {},
    80  		},
    81  	}
    82  
    83  	cc = &gossip.Chaincode{
    84  		Name:    "mycc",
    85  		Version: "1.0",
    86  	}
    87  
    88  	cc2 = &gossip.Chaincode{
    89  		Name:    "mycc2",
    90  		Version: "1.0",
    91  	}
    92  
    93  	cc3 = &gossip.Chaincode{
    94  		Name:    "mycc3",
    95  		Version: "1.0",
    96  	}
    97  
    98  	propertiesWithChaincodes = &gossip.Properties{
    99  		Chaincodes: []*gossip.Chaincode{cc, cc2, cc3},
   100  	}
   101  
   102  	expectedConf = &discovery.ConfigResult{
   103  		Msps: map[string]*msp.FabricMSPConfig{
   104  			"A": {},
   105  			"B": {},
   106  			"C": {},
   107  			"D": {},
   108  		},
   109  		Orderers: map[string]*discovery.Endpoints{
   110  			"A": {},
   111  			"B": {},
   112  		},
   113  	}
   114  
   115  	channelPeersWithChaincodes = gdisc.Members{
   116  		newPeer(0, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   117  		newPeer(1, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   118  		newPeer(2, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   119  		newPeer(3, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   120  		newPeer(4, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   121  		newPeer(5, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   122  		newPeer(6, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   123  		newPeer(7, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
   124  	}
   125  
   126  	channelPeersWithoutChaincodes = gdisc.Members{
   127  		newPeer(0, stateInfoMessage(), nil).NetworkMember,
   128  		newPeer(1, stateInfoMessage(), nil).NetworkMember,
   129  		newPeer(2, stateInfoMessage(), nil).NetworkMember,
   130  		newPeer(3, stateInfoMessage(), nil).NetworkMember,
   131  		newPeer(4, stateInfoMessage(), nil).NetworkMember,
   132  		newPeer(5, stateInfoMessage(), nil).NetworkMember,
   133  		newPeer(6, stateInfoMessage(), nil).NetworkMember,
   134  		newPeer(7, stateInfoMessage(), nil).NetworkMember,
   135  	}
   136  
   137  	channelPeersWithDifferentLedgerHeights = gdisc.Members{
   138  		newPeer(0, stateInfoMessageWithHeight(100, cc3), propertiesWithChaincodes).NetworkMember,
   139  		newPeer(1, stateInfoMessageWithHeight(106, cc3), propertiesWithChaincodes).NetworkMember,
   140  		newPeer(2, stateInfoMessageWithHeight(107, cc3), propertiesWithChaincodes).NetworkMember,
   141  		newPeer(3, stateInfoMessageWithHeight(108, cc3), propertiesWithChaincodes).NetworkMember,
   142  		newPeer(4, stateInfoMessageWithHeight(101, cc3), propertiesWithChaincodes).NetworkMember,
   143  		newPeer(5, stateInfoMessageWithHeight(108, cc3), propertiesWithChaincodes).NetworkMember,
   144  		newPeer(6, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember,
   145  		newPeer(7, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember,
   146  		newPeer(8, stateInfoMessageWithHeight(100, cc3), propertiesWithChaincodes).NetworkMember,
   147  		newPeer(9, stateInfoMessageWithHeight(107, cc3), propertiesWithChaincodes).NetworkMember,
   148  		newPeer(10, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember,
   149  		newPeer(11, stateInfoMessageWithHeight(111, cc3), propertiesWithChaincodes).NetworkMember,
   150  		newPeer(12, stateInfoMessageWithHeight(105, cc3), propertiesWithChaincodes).NetworkMember,
   151  		newPeer(13, stateInfoMessageWithHeight(103, cc3), propertiesWithChaincodes).NetworkMember,
   152  		newPeer(14, stateInfoMessageWithHeight(109, cc3), propertiesWithChaincodes).NetworkMember,
   153  		newPeer(15, stateInfoMessageWithHeight(111, cc3), propertiesWithChaincodes).NetworkMember,
   154  	}
   155  
   156  	membershipPeers = gdisc.Members{
   157  		newPeer(0, aliveMessage(0), nil).NetworkMember,
   158  		newPeer(1, aliveMessage(1), nil).NetworkMember,
   159  		newPeer(2, aliveMessage(2), nil).NetworkMember,
   160  		newPeer(3, aliveMessage(3), nil).NetworkMember,
   161  		newPeer(4, aliveMessage(4), nil).NetworkMember,
   162  		newPeer(5, aliveMessage(5), nil).NetworkMember,
   163  		newPeer(6, aliveMessage(6), nil).NetworkMember,
   164  		newPeer(7, aliveMessage(7), nil).NetworkMember,
   165  		newPeer(8, aliveMessage(8), nil).NetworkMember,
   166  		newPeer(9, aliveMessage(9), nil).NetworkMember,
   167  		newPeer(10, aliveMessage(10), nil).NetworkMember,
   168  		newPeer(11, aliveMessage(11), nil).NetworkMember,
   169  		newPeer(12, aliveMessage(12), nil).NetworkMember,
   170  		newPeer(13, aliveMessage(13), nil).NetworkMember,
   171  		newPeer(14, aliveMessage(14), nil).NetworkMember,
   172  		newPeer(15, aliveMessage(15), nil).NetworkMember,
   173  	}
   174  
   175  	peerIdentities = api.PeerIdentitySet{
   176  		peerIdentity("A", 0),
   177  		peerIdentity("A", 1),
   178  		peerIdentity("B", 2),
   179  		peerIdentity("B", 3),
   180  		peerIdentity("C", 4),
   181  		peerIdentity("C", 5),
   182  		peerIdentity("D", 6),
   183  		peerIdentity("D", 7),
   184  		peerIdentity("A", 8),
   185  		peerIdentity("A", 9),
   186  		peerIdentity("B", 10),
   187  		peerIdentity("B", 11),
   188  		peerIdentity("C", 12),
   189  		peerIdentity("C", 13),
   190  		peerIdentity("D", 14),
   191  		peerIdentity("D", 15),
   192  	}
   193  
   194  	resultsWithoutEnvelopes = &discovery.QueryResult_CcQueryRes{
   195  		CcQueryRes: &discovery.ChaincodeQueryResult{
   196  			Content: []*discovery.EndorsementDescriptor{
   197  				{
   198  					Chaincode: "mycc",
   199  					EndorsersByGroups: map[string]*discovery.Peers{
   200  						"A": {
   201  							Peers: []*discovery.Peer{
   202  								{},
   203  							},
   204  						},
   205  					},
   206  					Layouts: []*discovery.Layout{
   207  						{
   208  							QuantitiesByGroup: map[string]uint32{},
   209  						},
   210  					},
   211  				},
   212  			},
   213  		},
   214  	}
   215  
   216  	resultsWithEnvelopesButWithInsufficientPeers = &discovery.QueryResult_CcQueryRes{
   217  		CcQueryRes: &discovery.ChaincodeQueryResult{
   218  			Content: []*discovery.EndorsementDescriptor{
   219  				{
   220  					Chaincode: "mycc",
   221  					EndorsersByGroups: map[string]*discovery.Peers{
   222  						"A": {
   223  							Peers: []*discovery.Peer{
   224  								{
   225  									StateInfo:      stateInfoMessage(),
   226  									MembershipInfo: aliveMessage(0),
   227  									Identity:       peerIdentity("A", 0).Identity,
   228  								},
   229  							},
   230  						},
   231  					},
   232  					Layouts: []*discovery.Layout{
   233  						{
   234  							QuantitiesByGroup: map[string]uint32{
   235  								"A": 2,
   236  							},
   237  						},
   238  					},
   239  				},
   240  			},
   241  		},
   242  	}
   243  
   244  	resultsWithEnvelopesButWithMismatchedLayout = &discovery.QueryResult_CcQueryRes{
   245  		CcQueryRes: &discovery.ChaincodeQueryResult{
   246  			Content: []*discovery.EndorsementDescriptor{
   247  				{
   248  					Chaincode: "mycc",
   249  					EndorsersByGroups: map[string]*discovery.Peers{
   250  						"A": {
   251  							Peers: []*discovery.Peer{
   252  								{
   253  									StateInfo:      stateInfoMessage(),
   254  									MembershipInfo: aliveMessage(0),
   255  									Identity:       peerIdentity("A", 0).Identity,
   256  								},
   257  							},
   258  						},
   259  					},
   260  					Layouts: []*discovery.Layout{
   261  						{
   262  							QuantitiesByGroup: map[string]uint32{
   263  								"B": 2,
   264  							},
   265  						},
   266  					},
   267  				},
   268  			},
   269  		},
   270  	}
   271  )
   272  
   273  func loadFileOrPanic(file string) []byte {
   274  	b, err := ioutil.ReadFile(file)
   275  	if err != nil {
   276  		panic(err)
   277  	}
   278  	return b
   279  }
   280  
   281  func createGRPCServer(t *testing.T) *comm.GRPCServer {
   282  	serverCert := loadFileOrPanic(filepath.Join("testdata", "server", "cert.pem"))
   283  	serverKey := loadFileOrPanic(filepath.Join("testdata", "server", "key.pem"))
   284  	s, err := comm.NewGRPCServer("localhost:0", comm.ServerConfig{
   285  		SecOpts: comm.SecureOptions{
   286  			UseTLS:      true,
   287  			Certificate: serverCert,
   288  			Key:         serverKey,
   289  		},
   290  	})
   291  	assert.NoError(t, err)
   292  	return s
   293  }
   294  
   295  func createConnector(t *testing.T, certificate tls.Certificate, targetPort int) func() (*grpc.ClientConn, error) {
   296  	caCert := loadFileOrPanic(filepath.Join("testdata", "server", "ca.pem"))
   297  	tlsConf := &tls.Config{
   298  		RootCAs:      x509.NewCertPool(),
   299  		Certificates: []tls.Certificate{certificate},
   300  	}
   301  	tlsConf.RootCAs.AppendCertsFromPEM(caCert)
   302  
   303  	addr := fmt.Sprintf("localhost:%d", targetPort)
   304  	return func() (*grpc.ClientConn, error) {
   305  		conn, err := grpc.Dial(addr, grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)))
   306  		assert.NoError(t, err)
   307  		if err != nil {
   308  			panic(err)
   309  		}
   310  		return conn, nil
   311  	}
   312  }
   313  
   314  func createDiscoveryService(sup *mockSupport) discovery.DiscoveryServer {
   315  	conf := fabricdisc.Config{TLS: true}
   316  	mdf := &ccMetadataFetcher{}
   317  	pe := &principalEvaluator{}
   318  	pf := &policyFetcher{}
   319  
   320  	sigPol, _ := policydsl.FromString("OR(AND('A.member', 'B.member'), 'C.member', AND('A.member', 'D.member'))")
   321  	polBytes, _ := proto.Marshal(sigPol)
   322  	mdf.On("Metadata", "mycc").Return(&chaincode.Metadata{
   323  		Policy:  polBytes,
   324  		Name:    "mycc",
   325  		Version: "1.0",
   326  		Id:      []byte{1, 2, 3},
   327  	})
   328  
   329  	pf.On("PoliciesByChaincode", "mycc").Return(&inquireablePolicy{
   330  		orgCombinations: orgCombinationsThatSatisfyPolicy,
   331  	})
   332  
   333  	sigPol, _ = policydsl.FromString("AND('B.member', 'C.member')")
   334  	polBytes, _ = proto.Marshal(sigPol)
   335  	mdf.On("Metadata", "mycc2").Return(&chaincode.Metadata{
   336  		Policy:  polBytes,
   337  		Name:    "mycc2",
   338  		Version: "1.0",
   339  		Id:      []byte{1, 2, 3},
   340  		CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
   341  			"col": {memberPrincipal("B"), memberPrincipal("C"), memberPrincipal("D")},
   342  		}),
   343  	})
   344  
   345  	pf.On("PoliciesByChaincode", "mycc2").Return(&inquireablePolicy{
   346  		orgCombinations: orgCombinationsThatSatisfyPolicy2,
   347  	})
   348  
   349  	sigPol, _ = policydsl.FromString("AND('A.member', 'B.member', 'C.member', 'D.member')")
   350  	polBytes, _ = proto.Marshal(sigPol)
   351  	mdf.On("Metadata", "mycc3").Return(&chaincode.Metadata{
   352  		Policy:  polBytes,
   353  		Name:    "mycc3",
   354  		Version: "1.0",
   355  		Id:      []byte{1, 2, 3},
   356  	})
   357  
   358  	pf.On("PoliciesByChaincode", "mycc3").Return(&inquireablePolicy{
   359  		orgCombinations: [][]string{{"A", "B", "C", "D"}},
   360  	})
   361  
   362  	sup.On("Config", "mychannel").Return(expectedConf)
   363  	sup.On("Peers").Return(membershipPeers)
   364  	sup.endorsementAnalyzer = endorsement.NewEndorsementAnalyzer(sup, pf, pe, mdf)
   365  	sup.On("IdentityInfo").Return(peerIdentities)
   366  	return fabricdisc.NewService(conf, sup)
   367  }
   368  
   369  func TestClient(t *testing.T) {
   370  	clientCert := loadFileOrPanic(filepath.Join("testdata", "client", "cert.pem"))
   371  	clientKey := loadFileOrPanic(filepath.Join("testdata", "client", "key.pem"))
   372  	clientTLSCert, err := tls.X509KeyPair(clientCert, clientKey)
   373  	assert.NoError(t, err)
   374  	server := createGRPCServer(t)
   375  	sup := &mockSupport{}
   376  	service := createDiscoveryService(sup)
   377  	discovery.RegisterDiscoveryServer(server.Server(), service)
   378  	go server.Start()
   379  
   380  	_, portStr, _ := net.SplitHostPort(server.Address())
   381  	port, _ := strconv.ParseInt(portStr, 10, 64)
   382  	connect := createConnector(t, clientTLSCert, int(port))
   383  
   384  	signer := func(msg []byte) ([]byte, error) {
   385  		return msg, nil
   386  	}
   387  	authInfo := &discovery.AuthInfo{
   388  		ClientIdentity:    []byte{1, 2, 3},
   389  		ClientTlsCertHash: util.ComputeSHA256(clientTLSCert.Certificate[0]),
   390  	}
   391  	cl := NewClient(connect, signer, signerCacheSize)
   392  
   393  	sup.On("PeersOfChannel").Return(channelPeersWithoutChaincodes).Times(2)
   394  	req := NewRequest()
   395  	req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddLocalPeersQuery().AddEndorsersQuery(interest("mycc"))
   396  	r, err := cl.Send(ctx, req, authInfo)
   397  	assert.NoError(t, err)
   398  
   399  	t.Run("Channel mismatch", func(t *testing.T) {
   400  		// Check behavior for channels that we didn't query for.
   401  		fakeChannel := r.ForChannel("fakeChannel")
   402  		peers, err := fakeChannel.Peers()
   403  		assert.Equal(t, ErrNotFound, err)
   404  		assert.Nil(t, peers)
   405  
   406  		endorsers, err := fakeChannel.Endorsers(ccCall("mycc"), NoFilter)
   407  		assert.Equal(t, ErrNotFound, err)
   408  		assert.Nil(t, endorsers)
   409  
   410  		conf, err := fakeChannel.Config()
   411  		assert.Equal(t, ErrNotFound, err)
   412  		assert.Nil(t, conf)
   413  	})
   414  
   415  	t.Run("Peer membership query", func(t *testing.T) {
   416  		// Check response for the correct channel
   417  		mychannel := r.ForChannel("mychannel")
   418  		conf, err := mychannel.Config()
   419  		assert.NoError(t, err)
   420  		assert.Equal(t, expectedConf.Msps, conf.Msps)
   421  		assert.Equal(t, expectedConf.Orderers, conf.Orderers)
   422  		peers, err := mychannel.Peers()
   423  		assert.NoError(t, err)
   424  		// We should see all peers as provided above
   425  		assert.Len(t, peers, 8)
   426  		// Check response for peers when doing a local query
   427  		peers, err = r.ForLocal().Peers()
   428  		assert.NoError(t, err)
   429  		assert.Len(t, peers, len(peerIdentities))
   430  	})
   431  
   432  	t.Run("Endorser query without chaincode installed", func(t *testing.T) {
   433  		mychannel := r.ForChannel("mychannel")
   434  		endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter)
   435  		// However, since we didn't provide any chaincodes to these peers - the server shouldn't
   436  		// be able to construct the descriptor.
   437  		// Just check that the appropriate error is returned, and nothing crashes.
   438  		assert.Contains(t, err.Error(), "failed constructing descriptor for chaincode")
   439  		assert.Nil(t, endorsers)
   440  	})
   441  
   442  	t.Run("Endorser query with chaincodes installed", func(t *testing.T) {
   443  		// Next, we check the case when the peers publish chaincode for themselves.
   444  		sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Times(2)
   445  		req = NewRequest()
   446  		req.OfChannel("mychannel").AddPeersQuery().AddEndorsersQuery(interest("mycc"))
   447  		r, err = cl.Send(ctx, req, authInfo)
   448  		assert.NoError(t, err)
   449  
   450  		mychannel := r.ForChannel("mychannel")
   451  		peers, err := mychannel.Peers()
   452  		assert.NoError(t, err)
   453  		assert.Len(t, peers, 8)
   454  
   455  		// We should get a valid endorsement descriptor from the service
   456  		endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter)
   457  		assert.NoError(t, err)
   458  		// The combinations of endorsers should be in the expected combinations
   459  		assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers))
   460  	})
   461  
   462  	t.Run("Endorser query with cc2cc and collections", func(t *testing.T) {
   463  		sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Twice()
   464  		req = NewRequest()
   465  		myccOnly := ccCall("mycc")
   466  		myccAndmycc2 := ccCall("mycc", "mycc2")
   467  		myccAndmycc2[1].CollectionNames = append(myccAndmycc2[1].CollectionNames, "col")
   468  		req.OfChannel("mychannel").AddEndorsersQuery(cc2ccInterests(myccAndmycc2, myccOnly)...)
   469  		r, err = cl.Send(ctx, req, authInfo)
   470  		assert.NoError(t, err)
   471  		mychannel := r.ForChannel("mychannel")
   472  
   473  		// Check the endorsers for the non cc2cc call
   474  		endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter)
   475  		assert.NoError(t, err)
   476  		assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers))
   477  		// Check the endorsers for the cc2cc call with collections
   478  		call := ccCall("mycc", "mycc2")
   479  		call[1].CollectionNames = append(call[1].CollectionNames, "col")
   480  		endorsers, err = mychannel.Endorsers(call, NoFilter)
   481  		assert.NoError(t, err)
   482  		assert.Contains(t, expectedOrgCombinations2, getMSPs(endorsers))
   483  	})
   484  
   485  	t.Run("Peer membership query with collections and chaincodes", func(t *testing.T) {
   486  		sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Once()
   487  		interest := ccCall("mycc2")
   488  		interest[0].CollectionNames = append(interest[0].CollectionNames, "col")
   489  		req = NewRequest().OfChannel("mychannel").AddPeersQuery(interest...)
   490  		r, err = cl.Send(ctx, req, authInfo)
   491  		assert.NoError(t, err)
   492  		mychannel := r.ForChannel("mychannel")
   493  		peers, err := mychannel.Peers(interest...)
   494  		assert.NoError(t, err)
   495  		// We should see all peers that aren't in ORG A since it's not part of the collection
   496  		for _, p := range peers {
   497  			assert.NotEqual(t, "A", p.MSPID)
   498  		}
   499  		assert.Len(t, peers, 6)
   500  	})
   501  
   502  	t.Run("Endorser query with PrioritiesByHeight selector", func(t *testing.T) {
   503  		sup.On("PeersOfChannel").Return(channelPeersWithDifferentLedgerHeights).Twice()
   504  		req = NewRequest()
   505  		req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc3"))
   506  		r, err = cl.Send(ctx, req, authInfo)
   507  		assert.NoError(t, err)
   508  		mychannel := r.ForChannel("mychannel")
   509  
   510  		// acceptablePeers are the ones at the highest ledger height for each org
   511  		acceptablePeers := []string{"p5", "p9", "p11", "p15"}
   512  		used := make(map[string]struct{})
   513  		endorsers, err := mychannel.Endorsers(ccCall("mycc3"), NewFilter(PrioritiesByHeight, NoExclusion))
   514  		assert.NoError(t, err)
   515  		names := getNames(endorsers)
   516  		assert.Subset(t, acceptablePeers, names)
   517  		for _, name := range names {
   518  			used[name] = struct{}{}
   519  		}
   520  		assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once")
   521  	})
   522  
   523  	t.Run("Endorser query with custom filter", func(t *testing.T) {
   524  		sup.On("PeersOfChannel").Return(channelPeersWithDifferentLedgerHeights).Twice()
   525  		req = NewRequest()
   526  		req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc3"))
   527  		r, err = cl.Send(ctx, req, authInfo)
   528  		assert.NoError(t, err)
   529  		mychannel := r.ForChannel("mychannel")
   530  
   531  		threshold := uint64(3) // Use peers within 3 of the max height of the org peers
   532  		acceptablePeers := []string{"p1", "p9", "p3", "p5", "p6", "p7", "p10", "p11", "p12", "p14", "p15"}
   533  		used := make(map[string]struct{})
   534  
   535  		for i := 0; i < 90; i++ {
   536  			endorsers, err := mychannel.Endorsers(ccCall("mycc3"), &ledgerHeightFilter{threshold: threshold})
   537  			assert.NoError(t, err)
   538  			names := getNames(endorsers)
   539  			assert.Subset(t, acceptablePeers, names)
   540  			for _, name := range names {
   541  				used[name] = struct{}{}
   542  			}
   543  		}
   544  		assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once")
   545  
   546  		threshold = 0 // only use the peers at the highest ledger height (same as using the PrioritiesByHeight selector)
   547  		acceptablePeers = []string{"p5", "p9", "p11", "p15"}
   548  		used = make(map[string]struct{})
   549  		endorsers, err := mychannel.Endorsers(ccCall("mycc3"), &ledgerHeightFilter{threshold: threshold})
   550  		assert.NoError(t, err)
   551  		names := getNames(endorsers)
   552  		assert.Subset(t, acceptablePeers, names)
   553  		for _, name := range names {
   554  			used[name] = struct{}{}
   555  		}
   556  		t.Logf("Used peers: %#v\n", used)
   557  		assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once")
   558  	})
   559  }
   560  
   561  func TestUnableToSign(t *testing.T) {
   562  	signer := func(msg []byte) ([]byte, error) {
   563  		return nil, errors.New("not enough entropy")
   564  	}
   565  	failToConnect := func() (*grpc.ClientConn, error) {
   566  		return nil, nil
   567  	}
   568  	authInfo := &discovery.AuthInfo{
   569  		ClientIdentity: []byte{1, 2, 3},
   570  	}
   571  	cl := NewClient(failToConnect, signer, signerCacheSize)
   572  	req := NewRequest()
   573  	req = req.OfChannel("mychannel")
   574  	resp, err := cl.Send(ctx, req, authInfo)
   575  	assert.Nil(t, resp)
   576  	assert.Contains(t, err.Error(), "not enough entropy")
   577  }
   578  
   579  func TestUnableToConnect(t *testing.T) {
   580  	signer := func(msg []byte) ([]byte, error) {
   581  		return msg, nil
   582  	}
   583  	failToConnect := func() (*grpc.ClientConn, error) {
   584  		return nil, errors.New("unable to connect")
   585  	}
   586  	auth := &discovery.AuthInfo{
   587  		ClientIdentity: []byte{1, 2, 3},
   588  	}
   589  	cl := NewClient(failToConnect, signer, signerCacheSize)
   590  	req := NewRequest()
   591  	req = req.OfChannel("mychannel")
   592  	resp, err := cl.Send(ctx, req, auth)
   593  	assert.Nil(t, resp)
   594  	assert.Contains(t, err.Error(), "unable to connect")
   595  }
   596  
   597  func TestBadResponses(t *testing.T) {
   598  	signer := func(msg []byte) ([]byte, error) {
   599  		return msg, nil
   600  	}
   601  	svc := newMockDiscoveryService()
   602  	t.Logf("Started mock discovery service on port %d", svc.port)
   603  	defer svc.shutdown()
   604  
   605  	connect := func() (*grpc.ClientConn, error) {
   606  		return grpc.Dial(fmt.Sprintf("localhost:%d", svc.port), grpc.WithInsecure())
   607  	}
   608  
   609  	auth := &discovery.AuthInfo{
   610  		ClientIdentity: []byte{1, 2, 3},
   611  	}
   612  	cl := NewClient(connect, signer, signerCacheSize)
   613  
   614  	// Scenario I: discovery service sends back an error
   615  	svc.On("Discover").Return(nil, errors.New("foo")).Once()
   616  	req := NewRequest()
   617  	req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc"))
   618  	r, err := cl.Send(ctx, req, auth)
   619  	assert.Contains(t, err.Error(), "foo")
   620  	assert.Nil(t, r)
   621  
   622  	// Scenario II: discovery service sends back an empty response
   623  	svc.On("Discover").Return(&discovery.Response{}, nil).Once()
   624  	req = NewRequest()
   625  	req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc"))
   626  	r, err = cl.Send(ctx, req, auth)
   627  	assert.Equal(t, "Sent 3 queries but received 0 responses back", err.Error())
   628  	assert.Nil(t, r)
   629  
   630  	// Scenario III: discovery service sends back a layout for the wrong chaincode
   631  	svc.On("Discover").Return(&discovery.Response{
   632  		Results: []*discovery.QueryResult{
   633  			{
   634  				Result: &discovery.QueryResult_CcQueryRes{
   635  					CcQueryRes: &discovery.ChaincodeQueryResult{
   636  						Content: []*discovery.EndorsementDescriptor{
   637  							{
   638  								Chaincode: "notmycc",
   639  							},
   640  						},
   641  					},
   642  				},
   643  			},
   644  		},
   645  	}, nil).Once()
   646  	req = NewRequest()
   647  	req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
   648  	r, err = cl.Send(ctx, req, auth)
   649  	assert.Nil(t, r)
   650  	assert.Contains(t, err.Error(), "expected chaincode mycc but got endorsement descriptor for notmycc")
   651  
   652  	// Scenario IV: discovery service sends back a layout that has empty envelopes
   653  	svc.On("Discover").Return(&discovery.Response{
   654  		Results: []*discovery.QueryResult{
   655  			{
   656  				Result: resultsWithoutEnvelopes,
   657  			},
   658  		},
   659  	}, nil).Once()
   660  	req = NewRequest()
   661  	req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
   662  	r, err = cl.Send(ctx, req, auth)
   663  	assert.Contains(t, err.Error(), "received empty envelope(s) for endorsers for chaincode mycc")
   664  	assert.Nil(t, r)
   665  
   666  	// Scenario V: discovery service sends back a layout that has a group that requires more
   667  	// members than are present.
   668  	svc.On("Discover").Return(&discovery.Response{
   669  		Results: []*discovery.QueryResult{
   670  			{
   671  				Result: resultsWithEnvelopesButWithInsufficientPeers,
   672  			},
   673  		},
   674  	}, nil).Once()
   675  	req = NewRequest()
   676  	req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
   677  	r, err = cl.Send(ctx, req, auth)
   678  	assert.NoError(t, err)
   679  	mychannel := r.ForChannel("mychannel")
   680  	endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter)
   681  	assert.Nil(t, endorsers)
   682  	assert.Contains(t, err.Error(), "no endorsement combination can be satisfied")
   683  
   684  	// Scenario VI: discovery service sends back a layout that has a group that doesn't have a matching peer set
   685  	svc.On("Discover").Return(&discovery.Response{
   686  		Results: []*discovery.QueryResult{
   687  			{
   688  				Result: resultsWithEnvelopesButWithMismatchedLayout,
   689  			},
   690  		},
   691  	}, nil).Once()
   692  	req = NewRequest()
   693  	req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
   694  	r, err = cl.Send(ctx, req, auth)
   695  	assert.Contains(t, err.Error(), "group B isn't mapped to endorsers, but exists in a layout")
   696  	assert.Empty(t, r)
   697  }
   698  
   699  func TestAddEndorsersQueryInvalidInput(t *testing.T) {
   700  	_, err := NewRequest().AddEndorsersQuery()
   701  	assert.Contains(t, err.Error(), "no chaincode interests given")
   702  
   703  	_, err = NewRequest().AddEndorsersQuery(nil)
   704  	assert.Contains(t, err.Error(), "chaincode interest is nil")
   705  
   706  	_, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{})
   707  	assert.Contains(t, err.Error(), "invocation chain should not be empty")
   708  
   709  	_, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{
   710  		Chaincodes: []*discovery.ChaincodeCall{{}},
   711  	})
   712  	assert.Contains(t, err.Error(), "chaincode name should not be empty")
   713  }
   714  
   715  func TestValidateAliveMessage(t *testing.T) {
   716  	am := aliveMessage(1)
   717  	msg, _ := protoext.EnvelopeToGossipMessage(am)
   718  
   719  	// Scenario I: Valid alive message
   720  	assert.NoError(t, validateAliveMessage(msg))
   721  
   722  	// Scenario II: Nullify timestamp
   723  	msg.GetAliveMsg().Timestamp = nil
   724  	err := validateAliveMessage(msg)
   725  	assert.Equal(t, "timestamp is nil", err.Error())
   726  
   727  	// Scenario III: Nullify membership
   728  	msg.GetAliveMsg().Membership = nil
   729  	err = validateAliveMessage(msg)
   730  	assert.Equal(t, "membership is empty", err.Error())
   731  
   732  	// Scenario IV: Nullify the entire alive message part
   733  	msg.Content = nil
   734  	err = validateAliveMessage(msg)
   735  	assert.Equal(t, "message isn't an alive message", err.Error())
   736  }
   737  
   738  func TestValidateStateInfoMessage(t *testing.T) {
   739  	si := stateInfoWithHeight(100)
   740  
   741  	// Scenario I: Valid state info message
   742  	assert.NoError(t, validateStateInfoMessage(si))
   743  
   744  	// Scenario II: Nullify properties
   745  	si.GetStateInfo().Properties = nil
   746  	err := validateStateInfoMessage(si)
   747  	assert.Equal(t, "properties is nil", err.Error())
   748  
   749  	// Scenario III: Nullify timestamp
   750  	si.GetStateInfo().Timestamp = nil
   751  	err = validateStateInfoMessage(si)
   752  	assert.Equal(t, "timestamp is nil", err.Error())
   753  
   754  	// Scenario IV: Nullify the state info message part
   755  	si.Content = nil
   756  	err = validateStateInfoMessage(si)
   757  	assert.Equal(t, "message isn't a stateInfo message", err.Error())
   758  }
   759  
   760  func TestString(t *testing.T) {
   761  	var ic InvocationChain
   762  	ic = append(ic, &discovery.ChaincodeCall{
   763  		Name:            "foo",
   764  		CollectionNames: []string{"c1", "c2"},
   765  	})
   766  	ic = append(ic, &discovery.ChaincodeCall{
   767  		Name:            "bar",
   768  		CollectionNames: []string{"c3", "c4"},
   769  	})
   770  	expected := `[{"name":"foo","collection_names":["c1","c2"]},{"name":"bar","collection_names":["c3","c4"]}]`
   771  	assert.Equal(t, expected, ic.String())
   772  }
   773  
   774  func getMSP(peer *Peer) string {
   775  	endpoint := peer.AliveMessage.GetAliveMsg().Membership.Endpoint
   776  	id, _ := strconv.ParseInt(endpoint[1:], 10, 64)
   777  	switch id / 2 {
   778  	case 0, 4:
   779  		return "A"
   780  	case 1, 5:
   781  		return "B"
   782  	case 2, 6:
   783  		return "C"
   784  	default:
   785  		return "D"
   786  	}
   787  }
   788  
   789  func getMSPs(endorsers []*Peer) map[string]struct{} {
   790  	m := make(map[string]struct{})
   791  	for _, endorser := range endorsers {
   792  		m[getMSP(endorser)] = struct{}{}
   793  	}
   794  	return m
   795  }
   796  
   797  type ccMetadataFetcher struct {
   798  	mock.Mock
   799  }
   800  
   801  func (mdf *ccMetadataFetcher) Metadata(channel string, cc string, _ ...string) *chaincode.Metadata {
   802  	return mdf.Called(cc).Get(0).(*chaincode.Metadata)
   803  }
   804  
   805  type principalEvaluator struct {
   806  }
   807  
   808  func (pe *principalEvaluator) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error {
   809  	sID := &msp.SerializedIdentity{}
   810  	proto.Unmarshal(identity, sID)
   811  	p := &msp.MSPRole{}
   812  	proto.Unmarshal(principal.Principal, p)
   813  	if sID.Mspid == p.MspIdentifier {
   814  		return nil
   815  	}
   816  	return errors.Errorf("peer %s has MSP %s but should have MSP %s", string(sID.IdBytes), sID.Mspid, p.MspIdentifier)
   817  }
   818  
   819  type policyFetcher struct {
   820  	mock.Mock
   821  }
   822  
   823  func (pf *policyFetcher) PoliciesByChaincode(channel string, cc string, collections ...string) []policies.InquireablePolicy {
   824  	return []policies.InquireablePolicy{pf.Called(cc).Get(0).(policies.InquireablePolicy)}
   825  }
   826  
   827  type endorsementAnalyzer interface {
   828  	PeersForEndorsement(chainID gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error)
   829  
   830  	PeersAuthorizedByCriteria(chainID gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (gdisc.Members, error)
   831  }
   832  
   833  type inquireablePolicy struct {
   834  	principals      []*msp.MSPPrincipal
   835  	orgCombinations [][]string
   836  }
   837  
   838  func (ip *inquireablePolicy) appendPrincipal(orgName string) {
   839  	ip.principals = append(ip.principals, &msp.MSPPrincipal{
   840  		PrincipalClassification: msp.MSPPrincipal_ROLE,
   841  		Principal:               protoutil.MarshalOrPanic(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: orgName})})
   842  }
   843  
   844  func (ip *inquireablePolicy) SatisfiedBy() []policies.PrincipalSet {
   845  	var res []policies.PrincipalSet
   846  	for _, orgs := range ip.orgCombinations {
   847  		for _, org := range orgs {
   848  			ip.appendPrincipal(org)
   849  		}
   850  		res = append(res, ip.principals)
   851  		ip.principals = nil
   852  	}
   853  	return res
   854  }
   855  
   856  func peerIdentity(mspID string, i int) api.PeerIdentityInfo {
   857  	p := []byte(fmt.Sprintf("p%d", i))
   858  	sID := &msp.SerializedIdentity{
   859  		Mspid:   mspID,
   860  		IdBytes: p,
   861  	}
   862  	b, _ := proto.Marshal(sID)
   863  	return api.PeerIdentityInfo{
   864  		Identity:     api.PeerIdentityType(b),
   865  		PKIId:        gossipcommon.PKIidType(p),
   866  		Organization: api.OrgIdentityType(mspID),
   867  	}
   868  }
   869  
   870  type peerInfo struct {
   871  	identity api.PeerIdentityType
   872  	pkiID    gossipcommon.PKIidType
   873  	gdisc.NetworkMember
   874  }
   875  
   876  func aliveMessage(id int) *gossip.Envelope {
   877  	g := &gossip.GossipMessage{
   878  		Content: &gossip.GossipMessage_AliveMsg{
   879  			AliveMsg: &gossip.AliveMessage{
   880  				Timestamp: &gossip.PeerTime{
   881  					SeqNum: uint64(id),
   882  					IncNum: uint64(time.Now().UnixNano()),
   883  				},
   884  				Membership: &gossip.Member{
   885  					Endpoint: fmt.Sprintf("p%d", id),
   886  				},
   887  			},
   888  		},
   889  	}
   890  	sMsg, _ := protoext.NoopSign(g)
   891  	return sMsg.Envelope
   892  }
   893  
   894  func stateInfoMessage(chaincodes ...*gossip.Chaincode) *gossip.Envelope {
   895  	return stateInfoMessageWithHeight(0, chaincodes...)
   896  }
   897  
   898  func stateInfoMessageWithHeight(ledgerHeight uint64, chaincodes ...*gossip.Chaincode) *gossip.Envelope {
   899  	g := &gossip.GossipMessage{
   900  		Content: &gossip.GossipMessage_StateInfo{
   901  			StateInfo: &gossip.StateInfo{
   902  				Timestamp: &gossip.PeerTime{
   903  					SeqNum: 5,
   904  					IncNum: uint64(time.Now().UnixNano()),
   905  				},
   906  				Properties: &gossip.Properties{
   907  					Chaincodes:   chaincodes,
   908  					LedgerHeight: ledgerHeight,
   909  				},
   910  			},
   911  		},
   912  	}
   913  	sMsg, _ := protoext.NoopSign(g)
   914  	return sMsg.Envelope
   915  }
   916  
   917  func newPeer(i int, env *gossip.Envelope, properties *gossip.Properties) *peerInfo {
   918  	p := fmt.Sprintf("p%d", i)
   919  	return &peerInfo{
   920  		pkiID:    gossipcommon.PKIidType(p),
   921  		identity: api.PeerIdentityType(p),
   922  		NetworkMember: gdisc.NetworkMember{
   923  			PKIid:            gossipcommon.PKIidType(p),
   924  			Endpoint:         p,
   925  			InternalEndpoint: p,
   926  			Envelope:         env,
   927  			Properties:       properties,
   928  		},
   929  	}
   930  }
   931  
   932  type mockSupport struct {
   933  	seq uint64
   934  	mock.Mock
   935  	endorsementAnalyzer
   936  }
   937  
   938  func (ms *mockSupport) ConfigSequence(channel string) uint64 {
   939  	// Ensure cache is bypassed
   940  	ms.seq++
   941  	return ms.seq
   942  }
   943  
   944  func (ms *mockSupport) IdentityInfo() api.PeerIdentitySet {
   945  	return ms.Called().Get(0).(api.PeerIdentitySet)
   946  }
   947  
   948  func (*mockSupport) ChannelExists(channel string) bool {
   949  	return true
   950  }
   951  
   952  func (ms *mockSupport) PeersOfChannel(gossipcommon.ChannelID) gdisc.Members {
   953  	return ms.Called().Get(0).(gdisc.Members)
   954  }
   955  
   956  func (ms *mockSupport) Peers() gdisc.Members {
   957  	return ms.Called().Get(0).(gdisc.Members)
   958  }
   959  
   960  func (ms *mockSupport) PeersForEndorsement(channel gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
   961  	return ms.endorsementAnalyzer.PeersForEndorsement(channel, interest)
   962  }
   963  
   964  func (ms *mockSupport) PeersAuthorizedByCriteria(channel gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (gdisc.Members, error) {
   965  	return ms.endorsementAnalyzer.PeersAuthorizedByCriteria(channel, interest)
   966  }
   967  
   968  func (*mockSupport) EligibleForService(channel string, data protoutil.SignedData) error {
   969  	return nil
   970  }
   971  
   972  func (ms *mockSupport) Config(channel string) (*discovery.ConfigResult, error) {
   973  	return ms.Called(channel).Get(0).(*discovery.ConfigResult), nil
   974  }
   975  
   976  type mockDiscoveryServer struct {
   977  	mock.Mock
   978  	*grpc.Server
   979  	port int64
   980  }
   981  
   982  func newMockDiscoveryService() *mockDiscoveryServer {
   983  	l, err := net.Listen("tcp", "localhost:0")
   984  	if err != nil {
   985  		panic(err)
   986  	}
   987  	s := grpc.NewServer()
   988  	d := &mockDiscoveryServer{
   989  		Server: s,
   990  	}
   991  	discovery.RegisterDiscoveryServer(s, d)
   992  	go s.Serve(l)
   993  	_, portStr, _ := net.SplitHostPort(l.Addr().String())
   994  	d.port, _ = strconv.ParseInt(portStr, 10, 64)
   995  	return d
   996  }
   997  
   998  func (ds *mockDiscoveryServer) shutdown() {
   999  	ds.Server.Stop()
  1000  }
  1001  
  1002  func (ds *mockDiscoveryServer) Discover(context.Context, *discovery.SignedRequest) (*discovery.Response, error) {
  1003  	args := ds.Called()
  1004  	if args.Get(0) == nil {
  1005  		return nil, args.Error(1)
  1006  	}
  1007  	return args.Get(0).(*discovery.Response), nil
  1008  }
  1009  
  1010  func ccCall(ccNames ...string) []*discovery.ChaincodeCall {
  1011  	var call []*discovery.ChaincodeCall
  1012  	for _, ccName := range ccNames {
  1013  		call = append(call, &discovery.ChaincodeCall{
  1014  			Name: ccName,
  1015  		})
  1016  	}
  1017  	return call
  1018  }
  1019  
  1020  func cc2ccInterests(invocationsChains ...[]*discovery.ChaincodeCall) []*discovery.ChaincodeInterest {
  1021  	var interests []*discovery.ChaincodeInterest
  1022  	for _, invocationChain := range invocationsChains {
  1023  		interests = append(interests, &discovery.ChaincodeInterest{
  1024  			Chaincodes: invocationChain,
  1025  		})
  1026  	}
  1027  	return interests
  1028  }
  1029  
  1030  func interest(ccNames ...string) *discovery.ChaincodeInterest {
  1031  	interest := &discovery.ChaincodeInterest{
  1032  		Chaincodes: []*discovery.ChaincodeCall{},
  1033  	}
  1034  	for _, cc := range ccNames {
  1035  		interest.Chaincodes = append(interest.Chaincodes, &discovery.ChaincodeCall{
  1036  			Name: cc,
  1037  		})
  1038  	}
  1039  	return interest
  1040  }
  1041  
  1042  func buildCollectionConfig(col2principals map[string][]*msp.MSPPrincipal) *peer.CollectionConfigPackage {
  1043  	collections := &peer.CollectionConfigPackage{}
  1044  	for col, principals := range col2principals {
  1045  		collections.Config = append(collections.Config, &peer.CollectionConfig{
  1046  			Payload: &peer.CollectionConfig_StaticCollectionConfig{
  1047  				StaticCollectionConfig: &peer.StaticCollectionConfig{
  1048  					Name: col,
  1049  					MemberOrgsPolicy: &peer.CollectionPolicyConfig{
  1050  						Payload: &peer.CollectionPolicyConfig_SignaturePolicy{
  1051  							SignaturePolicy: &common.SignaturePolicyEnvelope{
  1052  								Identities: principals,
  1053  							},
  1054  						},
  1055  					},
  1056  				},
  1057  			},
  1058  		})
  1059  	}
  1060  	return collections
  1061  }
  1062  
  1063  func memberPrincipal(mspID string) *msp.MSPPrincipal {
  1064  	return &msp.MSPPrincipal{
  1065  		PrincipalClassification: msp.MSPPrincipal_ROLE,
  1066  		Principal: protoutil.MarshalOrPanic(&msp.MSPRole{
  1067  			MspIdentifier: mspID,
  1068  			Role:          msp.MSPRole_MEMBER,
  1069  		}),
  1070  	}
  1071  }
  1072  
  1073  // ledgerHeightFilter is a filter that uses ledger height to prioritize endorsers, although it provides more
  1074  // even balancing than simply prioritizing by highest ledger height. Certain peers tend to always be at a slightly
  1075  // higher ledger height than others (such as leaders) but we shouldn't always be selecting leaders.
  1076  // This filter treats endorsers that are within a certain block height threshold equally and sorts them randomly.
  1077  type ledgerHeightFilter struct {
  1078  	threshold uint64
  1079  }
  1080  
  1081  // Filter returns a random set of endorsers that are above the configured ledger height threshold.
  1082  func (f *ledgerHeightFilter) Filter(endorsers Endorsers) Endorsers {
  1083  	if len(endorsers) <= 1 {
  1084  		return endorsers
  1085  	}
  1086  
  1087  	if f.threshold < 0 {
  1088  		return endorsers.Shuffle()
  1089  	}
  1090  
  1091  	maxHeight := getMaxLedgerHeight(endorsers)
  1092  
  1093  	if maxHeight <= f.threshold {
  1094  		return endorsers.Shuffle()
  1095  	}
  1096  
  1097  	cutoffHeight := maxHeight - f.threshold
  1098  
  1099  	var filteredEndorsers Endorsers
  1100  	for _, p := range endorsers {
  1101  		ledgerHeight := getLedgerHeight(p)
  1102  		if ledgerHeight >= cutoffHeight {
  1103  			filteredEndorsers = append(filteredEndorsers, p)
  1104  		}
  1105  	}
  1106  	return filteredEndorsers.Shuffle()
  1107  }
  1108  
  1109  func getLedgerHeight(endorser *Peer) uint64 {
  1110  	return endorser.StateInfoMessage.GetStateInfo().GetProperties().LedgerHeight
  1111  }
  1112  
  1113  func getMaxLedgerHeight(endorsers Endorsers) uint64 {
  1114  	var maxHeight uint64
  1115  	for _, peer := range endorsers {
  1116  		height := getLedgerHeight(peer)
  1117  		if height > maxHeight {
  1118  			maxHeight = height
  1119  		}
  1120  	}
  1121  	return maxHeight
  1122  }
  1123  
  1124  func getNames(endorsers Endorsers) []string {
  1125  	var names []string
  1126  	for _, p := range endorsers {
  1127  		names = append(names, p.AliveMessage.GetAliveMsg().Membership.Endpoint)
  1128  	}
  1129  	return names
  1130  }