go_study/fabric-main/core/chaincode/lifecycle/cache.go

693 lines
24 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package lifecycle
import (
"fmt"
"sort"
"strconv"
"sync"
lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/implicitcollection"
"github.com/hyperledger/fabric/core/chaincode/persistence"
"github.com/hyperledger/fabric/core/container/externalbuilder"
"github.com/hyperledger/fabric/core/ledger"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
)
type LocalChaincodeInfo struct {
Definition *ChaincodeDefinition
Approved bool
InstallInfo *ChaincodeInstallInfo
}
type ChaincodeInstallInfo struct {
PackageID string
Type string
Path string
Label string
}
type CachedChaincodeDefinition struct {
Definition *ChaincodeDefinition
Approved bool
InstallInfo *ChaincodeInstallInfo
// Hashes is the list of hashed keys in the implicit collection referring to this definition.
// These hashes are determined by the current sequence number of chaincode definition. When dirty,
// these hashes will be empty, and when not, they will be populated.
Hashes []string
}
type ChannelCache struct {
Chaincodes map[string]*CachedChaincodeDefinition
// InterestingHashes is a map of hashed key names to the chaincode name which they affect.
// These are to be used for the state listener, to mark chaincode definitions dirty when
// a write is made into the implicit collection for this org. Interesting hashes are
// added when marking a definition clean, and deleted when marking it dirty.
InterestingHashes map[string]string
}
// MetadataHandler is the interface through which the cache drives
// metadata updates for listeners such as gossip and service discovery
type MetadataHandler interface {
InitializeMetadata(channel string, chaincodes chaincode.MetadataSet)
UpdateMetadata(channel string, chaincodes chaincode.MetadataSet)
}
type Cache struct {
definedChaincodes map[string]*ChannelCache
Resources *Resources
MyOrgMSPID string
// mutex serializes lifecycle operations globally for the peer. It will cause a lifecycle update
// in one channel to potentially affect the throughput of another. However, relative to standard
// transactions, lifecycle updates should be quite rare, and this is a RW lock so in general, there
// should not be contention in the normal case. Because chaincode package installation is a peer global
// event, by synchronizing at a peer global level, we drastically simplify accounting for which
// chaincodes are installed and which channels that installed chaincode is currently in use on.
mutex sync.RWMutex
// localChaincodes is a map from the hash of the locally installed chaincode's proto
// encoded hash (yes, the hash of the hash), to a set of channels, to a set of chaincode
// definitions which reference this local installed chaincode hash.
localChaincodes map[string]*LocalChaincode
eventBroker *EventBroker
MetadataHandler MetadataHandler
chaincodeCustodian *ChaincodeCustodian
}
type LocalChaincode struct {
Info *ChaincodeInstallInfo
References map[string]map[string]*CachedChaincodeDefinition
}
// ToInstalledChaincode converts a LocalChaincode to an InstalledChaincode,
// which is returned by lifecycle queries.
func (l *LocalChaincode) ToInstalledChaincode() *chaincode.InstalledChaincode {
references := l.createMetadataMapFromReferences()
return &chaincode.InstalledChaincode{
PackageID: l.Info.PackageID,
Label: l.Info.Label,
References: references,
}
}
// createMetadataMapFromReferences returns a map of channel name to a slice
// of chaincode metadata for the chaincode definitions that reference a
// specific local chaincode. This function should only be called by code that
// holds the lock on the cache.
func (l *LocalChaincode) createMetadataMapFromReferences() map[string][]*chaincode.Metadata {
references := map[string][]*chaincode.Metadata{}
for channel, chaincodeMap := range l.References {
metadata := []*chaincode.Metadata{}
for cc, cachedDefinition := range chaincodeMap {
metadata = append(metadata, &chaincode.Metadata{
Name: cc,
Version: cachedDefinition.Definition.EndorsementInfo.Version,
})
}
references[channel] = metadata
}
return references
}
func NewCache(resources *Resources, myOrgMSPID string, metadataManager MetadataHandler, custodian *ChaincodeCustodian, ebMetadata *externalbuilder.MetadataProvider) *Cache {
return &Cache{
chaincodeCustodian: custodian,
definedChaincodes: map[string]*ChannelCache{},
localChaincodes: map[string]*LocalChaincode{},
Resources: resources,
MyOrgMSPID: myOrgMSPID,
eventBroker: NewEventBroker(resources.ChaincodeStore, resources.PackageParser, ebMetadata),
MetadataHandler: metadataManager,
}
}
// InitializeLocalChaincodes should be called once after cache creation (timing doesn't matter,
// though already installed chaincodes will not be invokable until it completes). Ideally,
// this would be part of the constructor, but, we cannot rely on the chaincode store being created
// before the cache is created.
func (c *Cache) InitializeLocalChaincodes() error {
c.mutex.Lock()
defer c.mutex.Unlock()
ccPackages, err := c.Resources.ChaincodeStore.ListInstalledChaincodes()
if err != nil {
return errors.WithMessage(err, "could not list installed chaincodes")
}
for _, ccPackage := range ccPackages {
ccPackageBytes, err := c.Resources.ChaincodeStore.Load(ccPackage.PackageID)
if err != nil {
return errors.WithMessagef(err, "could not load chaincode with package ID '%s'", ccPackage.PackageID)
}
parsedCCPackage, err := c.Resources.PackageParser.Parse(ccPackageBytes)
if err != nil {
return errors.WithMessagef(err, "could not parse chaincode with package ID '%s'", ccPackage.PackageID)
}
c.handleChaincodeInstalledWhileLocked(true, parsedCCPackage.Metadata, ccPackage.PackageID)
}
logger.Infof("Initialized lifecycle cache with %d already installed chaincodes", len(c.localChaincodes))
for channelID, chaincodeCache := range c.definedChaincodes {
approved, installed, runnable := 0, 0, 0
for _, cachedChaincode := range chaincodeCache.Chaincodes {
if cachedChaincode.Approved {
approved++
}
if cachedChaincode.InstallInfo != nil {
installed++
}
if cachedChaincode.Approved && cachedChaincode.InstallInfo != nil {
runnable++
}
}
logger.Infof("Initialized lifecycle cache for channel '%s' with %d chaincodes runnable (%d approved, %d installed)", channelID, runnable, approved, installed)
}
return nil
}
// Name returns the name of the listener
func (c *Cache) Name() string {
return "lifecycle cache listener"
}
// Initialize will populate the set of currently committed chaincode definitions
// for a channel into the cache. Note, it this looks like a bit of a DRY violation
// with respect to 'Update', but, the error handling is quite different and attempting
// to factor out the common pieces results in a net total of more code.
func (c *Cache) Initialize(channelID string, qe ledger.SimpleQueryExecutor) error {
c.mutex.Lock()
defer c.mutex.Unlock()
publicState := &SimpleQueryExecutorShim{
Namespace: LifecycleNamespace,
SimpleQueryExecutor: qe,
}
metadatas, err := c.Resources.Serializer.DeserializeAllMetadata(NamespacesName, publicState)
if err != nil {
return errors.WithMessage(err, "could not query namespace metadata")
}
dirtyChaincodes := map[string]struct{}{}
for namespace, metadata := range metadatas {
switch metadata.Datatype {
case ChaincodeDefinitionType:
dirtyChaincodes[namespace] = struct{}{}
default:
// non-chaincode
}
}
return c.update(true, channelID, dirtyChaincodes, qe)
}
// HandleChaincodeInstalled should be invoked whenever a new chaincode is installed
func (c *Cache) HandleChaincodeInstalled(md *persistence.ChaincodePackageMetadata, packageID string) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.handleChaincodeInstalledWhileLocked(false, md, packageID)
}
func (c *Cache) handleChaincodeInstalledWhileLocked(initializing bool, md *persistence.ChaincodePackageMetadata, packageID string) {
// it would be nice to get this value from the serialization package, but it was not obvious
// how to expose this in a nice way, so we manually compute it.
encodedCCHash := protoutil.MarshalOrPanic(&lb.StateData{
Type: &lb.StateData_String_{String_: packageID},
})
hashOfCCHash := string(util.ComputeSHA256(encodedCCHash))
localChaincode, ok := c.localChaincodes[hashOfCCHash]
if !ok {
localChaincode = &LocalChaincode{
References: map[string]map[string]*CachedChaincodeDefinition{},
}
c.localChaincodes[hashOfCCHash] = localChaincode
c.chaincodeCustodian.NotifyInstalled(packageID)
}
localChaincode.Info = &ChaincodeInstallInfo{
PackageID: packageID,
Type: md.Type,
Path: md.Path,
Label: md.Label,
}
for channelID, channelCache := range localChaincode.References {
for chaincodeName, cachedChaincode := range channelCache {
cachedChaincode.InstallInfo = localChaincode.Info
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)
c.chaincodeCustodian.NotifyInstalledAndRunnable(packageID)
}
}
if !initializing {
c.eventBroker.ProcessInstallEvent(localChaincode)
c.handleMetadataUpdates(localChaincode)
}
}
// HandleStateUpdates is required to implement the ledger state listener interface. It applies
// any state updates to the cache.
func (c *Cache) HandleStateUpdates(trigger *ledger.StateUpdateTrigger) error {
c.mutex.Lock()
defer c.mutex.Unlock()
channelID := trigger.LedgerID
updates, ok := trigger.StateUpdates[LifecycleNamespace]
if !ok {
return errors.Errorf("no state updates for promised namespace _lifecycle")
}
dirtyChaincodes := map[string]struct{}{}
for _, publicUpdate := range updates.PublicUpdates {
matches := SequenceMatcher.FindStringSubmatch(publicUpdate.Key)
if len(matches) != 2 {
continue
}
dirtyChaincodes[matches[1]] = struct{}{}
}
channelCache, ok := c.definedChaincodes[channelID]
// if the channel cache does not yet exist, there are no interesting hashes, so skip
if ok {
for collection, privateUpdates := range updates.CollHashUpdates {
isImplicitCollection, mspID := implicitcollection.MspIDIfImplicitCollection(collection)
if !isImplicitCollection {
continue
}
if mspID != c.MyOrgMSPID {
// This is not our implicit collection
continue
}
for _, privateUpdate := range privateUpdates {
chaincodeName, ok := channelCache.InterestingHashes[string(privateUpdate.KeyHash)]
if ok {
dirtyChaincodes[chaincodeName] = struct{}{}
}
}
}
}
err := c.update(false, channelID, dirtyChaincodes, trigger.PostCommitQueryExecutor)
if err != nil {
return errors.WithMessage(err, "error updating cache")
}
return nil
}
// InterestedInNamespaces is required to implement the ledger state listener interface
func (c *Cache) InterestedInNamespaces() []string {
return []string{LifecycleNamespace}
}
// StateCommitDone is required to implement the ledger state listener interface
func (c *Cache) StateCommitDone(channelName string) {
// NOTE: It's extremely tempting to acquire the write lock in HandleStateUpdate
// and release it here, however, this is asking for a deadlock. In particular,
// because the 'write lock' on the state is only held for a short period
// between HandleStateUpdate and StateCommitDone, it's possible (in fact likely)
// that a chaincode invocation will acquire a read-lock on the world state, then attempt
// to get chaincode info from the cache, resulting in a deadlock. So, we choose
// potential inconsistency between the cache and the world state which the callers
// must detect and cope with as necessary. Note, the cache will always be _at least_
// as current as the committed state.
c.eventBroker.ApproveOrDefineCommitted(channelName)
}
// ChaincodeInfo returns the chaincode definition and its install info.
// An error is returned only if either the channel or the chaincode do not exist.
func (c *Cache) ChaincodeInfo(channelID, name string) (*LocalChaincodeInfo, error) {
if name == LifecycleNamespace {
ac, ok := c.Resources.ChannelConfigSource.GetStableChannelConfig(channelID).ApplicationConfig()
if !ok {
return nil, errors.Errorf("application config does not exist for channel '%s'", channelID)
}
if !ac.Capabilities().LifecycleV20() {
return nil, errors.Errorf("cannot use _lifecycle without V2_0 application capabilities enabled for channel '%s'", channelID)
}
return c.getLifecycleSCCChaincodeInfo(channelID)
}
c.mutex.RLock()
defer c.mutex.RUnlock()
channelChaincodes, ok := c.definedChaincodes[channelID]
if !ok {
return nil, errors.Errorf("unknown channel '%s'", channelID)
}
cachedChaincode, ok := channelChaincodes.Chaincodes[name]
if !ok {
return nil, errors.Errorf("unknown chaincode '%s' for channel '%s'", name, channelID)
}
return &LocalChaincodeInfo{
Definition: cachedChaincode.Definition,
InstallInfo: cachedChaincode.InstallInfo,
Approved: cachedChaincode.Approved,
}, nil
}
func (c *Cache) getLifecycleSCCChaincodeInfo(channelID string) (*LocalChaincodeInfo, error) {
policyBytes, err := c.Resources.LifecycleEndorsementPolicyAsBytes(channelID)
if err != nil {
return nil, err
}
return &LocalChaincodeInfo{
Definition: &ChaincodeDefinition{
ValidationInfo: &lb.ChaincodeValidationInfo{
ValidationParameter: policyBytes,
},
Sequence: 1,
},
Approved: true,
InstallInfo: &ChaincodeInstallInfo{},
}, nil
}
// ListInstalledChaincodes returns a slice containing all of the information
// about the installed chaincodes.
func (c *Cache) ListInstalledChaincodes() []*chaincode.InstalledChaincode {
c.mutex.RLock()
defer c.mutex.RUnlock()
installedChaincodes := []*chaincode.InstalledChaincode{}
for _, lc := range c.localChaincodes {
if lc.Info == nil {
// the update function adds an entry to localChaincodes
// even if it isn't yet installed
continue
}
installedChaincodes = append(installedChaincodes, lc.ToInstalledChaincode())
}
return installedChaincodes
}
// GetInstalledChaincode returns all of the information about a specific
// installed chaincode.
func (c *Cache) GetInstalledChaincode(packageID string) (*chaincode.InstalledChaincode, error) {
c.mutex.RLock()
defer c.mutex.RUnlock()
for _, lc := range c.localChaincodes {
if lc.Info == nil {
// the update function adds an entry to localChaincodes
// even if it isn't yet installed
continue
}
if lc.Info.PackageID == packageID {
return lc.ToInstalledChaincode(), nil
}
}
return nil, errors.Errorf("could not find chaincode with package id '%s'", packageID)
}
// update should only be called with the write lock already held
func (c *Cache) update(initializing bool, channelID string, dirtyChaincodes map[string]struct{}, qe ledger.SimpleQueryExecutor) error {
channelCache, ok := c.definedChaincodes[channelID]
if !ok {
channelCache = &ChannelCache{
Chaincodes: map[string]*CachedChaincodeDefinition{},
InterestingHashes: map[string]string{},
}
c.definedChaincodes[channelID] = channelCache
}
publicState := &SimpleQueryExecutorShim{
Namespace: LifecycleNamespace,
SimpleQueryExecutor: qe,
}
orgState := &PrivateQueryExecutorShim{
Namespace: LifecycleNamespace,
Collection: implicitcollection.NameForOrg(c.MyOrgMSPID),
State: qe,
}
for name := range dirtyChaincodes {
logger.Infof("Updating cached definition for chaincode '%s' on channel '%s'", name, channelID)
cachedChaincode, ok := channelCache.Chaincodes[name]
if !ok {
cachedChaincode = &CachedChaincodeDefinition{}
channelCache.Chaincodes[name] = cachedChaincode
}
for _, hash := range cachedChaincode.Hashes {
delete(channelCache.InterestingHashes, hash)
}
exists, chaincodeDefinition, err := c.Resources.ChaincodeDefinitionIfDefined(name, publicState)
if err != nil {
return errors.WithMessagef(err, "could not get chaincode definition for '%s' on channel '%s'", name, channelID)
}
if !exists {
// the chaincode definition was deleted, this is currently not
// possible, but there should be no problems with that.
delete(channelCache.Chaincodes, name)
continue
}
privateName := fmt.Sprintf("%s#%d", name, chaincodeDefinition.Sequence)
hashKey := FieldKey(ChaincodeSourcesName, privateName, "PackageID")
hashOfCCHash, err := orgState.GetStateHash(hashKey)
if err != nil {
return errors.WithMessagef(err, "could not check opaque org state for chaincode source hash for '%s' on channel '%s'", name, channelID)
}
localChaincode, ok := c.localChaincodes[string(hashOfCCHash)]
if !ok {
localChaincode = &LocalChaincode{
References: map[string]map[string]*CachedChaincodeDefinition{},
}
c.localChaincodes[string(hashOfCCHash)] = localChaincode
}
if !initializing {
// check for existing local chaincodes that reference this chaincode
// name on this channel
for _, lc := range c.localChaincodes {
if ref, ok := lc.References[channelID][name]; ok {
if ref.InstallInfo == nil {
continue
}
if localChaincode.Info != nil {
if ref.InstallInfo.PackageID == localChaincode.Info.PackageID {
continue
}
}
// remove existing local chaincode reference, which referred to a
// previous chaincode definition
delete(lc.References[channelID], name)
if len(lc.References[channelID]) == 0 {
delete(lc.References, channelID)
// check to see if this "local" chaincode is installed (an entry
// is added into local chaincodes for active chaincode definition
// references regardless of whether the peer has a chaincode
// package installed)
if lc.Info == nil {
continue
}
// finally, check to see if this chaincode is referenced in any
// channel. if not, stop the chaincode here
if len(lc.References) == 0 {
logger.Debugf("chaincode package with label %s is no longer referenced and will be stopped", lc.Info.Label)
c.chaincodeCustodian.NotifyStoppable(lc.Info.PackageID)
}
}
}
}
}
cachedChaincode.Definition = chaincodeDefinition
cachedChaincode.Approved = false
cachedChaincode.Hashes = []string{
string(util.ComputeSHA256([]byte(MetadataKey(NamespacesName, privateName)))),
string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "EndorsementInfo")))),
string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "ValidationInfo")))),
string(util.ComputeSHA256([]byte(FieldKey(NamespacesName, privateName, "Collections")))),
string(util.ComputeSHA256([]byte(FieldKey(ChaincodeSourcesName, privateName, "PackageID")))),
}
for _, hash := range cachedChaincode.Hashes {
channelCache.InterestingHashes[hash] = name
}
ok, err = c.Resources.Serializer.IsSerialized(NamespacesName, privateName, chaincodeDefinition.Parameters(), orgState)
if err != nil {
return errors.WithMessagef(err, "could not check opaque org state for '%s' on channel '%s'", name, channelID)
}
if !ok {
logger.Debugf("Channel %s for chaincode definition %s:%s does not have our org's approval", channelID, name, chaincodeDefinition.EndorsementInfo.Version)
continue
}
cachedChaincode.Approved = true
isLocalPackage, err := c.Resources.Serializer.IsMetadataSerialized(ChaincodeSourcesName, privateName, &ChaincodeLocalPackage{}, orgState)
if err != nil {
return errors.WithMessagef(err, "could not check opaque org state for chaincode source for '%s' on channel '%s'", name, channelID)
}
if !isLocalPackage {
logger.Debugf("Channel %s for chaincode definition %s:%s does not have a chaincode source defined", channelID, name, chaincodeDefinition.EndorsementInfo.Version)
continue
}
cachedChaincode.InstallInfo = localChaincode.Info
if localChaincode.Info != nil {
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)
c.chaincodeCustodian.NotifyInstalledAndRunnable(localChaincode.Info.PackageID)
} else {
logger.Debugf("Chaincode definition for chaincode '%s' on channel '%s' is approved, but not installed", name, channelID)
}
channelReferences, ok := localChaincode.References[channelID]
if !ok {
channelReferences = map[string]*CachedChaincodeDefinition{}
localChaincode.References[channelID] = channelReferences
}
channelReferences[name] = cachedChaincode
if !initializing {
c.eventBroker.ProcessApproveOrDefineEvent(channelID, name, cachedChaincode)
}
}
if !initializing {
c.handleMetadataUpdatesForChannel(channelID)
}
return nil
}
// RegisterListener registers an event listener for receiving an event when a chaincode becomes invokable
func (c *Cache) RegisterListener(
channelID string,
listener ledger.ChaincodeLifecycleEventListener,
needsExistingChaincodesDefinitions bool,
) error {
c.mutex.RLock()
defer c.mutex.RUnlock()
channelChaincodes, ok := c.definedChaincodes[channelID]
if !ok {
return errors.Errorf("unknown channel '%s'", channelID)
}
if needsExistingChaincodesDefinitions {
c.eventBroker.RegisterListener(channelID, listener, channelChaincodes.Chaincodes)
} else {
c.eventBroker.RegisterListener(channelID, listener, nil)
}
return nil
}
func (c *Cache) InitializeMetadata(channel string) {
c.mutex.RLock()
defer c.mutex.RUnlock()
ms, err := c.retrieveChaincodesMetadataSetWhileLocked(channel)
if err != nil {
logger.Warningf("no metadata found on channel '%s', err %s", channel, err)
return
}
c.MetadataHandler.InitializeMetadata(channel, ms)
}
func (c *Cache) retrieveChaincodesMetadataSetWhileLocked(channelID string) (chaincode.MetadataSet, error) {
channelChaincodes, ok := c.definedChaincodes[channelID]
if !ok {
return nil, errors.Errorf("unknown channel '%s'", channelID)
}
keys := make([]string, 0, len(channelChaincodes.Chaincodes))
for name := range channelChaincodes.Chaincodes {
keys = append(keys, name)
}
sort.Strings(keys)
metadataSet := chaincode.MetadataSet{}
for _, name := range keys {
def := channelChaincodes.Chaincodes[name]
// report the sequence as the version to service discovery since
// the version is no longer required to change when updating any
// part of the chaincode definition
metadataSet = append(metadataSet,
chaincode.Metadata{
Name: name,
Version: strconv.FormatInt(def.Definition.Sequence, 10),
Policy: def.Definition.ValidationInfo.ValidationParameter,
CollectionsConfig: def.Definition.Collections,
Approved: def.Approved,
Installed: def.InstallInfo != nil,
},
)
}
// get the chaincode info for _lifecycle
lc, err := c.getLifecycleSCCChaincodeInfo(channelID)
if err != nil {
return nil, err
}
// add it to the metadataset so _lifecycle can also be queried
// via service discovery
metadataSet = append(metadataSet,
chaincode.Metadata{
Name: LifecycleNamespace,
Version: strconv.FormatInt(lc.Definition.Sequence, 10),
Policy: lc.Definition.ValidationInfo.ValidationParameter,
Approved: lc.Approved,
Installed: lc.InstallInfo != nil,
},
)
return metadataSet, nil
}
func (c *Cache) handleMetadataUpdates(localChaincode *LocalChaincode) {
for channelID := range localChaincode.References {
c.handleMetadataUpdatesForChannel(channelID)
}
}
func (c *Cache) handleMetadataUpdatesForChannel(channelID string) {
ms, err := c.retrieveChaincodesMetadataSetWhileLocked(channelID)
if err != nil {
logger.Warningf("no metadata found on channel '%s': %s", channelID, err)
return
}
c.MetadataHandler.UpdateMetadata(channelID, ms)
}