github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/gossip/privdata/pvtdataprovider.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package privdata 8 9 import ( 10 "bytes" 11 "encoding/hex" 12 "fmt" 13 "time" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 17 "github.com/hyperledger/fabric-protos-go/msp" 18 "github.com/hyperledger/fabric-protos-go/peer" 19 vsccErrors "github.com/hyperledger/fabric/common/errors" 20 "github.com/hyperledger/fabric/common/metrics" 21 commonutil "github.com/hyperledger/fabric/common/util" 22 pvtdatasc "github.com/hyperledger/fabric/core/common/privdata" 23 "github.com/hyperledger/fabric/core/ledger" 24 "github.com/hyperledger/fabric/core/transientstore" 25 pvtdatacommon "github.com/hyperledger/fabric/gossip/privdata/common" 26 "github.com/hyperledger/fabric/gossip/util" 27 "github.com/hyperledger/fabric/protoutil" 28 ) 29 30 type sleeper struct { 31 sleep func(time.Duration) 32 } 33 34 func (s sleeper) Sleep(d time.Duration) { 35 if s.sleep == nil { 36 time.Sleep(d) 37 return 38 } 39 s.sleep(d) 40 } 41 42 type RetrievedPvtdata struct { 43 blockPvtdata *ledger.BlockPvtdata 44 pvtdataRetrievalInfo *pvtdataRetrievalInfo 45 transientStore *transientstore.Store 46 logger util.Logger 47 purgeDurationHistogram metrics.Histogram 48 blockNum uint64 49 transientBlockRetention uint64 50 } 51 52 // GetBlockPvtdata returns the BlockPvtdata 53 func (r *RetrievedPvtdata) GetBlockPvtdata() *ledger.BlockPvtdata { 54 return r.blockPvtdata 55 } 56 57 // Purge purges private data for transactions in the block from the transient store. 58 // Transactions older than the retention period are considered orphaned and also purged. 59 func (r *RetrievedPvtdata) Purge() { 60 purgeStart := time.Now() 61 62 if len(r.blockPvtdata.PvtData) > 0 { 63 // Finally, purge all transactions in block - valid or not valid. 64 if err := r.transientStore.PurgeByTxids(r.pvtdataRetrievalInfo.txns); err != nil { 65 r.logger.Errorf("Purging transactions %v failed: %s", r.pvtdataRetrievalInfo.txns, err) 66 } 67 } 68 69 blockNum := r.blockNum 70 if blockNum%r.transientBlockRetention == 0 && blockNum > r.transientBlockRetention { 71 err := r.transientStore.PurgeBelowHeight(blockNum - r.transientBlockRetention) 72 if err != nil { 73 r.logger.Errorf("Failed purging data from transient store at block [%d]: %s", blockNum, err) 74 } 75 } 76 77 r.purgeDurationHistogram.Observe(time.Since(purgeStart).Seconds()) 78 } 79 80 type eligibilityComputer struct { 81 logger util.Logger 82 storePvtdataOfInvalidTx bool 83 channelID string 84 selfSignedData protoutil.SignedData 85 idDeserializerFactory IdentityDeserializerFactory 86 } 87 88 // computeEligibility computes eligilibity of private data and 89 // groups all private data as either eligibleMissing or ineligibleMissing prior to fetching 90 func (ec *eligibilityComputer) computeEligibility(pvtdataToRetrieve []*ledger.TxPvtdataInfo) (*pvtdataRetrievalInfo, error) { 91 sources := make(map[rwSetKey][]*peer.Endorsement) 92 eligibleMissingKeys := make(rwsetKeys) 93 ineligibleMissingKeys := make(rwsetKeys) 94 95 var txList []string 96 for _, txPvtdata := range pvtdataToRetrieve { 97 txID := txPvtdata.TxID 98 seqInBlock := txPvtdata.SeqInBlock 99 invalid := txPvtdata.Invalid 100 txList = append(txList, txID) 101 if invalid && !ec.storePvtdataOfInvalidTx { 102 ec.logger.Debugf("Skipping Tx [%s] at sequence [%d] because it's invalid.", txID, seqInBlock) 103 continue 104 } 105 deserializer := ec.idDeserializerFactory.GetIdentityDeserializer(ec.channelID) 106 for _, colInfo := range txPvtdata.CollectionPvtdataInfo { 107 ns := colInfo.Namespace 108 col := colInfo.Collection 109 hash := colInfo.ExpectedHash 110 endorsers := colInfo.Endorsers 111 colConfig := colInfo.CollectionConfig 112 113 policy, err := pvtdatasc.NewSimpleCollection(colConfig, deserializer) 114 if err != nil { 115 ec.logger.Errorf("Failed to retrieve collection access policy for chaincode [%s], collection name [%s] for txID [%s]: %s.", 116 ns, col, txID, err) 117 return nil, &vsccErrors.VSCCExecutionFailureError{Err: err} 118 } 119 120 key := rwSetKey{ 121 txID: txID, 122 seqInBlock: seqInBlock, 123 hash: hex.EncodeToString(hash), 124 namespace: ns, 125 collection: col, 126 } 127 128 if !policy.AccessFilter()(ec.selfSignedData) { 129 ec.logger.Debugf("Peer is not eligible for collection: chaincode [%s], "+ 130 "collection name [%s], txID [%s] the policy is [%#v]. Skipping.", 131 ns, col, txID, policy) 132 ineligibleMissingKeys[key] = rwsetInfo{} 133 continue 134 } 135 136 // treat all eligible keys as missing 137 eligibleMissingKeys[key] = rwsetInfo{ 138 invalid: invalid, 139 } 140 141 sources[key] = endorsersFromEligibleOrgs(ns, col, endorsers, policy.MemberOrgs()) 142 } 143 } 144 145 return &pvtdataRetrievalInfo{ 146 sources: sources, 147 txns: txList, 148 eligibleMissingKeys: eligibleMissingKeys, 149 ineligibleMissingKeys: ineligibleMissingKeys, 150 }, nil 151 } 152 153 type PvtdataProvider struct { 154 selfSignedData protoutil.SignedData 155 logger util.Logger 156 listMissingPrivateDataDurationHistogram metrics.Histogram 157 fetchDurationHistogram metrics.Histogram 158 purgeDurationHistogram metrics.Histogram 159 transientStore *transientstore.Store 160 pullRetryThreshold time.Duration 161 prefetchedPvtdata util.PvtDataCollections 162 transientBlockRetention uint64 163 channelID string 164 blockNum uint64 165 storePvtdataOfInvalidTx bool 166 skipPullingInvalidTransactions bool 167 idDeserializerFactory IdentityDeserializerFactory 168 fetcher Fetcher 169 170 sleeper sleeper 171 } 172 173 // RetrievePvtdata is passed a list of private data items from a block, 174 // it determines which private data items this peer is eligible for, and then 175 // retrieves the private data from local cache, local transient store, or a remote peer. 176 func (pdp *PvtdataProvider) RetrievePvtdata(pvtdataToRetrieve []*ledger.TxPvtdataInfo) (*RetrievedPvtdata, error) { 177 retrievedPvtdata := &RetrievedPvtdata{ 178 transientStore: pdp.transientStore, 179 logger: pdp.logger, 180 purgeDurationHistogram: pdp.purgeDurationHistogram, 181 blockNum: pdp.blockNum, 182 transientBlockRetention: pdp.transientBlockRetention, 183 } 184 185 listMissingStart := time.Now() 186 eligibilityComputer := &eligibilityComputer{ 187 logger: pdp.logger, 188 storePvtdataOfInvalidTx: pdp.storePvtdataOfInvalidTx, 189 channelID: pdp.channelID, 190 selfSignedData: pdp.selfSignedData, 191 idDeserializerFactory: pdp.idDeserializerFactory, 192 } 193 194 pvtdataRetrievalInfo, err := eligibilityComputer.computeEligibility(pvtdataToRetrieve) 195 if err != nil { 196 return nil, err 197 } 198 pdp.listMissingPrivateDataDurationHistogram.Observe(time.Since(listMissingStart).Seconds()) 199 200 pvtdata := make(rwsetByKeys) 201 202 // POPULATE FROM CACHE 203 pdp.populateFromCache(pvtdata, pvtdataRetrievalInfo, pvtdataToRetrieve) 204 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 205 pdp.logger.Debug("No missing collection private write sets to fetch from transient store") 206 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 207 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 208 return retrievedPvtdata, nil 209 } 210 211 // POPULATE FROM TRANSIENT STORE 212 pdp.logger.Debugf("Could not find all collection private write sets in cache for block [%d]", pdp.blockNum) 213 pdp.logger.Debugf("Fetching %d collection private write sets from transient store", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 214 pdp.populateFromTransientStore(pvtdata, pvtdataRetrievalInfo) 215 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 216 pdp.logger.Debug("No missing collection private write sets to fetch from remote peers") 217 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 218 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 219 return retrievedPvtdata, nil 220 } 221 222 // POPULATE FROM REMOTE PEERS 223 retryThresh := pdp.pullRetryThreshold 224 pdp.logger.Debugf("Could not find all collection private write sets in local peer transient store for block [%d]", pdp.blockNum) 225 pdp.logger.Debugf("Fetching %d collection private write sets from remote peers for a maximum duration of %s", len(pvtdataRetrievalInfo.eligibleMissingKeys), retryThresh) 226 startPull := time.Now() 227 for len(pvtdataRetrievalInfo.eligibleMissingKeys) > 0 && time.Since(startPull) < retryThresh { 228 pdp.populateFromRemotePeers(pvtdata, pvtdataRetrievalInfo) 229 230 // If succeeded to fetch everything, break to skip sleep 231 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 232 break 233 } 234 235 // If there are still missing keys, sleep before retry 236 pdp.sleeper.Sleep(pullRetrySleepInterval) 237 } 238 elapsedPull := int64(time.Since(startPull) / time.Millisecond) // duration in ms 239 pdp.fetchDurationHistogram.Observe(time.Since(startPull).Seconds()) 240 241 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 242 pdp.logger.Debugf("Fetched all missing collection private write sets from remote peers for block [%d] (%dms)", pdp.blockNum, elapsedPull) 243 } else { 244 pdp.logger.Debugf("Could not fetch all missing collection private write sets from remote peers for block [%d]", 245 pdp.blockNum) 246 } 247 248 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 249 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 250 return retrievedPvtdata, nil 251 } 252 253 // populateFromCache populates pvtdata with data fetched from cache and updates 254 // pvtdataRetrievalInfo by removing missing data that was fetched from cache 255 func (pdp *PvtdataProvider) populateFromCache(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo, pvtdataToRetrieve []*ledger.TxPvtdataInfo) { 256 pdp.logger.Debugf("Attempting to retrieve %d private write sets from cache.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 257 258 for _, txPvtdata := range pdp.prefetchedPvtdata { 259 txID := getTxIDBySeqInBlock(txPvtdata.SeqInBlock, pvtdataToRetrieve) 260 // if can't match txID from query, then the data was never requested so skip the entire tx 261 if txID == "" { 262 pdp.logger.Warningf("Found extra data in prefetched at sequence [%d]. Skipping.", txPvtdata.SeqInBlock) 263 continue 264 } 265 for _, ns := range txPvtdata.WriteSet.NsPvtRwset { 266 for _, col := range ns.CollectionPvtRwset { 267 key := rwSetKey{ 268 txID: txID, 269 seqInBlock: txPvtdata.SeqInBlock, 270 collection: col.CollectionName, 271 namespace: ns.Namespace, 272 hash: hex.EncodeToString(commonutil.ComputeSHA256(col.Rwset)), 273 } 274 // skip if key not originally missing 275 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 276 pdp.logger.Warningf("Found extra data in prefetched:[%v]. Skipping.", key) 277 continue 278 } 279 // populate the pvtdata with the RW set from the cache 280 pvtdata[key] = col.Rwset 281 // remove key from missing 282 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 283 } // iterate over collections in the namespace 284 } // iterate over the namespaces in the WSet 285 } // iterate over cached private data in the block 286 } 287 288 // populateFromTransientStore populates pvtdata with data fetched from transient store 289 // and updates pvtdataRetrievalInfo by removing missing data that was fetched from transient store 290 func (pdp *PvtdataProvider) populateFromTransientStore(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) { 291 pdp.logger.Debugf("Attempting to retrieve %d private write sets from transient store.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 292 293 // Put into pvtdata RW sets that are missing and found in the transient store 294 for k := range pvtdataRetrievalInfo.eligibleMissingKeys { 295 filter := ledger.NewPvtNsCollFilter() 296 filter.Add(k.namespace, k.collection) 297 iterator, err := pdp.transientStore.GetTxPvtRWSetByTxid(k.txID, filter) 298 if err != nil { 299 pdp.logger.Warningf("Failed fetching private data from transient store: Failed obtaining iterator from transient store: %s", err) 300 return 301 } 302 defer iterator.Close() 303 for { 304 res, err := iterator.Next() 305 if err != nil { 306 pdp.logger.Warningf("Failed fetching private data from transient store: Failed iterating over transient store data: %s", err) 307 return 308 } 309 if res == nil { 310 // End of iteration 311 break 312 } 313 if res.PvtSimulationResultsWithConfig == nil { 314 pdp.logger.Warningf("Resultset's PvtSimulationResultsWithConfig for txID [%s] is nil. Skipping.", k.txID) 315 continue 316 } 317 simRes := res.PvtSimulationResultsWithConfig 318 if simRes.PvtRwset == nil { 319 pdp.logger.Warningf("The PvtRwset of PvtSimulationResultsWithConfig for txID [%s] is nil. Skipping.", k.txID) 320 continue 321 } 322 for _, ns := range simRes.PvtRwset.NsPvtRwset { 323 for _, col := range ns.CollectionPvtRwset { 324 key := rwSetKey{ 325 txID: k.txID, 326 seqInBlock: k.seqInBlock, 327 collection: col.CollectionName, 328 namespace: ns.Namespace, 329 hash: hex.EncodeToString(commonutil.ComputeSHA256(col.Rwset)), 330 } 331 // skip if not missing 332 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 333 continue 334 } 335 // populate the pvtdata with the RW set from the transient store 336 pvtdata[key] = col.Rwset 337 // remove key from missing 338 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 339 } // iterating over all collections 340 } // iterating over all namespaces 341 } // iterating over the TxPvtRWSet results 342 } 343 } 344 345 // populateFromRemotePeers populates pvtdata with data fetched from remote peers and updates 346 // pvtdataRetrievalInfo by removing missing data that was fetched from remote peers 347 func (pdp *PvtdataProvider) populateFromRemotePeers(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) { 348 pdp.logger.Debugf("Attempting to retrieve %d private write sets from remote peers.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 349 350 dig2src := make(map[pvtdatacommon.DigKey][]*peer.Endorsement) 351 for k, v := range pvtdataRetrievalInfo.eligibleMissingKeys { 352 if v.invalid && pdp.skipPullingInvalidTransactions { 353 pdp.logger.Debugf("Skipping invalid key [%v] because peer is configured to skip pulling rwsets of invalid transactions.", k) 354 continue 355 } 356 pdp.logger.Debugf("Fetching [%v] from remote peers", k) 357 dig := pvtdatacommon.DigKey{ 358 TxId: k.txID, 359 SeqInBlock: k.seqInBlock, 360 Collection: k.collection, 361 Namespace: k.namespace, 362 BlockSeq: pdp.blockNum, 363 } 364 dig2src[dig] = pvtdataRetrievalInfo.sources[k] 365 } 366 fetchedData, err := pdp.fetcher.fetch(dig2src) 367 if err != nil { 368 pdp.logger.Warningf("Failed fetching private data from remote peers for dig2src:[%v], err: %s", dig2src, err) 369 return 370 } 371 372 // Iterate over data fetched from remote peers 373 for _, element := range fetchedData.AvailableElements { 374 dig := element.Digest 375 for _, rws := range element.Payload { 376 key := rwSetKey{ 377 txID: dig.TxId, 378 namespace: dig.Namespace, 379 collection: dig.Collection, 380 seqInBlock: dig.SeqInBlock, 381 hash: hex.EncodeToString(commonutil.ComputeSHA256(rws)), 382 } 383 // skip if not missing 384 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 385 // key isn't missing and was never fetched earlier, log that it wasn't originally requested 386 if _, exists := pvtdata[key]; !exists { 387 pdp.logger.Debugf("Ignoring [%v] because it was never requested.", key) 388 } 389 continue 390 } 391 // populate the pvtdata with the RW set from the remote peer 392 pvtdata[key] = rws 393 // remove key from missing 394 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 395 pdp.logger.Debugf("Fetched [%v]", key) 396 } 397 } 398 // Iterate over purged data 399 for _, dig := range fetchedData.PurgedElements { 400 // delete purged key from missing keys 401 for missingPvtRWKey := range pvtdataRetrievalInfo.eligibleMissingKeys { 402 if missingPvtRWKey.namespace == dig.Namespace && 403 missingPvtRWKey.collection == dig.Collection && 404 missingPvtRWKey.seqInBlock == dig.SeqInBlock && 405 missingPvtRWKey.txID == dig.TxId { 406 delete(pvtdataRetrievalInfo.eligibleMissingKeys, missingPvtRWKey) 407 pdp.logger.Warningf("Missing key because was purged or will soon be purged, "+ 408 "continue block commit without [%+v] in private rwset", missingPvtRWKey) 409 } 410 } 411 } 412 } 413 414 // prepareBlockPvtdata consolidates the fetched private data as well as ineligible and eligible 415 // missing private data into a ledger.BlockPvtdata for the PvtdataProvider to return to the consumer 416 func (pdp *PvtdataProvider) prepareBlockPvtdata(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) *ledger.BlockPvtdata { 417 blockPvtdata := &ledger.BlockPvtdata{ 418 PvtData: make(ledger.TxPvtDataMap), 419 MissingPvtData: make(ledger.TxMissingPvtDataMap), 420 } 421 422 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 423 pdp.logger.Infof("Successfully fetched all eligible collection private write sets for block [%d]", pdp.blockNum) 424 } else { 425 pdp.logger.Warningf("Could not fetch all missing eligible collection private write sets for block [%d]. Will commit block with missing private write sets:[%v]", 426 pdp.blockNum, pvtdataRetrievalInfo.eligibleMissingKeys) 427 } 428 429 for seqInBlock, nsRWS := range pvtdata.bySeqsInBlock() { 430 // add all found pvtdata to blockPvtDataPvtdata for seqInBlock 431 blockPvtdata.PvtData[seqInBlock] = &ledger.TxPvtData{ 432 SeqInBlock: seqInBlock, 433 WriteSet: nsRWS.toRWSet(), 434 } 435 } 436 437 for key := range pvtdataRetrievalInfo.eligibleMissingKeys { 438 blockPvtdata.MissingPvtData.Add(key.seqInBlock, key.namespace, key.collection, true) 439 } 440 441 for key := range pvtdataRetrievalInfo.ineligibleMissingKeys { 442 blockPvtdata.MissingPvtData.Add(key.seqInBlock, key.namespace, key.collection, false) 443 } 444 445 return blockPvtdata 446 } 447 448 type pvtdataRetrievalInfo struct { 449 sources map[rwSetKey][]*peer.Endorsement 450 txns []string 451 eligibleMissingKeys rwsetKeys 452 ineligibleMissingKeys rwsetKeys 453 } 454 455 // rwset types 456 457 type readWriteSets []*readWriteSet 458 459 func (s readWriteSets) toRWSet() *rwset.TxPvtReadWriteSet { 460 namespaces := make(map[string]*rwset.NsPvtReadWriteSet) 461 dataModel := rwset.TxReadWriteSet_KV 462 for _, rws := range s { 463 if _, exists := namespaces[rws.namespace]; !exists { 464 namespaces[rws.namespace] = &rwset.NsPvtReadWriteSet{ 465 Namespace: rws.namespace, 466 } 467 } 468 col := &rwset.CollectionPvtReadWriteSet{ 469 CollectionName: rws.collection, 470 Rwset: rws.rws, 471 } 472 namespaces[rws.namespace].CollectionPvtRwset = append(namespaces[rws.namespace].CollectionPvtRwset, col) 473 } 474 475 var namespaceSlice []*rwset.NsPvtReadWriteSet 476 for _, nsRWset := range namespaces { 477 namespaceSlice = append(namespaceSlice, nsRWset) 478 } 479 480 return &rwset.TxPvtReadWriteSet{ 481 DataModel: dataModel, 482 NsPvtRwset: namespaceSlice, 483 } 484 } 485 486 type readWriteSet struct { 487 rwSetKey 488 rws []byte 489 } 490 491 type rwsetByKeys map[rwSetKey][]byte 492 493 func (s rwsetByKeys) bySeqsInBlock() map[uint64]readWriteSets { 494 res := make(map[uint64]readWriteSets) 495 for k, rws := range s { 496 res[k.seqInBlock] = append(res[k.seqInBlock], &readWriteSet{ 497 rws: rws, 498 rwSetKey: k, 499 }) 500 } 501 return res 502 } 503 504 type rwsetInfo struct { 505 invalid bool 506 } 507 508 type rwsetKeys map[rwSetKey]rwsetInfo 509 510 // String returns a string representation of the rwsetKeys 511 func (s rwsetKeys) String() string { 512 var buffer bytes.Buffer 513 for k := range s { 514 buffer.WriteString(fmt.Sprintf("%s\n", k.String())) 515 } 516 return buffer.String() 517 } 518 519 type rwSetKey struct { 520 txID string 521 seqInBlock uint64 522 namespace string 523 collection string 524 hash string 525 } 526 527 // String returns a string representation of the rwSetKey 528 func (k *rwSetKey) String() string { 529 return fmt.Sprintf("txID: %s, seq: %d, namespace: %s, collection: %s, hash: %s", k.txID, k.seqInBlock, k.namespace, k.collection, k.hash) 530 } 531 532 func getTxIDBySeqInBlock(seqInBlock uint64, pvtdataToRetrieve []*ledger.TxPvtdataInfo) string { 533 for _, txPvtdataItem := range pvtdataToRetrieve { 534 if txPvtdataItem.SeqInBlock == seqInBlock { 535 return txPvtdataItem.TxID 536 } 537 } 538 539 return "" 540 } 541 542 func endorsersFromEligibleOrgs(ns string, col string, endorsers []*peer.Endorsement, orgs []string) []*peer.Endorsement { 543 var res []*peer.Endorsement 544 for _, e := range endorsers { 545 sID := &msp.SerializedIdentity{} 546 err := proto.Unmarshal(e.Endorser, sID) 547 if err != nil { 548 logger.Warning("Failed unmarshalling endorser:", err) 549 continue 550 } 551 if !util.Contains(sID.Mspid, orgs) { 552 logger.Debug(sID.Mspid, "isn't among the collection's orgs:", orgs, "for namespace", ns, ",collection", col) 553 continue 554 } 555 res = append(res, e) 556 } 557 return res 558 }