904 lines
33 KiB
Go
904 lines
33 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package lifecycle
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/hyperledger/fabric-chaincode-go/shim"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
mspprotos "github.com/hyperledger/fabric-protos-go/msp"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
|
|
"github.com/hyperledger/fabric/common/cauthdsl"
|
|
"github.com/hyperledger/fabric/common/chaincode"
|
|
"github.com/hyperledger/fabric/common/channelconfig"
|
|
"github.com/hyperledger/fabric/core/aclmgmt"
|
|
"github.com/hyperledger/fabric/core/chaincode/implicitcollection"
|
|
"github.com/hyperledger/fabric/core/chaincode/persistence"
|
|
"github.com/hyperledger/fabric/core/dispatcher"
|
|
"github.com/hyperledger/fabric/core/ledger"
|
|
"github.com/hyperledger/fabric/msp"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
const (
|
|
// LifecycleNamespace is the namespace in the statedb where lifecycle
|
|
// information is stored
|
|
LifecycleNamespace = "_lifecycle"
|
|
|
|
// InstallChaincodeFuncName is the chaincode function name used to install
|
|
// a chaincode
|
|
InstallChaincodeFuncName = "InstallChaincode"
|
|
|
|
// QueryInstalledChaincodeFuncName is the chaincode function name used to
|
|
// query an installed chaincode
|
|
QueryInstalledChaincodeFuncName = "QueryInstalledChaincode"
|
|
|
|
// QueryInstalledChaincodesFuncName is the chaincode function name used to
|
|
// query all installed chaincodes
|
|
QueryInstalledChaincodesFuncName = "QueryInstalledChaincodes"
|
|
|
|
// ApproveChaincodeDefinitionForMyOrgFuncName is the chaincode function name
|
|
// used to approve a chaincode definition for execution by the user's own org
|
|
ApproveChaincodeDefinitionForMyOrgFuncName = "ApproveChaincodeDefinitionForMyOrg"
|
|
|
|
// QueryApprovedChaincodeDefinitionFuncName is the chaincode function name used to
|
|
// query a approved chaincode definition for the user's own org
|
|
QueryApprovedChaincodeDefinitionFuncName = "QueryApprovedChaincodeDefinition"
|
|
|
|
// CheckCommitReadinessFuncName is the chaincode function name used to check
|
|
// a specified chaincode definition is ready to be committed. It returns the
|
|
// approval status for a given definition over a given set of orgs
|
|
CheckCommitReadinessFuncName = "CheckCommitReadiness"
|
|
|
|
// CommitChaincodeDefinitionFuncName is the chaincode function name used to
|
|
// 'commit' (previously 'instantiate') a chaincode in a channel.
|
|
CommitChaincodeDefinitionFuncName = "CommitChaincodeDefinition"
|
|
|
|
// QueryChaincodeDefinitionFuncName is the chaincode function name used to
|
|
// query a committed chaincode definition in a channel.
|
|
QueryChaincodeDefinitionFuncName = "QueryChaincodeDefinition"
|
|
|
|
// QueryChaincodeDefinitionsFuncName is the chaincode function name used to
|
|
// query the committed chaincode definitions in a channel.
|
|
QueryChaincodeDefinitionsFuncName = "QueryChaincodeDefinitions"
|
|
)
|
|
|
|
// SCCFunctions provides a backing implementation with concrete arguments
|
|
// for each of the SCC functions
|
|
type SCCFunctions interface {
|
|
// InstallChaincode persists a chaincode definition to disk
|
|
InstallChaincode([]byte) (*chaincode.InstalledChaincode, error)
|
|
|
|
// QueryInstalledChaincode returns metadata for the chaincode with the supplied package ID.
|
|
QueryInstalledChaincode(packageID string) (*chaincode.InstalledChaincode, error)
|
|
|
|
// GetInstalledChaincodePackage returns the chaincode package
|
|
// installed on the peer as bytes.
|
|
GetInstalledChaincodePackage(packageID string) ([]byte, error)
|
|
|
|
// QueryInstalledChaincodes returns the currently installed chaincodes
|
|
QueryInstalledChaincodes() []*chaincode.InstalledChaincode
|
|
|
|
// ApproveChaincodeDefinitionForOrg records a chaincode definition into this org's implicit collection.
|
|
ApproveChaincodeDefinitionForOrg(chname, ccname string, cd *ChaincodeDefinition, packageID string, publicState ReadableState, orgState ReadWritableState) error
|
|
|
|
// QueryApprovedChaincodeDefinition returns a approved chaincode definition from this org's implicit collection.
|
|
QueryApprovedChaincodeDefinition(chname, ccname string, sequence int64, publicState ReadableState, orgState ReadableState) (*ApprovedChaincodeDefinition, error)
|
|
|
|
// CheckCommitReadiness returns a map containing the orgs
|
|
// whose orgStates were supplied and whether or not they have approved
|
|
// the specified definition.
|
|
CheckCommitReadiness(chname, ccname string, cd *ChaincodeDefinition, publicState ReadWritableState, orgStates []OpaqueState) (map[string]bool, error)
|
|
|
|
// CommitChaincodeDefinition records a new chaincode definition into the
|
|
// public state and returns a map containing the orgs whose orgStates
|
|
// were supplied and whether or not they have approved the definition.
|
|
CommitChaincodeDefinition(chname, ccname string, cd *ChaincodeDefinition, publicState ReadWritableState, orgStates []OpaqueState) (map[string]bool, error)
|
|
|
|
// QueryChaincodeDefinition returns a chaincode definition from the public
|
|
// state.
|
|
QueryChaincodeDefinition(name string, publicState ReadableState) (*ChaincodeDefinition, error)
|
|
|
|
// QueryOrgApprovals returns a map containing the orgs whose orgStates were
|
|
// supplied and whether or not they have approved a chaincode definition with
|
|
// the specified parameters.
|
|
QueryOrgApprovals(name string, cd *ChaincodeDefinition, orgStates []OpaqueState) (map[string]bool, error)
|
|
|
|
// QueryNamespaceDefinitions returns all defined namespaces
|
|
QueryNamespaceDefinitions(publicState RangeableState) (map[string]string, error)
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/channel_config_source.go --fake-name ChannelConfigSource . ChannelConfigSource
|
|
|
|
// ChannelConfigSource provides a way to retrieve the channel config for a given
|
|
// channel ID.
|
|
type ChannelConfigSource interface {
|
|
// GetStableChannelConfig returns the channel config for a given channel id.
|
|
// Note, it is a stable bundle, which means it will not be updated, even if
|
|
// the channel is, so it should be discarded after use.
|
|
GetStableChannelConfig(channelID string) channelconfig.Resources
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/queryexecutor_provider.go --fake-name QueryExecutorProvider . QueryExecutorProvider
|
|
|
|
// QueryExecutorProvider provides a way to retrieve the query executor assosciated with an invocation
|
|
type QueryExecutorProvider interface {
|
|
TxQueryExecutor(channelID, txID string) ledger.SimpleQueryExecutor
|
|
}
|
|
|
|
// SCC implements the required methods to satisfy the chaincode interface.
|
|
// It routes the invocation calls to the backing implementations.
|
|
type SCC struct {
|
|
OrgMSPID string
|
|
|
|
ACLProvider aclmgmt.ACLProvider
|
|
|
|
ChannelConfigSource ChannelConfigSource
|
|
|
|
DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
|
|
QueryExecutorProvider QueryExecutorProvider
|
|
|
|
// Functions provides the backing implementation of lifecycle.
|
|
Functions SCCFunctions
|
|
|
|
// Dispatcher handles the rote protobuf boilerplate for unmarshalling/marshaling
|
|
// the inputs and outputs of the SCC functions.
|
|
Dispatcher *dispatcher.Dispatcher
|
|
}
|
|
|
|
// Name returns "_lifecycle"
|
|
func (scc *SCC) Name() string {
|
|
return LifecycleNamespace
|
|
}
|
|
|
|
// Chaincode returns a reference to itself
|
|
func (scc *SCC) Chaincode() shim.Chaincode {
|
|
return scc
|
|
}
|
|
|
|
// Init is mostly useless for system chaincodes and always returns success
|
|
func (scc *SCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
|
|
return shim.Success(nil)
|
|
}
|
|
|
|
// Invoke takes chaincode invocation arguments and routes them to the correct
|
|
// underlying lifecycle operation. All functions take a single argument of
|
|
// type marshaled lb.<FunctionName>Args and return a marshaled lb.<FunctionName>Result
|
|
func (scc *SCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
|
|
args := stub.GetArgs()
|
|
if len(args) == 0 {
|
|
return shim.Error("lifecycle scc must be invoked with arguments")
|
|
}
|
|
|
|
if len(args) != 2 {
|
|
return shim.Error(fmt.Sprintf("lifecycle scc operations require exactly two arguments but received %d", len(args)))
|
|
}
|
|
|
|
var ac channelconfig.Application
|
|
var channelID string
|
|
if channelID = stub.GetChannelID(); channelID != "" {
|
|
channelConfig := scc.ChannelConfigSource.GetStableChannelConfig(channelID)
|
|
if channelConfig == nil {
|
|
return shim.Error(fmt.Sprintf("could not get channelconfig for channel '%s'", channelID))
|
|
}
|
|
var ok bool
|
|
ac, ok = channelConfig.ApplicationConfig()
|
|
if !ok {
|
|
return shim.Error(fmt.Sprintf("could not get application config for channel '%s'", channelID))
|
|
}
|
|
if !ac.Capabilities().LifecycleV20() {
|
|
return shim.Error(fmt.Sprintf("cannot use new lifecycle for channel '%s' as it does not have the required capabilities enabled", channelID))
|
|
}
|
|
}
|
|
|
|
// Handle ACL:
|
|
sp, err := stub.GetSignedProposal()
|
|
if err != nil {
|
|
return shim.Error(fmt.Sprintf("Failed getting signed proposal from stub: [%s]", err))
|
|
}
|
|
|
|
err = scc.ACLProvider.CheckACL(fmt.Sprintf("%s/%s", LifecycleNamespace, args[0]), stub.GetChannelID(), sp)
|
|
if err != nil {
|
|
return shim.Error(fmt.Sprintf("Failed to authorize invocation due to failed ACL check: %s", err))
|
|
}
|
|
|
|
outputBytes, err := scc.Dispatcher.Dispatch(
|
|
args[1],
|
|
string(args[0]),
|
|
&Invocation{
|
|
ChannelID: channelID,
|
|
ApplicationConfig: ac,
|
|
SCC: scc,
|
|
Stub: stub,
|
|
},
|
|
)
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case ErrNamespaceNotDefined, persistence.CodePackageNotFoundErr:
|
|
return pb.Response{
|
|
Status: 404,
|
|
Message: err.Error(),
|
|
}
|
|
default:
|
|
return shim.Error(fmt.Sprintf("failed to invoke backing implementation of '%s': %s", string(args[0]), err.Error()))
|
|
}
|
|
}
|
|
|
|
return shim.Success(outputBytes)
|
|
}
|
|
|
|
type Invocation struct {
|
|
ChannelID string
|
|
ApplicationConfig channelconfig.Application // Note this may be nil
|
|
Stub shim.ChaincodeStubInterface
|
|
SCC *SCC
|
|
}
|
|
|
|
// InstallChaincode is a SCC function that may be dispatched to which routes
|
|
// to the underlying lifecycle implementation.
|
|
func (i *Invocation) InstallChaincode(input *lb.InstallChaincodeArgs) (proto.Message, error) {
|
|
if logger.IsEnabledFor(zapcore.DebugLevel) {
|
|
end := 35
|
|
if len(input.ChaincodeInstallPackage) < end {
|
|
end = len(input.ChaincodeInstallPackage)
|
|
}
|
|
|
|
// the first tens of bytes contain the (compressed) portion
|
|
// of the package metadata and so they'll be different across
|
|
// different packages, acting as a package fingerprint useful
|
|
// to identify various packages from the content
|
|
packageFingerprint := input.ChaincodeInstallPackage[0:end]
|
|
logger.Debugf("received invocation of InstallChaincode for install package %x...",
|
|
packageFingerprint,
|
|
)
|
|
}
|
|
|
|
installedCC, err := i.SCC.Functions.InstallChaincode(input.ChaincodeInstallPackage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lb.InstallChaincodeResult{
|
|
Label: installedCC.Label,
|
|
PackageId: installedCC.PackageID,
|
|
}, nil
|
|
}
|
|
|
|
// QueryInstalledChaincode is a SCC function that may be dispatched to which
|
|
// routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) QueryInstalledChaincode(input *lb.QueryInstalledChaincodeArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of QueryInstalledChaincode for install package ID '%s'",
|
|
input.PackageId,
|
|
)
|
|
|
|
chaincode, err := i.SCC.Functions.QueryInstalledChaincode(input.PackageId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
references := map[string]*lb.QueryInstalledChaincodeResult_References{}
|
|
for channel, chaincodeMetadata := range chaincode.References {
|
|
chaincodes := make([]*lb.QueryInstalledChaincodeResult_Chaincode, len(chaincodeMetadata))
|
|
for i, metadata := range chaincodeMetadata {
|
|
chaincodes[i] = &lb.QueryInstalledChaincodeResult_Chaincode{
|
|
Name: metadata.Name,
|
|
Version: metadata.Version,
|
|
}
|
|
}
|
|
|
|
references[channel] = &lb.QueryInstalledChaincodeResult_References{
|
|
Chaincodes: chaincodes,
|
|
}
|
|
}
|
|
|
|
return &lb.QueryInstalledChaincodeResult{
|
|
Label: chaincode.Label,
|
|
PackageId: chaincode.PackageID,
|
|
References: references,
|
|
}, nil
|
|
}
|
|
|
|
// GetInstalledChaincodePackage is a SCC function that may be dispatched to
|
|
// which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) GetInstalledChaincodePackage(input *lb.GetInstalledChaincodePackageArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of GetInstalledChaincodePackage")
|
|
|
|
pkgBytes, err := i.SCC.Functions.GetInstalledChaincodePackage(input.PackageId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lb.GetInstalledChaincodePackageResult{
|
|
ChaincodeInstallPackage: pkgBytes,
|
|
}, nil
|
|
}
|
|
|
|
// QueryInstalledChaincodes is a SCC function that may be dispatched to which
|
|
// routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) QueryInstalledChaincodes(input *lb.QueryInstalledChaincodesArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of QueryInstalledChaincodes")
|
|
|
|
chaincodes := i.SCC.Functions.QueryInstalledChaincodes()
|
|
|
|
result := &lb.QueryInstalledChaincodesResult{}
|
|
for _, chaincode := range chaincodes {
|
|
references := map[string]*lb.QueryInstalledChaincodesResult_References{}
|
|
for channel, chaincodeMetadata := range chaincode.References {
|
|
chaincodes := make([]*lb.QueryInstalledChaincodesResult_Chaincode, len(chaincodeMetadata))
|
|
for i, metadata := range chaincodeMetadata {
|
|
chaincodes[i] = &lb.QueryInstalledChaincodesResult_Chaincode{
|
|
Name: metadata.Name,
|
|
Version: metadata.Version,
|
|
}
|
|
}
|
|
|
|
references[channel] = &lb.QueryInstalledChaincodesResult_References{
|
|
Chaincodes: chaincodes,
|
|
}
|
|
}
|
|
|
|
result.InstalledChaincodes = append(result.InstalledChaincodes,
|
|
&lb.QueryInstalledChaincodesResult_InstalledChaincode{
|
|
Label: chaincode.Label,
|
|
PackageId: chaincode.PackageID,
|
|
References: references,
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// ApproveChaincodeDefinitionForMyOrg is a SCC function that may be dispatched
|
|
// to which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) ApproveChaincodeDefinitionForMyOrg(input *lb.ApproveChaincodeDefinitionForMyOrgArgs) (proto.Message, error) {
|
|
if err := i.validateInput(input.Name, input.Version, input.Collections); err != nil {
|
|
return nil, errors.WithMessage(err, "error validating chaincode definition")
|
|
}
|
|
collectionName := implicitcollection.NameForOrg(i.SCC.OrgMSPID)
|
|
var collectionConfig []*pb.CollectionConfig
|
|
if input.Collections != nil {
|
|
collectionConfig = input.Collections.Config
|
|
}
|
|
|
|
var packageID string
|
|
if input.Source != nil {
|
|
switch source := input.Source.Type.(type) {
|
|
case *lb.ChaincodeSource_LocalPackage:
|
|
packageID = source.LocalPackage.PackageId
|
|
case *lb.ChaincodeSource_Unavailable_:
|
|
default:
|
|
}
|
|
}
|
|
|
|
cd := &ChaincodeDefinition{
|
|
Sequence: input.Sequence,
|
|
EndorsementInfo: &lb.ChaincodeEndorsementInfo{
|
|
Version: input.Version,
|
|
EndorsementPlugin: input.EndorsementPlugin,
|
|
InitRequired: input.InitRequired,
|
|
},
|
|
ValidationInfo: &lb.ChaincodeValidationInfo{
|
|
ValidationPlugin: input.ValidationPlugin,
|
|
ValidationParameter: input.ValidationParameter,
|
|
},
|
|
Collections: &pb.CollectionConfigPackage{
|
|
Config: collectionConfig,
|
|
},
|
|
}
|
|
|
|
logger.Debugf("received invocation of ApproveChaincodeDefinitionForMyOrg on channel '%s' for definition '%s'",
|
|
i.Stub.GetChannelID(),
|
|
cd,
|
|
)
|
|
|
|
if err := i.SCC.Functions.ApproveChaincodeDefinitionForOrg(
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
cd,
|
|
packageID,
|
|
i.Stub,
|
|
&ChaincodePrivateLedgerShim{
|
|
Collection: collectionName,
|
|
Stub: i.Stub,
|
|
},
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
return &lb.ApproveChaincodeDefinitionForMyOrgResult{}, nil
|
|
}
|
|
|
|
// QueryApprovedChaincodeDefinition is a SCC function that may be dispatched
|
|
// to which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) QueryApprovedChaincodeDefinition(input *lb.QueryApprovedChaincodeDefinitionArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of QueryApprovedChaincodeDefinition on channel '%s' for chaincode '%s'",
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
)
|
|
collectionName := implicitcollection.NameForOrg(i.SCC.OrgMSPID)
|
|
|
|
ca, err := i.SCC.Functions.QueryApprovedChaincodeDefinition(
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
input.Sequence,
|
|
i.Stub,
|
|
&ChaincodePrivateLedgerShim{
|
|
Collection: collectionName,
|
|
Stub: i.Stub,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lb.QueryApprovedChaincodeDefinitionResult{
|
|
Sequence: ca.Sequence,
|
|
Version: ca.EndorsementInfo.Version,
|
|
EndorsementPlugin: ca.EndorsementInfo.EndorsementPlugin,
|
|
ValidationPlugin: ca.ValidationInfo.ValidationPlugin,
|
|
ValidationParameter: ca.ValidationInfo.ValidationParameter,
|
|
InitRequired: ca.EndorsementInfo.InitRequired,
|
|
Collections: ca.Collections,
|
|
Source: ca.Source,
|
|
}, nil
|
|
}
|
|
|
|
// CheckCommitReadiness is a SCC function that may be dispatched
|
|
// to the underlying lifecycle implementation.
|
|
func (i *Invocation) CheckCommitReadiness(input *lb.CheckCommitReadinessArgs) (proto.Message, error) {
|
|
opaqueStates, err := i.createOpaqueStates()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cd := &ChaincodeDefinition{
|
|
Sequence: input.Sequence,
|
|
EndorsementInfo: &lb.ChaincodeEndorsementInfo{
|
|
Version: input.Version,
|
|
EndorsementPlugin: input.EndorsementPlugin,
|
|
InitRequired: input.InitRequired,
|
|
},
|
|
ValidationInfo: &lb.ChaincodeValidationInfo{
|
|
ValidationPlugin: input.ValidationPlugin,
|
|
ValidationParameter: input.ValidationParameter,
|
|
},
|
|
Collections: input.Collections,
|
|
}
|
|
|
|
logger.Debugf("received invocation of CheckCommitReadiness on channel '%s' for definition '%s'",
|
|
i.Stub.GetChannelID(),
|
|
cd,
|
|
)
|
|
|
|
approvals, err := i.SCC.Functions.CheckCommitReadiness(
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
cd,
|
|
i.Stub,
|
|
opaqueStates,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lb.CheckCommitReadinessResult{
|
|
Approvals: approvals,
|
|
}, nil
|
|
}
|
|
|
|
// CommitChaincodeDefinition is a SCC function that may be dispatched
|
|
// to which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) CommitChaincodeDefinition(input *lb.CommitChaincodeDefinitionArgs) (proto.Message, error) {
|
|
if err := i.validateInput(input.Name, input.Version, input.Collections); err != nil {
|
|
return nil, errors.WithMessage(err, "error validating chaincode definition")
|
|
}
|
|
|
|
if i.ApplicationConfig == nil {
|
|
return nil, errors.Errorf("no application config for channel '%s'", i.Stub.GetChannelID())
|
|
}
|
|
|
|
orgs := i.ApplicationConfig.Organizations()
|
|
opaqueStates := make([]OpaqueState, 0, len(orgs))
|
|
var myOrg string
|
|
for _, org := range orgs {
|
|
opaqueStates = append(opaqueStates, &ChaincodePrivateLedgerShim{
|
|
Collection: implicitcollection.NameForOrg(org.MSPID()),
|
|
Stub: i.Stub,
|
|
})
|
|
if org.MSPID() == i.SCC.OrgMSPID {
|
|
myOrg = i.SCC.OrgMSPID
|
|
}
|
|
}
|
|
|
|
if myOrg == "" {
|
|
return nil, errors.Errorf("impossibly, this peer's org is processing requests for a channel it is not a member of")
|
|
}
|
|
|
|
cd := &ChaincodeDefinition{
|
|
Sequence: input.Sequence,
|
|
EndorsementInfo: &lb.ChaincodeEndorsementInfo{
|
|
Version: input.Version,
|
|
EndorsementPlugin: input.EndorsementPlugin,
|
|
InitRequired: input.InitRequired,
|
|
},
|
|
ValidationInfo: &lb.ChaincodeValidationInfo{
|
|
ValidationPlugin: input.ValidationPlugin,
|
|
ValidationParameter: input.ValidationParameter,
|
|
},
|
|
Collections: input.Collections,
|
|
}
|
|
|
|
logger.Debugf("received invocation of CommitChaincodeDefinition on channel '%s' for definition '%s'",
|
|
i.Stub.GetChannelID(),
|
|
cd,
|
|
)
|
|
|
|
approvals, err := i.SCC.Functions.CommitChaincodeDefinition(
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
cd,
|
|
i.Stub,
|
|
opaqueStates,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !approvals[myOrg] {
|
|
return nil, errors.Errorf("chaincode definition not agreed to by this org (%s)", i.SCC.OrgMSPID)
|
|
}
|
|
|
|
logger.Infof("Successfully endorsed commit for chaincode name '%s' on channel '%s' with definition {%s}", input.Name, i.Stub.GetChannelID(), cd)
|
|
|
|
return &lb.CommitChaincodeDefinitionResult{}, nil
|
|
}
|
|
|
|
// QueryChaincodeDefinition is a SCC function that may be dispatched
|
|
// to which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) QueryChaincodeDefinition(input *lb.QueryChaincodeDefinitionArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of QueryChaincodeDefinition on channel '%s' for chaincode '%s'",
|
|
i.Stub.GetChannelID(),
|
|
input.Name,
|
|
)
|
|
|
|
definedChaincode, err := i.SCC.Functions.QueryChaincodeDefinition(input.Name, i.Stub)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opaqueStates, err := i.createOpaqueStates()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var approvals map[string]bool
|
|
if approvals, err = i.SCC.Functions.QueryOrgApprovals(input.Name, definedChaincode, opaqueStates); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lb.QueryChaincodeDefinitionResult{
|
|
Sequence: definedChaincode.Sequence,
|
|
Version: definedChaincode.EndorsementInfo.Version,
|
|
EndorsementPlugin: definedChaincode.EndorsementInfo.EndorsementPlugin,
|
|
ValidationPlugin: definedChaincode.ValidationInfo.ValidationPlugin,
|
|
ValidationParameter: definedChaincode.ValidationInfo.ValidationParameter,
|
|
InitRequired: definedChaincode.EndorsementInfo.InitRequired,
|
|
Collections: definedChaincode.Collections,
|
|
Approvals: approvals,
|
|
}, nil
|
|
}
|
|
|
|
// QueryChaincodeDefinitions is a SCC function that may be dispatched
|
|
// to which routes to the underlying lifecycle implementation.
|
|
func (i *Invocation) QueryChaincodeDefinitions(input *lb.QueryChaincodeDefinitionsArgs) (proto.Message, error) {
|
|
logger.Debugf("received invocation of QueryChaincodeDefinitions on channel '%s'",
|
|
i.Stub.GetChannelID(),
|
|
)
|
|
|
|
namespaces, err := i.SCC.Functions.QueryNamespaceDefinitions(&ChaincodePublicLedgerShim{ChaincodeStubInterface: i.Stub})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
chaincodeDefinitions := []*lb.QueryChaincodeDefinitionsResult_ChaincodeDefinition{}
|
|
for namespace, nType := range namespaces {
|
|
if nType == FriendlyChaincodeDefinitionType {
|
|
definedChaincode, err := i.SCC.Functions.QueryChaincodeDefinition(namespace, i.Stub)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
chaincodeDefinitions = append(chaincodeDefinitions, &lb.QueryChaincodeDefinitionsResult_ChaincodeDefinition{
|
|
Name: namespace,
|
|
Sequence: definedChaincode.Sequence,
|
|
Version: definedChaincode.EndorsementInfo.Version,
|
|
EndorsementPlugin: definedChaincode.EndorsementInfo.EndorsementPlugin,
|
|
ValidationPlugin: definedChaincode.ValidationInfo.ValidationPlugin,
|
|
ValidationParameter: definedChaincode.ValidationInfo.ValidationParameter,
|
|
InitRequired: definedChaincode.EndorsementInfo.InitRequired,
|
|
Collections: definedChaincode.Collections,
|
|
})
|
|
}
|
|
}
|
|
|
|
return &lb.QueryChaincodeDefinitionsResult{
|
|
ChaincodeDefinitions: chaincodeDefinitions,
|
|
}, nil
|
|
}
|
|
|
|
var (
|
|
// NOTE the chaincode name/version regular expressions should stay in sync
|
|
// with those defined in core/scc/lscc/lscc.go until LSCC has been removed.
|
|
ChaincodeNameRegExp = regexp.MustCompile("^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$")
|
|
ChaincodeVersionRegExp = regexp.MustCompile("^[A-Za-z0-9_.+-]+$")
|
|
|
|
collectionNameRegExp = regexp.MustCompile("^[A-Za-z0-9-]+([A-Za-z0-9_-]+)*$")
|
|
|
|
// currently defined system chaincode names that shouldn't
|
|
// be allowed as user-defined chaincode names
|
|
systemChaincodeNames = map[string]struct{}{
|
|
"cscc": {},
|
|
"escc": {},
|
|
"lscc": {},
|
|
"qscc": {},
|
|
"vscc": {},
|
|
}
|
|
)
|
|
|
|
func (i *Invocation) validateInput(name, version string, collections *pb.CollectionConfigPackage) error {
|
|
if !ChaincodeNameRegExp.MatchString(name) {
|
|
return errors.Errorf("invalid chaincode name '%s'. Names can only consist of alphanumerics, '_', and '-' and can only begin with alphanumerics", name)
|
|
}
|
|
if _, ok := systemChaincodeNames[name]; ok {
|
|
return errors.Errorf("chaincode name '%s' is the name of a system chaincode", name)
|
|
}
|
|
|
|
if !ChaincodeVersionRegExp.MatchString(version) {
|
|
return errors.Errorf("invalid chaincode version '%s'. Versions can only consist of alphanumerics, '_', '-', '+', and '.'", version)
|
|
}
|
|
|
|
collConfigs, err := extractStaticCollectionConfigs(collections)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// we extract the channel config to check whether the supplied collection configuration
|
|
// complies to the given msp configuration and performs semantic validation.
|
|
// Channel config may change afterwards (i.e., after endorsement or commit of this transaction).
|
|
// Fabric will deal with the situation where some collection configs are no longer meaningful.
|
|
// Therefore, the use of channel config for verifying during endorsement is more
|
|
// towards catching manual errors in the config as oppose to any attempt of serializability.
|
|
channelConfig := i.SCC.ChannelConfigSource.GetStableChannelConfig(i.ChannelID)
|
|
if channelConfig == nil {
|
|
return errors.Errorf("could not get channelconfig for channel '%s'", i.ChannelID)
|
|
}
|
|
mspMgr := channelConfig.MSPManager()
|
|
if mspMgr == nil {
|
|
return errors.Errorf("could not get MSP manager for channel '%s'", i.ChannelID)
|
|
}
|
|
|
|
if err := validateCollectionConfigs(collConfigs, mspMgr); err != nil {
|
|
return err
|
|
}
|
|
|
|
// validate against collection configs in the committed definition
|
|
qe := i.SCC.QueryExecutorProvider.TxQueryExecutor(i.Stub.GetChannelID(), i.Stub.GetTxID())
|
|
committedCCDef, err := i.SCC.DeployedCCInfoProvider.ChaincodeInfo(i.ChannelID, name, qe)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "could not retrieve committed definition for chaincode '%s'", name)
|
|
}
|
|
if committedCCDef == nil {
|
|
return nil
|
|
}
|
|
if err := validateCollConfigsAgainstCommittedDef(collConfigs, committedCCDef.ExplicitCollectionConfigPkg); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func extractStaticCollectionConfigs(collConfigPkg *pb.CollectionConfigPackage) ([]*pb.StaticCollectionConfig, error) {
|
|
if collConfigPkg == nil || len(collConfigPkg.Config) == 0 {
|
|
return nil, nil
|
|
}
|
|
collConfigs := make([]*pb.StaticCollectionConfig, len(collConfigPkg.Config))
|
|
for i, c := range collConfigPkg.Config {
|
|
switch t := c.Payload.(type) {
|
|
case *pb.CollectionConfig_StaticCollectionConfig:
|
|
collConfig := t.StaticCollectionConfig
|
|
if collConfig == nil {
|
|
return nil, errors.Errorf("collection configuration is empty")
|
|
}
|
|
collConfigs[i] = collConfig
|
|
default:
|
|
// this should only occur if a developer has added a new
|
|
// collection config type
|
|
return nil, errors.Errorf("collection config contains unexpected payload type: %T", t)
|
|
}
|
|
}
|
|
return collConfigs, nil
|
|
}
|
|
|
|
func validateCollectionConfigs(collConfigs []*pb.StaticCollectionConfig, mspMgr msp.MSPManager) error {
|
|
if len(collConfigs) == 0 {
|
|
return nil
|
|
}
|
|
collNamesMap := map[string]struct{}{}
|
|
// Process each collection config from a set of collection configs
|
|
for _, c := range collConfigs {
|
|
if !collectionNameRegExp.MatchString(c.Name) {
|
|
return errors.Errorf("invalid collection name '%s'. Names can only consist of alphanumerics, '_', and '-' and cannot begin with '_'",
|
|
c.Name)
|
|
}
|
|
// Ensure that there are no duplicate collection names
|
|
if _, ok := collNamesMap[c.Name]; ok {
|
|
return errors.Errorf("collection-name: %s -- found duplicate in collection configuration",
|
|
c.Name)
|
|
}
|
|
collNamesMap[c.Name] = struct{}{}
|
|
// Validate gossip related parameters present in the collection config
|
|
if c.MaximumPeerCount < c.RequiredPeerCount {
|
|
return errors.Errorf("collection-name: %s -- maximum peer count (%d) cannot be less than the required peer count (%d)",
|
|
c.Name, c.MaximumPeerCount, c.RequiredPeerCount)
|
|
}
|
|
if c.RequiredPeerCount < 0 {
|
|
return errors.Errorf("collection-name: %s -- requiredPeerCount (%d) cannot be less than zero",
|
|
c.Name, c.RequiredPeerCount)
|
|
}
|
|
if err := validateCollectionConfigMemberOrgsPolicy(c, mspMgr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateCollectionConfigAgainstMsp checks whether the supplied collection configuration
|
|
// complies to the given msp configuration
|
|
func validateCollectionConfigMemberOrgsPolicy(coll *pb.StaticCollectionConfig, mspMgr msp.MSPManager) error {
|
|
if coll.MemberOrgsPolicy == nil {
|
|
return errors.Errorf("collection member policy is not set for collection '%s'", coll.Name)
|
|
}
|
|
if coll.MemberOrgsPolicy.GetSignaturePolicy() == nil {
|
|
return errors.Errorf("collection member org policy is empty for collection '%s'", coll.Name)
|
|
}
|
|
|
|
// calling this constructor ensures extra semantic validation for the policy
|
|
pp := &cauthdsl.EnvelopeBasedPolicyProvider{Deserializer: mspMgr}
|
|
if _, err := pp.NewPolicy(coll.MemberOrgsPolicy.GetSignaturePolicy()); err != nil {
|
|
return errors.WithMessagef(err, "invalid member org policy for collection '%s'", coll.Name)
|
|
}
|
|
|
|
// make sure that the signature policy is meaningful (only consists of ORs)
|
|
if err := validateSpOrConcat(coll.MemberOrgsPolicy.GetSignaturePolicy().Rule); err != nil {
|
|
return errors.WithMessagef(err, "collection-name: %s -- error in member org policy", coll.Name)
|
|
}
|
|
|
|
msps, err := mspMgr.GetMSPs()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "could not get MSPs")
|
|
}
|
|
|
|
// make sure that the orgs listed are actually part of the channel
|
|
// check all principals in the signature policy
|
|
for _, principal := range coll.MemberOrgsPolicy.GetSignaturePolicy().Identities {
|
|
var orgID string
|
|
// the member org policy only supports certain principal types
|
|
switch principal.PrincipalClassification {
|
|
|
|
case mspprotos.MSPPrincipal_ROLE:
|
|
msprole := &mspprotos.MSPRole{}
|
|
err := proto.Unmarshal(principal.Principal, msprole)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "collection-name: %s -- cannot unmarshal identity bytes into MSPRole", coll.GetName())
|
|
}
|
|
orgID = msprole.MspIdentifier
|
|
// the msp map is indexed using msp IDs - this behavior is implementation specific, making the following check a bit of a hack
|
|
_, ok := msps[orgID]
|
|
if !ok {
|
|
return errors.Errorf("collection-name: %s -- collection member '%s' is not part of the channel", coll.GetName(), orgID)
|
|
}
|
|
|
|
case mspprotos.MSPPrincipal_ORGANIZATION_UNIT:
|
|
mspou := &mspprotos.OrganizationUnit{}
|
|
err := proto.Unmarshal(principal.Principal, mspou)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "collection-name: %s -- cannot unmarshal identity bytes into OrganizationUnit", coll.GetName())
|
|
}
|
|
orgID = mspou.MspIdentifier
|
|
// the msp map is indexed using msp IDs - this behavior is implementation specific, making the following check a bit of a hack
|
|
_, ok := msps[orgID]
|
|
if !ok {
|
|
return errors.Errorf("collection-name: %s -- collection member '%s' is not part of the channel", coll.GetName(), orgID)
|
|
}
|
|
|
|
case mspprotos.MSPPrincipal_IDENTITY:
|
|
if _, err := mspMgr.DeserializeIdentity(principal.Principal); err != nil {
|
|
return errors.Errorf("collection-name: %s -- contains an identity that is not part of the channel", coll.GetName())
|
|
}
|
|
|
|
default:
|
|
return errors.Errorf("collection-name: %s -- principal type %v is not supported", coll.GetName(), principal.PrincipalClassification)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateSpOrConcat checks if the supplied signature policy is just an OR-concatenation of identities
|
|
func validateSpOrConcat(sp *common.SignaturePolicy) error {
|
|
if sp.GetNOutOf() == nil {
|
|
return nil
|
|
}
|
|
// check if N == 1 (OR concatenation)
|
|
if sp.GetNOutOf().N != 1 {
|
|
return errors.Errorf("signature policy is not an OR concatenation, NOutOf %d", sp.GetNOutOf().N)
|
|
}
|
|
// recurse into all sub-rules
|
|
for _, rule := range sp.GetNOutOf().Rules {
|
|
err := validateSpOrConcat(rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateCollConfigsAgainstCommittedDef(
|
|
proposedCollConfs []*pb.StaticCollectionConfig,
|
|
committedCollConfPkg *pb.CollectionConfigPackage,
|
|
) error {
|
|
if committedCollConfPkg == nil || len(committedCollConfPkg.Config) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(proposedCollConfs) == 0 {
|
|
return errors.Errorf("the proposed collection config does not contain previously defined collections")
|
|
}
|
|
|
|
proposedCollsMap := map[string]*pb.StaticCollectionConfig{}
|
|
for _, c := range proposedCollConfs {
|
|
proposedCollsMap[c.Name] = c
|
|
}
|
|
|
|
// In the new collection config package, ensure that there is one entry per old collection. Any
|
|
// number of new collections are allowed.
|
|
for _, committedCollConfig := range committedCollConfPkg.Config {
|
|
committedColl := committedCollConfig.GetStaticCollectionConfig()
|
|
// It cannot be nil
|
|
if committedColl == nil {
|
|
return errors.Errorf("unknown collection configuration type")
|
|
}
|
|
|
|
newCollection, ok := proposedCollsMap[committedColl.Name]
|
|
if !ok {
|
|
return errors.Errorf("existing collection [%s] missing in the proposed collection configuration", committedColl.Name)
|
|
}
|
|
|
|
if newCollection.BlockToLive != committedColl.BlockToLive {
|
|
return errors.Errorf("the BlockToLive in an existing collection [%s] modified. Existing value [%d]", committedColl.Name, committedColl.BlockToLive)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *Invocation) createOpaqueStates() ([]OpaqueState, error) {
|
|
if i.ApplicationConfig == nil {
|
|
return nil, errors.Errorf("no application config for channel '%s'", i.Stub.GetChannelID())
|
|
}
|
|
orgs := i.ApplicationConfig.Organizations()
|
|
opaqueStates := make([]OpaqueState, 0, len(orgs))
|
|
for _, org := range orgs {
|
|
opaqueStates = append(opaqueStates, &ChaincodePrivateLedgerShim{
|
|
Collection: implicitcollection.NameForOrg(org.MSPID()),
|
|
Stub: i.Stub,
|
|
})
|
|
}
|
|
return opaqueStates, nil
|
|
}
|