github.com/ewagmig/fabric@v2.1.1+incompatible/core/chaincode/lifecycle/cache.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package lifecycle 8 9 import ( 10 "fmt" 11 "sort" 12 "strconv" 13 "sync" 14 15 lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle" 16 "github.com/hyperledger/fabric/common/chaincode" 17 "github.com/hyperledger/fabric/common/util" 18 "github.com/hyperledger/fabric/core/chaincode/persistence" 19 "github.com/hyperledger/fabric/core/container/externalbuilder" 20 "github.com/hyperledger/fabric/core/ledger" 21 "github.com/hyperledger/fabric/protoutil" 22 23 "github.com/pkg/errors" 24 ) 25 26 type LocalChaincodeInfo struct { 27 Definition *ChaincodeDefinition 28 Approved bool 29 InstallInfo *ChaincodeInstallInfo 30 } 31 32 type ChaincodeInstallInfo struct { 33 PackageID string 34 Type string 35 Path string 36 Label string 37 } 38 39 type CachedChaincodeDefinition struct { 40 Definition *ChaincodeDefinition 41 Approved bool 42 InstallInfo *ChaincodeInstallInfo 43 44 // Hashes is the list of hashed keys in the implicit collection referring to this definition. 45 // These hashes are determined by the current sequence number of chaincode definition. When dirty, 46 // these hashes will be empty, and when not, they will be populated. 47 Hashes []string 48 } 49 50 type ChannelCache struct { 51 Chaincodes map[string]*CachedChaincodeDefinition 52 53 // InterestingHashes is a map of hashed key names to the chaincode name which they affect. 54 // These are to be used for the state listener, to mark chaincode definitions dirty when 55 // a write is made into the implicit collection for this org. Interesting hashes are 56 // added when marking a definition clean, and deleted when marking it dirty. 57 InterestingHashes map[string]string 58 } 59 60 // MetadataHandler is the interface through which the cache drives 61 // metadata updates for listeners such as gossip and service discovery 62 type MetadataHandler interface { 63 InitializeMetadata(channel string, chaincodes chaincode.MetadataSet) 64 UpdateMetadata(channel string, chaincodes chaincode.MetadataSet) 65 } 66 67 type Cache struct { 68 definedChaincodes map[string]*ChannelCache 69 Resources *Resources 70 MyOrgMSPID string 71 72 // mutex serializes lifecycle operations globally for the peer. It will cause a lifecycle update 73 // in one channel to potentially affect the throughput of another. However, relative to standard 74 // transactions, lifecycle updates should be quite rare, and this is a RW lock so in general, there 75 // should not be contention in the normal case. Because chaincode package installation is a peer global 76 // event, by synchronizing at a peer global level, we drastically simplify accounting for which 77 // chaincodes are installed and which channels that installed chaincode is currently in use on. 78 mutex sync.RWMutex 79 80 // localChaincodes is a map from the hash of the locally installed chaincode's proto 81 // encoded hash (yes, the hash of the hash), to a set of channels, to a set of chaincode 82 // definitions which reference this local installed chaincode hash. 83 localChaincodes map[string]*LocalChaincode 84 eventBroker *EventBroker 85 MetadataHandler MetadataHandler 86 87 chaincodeCustodian *ChaincodeCustodian 88 } 89 90 type LocalChaincode struct { 91 Info *ChaincodeInstallInfo 92 References map[string]map[string]*CachedChaincodeDefinition 93 } 94 95 // ToInstalledChaincode converts a LocalChaincode to an InstalledChaincode, 96 // which is returned by lifecycle queries. 97 func (l *LocalChaincode) ToInstalledChaincode() *chaincode.InstalledChaincode { 98 references := l.createMetadataMapFromReferences() 99 return &chaincode.InstalledChaincode{ 100 PackageID: l.Info.PackageID, 101 Label: l.Info.Label, 102 References: references, 103 } 104 } 105 106 // createMetadataMapFromReferences returns a map of channel name to a slice 107 // of chaincode metadata for the chaincode definitions that reference a 108 // specific local chaincode. This function should only be called by code that 109 // holds the lock on the cache. 110 func (l *LocalChaincode) createMetadataMapFromReferences() map[string][]*chaincode.Metadata { 111 references := map[string][]*chaincode.Metadata{} 112 for channel, chaincodeMap := range l.References { 113 metadata := []*chaincode.Metadata{} 114 for cc, cachedDefinition := range chaincodeMap { 115 metadata = append(metadata, &chaincode.Metadata{ 116 Name: cc, 117 Version: cachedDefinition.Definition.EndorsementInfo.Version, 118 }) 119 } 120 references[channel] = metadata 121 } 122 return references 123 } 124 125 func NewCache(resources *Resources, myOrgMSPID string, metadataManager MetadataHandler, custodian *ChaincodeCustodian, ebMetadata *externalbuilder.MetadataProvider) *Cache { 126 return &Cache{ 127 chaincodeCustodian: custodian, 128 definedChaincodes: map[string]*ChannelCache{}, 129 localChaincodes: map[string]*LocalChaincode{}, 130 Resources: resources, 131 MyOrgMSPID: myOrgMSPID, 132 eventBroker: NewEventBroker(resources.ChaincodeStore, resources.PackageParser, ebMetadata), 133 MetadataHandler: metadataManager, 134 } 135 } 136 137 // InitializeLocalChaincodes should be called once after cache creation (timing doesn't matter, 138 // though already installed chaincodes will not be invokable until it it completes). Ideally, 139 // this would be part of the constructor, but, we cannot rely on the chaincode store being created 140 // before the cache is created. 141 func (c *Cache) InitializeLocalChaincodes() error { 142 c.mutex.Lock() 143 defer c.mutex.Unlock() 144 ccPackages, err := c.Resources.ChaincodeStore.ListInstalledChaincodes() 145 if err != nil { 146 return errors.WithMessage(err, "could not list installed chaincodes") 147 } 148 149 for _, ccPackage := range ccPackages { 150 ccPackageBytes, err := c.Resources.ChaincodeStore.Load(ccPackage.PackageID) 151 if err != nil { 152 return errors.WithMessagef(err, "could not load chaincode with package ID '%s'", ccPackage.PackageID) 153 } 154 parsedCCPackage, err := c.Resources.PackageParser.Parse(ccPackageBytes) 155 if err != nil { 156 return errors.WithMessagef(err, "could not parse chaincode with package ID '%s'", ccPackage.PackageID) 157 } 158 c.handleChaincodeInstalledWhileLocked(true, parsedCCPackage.Metadata, ccPackage.PackageID) 159 } 160 161 logger.Infof("Initialized lifecycle cache with %d already installed chaincodes", len(c.localChaincodes)) 162 for channelID, chaincodeCache := range c.definedChaincodes { 163 approved, installed, runnable := 0, 0, 0 164 for _, cachedChaincode := range chaincodeCache.Chaincodes { 165 if cachedChaincode.Approved { 166 approved++ 167 } 168 if cachedChaincode.InstallInfo != nil { 169 installed++ 170 } 171 if cachedChaincode.Approved && cachedChaincode.InstallInfo != nil { 172 runnable++ 173 } 174 } 175 176 logger.Infof("Initialized lifecycle cache for channel '%s' with %d chaincodes runnable (%d approved, %d installed)", channelID, runnable, approved, installed) 177 } 178 179 return nil 180 } 181 182 // Initialize will populate the set of currently committed chaincode definitions 183 // for a channel into the cache. Note, it this looks like a bit of a DRY violation 184 // with respect to 'Update', but, the error handling is quite different and attempting 185 // to factor out the common pieces results in a net total of more code. 186 func (c *Cache) Initialize(channelID string, qe ledger.SimpleQueryExecutor) error { 187 c.mutex.Lock() 188 defer c.mutex.Unlock() 189 190 publicState := &SimpleQueryExecutorShim{ 191 Namespace: LifecycleNamespace, 192 SimpleQueryExecutor: qe, 193 } 194 195 metadatas, err := c.Resources.Serializer.DeserializeAllMetadata(NamespacesName, publicState) 196 if err != nil { 197 return errors.WithMessage(err, "could not query namespace metadata") 198 } 199 200 dirtyChaincodes := map[string]struct{}{} 201 202 for namespace, metadata := range metadatas { 203 switch metadata.Datatype { 204 case ChaincodeDefinitionType: 205 dirtyChaincodes[namespace] = struct{}{} 206 default: 207 // non-chaincode 208 } 209 } 210 211 return c.update(true, channelID, dirtyChaincodes, qe) 212 } 213 214 // HandleChaincodeInstalled should be invoked whenever a new chaincode is installed 215 func (c *Cache) HandleChaincodeInstalled(md *persistence.ChaincodePackageMetadata, packageID string) { 216 c.mutex.Lock() 217 defer c.mutex.Unlock() 218 c.handleChaincodeInstalledWhileLocked(false, md, packageID) 219 } 220 221 func (c *Cache) handleChaincodeInstalledWhileLocked(initializing bool, md *persistence.ChaincodePackageMetadata, packageID string) { 222 // it would be nice to get this value from the serialization package, but it was not obvious 223 // how to expose this in a nice way, so we manually compute it. 224 encodedCCHash := protoutil.MarshalOrPanic(&lb.StateData{ 225 Type: &lb.StateData_String_{String_: packageID}, 226 }) 227 hashOfCCHash := string(util.ComputeSHA256(encodedCCHash)) 228 localChaincode, ok := c.localChaincodes[hashOfCCHash] 229 if !ok { 230 localChaincode = &LocalChaincode{ 231 References: map[string]map[string]*CachedChaincodeDefinition{}, 232 } 233 c.localChaincodes[hashOfCCHash] = localChaincode 234 c.chaincodeCustodian.NotifyInstalled(packageID) 235 } 236 localChaincode.Info = &ChaincodeInstallInfo{ 237 PackageID: packageID, 238 Type: md.Type, 239 Path: md.Path, 240 Label: md.Label, 241 } 242 for channelID, channelCache := range localChaincode.References { 243 for chaincodeName, cachedChaincode := range channelCache { 244 cachedChaincode.InstallInfo = localChaincode.Info 245 logger.Infof("Installed chaincode with package ID '%s' now available on channel %s for chaincode definition %s:%s", packageID, channelID, chaincodeName, cachedChaincode.Definition.EndorsementInfo.Version) 246 c.chaincodeCustodian.NotifyInstalledAndRunnable(packageID) 247 } 248 } 249 250 if !initializing { 251 c.eventBroker.ProcessInstallEvent(localChaincode) 252 c.handleMetadataUpdates(localChaincode) 253 } 254 } 255 256 // HandleStateUpdates is required to implement the ledger state listener interface. It applies 257 // any state updates to the cache. 258 func (c *Cache) HandleStateUpdates(trigger *ledger.StateUpdateTrigger) error { 259 c.mutex.Lock() 260 defer c.mutex.Unlock() 261 channelID := trigger.LedgerID 262 updates, ok := trigger.StateUpdates[LifecycleNamespace] 263 if !ok { 264 return errors.Errorf("no state updates for promised namespace _lifecycle") 265 } 266 267 dirtyChaincodes := map[string]struct{}{} 268 269 for _, publicUpdate := range updates.PublicUpdates { 270 matches := SequenceMatcher.FindStringSubmatch(publicUpdate.Key) 271 if len(matches) != 2 { 272 continue 273 } 274 275 dirtyChaincodes[matches[1]] = struct{}{} 276 } 277 278 channelCache, ok := c.definedChaincodes[channelID] 279 280 // if the channel cache does not yet exist, there are no interesting hashes, so skip 281 if ok { 282 for collection, privateUpdates := range updates.CollHashUpdates { 283 matches := ImplicitCollectionMatcher.FindStringSubmatch(collection) 284 if len(matches) != 2 { 285 // This is not an implicit collection 286 continue 287 } 288 289 if matches[1] != c.MyOrgMSPID { 290 // This is not our implicit collection 291 continue 292 } 293 294 for _, privateUpdate := range privateUpdates { 295 chaincodeName, ok := channelCache.InterestingHashes[string(privateUpdate.KeyHash)] 296 if ok { 297 dirtyChaincodes[chaincodeName] = struct{}{} 298 } 299 } 300 } 301 } 302 303 err := c.update(false, channelID, dirtyChaincodes, trigger.PostCommitQueryExecutor) 304 if err != nil { 305 return errors.WithMessage(err, "error updating cache") 306 } 307 308 return nil 309 } 310 311 // InterestedInNamespaces is required to implement the ledger state listener interface 312 func (c *Cache) InterestedInNamespaces() []string { 313 return []string{LifecycleNamespace} 314 } 315 316 // StateCommitDone is required to implement the ledger state listener interface 317 func (c *Cache) StateCommitDone(channelName string) { 318 // NOTE: It's extremely tempting to acquire the write lock in HandleStateUpdate 319 // and release it here, however, this is asking for a deadlock. In particular, 320 // because the 'write lock' on the state is only held for a short period 321 // between HandleStateUpdate and StateCommitDone, it's possible (in fact likely) 322 // that a chaincode invocation will acquire a read-lock on the world state, then attempt 323 // to get chaincode info from the cache, resulting in a deadlock. So, we choose 324 // potential inconsistenty between the cache and the world state which the callers 325 // must detect and cope with as necessary. Note, the cache will always be _at least_ 326 // as current as the committed state. 327 c.eventBroker.ApproveOrDefineCommitted(channelName) 328 } 329 330 // ChaincodeInfo returns the chaincode definition and its install info. 331 // An error is returned only if either the channel or the chaincode do not exist. 332 func (c *Cache) ChaincodeInfo(channelID, name string) (*LocalChaincodeInfo, error) { 333 if name == LifecycleNamespace { 334 ac, ok := c.Resources.ChannelConfigSource.GetStableChannelConfig(channelID).ApplicationConfig() 335 if !ok { 336 return nil, errors.Errorf("application config does not exist for channel '%s'", channelID) 337 } 338 if !ac.Capabilities().LifecycleV20() { 339 return nil, errors.Errorf("cannot use _lifecycle without V2_0 application capabilities enabled for channel '%s'", channelID) 340 } 341 return c.getLifecycleSCCChaincodeInfo(channelID) 342 } 343 344 c.mutex.RLock() 345 defer c.mutex.RUnlock() 346 channelChaincodes, ok := c.definedChaincodes[channelID] 347 if !ok { 348 return nil, errors.Errorf("unknown channel '%s'", channelID) 349 } 350 351 cachedChaincode, ok := channelChaincodes.Chaincodes[name] 352 if !ok { 353 return nil, errors.Errorf("unknown chaincode '%s' for channel '%s'", name, channelID) 354 } 355 356 return &LocalChaincodeInfo{ 357 Definition: cachedChaincode.Definition, 358 InstallInfo: cachedChaincode.InstallInfo, 359 Approved: cachedChaincode.Approved, 360 }, nil 361 } 362 363 func (c *Cache) getLifecycleSCCChaincodeInfo(channelID string) (*LocalChaincodeInfo, error) { 364 policyBytes, err := c.Resources.LifecycleEndorsementPolicyAsBytes(channelID) 365 if err != nil { 366 return nil, err 367 } 368 369 return &LocalChaincodeInfo{ 370 Definition: &ChaincodeDefinition{ 371 ValidationInfo: &lb.ChaincodeValidationInfo{ 372 ValidationParameter: policyBytes, 373 }, 374 Sequence: 1, 375 }, 376 Approved: true, 377 InstallInfo: &ChaincodeInstallInfo{}, 378 }, nil 379 } 380 381 // ListInstalledChaincodes returns a slice containing all of the information 382 // about the installed chaincodes. 383 func (c *Cache) ListInstalledChaincodes() []*chaincode.InstalledChaincode { 384 c.mutex.RLock() 385 defer c.mutex.RUnlock() 386 387 installedChaincodes := []*chaincode.InstalledChaincode{} 388 for _, lc := range c.localChaincodes { 389 if lc.Info == nil { 390 // the update function adds an entry to localChaincodes 391 // even if it isn't yet installed 392 continue 393 } 394 installedChaincodes = append(installedChaincodes, lc.ToInstalledChaincode()) 395 } 396 397 return installedChaincodes 398 } 399 400 // GetInstalledChaincode returns all of the information about a specific 401 // installed chaincode. 402 func (c *Cache) GetInstalledChaincode(packageID string) (*chaincode.InstalledChaincode, error) { 403 c.mutex.RLock() 404 defer c.mutex.RUnlock() 405 406 for _, lc := range c.localChaincodes { 407 if lc.Info == nil { 408 // the update function adds an entry to localChaincodes 409 // even if it isn't yet installed 410 continue 411 } 412 if lc.Info.PackageID == packageID { 413 return lc.ToInstalledChaincode(), nil 414 } 415 } 416 417 return nil, errors.Errorf("could not find chaincode with package id '%s'", packageID) 418 } 419 420 // update should only be called with the write lock already held 421 func (c *Cache) update(initializing bool, channelID string, dirtyChaincodes map[string]struct{}, qe ledger.SimpleQueryExecutor) error { 422 channelCache, ok := c.definedChaincodes[channelID] 423 if !ok { 424 channelCache = &ChannelCache{ 425 Chaincodes: map[string]*CachedChaincodeDefinition{}, 426 InterestingHashes: map[string]string{}, 427 } 428 c.definedChaincodes[channelID] = channelCache 429 } 430 431 publicState := &SimpleQueryExecutorShim{ 432 Namespace: LifecycleNamespace, 433 SimpleQueryExecutor: qe, 434 } 435 436 orgState := &PrivateQueryExecutorShim{ 437 Namespace: LifecycleNamespace, 438 Collection: ImplicitCollectionNameForOrg(c.MyOrgMSPID), 439 State: qe, 440 } 441 442 for name := range dirtyChaincodes { 443 logger.Infof("Updating cached definition for chaincode '%s' on channel '%s'", name, channelID) 444 cachedChaincode, ok := channelCache.Chaincodes[name] 445 if !ok { 446 cachedChaincode = &CachedChaincodeDefinition{} 447 channelCache.Chaincodes[name] = cachedChaincode 448 } 449 450 for _, hash := range cachedChaincode.Hashes { 451 delete(channelCache.InterestingHashes, hash) 452 } 453 454 exists, chaincodeDefinition, err := c.Resources.ChaincodeDefinitionIfDefined(name, publicState) 455 if err != nil { 456 return errors.WithMessagef(err, "could not get chaincode definition for '%s' on channel '%s'", name, channelID) 457 } 458 459 if !exists { 460 // the chaincode definition was deleted, this is currently not 461 // possible, but there should be no problems with that. 462 delete(channelCache.Chaincodes, name) 463 continue 464 } 465 466 privateName := fmt.Sprintf("%s#%d", name, chaincodeDefinition.Sequence) 467 hashKey := FieldKey(ChaincodeSourcesName, privateName, "PackageID") 468 hashOfCCHash, err := orgState.GetStateHash(hashKey) 469 if err != nil { 470 return errors.WithMessagef(err, "could not check opaque org state for chaincode source hash for '%s' on channel '%s'", name, channelID) 471 } 472 473 localChaincode, ok := c.localChaincodes[string(hashOfCCHash)] 474 if !ok { 475 localChaincode = &LocalChaincode{ 476 References: map[string]map[string]*CachedChaincodeDefinition{}, 477 } 478 c.localChaincodes[string(hashOfCCHash)] = localChaincode 479 } 480 481 if !initializing { 482 // check for existing local chaincodes that reference this chaincode 483 // name on this channel 484 for _, lc := range c.localChaincodes { 485 if ref, ok := lc.References[channelID][name]; ok { 486 if ref.InstallInfo == nil || localChaincode.Info == nil { 487 continue 488 } 489 if ref.InstallInfo.PackageID == localChaincode.Info.PackageID { 490 continue 491 } 492 493 // remove existing local chaincode reference, which referred to a 494 // previous chaincode definition 495 delete(lc.References[channelID], name) 496 if len(lc.References[channelID]) == 0 { 497 delete(lc.References, channelID) 498 499 // check to see if this "local" chaincode is installed (an entry 500 // is added into local chaincodes for active chaincode definition 501 // references regardless of whether the peer has a chaincode 502 // package installed) 503 if lc.Info == nil { 504 continue 505 } 506 507 // finally, check to see if this chaincode is referenced in any 508 // channel. if not, stop the chaincode here 509 if len(lc.References) == 0 { 510 logger.Debugf("chaincode package with label %s is no longer referenced and will be stopped", lc.Info.Label) 511 c.chaincodeCustodian.NotifyStoppable(lc.Info.PackageID) 512 } 513 } 514 } 515 } 516 } 517 518 cachedChaincode.Definition = chaincodeDefinition 519 cachedChaincode.Approved = false 520 521 cachedChaincode.Hashes = []string{ 522 string(util.ComputeSHA256([]byte(MetadataKey(NamespacesName, privateName)))), 523 string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "EndorsementInfo")))), 524 string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "ValidationInfo")))), 525 string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "Collections")))), 526 string(util.ComputeSHA256([]byte(FieldKey(ChaincodeSourcesName, privateName, "PackageID")))), 527 } 528 529 for _, hash := range cachedChaincode.Hashes { 530 channelCache.InterestingHashes[hash] = name 531 } 532 533 ok, err = c.Resources.Serializer.IsSerialized(NamespacesName, privateName, chaincodeDefinition.Parameters(), orgState) 534 535 if err != nil { 536 return errors.WithMessagef(err, "could not check opaque org state for '%s' on channel '%s'", name, channelID) 537 } 538 if !ok { 539 logger.Debugf("Channel %s for chaincode definition %s:%s does not have our org's approval", channelID, name, chaincodeDefinition.EndorsementInfo.Version) 540 continue 541 } 542 543 cachedChaincode.Approved = true 544 545 isLocalPackage, err := c.Resources.Serializer.IsMetadataSerialized(ChaincodeSourcesName, privateName, &ChaincodeLocalPackage{}, orgState) 546 if err != nil { 547 return errors.WithMessagef(err, "could not check opaque org state for chaincode source for '%s' on channel '%s'", name, channelID) 548 } 549 550 if !isLocalPackage { 551 logger.Debugf("Channel %s for chaincode definition %s:%s does not have a chaincode source defined", channelID, name, chaincodeDefinition.EndorsementInfo.Version) 552 continue 553 } 554 555 cachedChaincode.InstallInfo = localChaincode.Info 556 if localChaincode.Info != nil { 557 logger.Infof("Chaincode with package ID '%s' now available on channel %s for chaincode definition %s:%s", localChaincode.Info.PackageID, channelID, name, cachedChaincode.Definition.EndorsementInfo.Version) 558 c.chaincodeCustodian.NotifyInstalledAndRunnable(localChaincode.Info.PackageID) 559 } else { 560 logger.Debugf("Chaincode definition for chaincode '%s' on channel '%s' is approved, but not installed", name, channelID) 561 } 562 563 channelReferences, ok := localChaincode.References[channelID] 564 if !ok { 565 channelReferences = map[string]*CachedChaincodeDefinition{} 566 localChaincode.References[channelID] = channelReferences 567 } 568 569 channelReferences[name] = cachedChaincode 570 571 if !initializing { 572 c.eventBroker.ProcessApproveOrDefineEvent(channelID, name, cachedChaincode) 573 } 574 } 575 576 if !initializing { 577 c.handleMetadataUpdatesForChannel(channelID) 578 } 579 580 return nil 581 } 582 583 // RegisterListener registers an event listener for receiving an event when a chaincode becomes invokable 584 func (c *Cache) RegisterListener(channelID string, listener ledger.ChaincodeLifecycleEventListener) { 585 c.eventBroker.RegisterListener(channelID, listener) 586 } 587 588 func (c *Cache) InitializeMetadata(channel string) { 589 c.mutex.RLock() 590 defer c.mutex.RUnlock() 591 592 ms, err := c.retrieveChaincodesMetadataSetWhileLocked(channel) 593 if err != nil { 594 logger.Warningf("no metadata found on channel '%s', err %s", channel, err) 595 return 596 } 597 598 c.MetadataHandler.InitializeMetadata(channel, ms) 599 } 600 601 func (c *Cache) retrieveChaincodesMetadataSetWhileLocked(channelID string) (chaincode.MetadataSet, error) { 602 channelChaincodes, ok := c.definedChaincodes[channelID] 603 if !ok { 604 return nil, errors.Errorf("unknown channel '%s'", channelID) 605 } 606 607 keys := make([]string, 0, len(channelChaincodes.Chaincodes)) 608 for name := range channelChaincodes.Chaincodes { 609 keys = append(keys, name) 610 } 611 sort.Strings(keys) 612 613 metadataSet := chaincode.MetadataSet{} 614 for _, name := range keys { 615 def := channelChaincodes.Chaincodes[name] 616 617 // report the sequence as the version to service discovery since 618 // the version is no longer required to change when updating any 619 // part of the chaincode definition 620 metadataSet = append(metadataSet, 621 chaincode.Metadata{ 622 Name: name, 623 Version: strconv.FormatInt(def.Definition.Sequence, 10), 624 Policy: def.Definition.ValidationInfo.ValidationParameter, 625 CollectionsConfig: def.Definition.Collections, 626 Approved: def.Approved, 627 Installed: def.InstallInfo != nil, 628 }, 629 ) 630 } 631 632 // get the chaincode info for _lifecycle 633 lc, err := c.getLifecycleSCCChaincodeInfo(channelID) 634 if err != nil { 635 return nil, err 636 } 637 638 // add it to the metadataset so _lifecycle can also be queried 639 // via service discovery 640 metadataSet = append(metadataSet, 641 chaincode.Metadata{ 642 Name: LifecycleNamespace, 643 Version: strconv.FormatInt(lc.Definition.Sequence, 10), 644 Policy: lc.Definition.ValidationInfo.ValidationParameter, 645 Approved: lc.Approved, 646 Installed: lc.InstallInfo != nil, 647 }, 648 ) 649 650 return metadataSet, nil 651 } 652 653 func (c *Cache) handleMetadataUpdates(localChaincode *LocalChaincode) { 654 for channelID := range localChaincode.References { 655 c.handleMetadataUpdatesForChannel(channelID) 656 } 657 } 658 659 func (c *Cache) handleMetadataUpdatesForChannel(channelID string) { 660 ms, err := c.retrieveChaincodesMetadataSetWhileLocked(channelID) 661 if err != nil { 662 logger.Warningf("no metadata found on channel '%s': %s", channelID, err) 663 return 664 } 665 666 c.MetadataHandler.UpdateMetadata(channelID, ms) 667 }