repo_description: Hyperledger Fabric is an enterprise-grade permissioned distributed ledger framework for developing solutions and applications. Its modular and versatile design satisfies a broad range of industry use cases. It offers a unique approach to consensus that enables performance at scale while preserving privacy.
repo_topics: blockchain confidentiality consensus distributed-ledger fabric hyperledger
code: /*
  Copyright IBM Corp. All Rights Reserved.
  
  SPDX-License-Identifier: Apache-2.0
  */
  
  package cclifecycle
  
  import (
  	"sync"
  
  	"github.com/hyperledger/fabric-lib-go/common/flogging"
  	"github.com/hyperledger/fabric/common/chaincode"
  	"github.com/pkg/errors"
  )
  
  // Logger is the logging instance for this package.
  // It's exported because the tests override its backend
  var Logger = flogging.MustGetLogger("discovery.lifecycle")
  
  // MetadataManager manages information about lscc chaincodes.
  type MetadataManager struct {
  	sync.RWMutex
  	listeners              []MetadataChangeListener
  	installedCCs           []chaincode.InstalledChaincode
  	deployedCCsByChannel   map[string]*chaincode.MetadataMapping
  	queryCreatorsByChannel map[string]QueryCreator
  }
  
  //go:generate mockery -dir . -name MetadataChangeListener -case underscore  -output mocks/
  
  // MetadataChangeListener runs whenever there is a change to the metadata
  // of a chaincode in the context of a specific channel
  type MetadataChangeListener interface {
  	HandleMetadataUpdate(channel string, chaincodes chaincode.MetadataSet)
  }
  
  // HandleMetadataUpdateFunc is triggered upon a change in the chaincode lifecycle
  type HandleMetadataUpdateFunc func(channel string, chaincodes chaincode.MetadataSet)
  
  // HandleMetadataUpdate runs whenever there is a change to the metadata
  // of a chaincode in the context of a specific channel
  func (handleMetadataUpdate HandleMetadataUpdateFunc) HandleMetadataUpdate(channel string, chaincodes chaincode.MetadataSet) {
  	handleMetadataUpdate(channel, chaincodes)
  }
  
  //go:generate mockery -dir . -name Enumerator -case underscore  -output mocks/
  
  // Enumerator enumerates chaincodes
  type Enumerator interface {
  	// Enumerate returns the installed chaincodes
  	Enumerate() ([]chaincode.InstalledChaincode, error)
  }
  
  // EnumerateFunc enumerates installed chaincodes
  type EnumerateFunc func() ([]chaincode.InstalledChaincode, error)
  
  // Enumerate enumerates chaincodes
  func (enumerate EnumerateFunc) Enumerate() ([]chaincode.InstalledChaincode, error) {
  	return enumerate()
  }
  
  //go:generate mockery -dir . -name Query -case underscore  -output mocks/
  
  // Query queries the state
  type Query interface {
  	// GetState gets the value for given namespace and key. For a chaincode, the namespace corresponds to the chaincodeId
  	GetState(namespace string, key string) ([]byte, error)
  
  	// Done releases resources occupied by the QueryExecutor
  	Done()
  }
  
  //go:generate mockery -dir . -name QueryCreator -case underscore  -output mocks/
  
  // QueryCreator creates queries
  type QueryCreator interface {
  	// NewQuery creates a new Query, or error on failure
  	NewQuery() (Query, error)
  }
  
  // QueryCreatorFunc creates a new query
  type QueryCreatorFunc func() (Query, error)
  
  // NewQuery creates a new Query, or error on failure
  func (queryCreator QueryCreatorFunc) NewQuery() (Query, error) {
  	return queryCreator()
  }
  
  // NewMetadataManager creates a metadata manager for lscc chaincodes.
  func NewMetadataManager(installedChaincodes Enumerator) (*MetadataManager, error) {
  	installedCCs, err := installedChaincodes.Enumerate()
  	if err != nil {
  		return nil, errors.Wrap(err, "failed listing installed chaincodes")
  	}
  
  	return &MetadataManager{
  		installedCCs:           installedCCs,
  		deployedCCsByChannel:   map[string]*chaincode.MetadataMapping{},
  		queryCreatorsByChannel: map[string]QueryCreator{},
  	}, nil
  }
  
  // Metadata returns the metadata of the chaincode on the given channel,
  // or nil if not found or an error occurred at retrieving it
  func (m *MetadataManager) Metadata(channel string, cc string, collections ...string) *chaincode.Metadata {
  	queryCreator := m.queryCreatorsByChannel[channel]
  	if queryCreator == nil {
  		Logger.Warning("Requested Metadata for non-existent channel", channel)
  		return nil
  	}
  	// Search the metadata in our local cache, and if it exists - return it, but only if
  	// no collections were specified in the invocation.
  	if md, found := m.deployedCCsByChannel[channel].Lookup(cc); found && len(collections) == 0 {
  		Logger.Debug("Returning metadata for channel", channel, ", chaincode", cc, ":", md)
  		return &md
  	}
  	query, err := queryCreator.NewQuery()
  	if err != nil {
  		Logger.Error("Failed obtaining new query for channel", channel, ":", err)
  		return nil
  	}
  	md, err := DeployedChaincodes(query, AcceptAll, len(collections) > 0, cc)
  	if err != nil {
  		Logger.Error("Failed querying LSCC for channel", channel, ":", err)
  		return nil
  	}
  	if len(md) == 0 {
  		Logger.Warn("Chaincode", cc, "isn't defined in channel", channel)
  		return nil
  	}
  
  	return &md[0]
  }
  
  func (m *MetadataManager) initMetadataForChannel(channel string, queryCreator QueryCreator) error {
  	if m.isChannelMetadataInitialized(channel) {
  		return nil
  	}
  	// Create a new metadata mapping for the channel
  	query, err := queryCreator.NewQuery()
  	if err != nil {
  		return errors.WithStack(err)
  	}
  	ccs, err := queryChaincodeDefinitions(query, m.installedCCs, DeployedChaincodes)
  	if err != nil {
  		return errors.WithStack(err)
  	}
  	m.createMetadataForChannel(channel, queryCreator)
  	m.updateState(channel, ccs)
  	return nil
  }
  
  func (m *MetadataManager) createMetadataForChannel(channel string, newQuery QueryCreator) {
  	m.Lock()
  	defer m.Unlock()
  	m.deployedCCsByChannel[channel] = chaincode.NewMetadataMapping()
  	m.queryCreatorsByChannel[channel] = newQuery
  }
  
  func (m *MetadataManager) isChannelMetadataInitialized(channel string) bool {
  	m.RLock()
  	defer m.RUnlock()
  	_, exists := m.deployedCCsByChannel[channel]
  	return exists
  }
  
  func (m *MetadataManager) updateState(channel string, ccUpdate chaincode.MetadataSet) {
  	m.RLock()
  	defer m.RUnlock()
  	for _, cc := range ccUpdate {
  		m.deployedCCsByChannel[channel].Update(cc)
  	}
  }
  
  func (m *MetadataManager) fireChangeListeners(channel string) {
  	m.RLock()
  	md := m.deployedCCsByChannel[channel]
  	m.RUnlock()
  	for _, listener := range m.listeners {
  		aggregatedMD := md.Aggregate()
  		listener.HandleMetadataUpdate(channel, aggregatedMD)
  	}
  	Logger.Debug("Listeners for channel", channel, "invoked")
  }
  
  // NewChannelSubscription subscribes to a channel
  func (m *MetadataManager) NewChannelSubscription(channel string, queryCreator QueryCreator) (*Subscription, error) {
  	sub := &Subscription{
  		metadataManager: m,
  		channel:         channel,
  		queryCreator:    queryCreator,
  	}
  	// Initialize metadata for the channel.
  	// This loads metadata about all installed chaincodes
  	if err := m.initMetadataForChannel(channel, queryCreator); err != nil {
  		return nil, errors.WithStack(err)
  	}
  	m.fireChangeListeners(channel)
  	return sub, nil
  }
  
  // AddListener registers the given listener to be triggered upon a lifecycle change
  func (m *MetadataManager) AddListener(listener MetadataChangeListener) {
  	m.Lock()
  	defer m.Unlock()
  	m.listeners = append(m.listeners, listener)
  }
  