github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/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 sources[key] = endorsersFromEligibleOrgs(ns, col, endorsers, policy.MemberOrgs()) 141 } 142 } 143 144 return &pvtdataRetrievalInfo{ 145 sources: sources, 146 txns: txList, 147 eligibleMissingKeys: eligibleMissingKeys, 148 ineligibleMissingKeys: ineligibleMissingKeys, 149 }, nil 150 } 151 152 type PvtdataProvider struct { 153 selfSignedData protoutil.SignedData 154 logger util.Logger 155 listMissingPrivateDataDurationHistogram metrics.Histogram 156 fetchDurationHistogram metrics.Histogram 157 purgeDurationHistogram metrics.Histogram 158 transientStore *transientstore.Store 159 pullRetryThreshold time.Duration 160 prefetchedPvtdata util.PvtDataCollections 161 transientBlockRetention uint64 162 channelID string 163 blockNum uint64 164 storePvtdataOfInvalidTx bool 165 skipPullingInvalidTransactions bool 166 idDeserializerFactory IdentityDeserializerFactory 167 fetcher Fetcher 168 169 sleeper sleeper 170 } 171 172 // RetrievePvtdata is passed a list of private data items from a block, 173 // it determines which private data items this peer is eligible for, and then 174 // retrieves the private data from local cache, local transient store, or a remote peer. 175 func (pdp *PvtdataProvider) RetrievePvtdata(pvtdataToRetrieve []*ledger.TxPvtdataInfo) (*RetrievedPvtdata, error) { 176 retrievedPvtdata := &RetrievedPvtdata{ 177 transientStore: pdp.transientStore, 178 logger: pdp.logger, 179 purgeDurationHistogram: pdp.purgeDurationHistogram, 180 blockNum: pdp.blockNum, 181 transientBlockRetention: pdp.transientBlockRetention, 182 } 183 184 listMissingStart := time.Now() 185 eligibilityComputer := &eligibilityComputer{ 186 logger: pdp.logger, 187 storePvtdataOfInvalidTx: pdp.storePvtdataOfInvalidTx, 188 channelID: pdp.channelID, 189 selfSignedData: pdp.selfSignedData, 190 idDeserializerFactory: pdp.idDeserializerFactory, 191 } 192 193 pvtdataRetrievalInfo, err := eligibilityComputer.computeEligibility(pvtdataToRetrieve) 194 if err != nil { 195 return nil, err 196 } 197 pdp.listMissingPrivateDataDurationHistogram.Observe(time.Since(listMissingStart).Seconds()) 198 199 pvtdata := make(rwsetByKeys) 200 201 // POPULATE FROM CACHE 202 pdp.populateFromCache(pvtdata, pvtdataRetrievalInfo, pvtdataToRetrieve) 203 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 204 pdp.logger.Debug("No missing collection private write sets to fetch from transient store") 205 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 206 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 207 return retrievedPvtdata, nil 208 } 209 210 // POPULATE FROM TRANSIENT STORE 211 pdp.logger.Debugf("Could not find all collection private write sets in cache for block [%d]", pdp.blockNum) 212 pdp.logger.Debugf("Fetching %d collection private write sets from transient store", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 213 pdp.populateFromTransientStore(pvtdata, pvtdataRetrievalInfo) 214 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 215 pdp.logger.Debug("No missing collection private write sets to fetch from remote peers") 216 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 217 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 218 return retrievedPvtdata, nil 219 } 220 221 // POPULATE FROM REMOTE PEERS 222 retryThresh := pdp.pullRetryThreshold 223 pdp.logger.Debugf("Could not find all collection private write sets in local peer transient store for block [%d]", pdp.blockNum) 224 pdp.logger.Debugf("Fetching %d collection private write sets from remote peers for a maximum duration of %s", len(pvtdataRetrievalInfo.eligibleMissingKeys), retryThresh) 225 startPull := time.Now() 226 for len(pvtdataRetrievalInfo.eligibleMissingKeys) > 0 && time.Since(startPull) < retryThresh { 227 if needToRetry := pdp.populateFromRemotePeers(pvtdata, pvtdataRetrievalInfo); !needToRetry { 228 break 229 } 230 // If there are still missing keys, sleep before retry 231 pdp.sleeper.Sleep(pullRetrySleepInterval) 232 } 233 elapsedPull := int64(time.Since(startPull) / time.Millisecond) // duration in ms 234 pdp.fetchDurationHistogram.Observe(time.Since(startPull).Seconds()) 235 236 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 237 pdp.logger.Debugf("Fetched all missing collection private write sets from remote peers for block [%d] (%dms)", pdp.blockNum, elapsedPull) 238 } else { 239 pdp.logger.Debugf("Could not fetch all missing collection private write sets from remote peers for block [%d]", 240 pdp.blockNum) 241 } 242 243 retrievedPvtdata.pvtdataRetrievalInfo = pvtdataRetrievalInfo 244 retrievedPvtdata.blockPvtdata = pdp.prepareBlockPvtdata(pvtdata, pvtdataRetrievalInfo) 245 return retrievedPvtdata, nil 246 } 247 248 // populateFromCache populates pvtdata with data fetched from cache and updates 249 // pvtdataRetrievalInfo by removing missing data that was fetched from cache 250 func (pdp *PvtdataProvider) populateFromCache(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo, pvtdataToRetrieve []*ledger.TxPvtdataInfo) { 251 pdp.logger.Debugf("Attempting to retrieve %d private write sets from cache.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 252 253 for _, txPvtdata := range pdp.prefetchedPvtdata { 254 txID := getTxIDBySeqInBlock(txPvtdata.SeqInBlock, pvtdataToRetrieve) 255 // if can't match txID from query, then the data was never requested so skip the entire tx 256 if txID == "" { 257 pdp.logger.Warningf("Found extra data in prefetched at sequence [%d]. Skipping.", txPvtdata.SeqInBlock) 258 continue 259 } 260 for _, ns := range txPvtdata.WriteSet.NsPvtRwset { 261 for _, col := range ns.CollectionPvtRwset { 262 key := rwSetKey{ 263 txID: txID, 264 seqInBlock: txPvtdata.SeqInBlock, 265 collection: col.CollectionName, 266 namespace: ns.Namespace, 267 hash: hex.EncodeToString(commonutil.ComputeSHA256(col.Rwset)), 268 } 269 // skip if key not originally missing 270 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 271 pdp.logger.Warningf("Found extra data in prefetched:[%v]. Skipping.", key) 272 continue 273 } 274 // populate the pvtdata with the RW set from the cache 275 pvtdata[key] = col.Rwset 276 // remove key from missing 277 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 278 } // iterate over collections in the namespace 279 } // iterate over the namespaces in the WSet 280 } // iterate over cached private data in the block 281 } 282 283 // populateFromTransientStore populates pvtdata with data fetched from transient store 284 // and updates pvtdataRetrievalInfo by removing missing data that was fetched from transient store 285 func (pdp *PvtdataProvider) populateFromTransientStore(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) { 286 pdp.logger.Debugf("Attempting to retrieve %d private write sets from transient store.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 287 288 // Put into pvtdata RW sets that are missing and found in the transient store 289 for k := range pvtdataRetrievalInfo.eligibleMissingKeys { 290 filter := ledger.NewPvtNsCollFilter() 291 filter.Add(k.namespace, k.collection) 292 iterator, err := pdp.transientStore.GetTxPvtRWSetByTxid(k.txID, filter) 293 if err != nil { 294 pdp.logger.Warningf("Failed fetching private data from transient store: Failed obtaining iterator from transient store: %s", err) 295 return 296 } 297 defer iterator.Close() 298 for { 299 res, err := iterator.Next() 300 if err != nil { 301 pdp.logger.Warningf("Failed fetching private data from transient store: Failed iterating over transient store data: %s", err) 302 return 303 } 304 if res == nil { 305 // End of iteration 306 break 307 } 308 if res.PvtSimulationResultsWithConfig == nil { 309 pdp.logger.Warningf("Resultset's PvtSimulationResultsWithConfig for txID [%s] is nil. Skipping.", k.txID) 310 continue 311 } 312 simRes := res.PvtSimulationResultsWithConfig 313 if simRes.PvtRwset == nil { 314 pdp.logger.Warningf("The PvtRwset of PvtSimulationResultsWithConfig for txID [%s] is nil. Skipping.", k.txID) 315 continue 316 } 317 for _, ns := range simRes.PvtRwset.NsPvtRwset { 318 for _, col := range ns.CollectionPvtRwset { 319 key := rwSetKey{ 320 txID: k.txID, 321 seqInBlock: k.seqInBlock, 322 collection: col.CollectionName, 323 namespace: ns.Namespace, 324 hash: hex.EncodeToString(commonutil.ComputeSHA256(col.Rwset)), 325 } 326 // skip if not missing 327 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 328 continue 329 } 330 // populate the pvtdata with the RW set from the transient store 331 pvtdata[key] = col.Rwset 332 // remove key from missing 333 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 334 } // iterating over all collections 335 } // iterating over all namespaces 336 } // iterating over the TxPvtRWSet results 337 } 338 } 339 340 // populateFromRemotePeers populates pvtdata with data fetched from remote peers and updates 341 // pvtdataRetrievalInfo by removing missing data that was fetched from remote peers 342 func (pdp *PvtdataProvider) populateFromRemotePeers(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) bool { 343 pdp.logger.Debugf("Attempting to retrieve %d private write sets from remote peers.", len(pvtdataRetrievalInfo.eligibleMissingKeys)) 344 345 dig2src := make(map[pvtdatacommon.DigKey][]*peer.Endorsement) 346 var skipped int 347 for k, v := range pvtdataRetrievalInfo.eligibleMissingKeys { 348 if v.invalid && pdp.skipPullingInvalidTransactions { 349 pdp.logger.Debugf("Skipping invalid key [%v] because peer is configured to skip pulling rwsets of invalid transactions.", k) 350 skipped++ 351 continue 352 } 353 pdp.logger.Debugf("Fetching [%v] from remote peers", k) 354 dig := pvtdatacommon.DigKey{ 355 TxId: k.txID, 356 SeqInBlock: k.seqInBlock, 357 Collection: k.collection, 358 Namespace: k.namespace, 359 BlockSeq: pdp.blockNum, 360 } 361 dig2src[dig] = pvtdataRetrievalInfo.sources[k] 362 } 363 364 if len(dig2src) == 0 { 365 return false 366 } 367 368 fetchedData, err := pdp.fetcher.fetch(dig2src) 369 if err != nil { 370 pdp.logger.Warningf("Failed fetching private data from remote peers for dig2src:[%v], err: %s", dig2src, err) 371 return true 372 } 373 374 // Iterate over data fetched from remote peers 375 for _, element := range fetchedData.AvailableElements { 376 dig := element.Digest 377 for _, rws := range element.Payload { 378 key := rwSetKey{ 379 txID: dig.TxId, 380 namespace: dig.Namespace, 381 collection: dig.Collection, 382 seqInBlock: dig.SeqInBlock, 383 hash: hex.EncodeToString(commonutil.ComputeSHA256(rws)), 384 } 385 // skip if not missing 386 if _, missing := pvtdataRetrievalInfo.eligibleMissingKeys[key]; !missing { 387 // key isn't missing and was never fetched earlier, log that it wasn't originally requested 388 if _, exists := pvtdata[key]; !exists { 389 pdp.logger.Debugf("Ignoring [%v] because it was never requested.", key) 390 } 391 continue 392 } 393 // populate the pvtdata with the RW set from the remote peer 394 pvtdata[key] = rws 395 // remove key from missing 396 delete(pvtdataRetrievalInfo.eligibleMissingKeys, key) 397 pdp.logger.Debugf("Fetched [%v]", key) 398 } 399 } 400 // Iterate over purged data 401 for _, dig := range fetchedData.PurgedElements { 402 // delete purged key from missing keys 403 for missingPvtRWKey := range pvtdataRetrievalInfo.eligibleMissingKeys { 404 if missingPvtRWKey.namespace == dig.Namespace && 405 missingPvtRWKey.collection == dig.Collection && 406 missingPvtRWKey.seqInBlock == dig.SeqInBlock && 407 missingPvtRWKey.txID == dig.TxId { 408 delete(pvtdataRetrievalInfo.eligibleMissingKeys, missingPvtRWKey) 409 pdp.logger.Warningf("Missing key because was purged or will soon be purged, "+ 410 "continue block commit without [%+v] in private rwset", missingPvtRWKey) 411 } 412 } 413 } 414 415 return len(pvtdataRetrievalInfo.eligibleMissingKeys) > skipped 416 } 417 418 // prepareBlockPvtdata consolidates the fetched private data as well as ineligible and eligible 419 // missing private data into a ledger.BlockPvtdata for the PvtdataProvider to return to the consumer 420 func (pdp *PvtdataProvider) prepareBlockPvtdata(pvtdata rwsetByKeys, pvtdataRetrievalInfo *pvtdataRetrievalInfo) *ledger.BlockPvtdata { 421 blockPvtdata := &ledger.BlockPvtdata{ 422 PvtData: make(ledger.TxPvtDataMap), 423 MissingPvtData: make(ledger.TxMissingPvtDataMap), 424 } 425 426 if len(pvtdataRetrievalInfo.eligibleMissingKeys) == 0 { 427 pdp.logger.Infof("Successfully fetched all eligible collection private write sets for block [%d]", pdp.blockNum) 428 } else { 429 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]", 430 pdp.blockNum, pvtdataRetrievalInfo.eligibleMissingKeys) 431 } 432 433 for seqInBlock, nsRWS := range pvtdata.bySeqsInBlock() { 434 // add all found pvtdata to blockPvtDataPvtdata for seqInBlock 435 blockPvtdata.PvtData[seqInBlock] = &ledger.TxPvtData{ 436 SeqInBlock: seqInBlock, 437 WriteSet: nsRWS.toRWSet(), 438 } 439 } 440 441 for key := range pvtdataRetrievalInfo.eligibleMissingKeys { 442 blockPvtdata.MissingPvtData.Add(key.seqInBlock, key.namespace, key.collection, true) 443 } 444 445 for key := range pvtdataRetrievalInfo.ineligibleMissingKeys { 446 blockPvtdata.MissingPvtData.Add(key.seqInBlock, key.namespace, key.collection, false) 447 } 448 449 return blockPvtdata 450 } 451 452 type pvtdataRetrievalInfo struct { 453 sources map[rwSetKey][]*peer.Endorsement 454 txns []string 455 eligibleMissingKeys rwsetKeys 456 ineligibleMissingKeys rwsetKeys 457 } 458 459 // rwset types 460 461 type readWriteSets []*readWriteSet 462 463 func (s readWriteSets) toRWSet() *rwset.TxPvtReadWriteSet { 464 namespaces := make(map[string]*rwset.NsPvtReadWriteSet) 465 dataModel := rwset.TxReadWriteSet_KV 466 for _, rws := range s { 467 if _, exists := namespaces[rws.namespace]; !exists { 468 namespaces[rws.namespace] = &rwset.NsPvtReadWriteSet{ 469 Namespace: rws.namespace, 470 } 471 } 472 col := &rwset.CollectionPvtReadWriteSet{ 473 CollectionName: rws.collection, 474 Rwset: rws.rws, 475 } 476 namespaces[rws.namespace].CollectionPvtRwset = append(namespaces[rws.namespace].CollectionPvtRwset, col) 477 } 478 479 var namespaceSlice []*rwset.NsPvtReadWriteSet 480 for _, nsRWset := range namespaces { 481 namespaceSlice = append(namespaceSlice, nsRWset) 482 } 483 484 return &rwset.TxPvtReadWriteSet{ 485 DataModel: dataModel, 486 NsPvtRwset: namespaceSlice, 487 } 488 } 489 490 type readWriteSet struct { 491 rwSetKey 492 rws []byte 493 } 494 495 type rwsetByKeys map[rwSetKey][]byte 496 497 func (s rwsetByKeys) bySeqsInBlock() map[uint64]readWriteSets { 498 res := make(map[uint64]readWriteSets) 499 for k, rws := range s { 500 res[k.seqInBlock] = append(res[k.seqInBlock], &readWriteSet{ 501 rws: rws, 502 rwSetKey: k, 503 }) 504 } 505 return res 506 } 507 508 type rwsetInfo struct { 509 invalid bool 510 } 511 512 type rwsetKeys map[rwSetKey]rwsetInfo 513 514 // String returns a string representation of the rwsetKeys 515 func (s rwsetKeys) String() string { 516 var buffer bytes.Buffer 517 for k := range s { 518 buffer.WriteString(fmt.Sprintf("%s\n", k.String())) 519 } 520 return buffer.String() 521 } 522 523 type rwSetKey struct { 524 txID string 525 seqInBlock uint64 526 namespace string 527 collection string 528 hash string 529 } 530 531 // String returns a string representation of the rwSetKey 532 func (k *rwSetKey) String() string { 533 return fmt.Sprintf("txID: %s, seq: %d, namespace: %s, collection: %s, hash: %s", k.txID, k.seqInBlock, k.namespace, k.collection, k.hash) 534 } 535 536 func getTxIDBySeqInBlock(seqInBlock uint64, pvtdataToRetrieve []*ledger.TxPvtdataInfo) string { 537 for _, txPvtdataItem := range pvtdataToRetrieve { 538 if txPvtdataItem.SeqInBlock == seqInBlock { 539 return txPvtdataItem.TxID 540 } 541 } 542 543 return "" 544 } 545 546 func endorsersFromEligibleOrgs(ns string, col string, endorsers []*peer.Endorsement, orgs []string) []*peer.Endorsement { 547 var res []*peer.Endorsement 548 for _, e := range endorsers { 549 sID := &msp.SerializedIdentity{} 550 err := proto.Unmarshal(e.Endorser, sID) 551 if err != nil { 552 logger.Warning("Failed unmarshalling endorser:", err) 553 continue 554 } 555 if !util.Contains(sID.Mspid, orgs) { 556 logger.Debug(sID.Mspid, "isn't among the collection's orgs:", orgs, "for namespace", ns, ",collection", col) 557 continue 558 } 559 res = append(res, e) 560 } 561 return res 562 }