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 }