/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package lifecycle

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"github.com/hyperledger/fabric-lib-go/common/flogging"
	cb "github.com/hyperledger/fabric-protos-go-apiv2/common"
	"github.com/hyperledger/fabric-protos-go-apiv2/msp"
	pb "github.com/hyperledger/fabric-protos-go-apiv2/peer"
	lb "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle"
	"github.com/hyperledger/fabric/common/chaincode"
	"github.com/hyperledger/fabric/common/policydsl"
	"github.com/hyperledger/fabric/core/chaincode/implicitcollection"
	"github.com/hyperledger/fabric/core/chaincode/persistence"
	"github.com/hyperledger/fabric/core/container"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"
)

var logger = flogging.MustGetLogger("lifecycle")

const (
	// NamespacesName is the prefix (or namespace) of the DB which will be used to store
	// the information about other namespaces (for things like chaincodes) in the DB.
	// We want a sub-namespaces within lifecycle in case other information needs to be stored here
	// in the future.
	NamespacesName = "namespaces"

	// ChaincodeSourcesName is the namespace reserved for storing the information about where
	// to find the chaincode (such as as a package on the local filesystem, or in the future,
	// at some network resource). This namespace is only populated in the org implicit collection.
	ChaincodeSourcesName = "chaincode-sources"

	// ChaincodeLocalPackageType is the name of the type of chaincode-sources which may be serialized
	// into the org's private data collection
	ChaincodeLocalPackageType = "ChaincodeLocalPackage"

	// ChaincodeParametersType is the name of the type used to store the parts of the chaincode definition
	// which are serialized as values in the statedb
	ChaincodeParametersType = "ChaincodeParameters"

	// ChaincodeDefinitionType is the name of the type used to store defined chaincodes
	ChaincodeDefinitionType = "ChaincodeDefinition"

	// FriendlyChaincodeDefinitionType is the name exposed to the outside world for the chaincode namespace
	FriendlyChaincodeDefinitionType = "Chaincode"

	// DefaultEndorsementPolicyRef is the name of the default endorsement policy for this channel
	DefaultEndorsementPolicyRef = "/Channel/Application/Endorsement"
)

var DefaultEndorsementPolicyBytes = protoutil.MarshalOrPanic(&pb.ApplicationPolicy{
	Type: &pb.ApplicationPolicy_ChannelConfigPolicyReference{
		ChannelConfigPolicyReference: DefaultEndorsementPolicyRef,
	},
})

// Sequences are the underpinning of the definition framework for lifecycle.
// All definitions must have a Sequence field in the public state.  This
// sequence is incremented by exactly 1 with each redefinition of the
// namespace. The private state org approvals also have a Sequence number
// embedded into the key which matches them either to the vote for the commit,
// or registers approval for an already committed definition.
//
// Public/World DB layout looks like the following:
// namespaces/metadata/<namespace> -> namespace metadata, including namespace type
// namespaces/fields/<namespace>/Sequence -> sequence for this namespace
// namespaces/fields/<namespace>/<field> -> field of namespace type
//
// So, for instance, a db might look like:
//
// namespaces/metadata/mycc:                   "ChaincodeDefinition"
// namespaces/fields/mycc/Sequence             1 (The current sequence)
// namespaces/fields/mycc/EndorsementInfo:     {Version: "1.3", EndorsementPlugin: "builtin", InitRequired: true}
// namespaces/fields/mycc/ValidationInfo:      {ValidationPlugin: "builtin", ValidationParameter: <application-policy>}
// namespaces/fields/mycc/Collections          {<collection info>}
//
// Private/Org Scope Implicit Collection layout looks like the following
// namespaces/metadata/<namespace>#<sequence_number> -> namespace metadata, including type
// namespaces/fields/<namespace>#<sequence_number>/<field>  -> field of namespace type
//
// namespaces/metadata/mycc#1:                   "ChaincodeParameters"
// namespaces/fields/mycc#1/EndorsementInfo:     {Version: "1.3", EndorsementPlugin: "builtin", InitRequired: true}
// namespaces/fields/mycc#1/ValidationInfo:      {ValidationPlugin: "builtin", ValidationParameter: <application-policy>}
// namespaces/fields/mycc#1/Collections          {<collection info>}
// namespaces/metadata/mycc#2:                   "ChaincodeParameters"
// namespaces/fields/mycc#2/EndorsementInfo:     {Version: "1.4", EndorsementPlugin: "builtin", InitRequired: true}
// namespaces/fields/mycc#2/ValidationInfo:      {ValidationPlugin: "builtin", ValidationParameter: <application-policy>}
// namespaces/fields/mycc#2/Collections          {<collection info>}
//
// chaincode-sources/metadata/mycc#1              "ChaincodeLocalPackage"
// chaincode-sources/fields/mycc#1/PackageID      "hash1"

