github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/lifecycle/event_broker.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  	"sync"
    11  
    12  	"github.com/hyperledger/fabric/core/container/externalbuilder"
    13  	"github.com/hyperledger/fabric/core/ledger"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // EventBroker receives events from lifecycle cache and in turn invokes the registered listeners
    18  type EventBroker struct {
    19  	chaincodeStore       ChaincodeStore
    20  	ebMetadata           *externalbuilder.MetadataProvider
    21  	pkgParser            PackageParser
    22  	defineCallbackStatus *sync.Map
    23  
    24  	mutex     sync.Mutex
    25  	listeners map[string][]ledger.ChaincodeLifecycleEventListener
    26  }
    27  
    28  func NewEventBroker(chaincodeStore ChaincodeStore, pkgParser PackageParser, ebMetadata *externalbuilder.MetadataProvider) *EventBroker {
    29  	return &EventBroker{
    30  		chaincodeStore:       chaincodeStore,
    31  		ebMetadata:           ebMetadata,
    32  		pkgParser:            pkgParser,
    33  		listeners:            make(map[string][]ledger.ChaincodeLifecycleEventListener),
    34  		defineCallbackStatus: &sync.Map{},
    35  	}
    36  }
    37  
    38  func (b *EventBroker) RegisterListener(channelID string, listener ledger.ChaincodeLifecycleEventListener) {
    39  	b.mutex.Lock()
    40  	defer b.mutex.Unlock()
    41  	b.listeners[channelID] = append(b.listeners[channelID], listener)
    42  }
    43  
    44  // ProcessInstallEvent gets invoked when a chaincode is installed
    45  func (b *EventBroker) ProcessInstallEvent(localChaincode *LocalChaincode) {
    46  	logger.Debugf("ProcessInstallEvent() - localChaincode = %s", localChaincode.Info)
    47  	dbArtifacts, err := b.loadDBArtifacts(localChaincode.Info.PackageID)
    48  	if err != nil {
    49  		logger.Errorf("Error while loading db artifacts for chaincode package with package ID [%s]: %s",
    50  			localChaincode.Info.PackageID, err)
    51  		return
    52  	}
    53  	for channelID, channelCache := range localChaincode.References {
    54  		listenersInvokedOnChannel := false
    55  		for chaincodeName, cachedChaincode := range channelCache {
    56  			if !isChaincodeInvokable(cachedChaincode) {
    57  				continue
    58  			}
    59  			ccdef := &ledger.ChaincodeDefinition{
    60  				Name:              chaincodeName,
    61  				Version:           cachedChaincode.Definition.EndorsementInfo.Version,
    62  				Hash:              []byte(cachedChaincode.InstallInfo.PackageID),
    63  				CollectionConfigs: cachedChaincode.Definition.Collections,
    64  			}
    65  			b.invokeListeners(channelID, ccdef, dbArtifacts)
    66  			listenersInvokedOnChannel = true
    67  		}
    68  		if listenersInvokedOnChannel {
    69  			// In the legacy lscc the install was split into two phases
    70  			// In the first phase, all the listener will be invoked and in the second phase,
    71  			// the install will proceed and finally will, give a call back whether the install
    72  			// is succeeded.
    73  			// The purpose of splitting this in two phases was to essentially not miss on an install
    74  			// event in the case of a system crash immediately after install and before the listeners
    75  			// gets a chance.
    76  			// However, in the current install model, the lifecycle cache receives the event only after
    77  			// the install is complete. So, for now, call the done on the listeners with a hard-wired 'true'
    78  			b.invokeDoneOnListeners(channelID, true)
    79  		}
    80  	}
    81  	return
    82  }
    83  
    84  // ProcessApproveOrDefineEvent gets invoked by an event that makes approve and define to be true
    85  // This should be OK even if this function gets invoked on defined and approved events separately because
    86  // the first check in this function evaluates the final condition. However, the current cache implementation
    87  // invokes this function when approve and define both become true.
    88  func (b *EventBroker) ProcessApproveOrDefineEvent(channelID string, chaincodeName string, cachedChaincode *CachedChaincodeDefinition) {
    89  	logger.Debugw("processApproveOrDefineEvent()", "channelID", channelID, "chaincodeName", chaincodeName, "cachedChaincode", cachedChaincode)
    90  	if !isChaincodeInvokable(cachedChaincode) {
    91  		return
    92  	}
    93  	dbArtifacts, err := b.loadDBArtifacts(cachedChaincode.InstallInfo.PackageID)
    94  	if err != nil {
    95  		logger.Errorf("Error while loading db artifacts for chaincode package with package ID [%s]: %s",
    96  			cachedChaincode.InstallInfo.PackageID, err)
    97  		return
    98  	}
    99  	ccdef := &ledger.ChaincodeDefinition{
   100  		Name:              chaincodeName,
   101  		Version:           cachedChaincode.Definition.EndorsementInfo.Version,
   102  		Hash:              []byte(cachedChaincode.InstallInfo.PackageID),
   103  		CollectionConfigs: cachedChaincode.Definition.Collections,
   104  	}
   105  	b.invokeListeners(channelID, ccdef, dbArtifacts)
   106  	b.defineCallbackStatus.Store(channelID, struct{}{})
   107  	return
   108  }
   109  
   110  // ApproveOrDefineCommitted gets invoked after the commit of state updates that triggered the invocation of
   111  // "ProcessApproveOrDefineEvent" function
   112  func (b *EventBroker) ApproveOrDefineCommitted(channelID string) {
   113  	_, ok := b.defineCallbackStatus.Load(channelID)
   114  	if !ok {
   115  		return
   116  	}
   117  	b.invokeDoneOnListeners(channelID, true)
   118  	b.defineCallbackStatus.Delete(channelID)
   119  }
   120  
   121  func (b *EventBroker) invokeListeners(channelID string, legacyDefinition *ledger.ChaincodeDefinition, dbArtifacts []byte) {
   122  	b.mutex.Lock()
   123  	defer b.mutex.Unlock()
   124  	channelListeners := b.listeners[channelID]
   125  	for _, l := range channelListeners {
   126  		if err := l.HandleChaincodeDeploy(legacyDefinition, dbArtifacts); err != nil {
   127  			// If a listener return this error and we propagate this error up the stack,
   128  			// following are the implications:
   129  			//
   130  			// 1) If this path gets called from the chaincode install operation, the install operation will need to
   131  			// handle the error, perhaps by aborting the install operation
   132  			// 2) If this path gets called from the block commit (that includes chaincode approve/define transaction)
   133  			// it will result in a peer panic.
   134  			//
   135  			// The behavior mentioned in (2) i.e., the installation of malformed chaincode package resulting in a
   136  			// peer panic on approve/define transaction commit may not be a desired behavior.
   137  			// Primarily because, a) the installation of chaincode is not a fundamental requirement for committer to function
   138  			// and b) typically, it may take longer dev cycles to fix the chaincode package issues as opposed to some admin
   139  			// operation (say, restart couchdb). Note that chaincode uninstall is not currently supported.
   140  			//
   141  			// In addition, another implication is that the behavior will be inconsistent on different peers. In the case of
   142  			// a faulty package, some peers may fail on install while others will report a success in installation and fail
   143  			// later at the approve/define commit time.
   144  			//
   145  			// So, instead of throwing this error up logging this here.
   146  			logger.Errorf("Error from listener during processing chaincode lifecycle event - %+v", errors.WithStack(err))
   147  		}
   148  	}
   149  }
   150  
   151  func (b *EventBroker) invokeDoneOnListeners(channelID string, succeeded bool) {
   152  	b.mutex.Lock()
   153  	defer b.mutex.Unlock()
   154  	channelListeners := b.listeners[channelID]
   155  	for _, l := range channelListeners {
   156  		l.ChaincodeDeployDone(succeeded)
   157  	}
   158  }
   159  
   160  func (b *EventBroker) loadDBArtifacts(packageID string) ([]byte, error) {
   161  	md, err := b.ebMetadata.PackageMetadata(packageID)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	if md != nil {
   167  		return md, nil
   168  	}
   169  
   170  	pkgBytes, err := b.chaincodeStore.Load(packageID)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	pkg, err := b.pkgParser.Parse(pkgBytes)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return pkg.DBArtifacts, nil
   179  }
   180  
   181  // isChaincodeInvokable returns true iff a chaincode is approved and installed and defined
   182  func isChaincodeInvokable(ccInfo *CachedChaincodeDefinition) bool {
   183  	return ccInfo.Approved && ccInfo.InstallInfo != nil && ccInfo.Definition != nil
   184  }