github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/discovery/client/client_test.go (about)

     1  /*
     2  Copyright hechain. 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/hechain20/hechain/common/chaincode"
    23  	"github.com/hechain20/hechain/common/policies"
    24  	"github.com/hechain20/hechain/common/policydsl"
    25  	"github.com/hechain20/hechain/common/util"
    26  	fabricdisc "github.com/hechain20/hechain/discovery"
    27  	"github.com/hechain20/hechain/discovery/endorsement"
    28  	"github.com/hechain20/hechain/gossip/api"
    29  	gossipcommon "github.com/hechain20/hechain/gossip/common"
    30  	gdisc "github.com/hechain20/hechain/gossip/discovery"
    31  	"github.com/hechain20/hechain/gossip/protoext"
    32  	"github.com/hechain20/hechain/internal/pkg/comm"
    33  	"github.com/hechain20/hechain/protoutil"
    34  	"github.com/hyperledger/fabric-protos-go/common"
    35  	"github.com/hyperledger/fabric-protos-go/discovery"
    36  	"github.com/hyperledger/fabric-protos-go/gossip"
    37  	"github.com/hyperledger/fabric-protos-go/msp"
    38  	"github.com/hyperledger/fabric-protos-go/peer"
    39  	"github.com/pkg/errors"
    40  	"github.com/stretchr/testify/mock"
    41  	"github.com/stretchr/testify/require"
    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  	require.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  		require.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  	require.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  	require.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  		require.Equal(t, ErrNotFound, err)
   404  		require.Nil(t, peers)
   405  
   406  		endorsers, err := fakeChannel.Endorsers(ccCall("mycc"), NoFilter)
   407  		require.Equal(t, ErrNotFound, err)
   408  		require.Nil(t, endorsers)
   409  
   410  		conf, err := fakeChannel.Config()
   411  		require.Equal(t, ErrNotFound, err)
   412  		require.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  		require.NoError(t, err)
   420  		require.Equal(t, expectedConf.Msps, conf.Msps)
   421  		require.Equal(t, expectedConf.Orderers, conf.Orderers)
   422  		peers, err := mychannel.Peers()
   423  		require.NoError(t, err)
   424  		// We should see all peers as provided above
   425  		require.Len(t, peers, 8)
   426  		// Check response for peers when doing a local query
   427  		peers, err = r.ForLocal().Peers()
   428  		require.NoError(t, err)
   429  		require.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  		require.Contains(t, err.Error(), "failed constructing descriptor for chaincode")
   439  		require.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  		require.NoError(t, err)
   449  
   450  		mychannel := r.ForChannel("mychannel")
   451  		peers, err := mychannel.Peers()
   452  		require.NoError(t, err)
   453  		require.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  		require.NoError(t, err)
   458  		// The combinations of endorsers should be in the expected combinations
   459  		require.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  		require.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  		require.NoError(t, err)
   476  		require.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  		require.NoError(t, err)
   482  		require.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  		require.NoError(t, err)
   492  		mychannel := r.ForChannel("mychannel")
   493  		peers, err := mychannel.Peers(interest...)
   494  		require.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  			require.NotEqual(t, "A", p.MSPID)
   498  		}
   499  		require.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  		require.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  		require.NoError(t, err)
   515  		names := getNames(endorsers)
   516  		require.Subset(t, acceptablePeers, names)
   517  		for _, name := range names {
   518  			used[name] = struct{}{}
   519  		}
   520  		require.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  		require.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  			require.NoError(t, err)
   538  			names := getNames(endorsers)
   539  			require.Subset(t, acceptablePeers, names)
   540  			for _, name := range names {
   541  				used[name] = struct{}{}
   542  			}
   543  		}
   544  		require.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  		require.NoError(t, err)
   551  		names := getNames(endorsers)
   552  		require.Subset(t, acceptablePeers, names)
   553  		for _, name := range names {
   554  			used[name] = struct{}{}
   555  		}
   556  		t.Logf("Used peers: %#v\n", used)
   557  		require.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  	require.Nil(t, resp)
   576  	require.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  	require.Nil(t, resp)
   594  	require.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  	require.Contains(t, err.Error(), "foo")
   620  	require.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  	require.Equal(t, "Sent 3 queries but received 0 responses back", err.Error())
   628  	require.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  	require.Nil(t, r)
   650  	require.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  	require.Contains(t, err.Error(), "received empty envelope(s) for endorsers for chaincode mycc")
   664  	require.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  	require.NoError(t, err)
   679  	mychannel := r.ForChannel("mychannel")
   680  	endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter)
   681  	require.Nil(t, endorsers)
   682  	require.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  	require.Contains(t, err.Error(), "group B isn't mapped to endorsers, but exists in a layout")
   696  	require.Empty(t, r)
   697  }
   698  
   699  func TestAddEndorsersQueryInvalidInput(t *testing.T) {
   700  	_, err := NewRequest().AddEndorsersQuery()
   701  	require.Contains(t, err.Error(), "no chaincode interests given")
   702  
   703  	_, err = NewRequest().AddEndorsersQuery(nil)
   704  	require.Contains(t, err.Error(), "chaincode interest is nil")
   705  
   706  	_, err = NewRequest().AddEndorsersQuery(&peer.ChaincodeInterest{})
   707  	require.Contains(t, err.Error(), "invocation chain should not be empty")
   708  
   709  	_, err = NewRequest().AddEndorsersQuery(&peer.ChaincodeInterest{
   710  		Chaincodes: []*peer.ChaincodeCall{{}},
   711  	})
   712  	require.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  	require.NoError(t, validateAliveMessage(msg))
   721  
   722  	// Scenario II: Nullify timestamp
   723  	msg.GetAliveMsg().Timestamp = nil
   724  	err := validateAliveMessage(msg)
   725  	require.Equal(t, "timestamp is nil", err.Error())
   726  
   727  	// Scenario III: Nullify membership
   728  	msg.GetAliveMsg().Membership = nil
   729  	err = validateAliveMessage(msg)
   730  	require.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  	require.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  	require.NoError(t, validateStateInfoMessage(si))
   743  
   744  	// Scenario II: Nullify properties
   745  	si.GetStateInfo().Properties = nil
   746  	err := validateStateInfoMessage(si)
   747  	require.Equal(t, "properties is nil", err.Error())
   748  
   749  	// Scenario III: Nullify timestamp
   750  	si.GetStateInfo().Timestamp = nil
   751  	err = validateStateInfoMessage(si)
   752  	require.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  	require.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, &peer.ChaincodeCall{
   763  		Name:            "foo",
   764  		CollectionNames: []string{"c1", "c2"},
   765  	})
   766  	ic = append(ic, &peer.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  	require.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  func (pe *principalEvaluator) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error {
   808  	sID := &msp.SerializedIdentity{}
   809  	proto.Unmarshal(identity, sID)
   810  	p := &msp.MSPRole{}
   811  	proto.Unmarshal(principal.Principal, p)
   812  	if sID.Mspid == p.MspIdentifier {
   813  		return nil
   814  	}
   815  	return errors.Errorf("peer %s has MSP %s but should have MSP %s", string(sID.IdBytes), sID.Mspid, p.MspIdentifier)
   816  }
   817  
   818  type policyFetcher struct {
   819  	mock.Mock
   820  }
   821  
   822  func (pf *policyFetcher) PoliciesByChaincode(channel string, cc string, collections ...string) []policies.InquireablePolicy {
   823  	return []policies.InquireablePolicy{pf.Called(cc).Get(0).(policies.InquireablePolicy)}
   824  }
   825  
   826  type endorsementAnalyzer interface {
   827  	PeersForEndorsement(chainID gossipcommon.ChannelID, interest *peer.ChaincodeInterest) (*discovery.EndorsementDescriptor, error)
   828  
   829  	PeersAuthorizedByCriteria(chainID gossipcommon.ChannelID, interest *peer.ChaincodeInterest) (gdisc.Members, error)
   830  }
   831  
   832  type inquireablePolicy struct {
   833  	principals      []*msp.MSPPrincipal
   834  	orgCombinations [][]string
   835  }
   836  
   837  func (ip *inquireablePolicy) appendPrincipal(orgName string) {
   838  	ip.principals = append(ip.principals, &msp.MSPPrincipal{
   839  		PrincipalClassification: msp.MSPPrincipal_ROLE,
   840  		Principal:               protoutil.MarshalOrPanic(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: orgName}),
   841  	})
   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 *peer.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
   961  	return ms.endorsementAnalyzer.PeersForEndorsement(channel, interest)
   962  }
   963  
   964  func (ms *mockSupport) PeersAuthorizedByCriteria(channel gossipcommon.ChannelID, interest *peer.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) []*peer.ChaincodeCall {
  1011  	var call []*peer.ChaincodeCall
  1012  	for _, ccName := range ccNames {
  1013  		call = append(call, &peer.ChaincodeCall{
  1014  			Name: ccName,
  1015  		})
  1016  	}
  1017  	return call
  1018  }
  1019  
  1020  func cc2ccInterests(invocationsChains ...[]*peer.ChaincodeCall) []*peer.ChaincodeInterest {
  1021  	var interests []*peer.ChaincodeInterest
  1022  	for _, invocationChain := range invocationsChains {
  1023  		interests = append(interests, &peer.ChaincodeInterest{
  1024  			Chaincodes: invocationChain,
  1025  		})
  1026  	}
  1027  	return interests
  1028  }
  1029  
  1030  func interest(ccNames ...string) *peer.ChaincodeInterest {
  1031  	interest := &peer.ChaincodeInterest{
  1032  		Chaincodes: []*peer.ChaincodeCall{},
  1033  	}
  1034  	for _, cc := range ccNames {
  1035  		interest.Chaincodes = append(interest.Chaincodes, &peer.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  	maxHeight := getMaxLedgerHeight(endorsers)
  1088  
  1089  	if maxHeight <= f.threshold {
  1090  		return endorsers.Shuffle()
  1091  	}
  1092  
  1093  	cutoffHeight := maxHeight - f.threshold
  1094  
  1095  	var filteredEndorsers Endorsers
  1096  	for _, p := range endorsers {
  1097  		ledgerHeight := getLedgerHeight(p)
  1098  		if ledgerHeight >= cutoffHeight {
  1099  			filteredEndorsers = append(filteredEndorsers, p)
  1100  		}
  1101  	}
  1102  	return filteredEndorsers.Shuffle()
  1103  }
  1104  
  1105  func getLedgerHeight(endorser *Peer) uint64 {
  1106  	return endorser.StateInfoMessage.GetStateInfo().GetProperties().LedgerHeight
  1107  }
  1108  
  1109  func getMaxLedgerHeight(endorsers Endorsers) uint64 {
  1110  	var maxHeight uint64
  1111  	for _, peer := range endorsers {
  1112  		height := getLedgerHeight(peer)
  1113  		if height > maxHeight {
  1114  			maxHeight = height
  1115  		}
  1116  	}
  1117  	return maxHeight
  1118  }
  1119  
  1120  func getNames(endorsers Endorsers) []string {
  1121  	var names []string
  1122  	for _, p := range endorsers {
  1123  		names = append(names, p.AliveMessage.GetAliveMsg().Membership.Endpoint)
  1124  	}
  1125  	return names
  1126  }