// ChaincodeLocalPackage is a type of chaincode-sources which may be serialized
// into the org's private data collection.
// WARNING: This structure is serialized/deserialized from the DB, re-ordering
// or adding fields will cause opaque checks to fail.
type ChaincodeLocalPackage struct {
	PackageID string
}

// ChaincodeParameters are the parts of the chaincode definition which are serialized
// as values in the statedb.  It is expected that any instance will have no nil fields once initialized.
// WARNING: This structure is serialized/deserialized from the DB, re-ordering or adding fields
// will cause opaque checks to fail.
type ChaincodeParameters struct {
	EndorsementInfo *lb.ChaincodeEndorsementInfo
	ValidationInfo  *lb.ChaincodeValidationInfo
	Collections     *pb.CollectionConfigPackage
}

func (cp *ChaincodeParameters) Equal(ocp *ChaincodeParameters) error {
	switch {
	case cp.EndorsementInfo.Version != ocp.EndorsementInfo.Version:
		return errors.Errorf("expected Version '%s' does not match passed Version '%s'", cp.EndorsementInfo.Version, ocp.EndorsementInfo.Version)
	case cp.EndorsementInfo.EndorsementPlugin != ocp.EndorsementInfo.EndorsementPlugin:
		return errors.Errorf("expected EndorsementPlugin '%s' does not match passed EndorsementPlugin '%s'", cp.EndorsementInfo.EndorsementPlugin, ocp.EndorsementInfo.EndorsementPlugin)
	case cp.EndorsementInfo.InitRequired != ocp.EndorsementInfo.InitRequired:
		return errors.Errorf("expected InitRequired '%t' does not match passed InitRequired '%t'", cp.EndorsementInfo.InitRequired, ocp.EndorsementInfo.InitRequired)
	case cp.ValidationInfo.ValidationPlugin != ocp.ValidationInfo.ValidationPlugin:
		return errors.Errorf("expected ValidationPlugin '%s' does not match passed ValidationPlugin '%s'", cp.ValidationInfo.ValidationPlugin, ocp.ValidationInfo.ValidationPlugin)
	case !bytes.Equal(cp.ValidationInfo.ValidationParameter, ocp.ValidationInfo.ValidationParameter):
		return errors.Errorf("expected ValidationParameter '%x' does not match passed ValidationParameter '%x'", cp.ValidationInfo.ValidationParameter, ocp.ValidationInfo.ValidationParameter)
	case !proto.Equal(cp.Collections, ocp.Collections):
		return errors.Errorf("Collections do not match")
	default:
	}
	return nil
}

// ChaincodeDefinition contains the chaincode parameters, as well as the sequence number of the definition.
// Note, it does not embed ChaincodeParameters so as not to complicate the serialization.  It is expected
// that any instance will have no nil fields once initialized.
// WARNING: This structure is serialized/deserialized from the DB, re-ordering or adding fields
// will cause opaque checks to fail.
type ChaincodeDefinition struct {
	Sequence        int64
	EndorsementInfo *lb.ChaincodeEndorsementInfo
	ValidationInfo  *lb.ChaincodeValidationInfo
	Collections     *pb.CollectionConfigPackage
}

type ApprovedChaincodeDefinition struct {
	Sequence        int64
	EndorsementInfo *lb.ChaincodeEndorsementInfo
	ValidationInfo  *lb.ChaincodeValidationInfo
	Collections     *pb.CollectionConfigPackage
	Source          *lb.ChaincodeSource
}

// Parameters returns the non-sequence info of the chaincode definition
func (cd *ChaincodeDefinition) Parameters() *ChaincodeParameters {
	return &ChaincodeParameters{
		EndorsementInfo: cd.EndorsementInfo,
		ValidationInfo:  cd.ValidationInfo,
		Collections:     cd.Collections,
	}
}

func (cd *ChaincodeDefinition) String() string {
	endorsementInfo := "endorsement info: <EMPTY>"
	if cd.EndorsementInfo != nil {
		endorsementInfo = fmt.Sprintf("endorsement info: (version: '%s', plugin: '%s', init required: %t)",
			cd.EndorsementInfo.Version,
			cd.EndorsementInfo.EndorsementPlugin,
			cd.EndorsementInfo.InitRequired,
		)
	}

	validationInfo := "validation info: <EMPTY>"
	if cd.ValidationInfo != nil {
		validationInfo = fmt.Sprintf("validation info: (plugin: '%s', policy: '%x')",
			cd.ValidationInfo.ValidationPlugin,
			cd.ValidationInfo.ValidationParameter,
		)
	}

	return fmt.Sprintf("sequence: %d, %s, %s, collections: (%+v)",
		cd.Sequence,
		endorsementInfo,
		validationInfo,
		cd.Collections,
	)
}

//go:generate counterfeiter -o mock/chaincode_builder.go --fake-name ChaincodeBuilder . ChaincodeBuilder

