github.com/defanghe/fabric@v2.1.1+incompatible/discovery/endorsement/endorsement_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package endorsement
     8  
     9  import (
    10  	"fmt"
    11  	"testing"
    12  
    13  	"github.com/golang/protobuf/proto"
    14  	discoveryprotos "github.com/hyperledger/fabric-protos-go/discovery"
    15  	"github.com/hyperledger/fabric-protos-go/gossip"
    16  	"github.com/hyperledger/fabric-protos-go/msp"
    17  	"github.com/hyperledger/fabric-protos-go/peer"
    18  	"github.com/hyperledger/fabric/common/chaincode"
    19  	"github.com/hyperledger/fabric/common/policies"
    20  	"github.com/hyperledger/fabric/common/policies/inquire"
    21  	"github.com/hyperledger/fabric/gossip/api"
    22  	"github.com/hyperledger/fabric/gossip/common"
    23  	"github.com/hyperledger/fabric/gossip/discovery"
    24  	"github.com/hyperledger/fabric/protoutil"
    25  	"github.com/pkg/errors"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/mock"
    28  )
    29  
    30  var pkiID2MSPID = map[string]string{
    31  	"p0":  "Org0MSP",
    32  	"p1":  "Org1MSP",
    33  	"p2":  "Org2MSP",
    34  	"p3":  "Org3MSP",
    35  	"p4":  "Org4MSP",
    36  	"p5":  "Org5MSP",
    37  	"p6":  "Org6MSP",
    38  	"p7":  "Org7MSP",
    39  	"p8":  "Org8MSP",
    40  	"p9":  "Org9MSP",
    41  	"p10": "Org10MSP",
    42  	"p11": "Org11MSP",
    43  	"p12": "Org12MSP",
    44  	"p13": "Org13MSP",
    45  	"p14": "Org14MSP",
    46  	"p15": "Org15MSP",
    47  }
    48  
    49  func TestPeersForEndorsement(t *testing.T) {
    50  	extractPeers := func(desc *discoveryprotos.EndorsementDescriptor) map[string]struct{} {
    51  		res := map[string]struct{}{}
    52  		for _, endorsers := range desc.EndorsersByGroups {
    53  			for _, p := range endorsers.Peers {
    54  				res[string(p.Identity)] = struct{}{}
    55  				assert.Equal(t, string(p.Identity), string(p.MembershipInfo.Payload))
    56  				assert.Equal(t, string(p.Identity), string(p.StateInfo.Payload))
    57  			}
    58  		}
    59  		return res
    60  	}
    61  	cc := "chaincode"
    62  	g := &gossipMock{}
    63  	pf := &policyFetcherMock{}
    64  	ccWithMissingPolicy := "chaincodeWithMissingPolicy"
    65  	channel := common.ChannelID("test")
    66  	alivePeers := peerSet{
    67  		newPeer(0),
    68  		newPeer(2),
    69  		newPeer(4),
    70  		newPeer(6),
    71  		newPeer(8),
    72  		newPeer(10),
    73  		newPeer(11),
    74  		newPeer(12),
    75  	}
    76  
    77  	identities := identitySet(pkiID2MSPID)
    78  
    79  	chanPeers := peerSet{
    80  		newPeer(0).withChaincode(cc, "1.0"),
    81  		newPeer(3).withChaincode(cc, "1.0"),
    82  		newPeer(6).withChaincode(cc, "1.0"),
    83  		newPeer(9).withChaincode(cc, "1.0"),
    84  		newPeer(11).withChaincode(cc, "1.0"),
    85  		newPeer(12).withChaincode(cc, "1.0"),
    86  	}
    87  	g.On("Peers").Return(alivePeers.toMembers())
    88  	g.On("IdentityInfo").Return(identities)
    89  
    90  	// Scenario I: Policy isn't found
    91  	t.Run("PolicyNotFound", func(t *testing.T) {
    92  		pf.On("PoliciesByChaincode", ccWithMissingPolicy).Return(nil).Once()
    93  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
    94  		mf := &metadataFetcher{}
    95  		mf.On("Metadata").Return(&chaincode.Metadata{
    96  			Name:    cc,
    97  			Version: "1.0",
    98  		}).Once()
    99  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   100  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   101  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   102  				{
   103  					Name: ccWithMissingPolicy,
   104  				},
   105  			},
   106  		})
   107  		assert.Nil(t, desc)
   108  		assert.Equal(t, "policy not found", err.Error())
   109  	})
   110  
   111  	t.Run("NotEnoughPeers", func(t *testing.T) {
   112  		// Scenario II: Policy is found but not enough peers to satisfy the policy.
   113  		// The policy requires a signature from:
   114  		// p1 and p6, or
   115  		// p11 x2 (twice), but we only have a single peer in the alive view for p11
   116  		pb := principalBuilder{}
   117  		policy := pb.newSet().addPrincipal(peerRole("p1")).addPrincipal(peerRole("p6")).
   118  			newSet().addPrincipal(peerRole("p11")).addPrincipal(peerRole("p11")).buildPolicy()
   119  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   120  		mf := &metadataFetcher{}
   121  		mf.On("Metadata").Return(&chaincode.Metadata{Name: cc, Version: "1.0"}).Once()
   122  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   123  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   124  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   125  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   126  				{
   127  					Name: cc,
   128  				},
   129  			},
   130  		})
   131  		assert.Nil(t, desc)
   132  		assert.Equal(t, err.Error(), "cannot satisfy any principal combination")
   133  	})
   134  
   135  	t.Run("DisjointViews", func(t *testing.T) {
   136  		pb := principalBuilder{}
   137  		// Scenario III: Policy is found and there are enough peers to satisfy
   138  		// only 1 type of principal combination: p0 and p6.
   139  		// However, the combination of a signature from p10 and p12
   140  		// cannot be satisfied because p10 is not in the channel view but only in the alive view
   141  		policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
   142  			newSet().addPrincipal(peerRole("p10")).addPrincipal(peerRole("p12")).buildPolicy()
   143  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   144  		mf := &metadataFetcher{}
   145  		mf.On("Metadata").Return(&chaincode.Metadata{
   146  			Name:    cc,
   147  			Version: "1.0",
   148  		}).Once()
   149  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   150  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   151  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   152  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   153  				{
   154  					Name: cc,
   155  				},
   156  			},
   157  		})
   158  		assert.NoError(t, err)
   159  		assert.NotNil(t, desc)
   160  		assert.Len(t, desc.Layouts, 1)
   161  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
   162  		assert.Equal(t, map[string]struct{}{
   163  			peerIdentityString("p0"): {},
   164  			peerIdentityString("p6"): {},
   165  		}, extractPeers(desc))
   166  	})
   167  
   168  	t.Run("MultipleCombinations", func(t *testing.T) {
   169  		// Scenario IV: Policy is found and there are enough peers to satisfy
   170  		// 2 principal combinations:
   171  		// p0 and p6, or
   172  		// p12 alone
   173  		pb := principalBuilder{}
   174  		policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
   175  			newSet().addPrincipal(peerRole("p12")).buildPolicy()
   176  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   177  		mf := &metadataFetcher{}
   178  		mf.On("Metadata").Return(&chaincode.Metadata{
   179  			Name:    cc,
   180  			Version: "1.0",
   181  		}).Once()
   182  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   183  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   184  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   185  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   186  				{
   187  					Name: cc,
   188  				},
   189  			},
   190  		})
   191  		assert.NoError(t, err)
   192  		assert.NotNil(t, desc)
   193  		assert.Len(t, desc.Layouts, 2)
   194  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
   195  		assert.Len(t, desc.Layouts[1].QuantitiesByGroup, 1)
   196  		assert.Equal(t, map[string]struct{}{
   197  			peerIdentityString("p0"):  {},
   198  			peerIdentityString("p6"):  {},
   199  			peerIdentityString("p12"): {},
   200  		}, extractPeers(desc))
   201  	})
   202  
   203  	t.Run("WrongVersionInstalled", func(t *testing.T) {
   204  		// Scenario V: Policy is found, and there are enough peers to satisfy policy combinations,
   205  		// but all peers have the wrong version installed on them.
   206  		mf := &metadataFetcher{}
   207  		mf.On("Metadata").Return(&chaincode.Metadata{
   208  			Name:    cc,
   209  			Version: "1.1",
   210  		}).Once()
   211  		pb := principalBuilder{}
   212  		policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
   213  			newSet().addPrincipal(peerRole("p12")).buildPolicy()
   214  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   215  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   216  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   217  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   218  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   219  				{
   220  					Name: cc,
   221  				},
   222  			},
   223  		})
   224  		assert.Nil(t, desc)
   225  		assert.Equal(t, "cannot satisfy any principal combination", err.Error())
   226  
   227  		// Scenario VI: Policy is found, there are enough peers to satisfy policy combinations,
   228  		// but some peers have the wrong chaincode version, and some don't even have it installed.
   229  		chanPeers := peerSet{
   230  			newPeer(0).withChaincode(cc, "0.6"),
   231  			newPeer(3).withChaincode(cc, "1.0"),
   232  			newPeer(6).withChaincode(cc, "1.0"),
   233  			newPeer(9).withChaincode(cc, "1.0"),
   234  			newPeer(12),
   235  		}
   236  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   237  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   238  		mf.On("Metadata").Return(&chaincode.Metadata{
   239  			Name:    cc,
   240  			Version: "1.0",
   241  		}).Once()
   242  		desc, err = analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   243  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   244  				{
   245  					Name: cc,
   246  				},
   247  			},
   248  		})
   249  		assert.Nil(t, desc)
   250  		assert.Equal(t, "cannot satisfy any principal combination", err.Error())
   251  	})
   252  
   253  	t.Run("NoChaincodeMetadataFromLedger", func(t *testing.T) {
   254  		// Scenario VII: Policy is found, there are enough peers to satisfy the policy,
   255  		// but the chaincode metadata cannot be fetched from the ledger.
   256  		pb := principalBuilder{}
   257  		policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
   258  			newSet().addPrincipal(peerRole("p12")).buildPolicy()
   259  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   260  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   261  		mf := &metadataFetcher{}
   262  		mf.On("Metadata").Return(nil).Once()
   263  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   264  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   265  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   266  				{
   267  					Name: cc,
   268  				},
   269  			},
   270  		})
   271  		assert.Nil(t, desc)
   272  		assert.Equal(t, "No metadata was found for chaincode chaincode in channel test", err.Error())
   273  	})
   274  
   275  	t.Run("Collections", func(t *testing.T) {
   276  		// Scenario VIII: Policy is found and there are enough peers to satisfy
   277  		// 2 principal combinations: p0 and p6, or p12 alone.
   278  		// However, the query contains a collection which has a policy that permits only p0 and p12,
   279  		// and thus - the combination of p0 and p6 is filtered out and we're left with p12 only.
   280  		collectionOrgs := []*msp.MSPPrincipal{
   281  			peerRole("p0"),
   282  			peerRole("p12"),
   283  		}
   284  		col2principals := map[string][]*msp.MSPPrincipal{
   285  			"collection": collectionOrgs,
   286  		}
   287  		mf := &metadataFetcher{}
   288  		mf.On("Metadata").Return(&chaincode.Metadata{
   289  			Name:              cc,
   290  			Version:           "1.0",
   291  			CollectionsConfig: buildCollectionConfig(col2principals),
   292  		}).Once()
   293  		pb := principalBuilder{}
   294  		policy := pb.newSet().addPrincipal(peerRole("p0")).
   295  			addPrincipal(peerRole("p6")).newSet().
   296  			addPrincipal(peerRole("p12")).buildPolicy()
   297  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   298  		pf.On("PoliciesByChaincode", cc).Return(policy).Once()
   299  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   300  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   301  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   302  				{
   303  					Name:            cc,
   304  					CollectionNames: []string{"collection"},
   305  				},
   306  			},
   307  		})
   308  		assert.NoError(t, err)
   309  		assert.NotNil(t, desc)
   310  		assert.Len(t, desc.Layouts, 1)
   311  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 1)
   312  		assert.Equal(t, map[string]struct{}{
   313  			peerIdentityString("p12"): {},
   314  		}, extractPeers(desc))
   315  	})
   316  
   317  	t.Run("Chaincode2Chaincode I", func(t *testing.T) {
   318  		// Scenario IX: A chaincode-to-chaincode query is made.
   319  		// Total organizations are 0, 2, 4, 6, 10, 12
   320  		// and the endorsement policies of the chaincodes are as follows:
   321  		// cc1: OR(AND(0, 2), AND(6, 10))
   322  		// cc2: AND(6, 10, 12)
   323  		// cc3: AND(4, 12)
   324  		// Therefore, the result should be: 4, 6, 10, 12
   325  
   326  		chanPeers := peerSet{}
   327  		for _, id := range []int{0, 2, 4, 6, 10, 12} {
   328  			peer := newPeer(id).withChaincode("cc1", "1.0").withChaincode("cc2", "1.0").withChaincode("cc3", "1.0")
   329  			chanPeers = append(chanPeers, peer)
   330  		}
   331  
   332  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   333  
   334  		mf := &metadataFetcher{}
   335  		mf.On("Metadata").Return(&chaincode.Metadata{
   336  			Name:    "cc1",
   337  			Version: "1.0",
   338  		}).Once()
   339  		mf.On("Metadata").Return(&chaincode.Metadata{
   340  			Name:    "cc2",
   341  			Version: "1.0",
   342  		}).Once()
   343  		mf.On("Metadata").Return(&chaincode.Metadata{
   344  			Name:    "cc3",
   345  			Version: "1.0",
   346  		}).Once()
   347  
   348  		pb := principalBuilder{}
   349  		cc1policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p2")).
   350  			newSet().addPrincipal(peerRole("p6")).addPrincipal(peerRole("p10")).buildPolicy()
   351  
   352  		pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()
   353  
   354  		cc2policy := pb.newSet().addPrincipal(peerRole("p6")).
   355  			addPrincipal(peerRole("p10")).addPrincipal(peerRole("p12")).buildPolicy()
   356  		pf.On("PoliciesByChaincode", "cc2").Return(cc2policy).Once()
   357  
   358  		cc3policy := pb.newSet().addPrincipal(peerRole("p4")).
   359  			addPrincipal(peerRole("p12")).buildPolicy()
   360  		pf.On("PoliciesByChaincode", "cc3").Return(cc3policy).Once()
   361  
   362  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   363  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   364  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   365  				{
   366  					Name: "cc1",
   367  				},
   368  				{
   369  					Name: "cc2",
   370  				},
   371  				{
   372  					Name: "cc3",
   373  				},
   374  			},
   375  		})
   376  		assert.NoError(t, err)
   377  		assert.NotNil(t, desc)
   378  		assert.Len(t, desc.Layouts, 1)
   379  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 4)
   380  		assert.Equal(t, map[string]struct{}{
   381  			peerIdentityString("p4"):  {},
   382  			peerIdentityString("p6"):  {},
   383  			peerIdentityString("p10"): {},
   384  			peerIdentityString("p12"): {},
   385  		}, extractPeers(desc))
   386  	})
   387  
   388  	t.Run("Chaincode2Chaincode II", func(t *testing.T) {
   389  		// Scenario X: A chaincode-to-chaincode query is made.
   390  		// and the endorsement policies of the chaincodes are as follows:
   391  		// cc1: OR(0, 1)
   392  		// cc2: AND(0, 1)
   393  		// Therefore, the result should be: (0, 1)
   394  
   395  		cc1 := "cc1"
   396  		cc2 := "cc2"
   397  		chanPeers := peerSet{
   398  			newPeer(0).withChaincode(cc1, "1.0").withChaincode(cc2, "1.0"),
   399  			newPeer(1).withChaincode(cc1, "1.0").withChaincode(cc2, "1.0"),
   400  		}.toMembers()
   401  
   402  		alivePeers := peerSet{
   403  			newPeer(0),
   404  			newPeer(1),
   405  		}.toMembers()
   406  
   407  		g := &gossipMock{}
   408  		g.On("Peers").Return(alivePeers)
   409  		g.On("IdentityInfo").Return(identities)
   410  		g.On("PeersOfChannel").Return(chanPeers).Once()
   411  
   412  		mf := &metadataFetcher{}
   413  		mf.On("Metadata").Return(&chaincode.Metadata{
   414  			Name:    "cc1",
   415  			Version: "1.0",
   416  		})
   417  		mf.On("Metadata").Return(&chaincode.Metadata{
   418  			Name:    "cc2",
   419  			Version: "1.0",
   420  		})
   421  
   422  		pb := principalBuilder{}
   423  		cc1policy := pb.newSet().addPrincipal(peerRole("p0")).
   424  			newSet().addPrincipal(peerRole("p1")).buildPolicy()
   425  		pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()
   426  
   427  		cc2policy := pb.newSet().addPrincipal(peerRole("p0")).
   428  			addPrincipal(peerRole("p1")).buildPolicy()
   429  		pf.On("PoliciesByChaincode", "cc2").Return(cc2policy).Once()
   430  
   431  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   432  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   433  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   434  				{
   435  					Name: "cc1",
   436  				},
   437  				{
   438  					Name: "cc2",
   439  				},
   440  			},
   441  		})
   442  		assert.NoError(t, err)
   443  		assert.NotNil(t, desc)
   444  		assert.Len(t, desc.Layouts, 1)
   445  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
   446  		assert.Equal(t, map[string]struct{}{
   447  			peerIdentityString("p0"): {},
   448  			peerIdentityString("p1"): {},
   449  		}, extractPeers(desc))
   450  	})
   451  
   452  	t.Run("Collection specific EP", func(t *testing.T) {
   453  		// Scenario XI: Policy is found and there are enough peers to satisfy
   454  		// 2 principal combinations: p0 and p6, or p12 alone.
   455  		// The collection has p0, p6, and p12 in it.
   456  		// The chaincode EP is (p0 and p6) or p12.
   457  		// However, the the chaincode has a collection level EP that requires p6 and p12.
   458  		// Thus, the only combination that can satisfy would be p6 and p12.
   459  		collectionOrgs := []*msp.MSPPrincipal{
   460  			peerRole("p0"),
   461  			peerRole("p6"),
   462  			peerRole("p12"),
   463  		}
   464  		col2principals := map[string][]*msp.MSPPrincipal{
   465  			"collection": collectionOrgs,
   466  		}
   467  
   468  		mf := &metadataFetcher{}
   469  		mf.On("Metadata").Return(&chaincode.Metadata{
   470  			Name:              cc,
   471  			Version:           "1.0",
   472  			CollectionsConfig: buildCollectionConfig(col2principals),
   473  		}).Once()
   474  		pb := principalBuilder{}
   475  		chaincodeEP := pb.newSet().addPrincipal(peerRole("p0")).
   476  			addPrincipal(peerRole("p6")).newSet().
   477  			addPrincipal(peerRole("p12")).buildPolicy()
   478  		collectionEP := pb.newSet().addPrincipal(peerRole("p6")).
   479  			addPrincipal(peerRole("p12")).buildPolicy()
   480  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   481  		pf := &policyFetcherMock{}
   482  		pf.On("PoliciesByChaincode", cc).Return([]policies.InquireablePolicy{chaincodeEP, collectionEP}).Once()
   483  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   484  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   485  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   486  				{
   487  					Name:            cc,
   488  					CollectionNames: []string{"collection"},
   489  				},
   490  			},
   491  		})
   492  		assert.NoError(t, err)
   493  		assert.NotNil(t, desc)
   494  		assert.Len(t, desc.Layouts, 1)
   495  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
   496  		assert.Equal(t, map[string]struct{}{
   497  			peerIdentityString("p6"):  {},
   498  			peerIdentityString("p12"): {},
   499  		}, extractPeers(desc))
   500  	})
   501  
   502  	t.Run("Private data blind write", func(t *testing.T) {
   503  		// Scenario XII: The collection has only p0 in it
   504  		// The chaincode EP is p6 or p0.
   505  		// The collection endorsement policy is p0 and p6.
   506  		// However p6 is not in the collection at all (only p0),
   507  		// so it doesn't have the pre-images.
   508  		// To that end, the client indicates that it's a blind write
   509  		// by turning on the "noPrivateRead" field in the request.
   510  		// This might seem like a pathological case, but it's
   511  		// effective because it is in the intersection of
   512  		// several use cases.
   513  
   514  		collectionOrgs := []*msp.MSPPrincipal{
   515  			peerRole("p0"),
   516  		}
   517  		col2principals := map[string][]*msp.MSPPrincipal{
   518  			"collection": collectionOrgs,
   519  		}
   520  
   521  		mf := &metadataFetcher{}
   522  		mf.On("Metadata").Return(&chaincode.Metadata{
   523  			Name:              cc,
   524  			Version:           "1.0",
   525  			CollectionsConfig: buildCollectionConfig(col2principals),
   526  		}).Once()
   527  		pb := principalBuilder{}
   528  		chaincodeEP := pb.newSet().addPrincipal(peerRole("p0")).newSet(). // p0 or p6
   529  											addPrincipal(peerRole("p6")).buildPolicy()
   530  		collectionEP := pb.newSet().addPrincipal(peerRole("p0")). // p0 and p6
   531  										addPrincipal(peerRole("p6")).buildPolicy()
   532  		g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
   533  		pf := &policyFetcherMock{}
   534  		pf.On("PoliciesByChaincode", cc).Return([]policies.InquireablePolicy{chaincodeEP, collectionEP}).Once()
   535  		analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   536  		desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{
   537  			Chaincodes: []*discoveryprotos.ChaincodeCall{
   538  				{
   539  					Name:            cc,
   540  					CollectionNames: []string{"collection"},
   541  					NoPrivateReads:  true, // This means a blind write
   542  				},
   543  			},
   544  		})
   545  		assert.NoError(t, err)
   546  		assert.NotNil(t, desc)
   547  		assert.Len(t, desc.Layouts, 1)
   548  		assert.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
   549  		assert.Equal(t, map[string]struct{}{
   550  			peerIdentityString("p0"): {},
   551  			peerIdentityString("p6"): {},
   552  		}, extractPeers(desc))
   553  	})
   554  }
   555  
   556  func TestPeersAuthorizedByCriteria(t *testing.T) {
   557  	cc1 := "cc1"
   558  	cc2 := "cc2"
   559  	members := peerSet{
   560  		newPeer(0).withChaincode(cc1, "1.0"),
   561  		newPeer(3).withChaincode(cc1, "1.0"),
   562  		newPeer(6).withChaincode(cc1, "1.0"),
   563  		newPeer(9).withChaincode(cc1, "1.0"),
   564  		newPeer(12).withChaincode(cc1, "1.0"),
   565  	}.toMembers()
   566  
   567  	members2 := append(discovery.Members{}, members...)
   568  	members2 = append(members2, peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers()...)
   569  	members2 = append(members2, peerSet{newPeer(14).withChaincode(cc1, "1.1")}.toMembers()...)
   570  	members2 = append(members2, peerSet{newPeer(15).withChaincode(cc2, "1.0")}.toMembers()...)
   571  
   572  	alivePeers := peerSet{
   573  		newPeer(0),
   574  		newPeer(2),
   575  		newPeer(4),
   576  		newPeer(6),
   577  		newPeer(8),
   578  		newPeer(10),
   579  		newPeer(11),
   580  		newPeer(12),
   581  		newPeer(13),
   582  		newPeer(14),
   583  		newPeer(15),
   584  	}.toMembers()
   585  
   586  	identities := identitySet(pkiID2MSPID)
   587  
   588  	for _, tst := range []struct {
   589  		name                 string
   590  		arguments            *discoveryprotos.ChaincodeInterest
   591  		totalExistingMembers discovery.Members
   592  		metadata             []*chaincode.Metadata
   593  		expected             discovery.Members
   594  	}{
   595  		{
   596  			name:                 "Nil interest",
   597  			arguments:            nil,
   598  			totalExistingMembers: members,
   599  			expected:             members,
   600  		},
   601  		{
   602  			name:                 "Empty interest invocation chain",
   603  			arguments:            &discoveryprotos.ChaincodeInterest{},
   604  			totalExistingMembers: members,
   605  			expected:             members,
   606  		},
   607  		{
   608  			name: "Chaincodes only installed on some peers",
   609  			arguments: &discoveryprotos.ChaincodeInterest{
   610  				Chaincodes: []*discoveryprotos.ChaincodeCall{
   611  					{Name: cc1},
   612  					{Name: cc2},
   613  				},
   614  			},
   615  			totalExistingMembers: members2,
   616  			metadata: []*chaincode.Metadata{
   617  				{
   618  					Name:    "cc1",
   619  					Version: "1.1",
   620  				},
   621  				{
   622  					Name:    "cc2",
   623  					Version: "1.0",
   624  				},
   625  			},
   626  			expected: peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers(),
   627  		},
   628  		{
   629  			name: "Only some peers authorized by collection",
   630  			arguments: &discoveryprotos.ChaincodeInterest{
   631  				Chaincodes: []*discoveryprotos.ChaincodeCall{
   632  					{Name: cc1, CollectionNames: []string{"collection"}},
   633  				},
   634  			},
   635  			totalExistingMembers: members,
   636  			metadata: []*chaincode.Metadata{
   637  				{
   638  					Name:    cc1,
   639  					Version: "1.0",
   640  					CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
   641  						"collection": {
   642  							peerRole("p0"),
   643  							peerRole("p12"),
   644  						},
   645  					}),
   646  				},
   647  				{
   648  					Name:    cc1,
   649  					Version: "1.0",
   650  					CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
   651  						"collection": {
   652  							peerRole("p3"),
   653  							peerRole("p9"),
   654  						},
   655  					}),
   656  				},
   657  			},
   658  			expected: peerSet{
   659  				newPeer(0).withChaincode(cc1, "1.0"),
   660  				newPeer(12).withChaincode(cc1, "1.0")}.toMembers(),
   661  		},
   662  	} {
   663  		t.Run(tst.name, func(t *testing.T) {
   664  			g := &gossipMock{}
   665  			pf := &policyFetcherMock{}
   666  			mf := &metadataFetcher{}
   667  			g.On("Peers").Return(alivePeers)
   668  			g.On("IdentityInfo").Return(identities)
   669  			g.On("PeersOfChannel").Return(tst.totalExistingMembers).Once()
   670  			for _, md := range tst.metadata {
   671  				mf.On("Metadata").Return(md).Once()
   672  			}
   673  
   674  			analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
   675  			actualMembers, err := analyzer.PeersAuthorizedByCriteria(common.ChannelID("mychannel"), tst.arguments)
   676  			assert.NoError(t, err)
   677  			assert.Equal(t, tst.expected, actualMembers)
   678  		})
   679  	}
   680  }
   681  
   682  func TestPop(t *testing.T) {
   683  	slice := []inquire.ComparablePrincipalSets{{}, {}}
   684  	assert.Len(t, slice, 2)
   685  	_, slice, err := popComparablePrincipalSets(slice)
   686  	assert.NoError(t, err)
   687  	assert.Len(t, slice, 1)
   688  	_, slice, err = popComparablePrincipalSets(slice)
   689  	assert.Len(t, slice, 0)
   690  	_, slice, err = popComparablePrincipalSets(slice)
   691  	assert.Error(t, err)
   692  	assert.Equal(t, "no principal sets remained after filtering", err.Error())
   693  }
   694  
   695  func TestMergePrincipalSetsNilInput(t *testing.T) {
   696  	_, err := mergePrincipalSets(nil)
   697  	assert.Error(t, err)
   698  	assert.Equal(t, "no principal sets remained after filtering", err.Error())
   699  }
   700  
   701  func TestComputePrincipalSetsNoPolicies(t *testing.T) {
   702  	// Tests a hypothetical case where no chaincodes populate the chaincode interest.
   703  
   704  	interest := &discoveryprotos.ChaincodeInterest{
   705  		Chaincodes: []*discoveryprotos.ChaincodeCall{},
   706  	}
   707  	ea := &endorsementAnalyzer{}
   708  	_, err := ea.computePrincipalSets(common.ChannelID("mychannel"), interest)
   709  	assert.Error(t, err)
   710  	assert.Contains(t, err.Error(), "no principal sets remained after filtering")
   711  }
   712  
   713  func TestLoadMetadataAndFiltersCollectionNotPresentInConfig(t *testing.T) {
   714  	interest := &discoveryprotos.ChaincodeInterest{
   715  		Chaincodes: []*discoveryprotos.ChaincodeCall{
   716  			{
   717  				Name:            "mycc",
   718  				CollectionNames: []string{"bar"},
   719  			},
   720  		},
   721  	}
   722  
   723  	org1AndOrg2 := []*msp.MSPPrincipal{
   724  		orgPrincipal("Org1MSP"),
   725  		orgPrincipal("Org2MSP"),
   726  	}
   727  	col2principals := map[string][]*msp.MSPPrincipal{
   728  		"foo": org1AndOrg2,
   729  	}
   730  	config := buildCollectionConfig(col2principals)
   731  
   732  	mdf := &metadataFetcher{}
   733  	mdf.On("Metadata").Return(&chaincode.Metadata{
   734  		Name:              "mycc",
   735  		CollectionsConfig: config,
   736  		Policy:            []byte{1, 2, 3},
   737  	})
   738  
   739  	_, err := loadMetadataAndFilters(metadataAndFilterContext{
   740  		identityInfoByID: nil,
   741  		evaluator:        nil,
   742  		chainID:          common.ChannelID("mychannel"),
   743  		fetch:            mdf,
   744  		interest:         interest,
   745  	})
   746  
   747  	assert.Equal(t, "collection bar doesn't exist in collection config for chaincode mycc", err.Error())
   748  }
   749  
   750  func TestLoadMetadataAndFiltersInvalidCollectionData(t *testing.T) {
   751  	interest := &discoveryprotos.ChaincodeInterest{
   752  		Chaincodes: []*discoveryprotos.ChaincodeCall{
   753  			{
   754  				Name:            "mycc",
   755  				CollectionNames: []string{"col1"},
   756  			},
   757  		},
   758  	}
   759  	mdf := &metadataFetcher{}
   760  	mdf.On("Metadata").Return(&chaincode.Metadata{
   761  		Name:              "mycc",
   762  		CollectionsConfig: &peer.CollectionConfigPackage{},
   763  		Policy:            []byte{1, 2, 3},
   764  	})
   765  
   766  	_, err := loadMetadataAndFilters(metadataAndFilterContext{
   767  		identityInfoByID: nil,
   768  		evaluator:        nil,
   769  		chainID:          common.ChannelID("mychannel"),
   770  		fetch:            mdf,
   771  		interest:         interest,
   772  	})
   773  	assert.Error(t, err)
   774  	assert.Contains(t, err.Error(), "collection col1 doesn't exist in collection config for chaincode mycc")
   775  }
   776  
   777  type peerSet []*peerInfo
   778  
   779  func (p peerSet) toMembers() discovery.Members {
   780  	var members discovery.Members
   781  	for _, peer := range p {
   782  		members = append(members, peer.NetworkMember)
   783  	}
   784  	return members
   785  }
   786  
   787  func identitySet(pkiID2MSPID map[string]string) api.PeerIdentitySet {
   788  	var res api.PeerIdentitySet
   789  	for pkiID, mspID := range pkiID2MSPID {
   790  		sID := &msp.SerializedIdentity{
   791  			Mspid:   pkiID2MSPID[pkiID],
   792  			IdBytes: []byte(pkiID),
   793  		}
   794  		res = append(res, api.PeerIdentityInfo{
   795  			Identity:     api.PeerIdentityType(protoutil.MarshalOrPanic(sID)),
   796  			PKIId:        common.PKIidType(pkiID),
   797  			Organization: api.OrgIdentityType(mspID),
   798  		})
   799  	}
   800  	return res
   801  }
   802  
   803  type peerInfo struct {
   804  	identity api.PeerIdentityType
   805  	pkiID    common.PKIidType
   806  	discovery.NetworkMember
   807  }
   808  
   809  func peerIdentityString(id string) string {
   810  	return string(protoutil.MarshalOrPanic(&msp.SerializedIdentity{
   811  		Mspid:   pkiID2MSPID[id],
   812  		IdBytes: []byte(id),
   813  	}))
   814  }
   815  
   816  func newPeer(i int) *peerInfo {
   817  	p := fmt.Sprintf("p%d", i)
   818  	identity := protoutil.MarshalOrPanic(&msp.SerializedIdentity{
   819  		Mspid:   pkiID2MSPID[p],
   820  		IdBytes: []byte(p),
   821  	})
   822  	return &peerInfo{
   823  		pkiID:    common.PKIidType(p),
   824  		identity: api.PeerIdentityType(identity),
   825  		NetworkMember: discovery.NetworkMember{
   826  			PKIid:            common.PKIidType(p),
   827  			Endpoint:         p,
   828  			InternalEndpoint: p,
   829  			Envelope: &gossip.Envelope{
   830  				Payload: []byte(identity),
   831  			},
   832  		},
   833  	}
   834  }
   835  
   836  func peerRole(pkiID string) *msp.MSPPrincipal {
   837  	return &msp.MSPPrincipal{
   838  		PrincipalClassification: msp.MSPPrincipal_ROLE,
   839  		Principal: protoutil.MarshalOrPanic(&msp.MSPRole{
   840  			MspIdentifier: pkiID2MSPID[pkiID],
   841  			Role:          msp.MSPRole_PEER,
   842  		}),
   843  	}
   844  }
   845  
   846  func (pi *peerInfo) withChaincode(name, version string) *peerInfo {
   847  	if pi.Properties == nil {
   848  		pi.Properties = &gossip.Properties{}
   849  	}
   850  	pi.Properties.Chaincodes = append(pi.Properties.Chaincodes, &gossip.Chaincode{
   851  		Name:    name,
   852  		Version: version,
   853  	})
   854  	return pi
   855  }
   856  
   857  type gossipMock struct {
   858  	mock.Mock
   859  }
   860  
   861  func (g *gossipMock) IdentityInfo() api.PeerIdentitySet {
   862  	return g.Called().Get(0).(api.PeerIdentitySet)
   863  }
   864  
   865  func (g *gossipMock) PeersOfChannel(_ common.ChannelID) discovery.Members {
   866  	members := g.Called().Get(0)
   867  	return members.(discovery.Members)
   868  }
   869  
   870  func (g *gossipMock) Peers() discovery.Members {
   871  	members := g.Called().Get(0)
   872  	return members.(discovery.Members)
   873  }
   874  
   875  type policyFetcherMock struct {
   876  	mock.Mock
   877  }
   878  
   879  func (pf *policyFetcherMock) PoliciesByChaincode(channel string, chaincode string, collections ...string) []policies.InquireablePolicy {
   880  	arg := pf.Called(chaincode)
   881  	if arg.Get(0) == nil {
   882  		return nil
   883  	}
   884  
   885  	singlePolicy, isSinglePolicy := arg.Get(0).(policies.InquireablePolicy)
   886  	if isSinglePolicy {
   887  		return []policies.InquireablePolicy{singlePolicy}
   888  	}
   889  
   890  	return arg.Get(0).([]policies.InquireablePolicy)
   891  }
   892  
   893  type principalBuilder struct {
   894  	ip inquireablePolicy
   895  }
   896  
   897  func (pb *principalBuilder) buildPolicy() inquireablePolicy {
   898  	defer func() {
   899  		pb.ip = nil
   900  	}()
   901  	return pb.ip
   902  }
   903  
   904  func (pb *principalBuilder) newSet() *principalBuilder {
   905  	pb.ip = append(pb.ip, make(policies.PrincipalSet, 0))
   906  	return pb
   907  }
   908  
   909  func (pb *principalBuilder) addPrincipal(principal *msp.MSPPrincipal) *principalBuilder {
   910  	pb.ip[len(pb.ip)-1] = append(pb.ip[len(pb.ip)-1], principal)
   911  	return pb
   912  }
   913  
   914  type inquireablePolicy []policies.PrincipalSet
   915  
   916  func (ip inquireablePolicy) SatisfiedBy() []policies.PrincipalSet {
   917  	return ip
   918  }
   919  
   920  type principalEvaluatorMock struct {
   921  }
   922  
   923  func (pe *principalEvaluatorMock) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error {
   924  	peerRole := &msp.MSPRole{}
   925  	if err := proto.Unmarshal(principal.Principal, peerRole); err != nil {
   926  		return err
   927  	}
   928  	sId := &msp.SerializedIdentity{}
   929  	if err := proto.Unmarshal(identity, sId); err != nil {
   930  		return err
   931  	}
   932  	if peerRole.MspIdentifier == sId.Mspid {
   933  		return nil
   934  	}
   935  	return errors.New("bingo")
   936  }
   937  
   938  type metadataFetcher struct {
   939  	mock.Mock
   940  }
   941  
   942  func (mf *metadataFetcher) Metadata(channel string, cc string, _ ...string) *chaincode.Metadata {
   943  	arg := mf.Called().Get(0)
   944  	if arg == nil {
   945  		return nil
   946  	}
   947  	return arg.(*chaincode.Metadata)
   948  }