github.com/yimialmonte/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 }