type ChaincodeBuilder interface {
	Build(ccid string) error
}

// ChaincodeStore provides a way to persist chaincodes
type ChaincodeStore interface {
	Save(label string, ccInstallPkg []byte) (string, error)
	ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error)
	Load(packageID string) (ccInstallPkg []byte, err error)
	Delete(packageID string) error
}

type PackageParser interface {
	Parse(data []byte) (*persistence.ChaincodePackage, error)
}

//go:generate counterfeiter -o mock/install_listener.go --fake-name InstallListener . InstallListener
type InstallListener interface {
	HandleChaincodeInstalled(md *persistence.ChaincodePackageMetadata, packageID string)
}

//go:generate counterfeiter -o mock/installed_chaincodes_lister.go --fake-name InstalledChaincodesLister . InstalledChaincodesLister
type InstalledChaincodesLister interface {
	ListInstalledChaincodes() []*chaincode.InstalledChaincode
	GetInstalledChaincode(packageID string) (*chaincode.InstalledChaincode, error)
}

// Resources stores the common functions needed by all components of the lifecycle
// by the SCC as well as internally.  It also has some utility methods attached to it
// for querying the lifecycle definitions.
type Resources struct {
	ChannelConfigSource ChannelConfigSource
	ChaincodeStore      ChaincodeStore
	PackageParser       PackageParser
	Serializer          *Serializer
}

// ChaincodeDefinitionIfDefined returns whether the chaincode name is defined in the new lifecycle, a shim around
// the SimpleQueryExecutor to work with the serializer, or an error.  If the namespace is defined, but it is
// not a chaincode, this is considered an error.
func (r *Resources) ChaincodeDefinitionIfDefined(chaincodeName string, state ReadableState) (bool, *ChaincodeDefinition, error) {
	if chaincodeName == LifecycleNamespace {
		return true, &ChaincodeDefinition{
			EndorsementInfo: &lb.ChaincodeEndorsementInfo{
				InitRequired: false,
			},
			ValidationInfo: &lb.ChaincodeValidationInfo{},
		}, nil
	}

	metadata, ok, err := r.Serializer.DeserializeMetadata(NamespacesName, chaincodeName, state)
	if err != nil {
		return false, nil, errors.WithMessagef(err, "could not deserialize metadata for chaincode %s", chaincodeName)
	}

	if !ok {
		return false, nil, nil
	}

	if metadata.Datatype != ChaincodeDefinitionType {
		return false, nil, errors.Errorf("not a chaincode type: %s", metadata.Datatype)
	}

	definedChaincode := &ChaincodeDefinition{}
	err = r.Serializer.Deserialize(NamespacesName, chaincodeName, metadata, definedChaincode, state)
	if err != nil {
		return false, nil, errors.WithMessagef(err, "could not deserialize chaincode definition for chaincode %s", chaincodeName)
	}

	return true, definedChaincode, nil
}

func (r *Resources) LifecycleEndorsementPolicyAsBytes(channelID string) ([]byte, error) {
	channelConfig := r.ChannelConfigSource.GetStableChannelConfig(channelID)
	if channelConfig == nil {
		return nil, errors.Errorf("could not get channel config for channel '%s'", channelID)
	}

	if _, ok := channelConfig.PolicyManager().GetPolicy(LifecycleEndorsementPolicyRef); ok {
		return LifecycleDefaultEndorsementPolicyBytes, nil
	}

	// This was a channel which was upgraded or did not define a lifecycle endorsement policy, use a default
	// of "a majority of orgs must have a member sign".
	ac, ok := channelConfig.ApplicationConfig()
	if !ok {
		return nil, errors.Errorf("could not get application config for channel '%s'", channelID)
	}
	orgs := ac.Organizations()
	mspids := make([]string, 0, len(orgs))
	for _, org := range orgs {
		mspids = append(mspids, org.MSPID())
	}

	return protoutil.MarshalOrPanic(&cb.ApplicationPolicy{
		Type: &cb.ApplicationPolicy_SignaturePolicy{
			SignaturePolicy: policydsl.SignedByNOutOfGivenRole(int32(len(mspids)/2+1), msp.MSPRole_MEMBER, mspids),
		},
	}), nil
}

// ExternalFunctions is intended primarily to support the SCC functions.
// In general, its methods signatures produce writes (which must be committed
// as part of an endorsement flow), or return human readable errors (for
// instance indicating a chaincode is not found) rather than sentinels.
// Instead, use the utility functions attached to the lifecycle Resources
// when needed.
type ExternalFunctions struct {
	Resources                 *Resources
	InstallListener           InstallListener
	InstalledChaincodesLister InstalledChaincodesLister
	ChaincodeBuilder          ChaincodeBuilder
	BuildRegistry             *container.BuildRegistry
	mutex                     sync.Mutex
	BuildLocks                map[string]*sync.Mutex
	concurrentInstalls        uint32
}

