github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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/osdi23p228/fabric/common/chaincode" 19 "github.com/osdi23p228/fabric/common/policies" 20 "github.com/osdi23p228/fabric/common/policies/inquire" 21 "github.com/osdi23p228/fabric/gossip/api" 22 "github.com/osdi23p228/fabric/gossip/common" 23 "github.com/osdi23p228/fabric/gossip/discovery" 24 "github.com/osdi23p228/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(), "no peer combination can satisfy the endorsement policy") 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, "required chaincodes are not installed on sufficient peers", 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, "required chaincodes are not installed on sufficient peers", 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.NoError(t, err) 690 assert.Len(t, slice, 0) 691 _, _, err = popComparablePrincipalSets(slice) 692 assert.Error(t, err) 693 assert.Equal(t, "no principal sets remained after filtering", err.Error()) 694 } 695 696 func TestMergePrincipalSetsNilInput(t *testing.T) { 697 _, err := mergePrincipalSets(nil) 698 assert.Error(t, err) 699 assert.Equal(t, "no principal sets remained after filtering", err.Error()) 700 } 701 702 func TestComputePrincipalSetsNoPolicies(t *testing.T) { 703 // Tests a hypothetical case where no chaincodes populate the chaincode interest. 704 705 interest := &discoveryprotos.ChaincodeInterest{ 706 Chaincodes: []*discoveryprotos.ChaincodeCall{}, 707 } 708 ea := &endorsementAnalyzer{} 709 _, err := ea.computePrincipalSets(common.ChannelID("mychannel"), interest) 710 assert.Error(t, err) 711 assert.Contains(t, err.Error(), "no principal sets remained after filtering") 712 } 713 714 func TestLoadMetadataAndFiltersCollectionNotPresentInConfig(t *testing.T) { 715 interest := &discoveryprotos.ChaincodeInterest{ 716 Chaincodes: []*discoveryprotos.ChaincodeCall{ 717 { 718 Name: "mycc", 719 CollectionNames: []string{"bar"}, 720 }, 721 }, 722 } 723 724 org1AndOrg2 := []*msp.MSPPrincipal{ 725 orgPrincipal("Org1MSP"), 726 orgPrincipal("Org2MSP"), 727 } 728 col2principals := map[string][]*msp.MSPPrincipal{ 729 "foo": org1AndOrg2, 730 } 731 config := buildCollectionConfig(col2principals) 732 733 mdf := &metadataFetcher{} 734 mdf.On("Metadata").Return(&chaincode.Metadata{ 735 Name: "mycc", 736 CollectionsConfig: config, 737 Policy: []byte{1, 2, 3}, 738 }) 739 740 _, err := loadMetadataAndFilters(metadataAndFilterContext{ 741 identityInfoByID: nil, 742 evaluator: nil, 743 chainID: common.ChannelID("mychannel"), 744 fetch: mdf, 745 interest: interest, 746 }) 747 748 assert.Equal(t, "collection bar doesn't exist in collection config for chaincode mycc", err.Error()) 749 } 750 751 func TestLoadMetadataAndFiltersInvalidCollectionData(t *testing.T) { 752 interest := &discoveryprotos.ChaincodeInterest{ 753 Chaincodes: []*discoveryprotos.ChaincodeCall{ 754 { 755 Name: "mycc", 756 CollectionNames: []string{"col1"}, 757 }, 758 }, 759 } 760 mdf := &metadataFetcher{} 761 mdf.On("Metadata").Return(&chaincode.Metadata{ 762 Name: "mycc", 763 CollectionsConfig: &peer.CollectionConfigPackage{}, 764 Policy: []byte{1, 2, 3}, 765 }) 766 767 _, err := loadMetadataAndFilters(metadataAndFilterContext{ 768 identityInfoByID: nil, 769 evaluator: nil, 770 chainID: common.ChannelID("mychannel"), 771 fetch: mdf, 772 interest: interest, 773 }) 774 assert.Error(t, err) 775 assert.Contains(t, err.Error(), "collection col1 doesn't exist in collection config for chaincode mycc") 776 } 777 778 type peerSet []*peerInfo 779 780 func (p peerSet) toMembers() discovery.Members { 781 var members discovery.Members 782 for _, peer := range p { 783 members = append(members, peer.NetworkMember) 784 } 785 return members 786 } 787 788 func identitySet(pkiID2MSPID map[string]string) api.PeerIdentitySet { 789 var res api.PeerIdentitySet 790 for pkiID, mspID := range pkiID2MSPID { 791 sID := &msp.SerializedIdentity{ 792 Mspid: pkiID2MSPID[pkiID], 793 IdBytes: []byte(pkiID), 794 } 795 res = append(res, api.PeerIdentityInfo{ 796 Identity: api.PeerIdentityType(protoutil.MarshalOrPanic(sID)), 797 PKIId: common.PKIidType(pkiID), 798 Organization: api.OrgIdentityType(mspID), 799 }) 800 } 801 return res 802 } 803 804 type peerInfo struct { 805 identity api.PeerIdentityType 806 pkiID common.PKIidType 807 discovery.NetworkMember 808 } 809 810 func peerIdentityString(id string) string { 811 return string(protoutil.MarshalOrPanic(&msp.SerializedIdentity{ 812 Mspid: pkiID2MSPID[id], 813 IdBytes: []byte(id), 814 })) 815 } 816 817 func newPeer(i int) *peerInfo { 818 p := fmt.Sprintf("p%d", i) 819 identity := protoutil.MarshalOrPanic(&msp.SerializedIdentity{ 820 Mspid: pkiID2MSPID[p], 821 IdBytes: []byte(p), 822 }) 823 return &peerInfo{ 824 pkiID: common.PKIidType(p), 825 identity: api.PeerIdentityType(identity), 826 NetworkMember: discovery.NetworkMember{ 827 PKIid: common.PKIidType(p), 828 Endpoint: p, 829 InternalEndpoint: p, 830 Envelope: &gossip.Envelope{ 831 Payload: []byte(identity), 832 }, 833 }, 834 } 835 } 836 837 func peerRole(pkiID string) *msp.MSPPrincipal { 838 return &msp.MSPPrincipal{ 839 PrincipalClassification: msp.MSPPrincipal_ROLE, 840 Principal: protoutil.MarshalOrPanic(&msp.MSPRole{ 841 MspIdentifier: pkiID2MSPID[pkiID], 842 Role: msp.MSPRole_PEER, 843 }), 844 } 845 } 846 847 func (pi *peerInfo) withChaincode(name, version string) *peerInfo { 848 if pi.Properties == nil { 849 pi.Properties = &gossip.Properties{} 850 } 851 pi.Properties.Chaincodes = append(pi.Properties.Chaincodes, &gossip.Chaincode{ 852 Name: name, 853 Version: version, 854 }) 855 return pi 856 } 857 858 type gossipMock struct { 859 mock.Mock 860 } 861 862 func (g *gossipMock) IdentityInfo() api.PeerIdentitySet { 863 return g.Called().Get(0).(api.PeerIdentitySet) 864 } 865 866 func (g *gossipMock) PeersOfChannel(_ common.ChannelID) discovery.Members { 867 members := g.Called().Get(0) 868 return members.(discovery.Members) 869 } 870 871 func (g *gossipMock) Peers() discovery.Members { 872 members := g.Called().Get(0) 873 return members.(discovery.Members) 874 } 875 876 type policyFetcherMock struct { 877 mock.Mock 878 } 879 880 func (pf *policyFetcherMock) PoliciesByChaincode(channel string, chaincode string, collections ...string) []policies.InquireablePolicy { 881 arg := pf.Called(chaincode) 882 if arg.Get(0) == nil { 883 return nil 884 } 885 886 singlePolicy, isSinglePolicy := arg.Get(0).(policies.InquireablePolicy) 887 if isSinglePolicy { 888 return []policies.InquireablePolicy{singlePolicy} 889 } 890 891 return arg.Get(0).([]policies.InquireablePolicy) 892 } 893 894 type principalBuilder struct { 895 ip inquireablePolicy 896 } 897 898 func (pb *principalBuilder) buildPolicy() inquireablePolicy { 899 defer func() { 900 pb.ip = nil 901 }() 902 return pb.ip 903 } 904 905 func (pb *principalBuilder) newSet() *principalBuilder { 906 pb.ip = append(pb.ip, make(policies.PrincipalSet, 0)) 907 return pb 908 } 909 910 func (pb *principalBuilder) addPrincipal(principal *msp.MSPPrincipal) *principalBuilder { 911 pb.ip[len(pb.ip)-1] = append(pb.ip[len(pb.ip)-1], principal) 912 return pb 913 } 914 915 type inquireablePolicy []policies.PrincipalSet 916 917 func (ip inquireablePolicy) SatisfiedBy() []policies.PrincipalSet { 918 return ip 919 } 920 921 type principalEvaluatorMock struct { 922 } 923 924 func (pe *principalEvaluatorMock) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error { 925 peerRole := &msp.MSPRole{} 926 if err := proto.Unmarshal(principal.Principal, peerRole); err != nil { 927 return err 928 } 929 sId := &msp.SerializedIdentity{} 930 if err := proto.Unmarshal(identity, sId); err != nil { 931 return err 932 } 933 if peerRole.MspIdentifier == sId.Mspid { 934 return nil 935 } 936 return errors.New("bingo") 937 } 938 939 type metadataFetcher struct { 940 mock.Mock 941 } 942 943 func (mf *metadataFetcher) Metadata(channel string, cc string, _ ...string) *chaincode.Metadata { 944 arg := mf.Called().Get(0) 945 if arg == nil { 946 return nil 947 } 948 return arg.(*chaincode.Metadata) 949 }