// CheckCommitReadiness takes a chaincode definition, checks that
// its sequence number is the next allowable sequence number and checks which
// organizations have approved the definition.
func (ef *ExternalFunctions) CheckCommitReadiness(chname, ccname string, cd *ChaincodeDefinition, publicState ReadWritableState, orgStates []OpaqueState) (approvals map[string]bool, mismatches map[string][]string, err error) {
	var currentSequence int64
	currentSequence, err = ef.Resources.Serializer.DeserializeFieldAsInt64(NamespacesName, ccname, "Sequence", publicState)
	if err != nil {
		err = errors.WithMessage(err, "could not get current sequence")
		return
	}

	if cd.Sequence != currentSequence+1 {
		err = errors.Errorf("requested sequence is %d, but new definition must be sequence %d", cd.Sequence, currentSequence+1)
		return
	}

	if err = ef.SetChaincodeDefinitionDefaults(chname, cd); err != nil {
		err = errors.WithMessagef(err, "could not set defaults for chaincode definition in channel %s", chname)
		return
	}

	if approvals, mismatches, err = ef.QueryOrgApprovals(ccname, cd, orgStates); err != nil {
		return
	}

	logger.Infof("Successfully checked commit readiness of chaincode name '%s' on channel '%s' with definition {%s}", ccname, chname, cd)

	return
}

// CommitChaincodeDefinition takes a chaincode definition, checks that its
// sequence number is the next allowable sequence number, checks which
// organizations have approved the definition, and applies the definition to
// the public world state. It is the responsibility of the caller to check
// the approvals to determine if the result is valid (typically, this means
// checking that the peer's own org has approved the definition).
func (ef *ExternalFunctions) CommitChaincodeDefinition(chname, ccname string, cd *ChaincodeDefinition, publicState ReadWritableState, orgStates []OpaqueState) (map[string]bool, error) {
	approvals, _, err := ef.CheckCommitReadiness(chname, ccname, cd, publicState, orgStates)
	if err != nil {
		return nil, err
	}

	if err = ef.Resources.Serializer.Serialize(NamespacesName, ccname, cd, publicState); err != nil {
		return nil, errors.WithMessage(err, "could not serialize chaincode definition")
	}

	return approvals, nil
}

// DefaultEndorsementPolicyAsBytes returns a marshalled version
// of the default chaincode endorsement policy in the supplied channel
func (ef *ExternalFunctions) DefaultEndorsementPolicyAsBytes(channelID string) ([]byte, error) {
	channelConfig := ef.Resources.ChannelConfigSource.GetStableChannelConfig(channelID)
	if channelConfig == nil {
		return nil, errors.Errorf("could not get channel config for channel '%s'", channelID)
	}

	// see if the channel defines a default
	if _, ok := channelConfig.PolicyManager().GetPolicy(DefaultEndorsementPolicyRef); ok {
		return DefaultEndorsementPolicyBytes, nil
	}

	return nil, errors.Errorf(
		"policy '%s' must be defined for channel '%s' before chaincode operations can be attempted",
		DefaultEndorsementPolicyRef,
		channelID,
	)
}

// SetChaincodeDefinitionDefaults fills any empty fields in the
// supplied ChaincodeDefinition with the supplied channel's defaults
func (ef *ExternalFunctions) SetChaincodeDefinitionDefaults(chname string, cd *ChaincodeDefinition) error {
	if cd.EndorsementInfo.EndorsementPlugin == "" {
		// TODO:
		// 1) rename to "default" or "builtin"
		// 2) retrieve from channel config
		cd.EndorsementInfo.EndorsementPlugin = "escc"
	}

	if cd.ValidationInfo.ValidationPlugin == "" {
		// TODO:
		// 1) rename to "default" or "builtin"
		// 2) retrieve from channel config
		cd.ValidationInfo.ValidationPlugin = "vscc"
	}

	if len(cd.ValidationInfo.ValidationParameter) == 0 {
		policyBytes, err := ef.DefaultEndorsementPolicyAsBytes(chname)
		if err != nil {
			return err
		}

		cd.ValidationInfo.ValidationParameter = policyBytes
	}

	return nil
}

// ApproveChaincodeDefinitionForOrg adds a chaincode definition entry into the passed in Org state.  The definition must be
// for either the currently defined sequence number or the next sequence number.  If the definition is
// for the current sequence number, then it must match exactly the current definition or it will be rejected.
func (ef *ExternalFunctions) ApproveChaincodeDefinitionForOrg(chname, ccname string, cd *ChaincodeDefinition, packageID string, publicState ReadableState, orgState ReadWritableState) error {
	// Get the current sequence from the public state
	currentSequence, err := ef.Resources.Serializer.DeserializeFieldAsInt64(NamespacesName, ccname, "Sequence", publicState)
	if err != nil {
		return errors.WithMessage(err, "could not get current sequence")
	}

	requestedSequence := cd.Sequence

	if currentSequence == requestedSequence && requestedSequence == 0 {
		return errors.Errorf("requested sequence is 0, but first definable sequence number is 1")
	}

	if requestedSequence < currentSequence {
		return errors.Errorf("currently defined sequence %d is larger than requested sequence %d", currentSequence, requestedSequence)
	}

	if requestedSequence > currentSequence+1 {
		return errors.Errorf("requested sequence %d is larger than the next available sequence number %d", requestedSequence, currentSequence+1)
	}

	if err := ef.SetChaincodeDefinitionDefaults(chname, cd); err != nil {
		return errors.WithMessagef(err, "could not set defaults for chaincode definition in channel %s", chname)
	}

	privateName := fmt.Sprintf("%s#%d", ccname, requestedSequence)

	if requestedSequence == currentSequence {
		metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(NamespacesName, ccname, publicState)
		if err != nil {
			return errors.WithMessage(err, "could not fetch metadata for current definition")
		}
		if !ok {
			return errors.Errorf("missing metadata for currently committed sequence number (%d)", currentSequence)
		}

		definedChaincode := &ChaincodeDefinition{}
		if err := ef.Resources.Serializer.Deserialize(NamespacesName, ccname, metadata, definedChaincode, publicState); err != nil {
			return errors.WithMessagef(err, "could not deserialize namespace %s as chaincode", ccname)
		}

		if err := definedChaincode.Parameters().Equal(cd.Parameters()); err != nil {
			return errors.WithMessagef(err, "attempted to redefine the current committed sequence (%d) for namespace %s with different parameters", currentSequence, ccname)
		}
		// it might be the case that some organization just installed
		// the chaincode and now would like to approve, therefore we
		// need to check to distinguish the case. hence need to read from
		// the orgState metadata and see whenever this is the case
		redefine, err := ef.isAttemptToRedefine(privateName, packageID, requestedSequence, cd, orgState)
		if err != nil {
			return err
		}
		if redefine {
			return errors.Errorf("attempted to redefine the current committed sequence (%d) for namespace %s", currentSequence, ccname)
		}
	}

	// if requested sequence is not committed, and attempt is made to update its content,
	// we need to check whether new definition actually contains updated content, to avoid
	// empty write set.
	if requestedSequence == currentSequence+1 {
		redefine, err := ef.isAttemptToRedefine(privateName, packageID, requestedSequence, cd, orgState)
		if err != nil {
			return err
		}
		if redefine {
			return errors.Errorf("attempted to redefine uncommitted sequence (%d) for namespace %s with unchanged content", requestedSequence, ccname)
		}
	}

	if err := ef.Resources.Serializer.Serialize(NamespacesName, privateName, cd.Parameters(), orgState); err != nil {
		return errors.WithMessage(err, "could not serialize chaincode parameters to state")
	}

	// set the package id - whether empty or not. Setting
	// an empty package ID means that the chaincode won't
	// be invocable. The package might be set empty after
	// the definition commits as a way of instructing the
	// peers of an org no longer to endorse invocations
	// for this chaincode
	if err := ef.Resources.Serializer.Serialize(ChaincodeSourcesName, privateName, &ChaincodeLocalPackage{
		PackageID: packageID,
	}, orgState); err != nil {
		return errors.WithMessage(err, "could not serialize chaincode package info to state")
	}

	logger.Infof("Successfully endorsed chaincode approval with name '%s', package ID '%s', on channel '%s' with definition {%s}", ccname, packageID, chname, cd)

	return nil
}

func (ef *ExternalFunctions) isAttemptToRedefine(privateName, packageID string, requestedSequence int64, cd *ChaincodeDefinition, orgState ReadWritableState) (bool, error) {
	metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(NamespacesName, privateName, orgState)
	if err != nil {
		return false, errors.WithMessagef(err, "could not fetch chaincode definition, chaincode %s, requested at sequence %d", privateName, requestedSequence)
	}

	if ok {
		logger.Debugf("Attempting to redefine chaincode definition, chaincode %s at sequence %d", privateName, requestedSequence)

		chaincodeParameters := &ChaincodeParameters{}
		if err := ef.Resources.Serializer.Deserialize(NamespacesName, privateName, metadata, chaincodeParameters, orgState); err != nil {
			return false, errors.WithMessagef(err, "could not deserialize namespace %s as chaincode", privateName)
		}

		if err := chaincodeParameters.Equal(cd.Parameters()); err == nil {
			// also check package ID updates
			metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(ChaincodeSourcesName, privateName, orgState)
			if err != nil {
				return false, errors.WithMessagef(err, "could not deserialize chaincode-source metadata for %s", privateName)
			}
			if ok {
				ccLocalPackage := &ChaincodeLocalPackage{}
				if err := ef.Resources.Serializer.Deserialize(ChaincodeSourcesName, privateName, metadata, ccLocalPackage, orgState); err != nil {
					return false, errors.WithMessagef(err, "could not deserialize chaincode package for %s", privateName)
				}

				if ccLocalPackage.PackageID == packageID {
					return true, nil
				}
			}
		}
	}

	return false, nil
}

// QueryApprovedChaincodeDefinition returns the approved chaincode definition in Org state by using the given parameters.
// If the parameter of sequence is not provided, this function returns the latest approved chaincode definition
// (latest: new one of the currently defined sequence number and the next sequence number).
func (ef *ExternalFunctions) QueryApprovedChaincodeDefinition(chname, ccname string, sequence int64, publicState ReadableState, orgState ReadableState) (*ApprovedChaincodeDefinition, error) {
	requestedSequence := sequence

	// If requested sequence is not provided,
	// set the latest sequence number (either the currently defined sequence number or the next sequence number)
	if requestedSequence == 0 {
		currentSequence, err := ef.Resources.Serializer.DeserializeFieldAsInt64(NamespacesName, ccname, "Sequence", publicState)
		if err != nil {
			return nil, errors.WithMessage(err, "could not get current sequence")
		}
		requestedSequence = currentSequence

		nextSequence := currentSequence + 1
		privateName := fmt.Sprintf("%s#%d", ccname, nextSequence)
		_, ok, err := ef.Resources.Serializer.DeserializeMetadata(NamespacesName, privateName, orgState)
		if err != nil {
			return nil, errors.WithMessagef(err, "could not deserialize namespace metadata for next sequence %d", nextSequence)
		}
		if ok {
			requestedSequence = nextSequence
		}
	}

	logger.Infof("Attempting to fetch approved definition (name: '%s', sequence: '%d') on channel '%s'", ccname, requestedSequence, chname)
	privateName := fmt.Sprintf("%s#%d", ccname, requestedSequence)
	metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(NamespacesName, privateName, orgState)
	if err != nil {
		return nil, errors.WithMessagef(err, "could not deserialize namespace metadata for %s", privateName)
	}

	if !ok {
		return nil, errors.Errorf("could not fetch approved chaincode definition (name: '%s', sequence: '%d') on channel '%s'", ccname, requestedSequence, chname)
	}

	return ef.fetchApprovedChaincodeDefinition(ccname, requestedSequence, metadata, orgState)
}

// QueryApprovedChaincodeDefinitions returns all approved chaincode definitions in Org state for the specified channel.
// The return value is a map where the key is a combination of chaincode namespace and sequence number in the format "<namespace>#<sequence_number>",
// and the value is the corresponding ApprovedChaincodeDefinition object.
func (ef *ExternalFunctions) QueryApprovedChaincodeDefinitions(chname string, orgState ReadRangeableState) (map[string]*ApprovedChaincodeDefinition, error) {
	// Get all metadatas for the channel
	metadatas, err := ef.Resources.Serializer.DeserializeAllMetadata(NamespacesName, orgState)
	if err != nil {
		return nil, errors.WithMessage(err, "could not fetch metadatas")
	}

	// Get all approved chaincode definitions using the metadatas
	definitions := map[string]*ApprovedChaincodeDefinition{}
	for privateName, metadata := range metadatas {
		// Extract chaincode name and sequence number from metadata
		ccname, sequence, err := extractChaincodeNameAndSequence(privateName)
		if err != nil {
			return nil, errors.WithMessagef(err, "could not extract chaincode name and sequence number from metadata: %s", privateName)
		}

		// Fetch the approved chaincode definition
		definition, err := ef.fetchApprovedChaincodeDefinition(ccname, sequence, metadata, orgState)
		if err != nil {
			return nil, errors.WithMessagef(err, "could not fetch approved chaincode definition (name: '%s', sequence: '%d') on channel '%s'", ccname, sequence, chname)
		}

		definitions[privateName] = definition
	}

	return definitions, nil
}

func (ef *ExternalFunctions) fetchApprovedChaincodeDefinition(ccname string, sequence int64, metadata *lb.StateMetadata, orgState ReadableState) (*ApprovedChaincodeDefinition, error) {
	privateName := fmt.Sprintf("%s#%d", ccname, sequence)
	// Verify that metadata type is chaincode parameters type
	if metadata.Datatype != ChaincodeParametersType {
		return nil, errors.Errorf("not a chaincode parameters type: %s", metadata.Datatype)
	}

	// Get chaincode parameters for the requested sequence
	ccParameters := &ChaincodeParameters{}
	if err := ef.Resources.Serializer.Deserialize(NamespacesName, privateName, metadata, ccParameters, orgState); err != nil {
		return nil, errors.WithMessagef(err, "could not deserialize chaincode parameters for %s", privateName)
	}

	// Get package ID for the requested sequence
	metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(ChaincodeSourcesName, privateName, orgState)
	if err != nil {
		return nil, errors.WithMessagef(err, "could not deserialize chaincode-source metadata for %s", privateName)
	}
	if !ok {
		return nil, errors.Errorf("could not fetch chaincode-source metadata for %s", privateName)
	}
	if metadata.Datatype != ChaincodeLocalPackageType {
		return nil, errors.Errorf("not a chaincode local package type: %s", metadata.Datatype)
	}

	ccLocalPackage := &ChaincodeLocalPackage{}
	if err := ef.Resources.Serializer.Deserialize(ChaincodeSourcesName, privateName, metadata, ccLocalPackage, orgState); err != nil {
		return nil, errors.WithMessagef(err, "could not deserialize chaincode package for %s", privateName)
	}

	// Convert to lb.ChaincodeSource
	// An empty package ID means that the chaincode won't be invocable
	var ccsrc *lb.ChaincodeSource
	if ccLocalPackage.PackageID != "" {
		ccsrc = &lb.ChaincodeSource{
			Type: &lb.ChaincodeSource_LocalPackage{
				LocalPackage: &lb.ChaincodeSource_Local{
					PackageId: ccLocalPackage.PackageID,
				},
			},
		}
	} else {
		ccsrc = &lb.ChaincodeSource{
			Type: &lb.ChaincodeSource_Unavailable_{
				Unavailable: &lb.ChaincodeSource_Unavailable{},
			},
		}
	}

	return &ApprovedChaincodeDefinition{
		Sequence:        sequence,
		EndorsementInfo: ccParameters.EndorsementInfo,
		ValidationInfo:  ccParameters.ValidationInfo,
		Collections:     ccParameters.Collections,
		Source:          ccsrc,
	}, nil
}

func extractChaincodeNameAndSequence(privateName string) (string, int64, error) {
	var ccname string
	var sequence int64

	split := strings.Split(privateName, "#")
	if len(split) != 2 {
		return "", 0, errors.Errorf("invalid private name format: %s", privateName)
	}

	ccname = split[0]
	sequenceStr := split[1]

	var err error
	sequence, err = strconv.ParseInt(sequenceStr, 10, 64)
	if err != nil {
		return "", 0, errors.WithMessagef(err, "invalid sequence number in private name: %s", privateName)
	}

	return ccname, sequence, nil
}

// ErrNamespaceNotDefined is the error returned when a namespace
// is not defined. This indicates that the chaincode definition
// has not been committed.
type ErrNamespaceNotDefined struct {
	Namespace string
}

func (e ErrNamespaceNotDefined) Error() string {
	return fmt.Sprintf("namespace %s is not defined", e.Namespace)
}

// QueryChaincodeDefinition returns the defined chaincode by the given name (if it is committed, and a chaincode)
// or otherwise returns an error.
func (ef *ExternalFunctions) QueryChaincodeDefinition(name string, publicState ReadableState) (*ChaincodeDefinition, error) {
	metadata, ok, err := ef.Resources.Serializer.DeserializeMetadata(NamespacesName, name, publicState)
	if err != nil {
		return nil, errors.WithMessagef(err, "could not fetch metadata for namespace %s", name)
	}
	if !ok {
		return nil, ErrNamespaceNotDefined{Namespace: name}
	}

	definedChaincode := &ChaincodeDefinition{}
	if err := ef.Resources.Serializer.Deserialize(NamespacesName, name, metadata, definedChaincode, publicState); err != nil {
		return nil, errors.WithMessagef(err, "could not deserialize namespace %s as chaincode", name)
	}

	logger.Infof("Successfully queried chaincode name '%s' with definition {%s},", name, definedChaincode)

	return definedChaincode, nil
}

// QueryOrgApprovals returns a map containing the orgs whose orgStates were
// provided and whether or not they have approved a chaincode definition with
// the specified parameters.
func (ef *ExternalFunctions) QueryOrgApprovals(name string, cd *ChaincodeDefinition, orgStates []OpaqueState) (approvals map[string]bool, mismatches map[string][]string, err error) {
	approvals = map[string]bool{}
	mismatches = map[string][]string{}
	privateName := fmt.Sprintf("%s#%d", name, cd.Sequence)
	for _, orgState := range orgStates {
		match, mismatchItems, err := ef.Resources.Serializer.IsSerialized(NamespacesName, privateName, cd.Parameters(), orgState)
		if err != nil {
			return nil, nil, errors.WithMessagef(err, "serialization check failed for key %s", privateName)
		}

		_, org := implicitcollection.MspIDIfImplicitCollection(orgState.CollectionName())
		logger.Infof("org %s's mismatch items are %v", org, mismatchItems)
		approvals[org] = match
		if len(mismatchItems) > 0 {
			mismatches[org] = mismatchItems
		}
	}

	return
}

// InstallChaincode installs a given chaincode to the peer's chaincode store.
// It returns the hash to reference the chaincode by or an error on failure.
func (ef *ExternalFunctions) InstallChaincode(chaincodeInstallPackage []byte) (*chaincode.InstalledChaincode, error) {
	// Let's validate that the chaincodeInstallPackage is at least well formed before writing it
	pkg, err := ef.Resources.PackageParser.Parse(chaincodeInstallPackage)
	if err != nil {
		return nil, errors.WithMessage(err, "could not parse as a chaincode install package")
	}

	if pkg.Metadata == nil {
		return nil, errors.New("empty metadata for supplied chaincode")
	}

	packageID, err := ef.Resources.ChaincodeStore.Save(pkg.Metadata.Label, chaincodeInstallPackage)
	if err != nil {
		return nil, errors.WithMessage(err, "could not save cc install package")
	}

	buildLock, cleanupBuildLocks := ef.getBuildLock(packageID)
	defer cleanupBuildLocks()

	buildLock.Lock()
	defer buildLock.Unlock()

	buildStatus, ok := ef.BuildRegistry.BuildStatus(packageID)
	if ok {
		// another invocation of lifecycle has concurrently
		// installed a chaincode with this package id
		<-buildStatus.Done()
		if buildStatus.Err() == nil {
			return nil, errors.Errorf("chaincode already successfully installed (package ID '%s')", packageID)
		}
		buildStatus = ef.BuildRegistry.ResetBuildStatus(packageID)
	}
	err = ef.ChaincodeBuilder.Build(packageID)
	buildStatus.Notify(err)
	<-buildStatus.Done()
	if err := buildStatus.Err(); err != nil {
		ef.Resources.ChaincodeStore.Delete(packageID)
		return nil, errors.WithMessage(err, "could not build chaincode")
	}

	if ef.InstallListener != nil {
		ef.InstallListener.HandleChaincodeInstalled(pkg.Metadata, packageID)
	}

	logger.Infof("Successfully installed chaincode with package ID '%s'", packageID)

	return &chaincode.InstalledChaincode{
		PackageID: packageID,
		Label:     pkg.Metadata.Label,
	}, nil
}

func (ef *ExternalFunctions) getBuildLock(packageID string) (*sync.Mutex, func()) {
	ef.mutex.Lock()
	defer ef.mutex.Unlock()

	ef.concurrentInstalls++

	cleanup := func() {
		ef.mutex.Lock()
		defer ef.mutex.Unlock()

		ef.concurrentInstalls--
		// If there are no other concurrent installs happening in parallel,
		// cleanup the build lock mapping to release memory
		if ef.concurrentInstalls == 0 {
			ef.BuildLocks = nil
		}
	}

	if ef.BuildLocks == nil {
		ef.BuildLocks = map[string]*sync.Mutex{}
	}

	_, ok := ef.BuildLocks[packageID]
	if !ok {
		ef.BuildLocks[packageID] = &sync.Mutex{}
	}

	return ef.BuildLocks[packageID], cleanup
}

// GetInstalledChaincodePackage retrieves the installed chaincode with the given package ID
// from the peer's chaincode store.
func (ef *ExternalFunctions) GetInstalledChaincodePackage(packageID string) ([]byte, error) {
	pkgBytes, err := ef.Resources.ChaincodeStore.Load(packageID)
	if err != nil {
		return nil, errors.WithMessage(err, "could not load cc install package")
	}

	return pkgBytes, nil
}

// QueryNamespaceDefinitions lists the publicly defined namespaces in a channel.  Today it should only ever
// find Datatype encodings of 'ChaincodeDefinition'.
func (ef *ExternalFunctions) QueryNamespaceDefinitions(publicState RangeableState) (map[string]string, error) {
	metadatas, err := ef.Resources.Serializer.DeserializeAllMetadata(NamespacesName, publicState)
	if err != nil {
		return nil, errors.WithMessage(err, "could not query namespace metadata")
	}

	result := map[string]string{}
	for key, value := range metadatas {
		switch value.Datatype {
		case ChaincodeDefinitionType:
			result[key] = FriendlyChaincodeDefinitionType
		default:
			// This should never execute, but seems preferable to returning an error
			result[key] = value.Datatype
		}
	}
	return result, nil
}

// QueryInstalledChaincode returns metadata for the chaincode with the supplied package ID.
func (ef *ExternalFunctions) QueryInstalledChaincode(packageID string) (*chaincode.InstalledChaincode, error) {
	return ef.InstalledChaincodesLister.GetInstalledChaincode(packageID)
}

// QueryInstalledChaincodes returns a list of installed chaincodes
func (ef *ExternalFunctions) QueryInstalledChaincodes() []*chaincode.InstalledChaincode {
	return ef.InstalledChaincodesLister.ListInstalledChaincodes()
}
