Skip to content

Commit

Permalink
[FAB-17299] Support collection level endorsement policies for discove…
Browse files Browse the repository at this point in the history
…ry support (hyperledger#472)

* PoliciesByChaincode now returns one InquireablePolicy per collection,
either the collection's endorsement policy if one exists or the
chaincode policy by default
* chaincode.Metadata now stores CollectionPolicies for _lifecycle
namespace chaincodes that have collection endorsement policies

Signed-off-by: Danny Cao <[email protected]>
Signed-off-by: Wenjian Qiao <[email protected]>
  • Loading branch information
yacovm authored Mar 5, 2020
1 parent 863433f commit 131c9e9
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 133 deletions.
14 changes: 9 additions & 5 deletions common/chaincode/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ type InstalledChaincode struct {

// Metadata defines channel-scoped metadata of a chaincode
type Metadata struct {
Name string
Version string
Policy []byte
Id []byte
CollectionsConfig *peer.CollectionConfigPackage
Name string
Version string
Policy []byte
// CollectionPolicies will only be set for _lifecycle
// chaincodes and stores a map from collection name to
// that collection's endorsement policy if one exists.
CollectionPolicies map[string][]byte
Id []byte
CollectionsConfig *peer.CollectionConfigPackage
// These two fields (Approved, Installed) are only set for
// _lifecycle chaincodes. They are used to ensure service
// discovery doesn't publish a stale chaincode definition
Expand Down
6 changes: 3 additions & 3 deletions core/cclifecycle/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ func NewMetadataManager(installedChaincodes Enumerator) (*MetadataManager, error

// Metadata returns the metadata of the chaincode on the given channel,
// or nil if not found or an error occurred at retrieving it
func (m *MetadataManager) Metadata(channel string, cc string, collections bool) *chaincode.Metadata {
func (m *MetadataManager) Metadata(channel string, cc string, collections ...string) *chaincode.Metadata {
queryCreator := m.queryCreatorsByChannel[channel]
if queryCreator == nil {
Logger.Warning("Requested Metadata for non-existent channel", channel)
return nil
}
// Search the metadata in our local cache, and if it exists - return it, but only if
// no collections were specified in the invocation.
if md, found := m.deployedCCsByChannel[channel].Lookup(cc); found && !collections {
if md, found := m.deployedCCsByChannel[channel].Lookup(cc); found && len(collections) == 0 {
Logger.Debug("Returning metadata for channel", channel, ", chaincode", cc, ":", md)
return &md
}
Expand All @@ -122,7 +122,7 @@ func (m *MetadataManager) Metadata(channel string, cc string, collections bool)
Logger.Error("Failed obtaining new query for channel", channel, ":", err)
return nil
}
md, err := DeployedChaincodes(query, AcceptAll, collections, cc)
md, err := DeployedChaincodes(query, AcceptAll, len(collections) > 0, cc)
if err != nil {
Logger.Error("Failed querying LSCC for channel", channel, ":", err)
return nil
Expand Down
16 changes: 8 additions & 8 deletions core/cclifecycle/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func TestMetadata(t *testing.T) {
assert.NoError(t, err)

// Scenario I: No subscription was invoked on the lifecycle
md := m.Metadata("mychannel", "cc1", false)
md := m.Metadata("mychannel", "cc1")
assert.Nil(t, md)
assertLogged(t, recorder, "Requested Metadata for non-existent channel mychannel")

Expand All @@ -398,7 +398,7 @@ func TestMetadata(t *testing.T) {
defer sub.ChaincodeDeployDone(true)
assert.NoError(t, err)
assert.NotNil(t, sub)
md = m.Metadata("mychannel", "cc1", false)
md = m.Metadata("mychannel", "cc1")
assert.Equal(t, &chaincode.Metadata{
Name: "cc1",
Version: "1.0",
Expand All @@ -410,31 +410,31 @@ func TestMetadata(t *testing.T) {
// Scenario III: A metadata retrieval is made and the chaincode is not in memory yet,
// and when the query is attempted to be made - it fails.
queryCreator.On("NewQuery").Return(nil, errors.New("failed obtaining query executor")).Once()
md = m.Metadata("mychannel", "cc2", false)
md = m.Metadata("mychannel", "cc2")
assert.Nil(t, md)
assertLogged(t, recorder, "Failed obtaining new query for channel mychannel : failed obtaining query executor")

// Scenario IV: A metadata retrieval is made and the chaincode is not in memory yet,
// and when the query is attempted to be made - it succeeds, but GetState fails.
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(nil, errors.New("GetState failed")).Once()
md = m.Metadata("mychannel", "cc2", false)
md = m.Metadata("mychannel", "cc2")
assert.Nil(t, md)
assertLogged(t, recorder, "Failed querying LSCC for channel mychannel : GetState failed")

// Scenario V: A metadata retrieval is made and the chaincode is not in memory yet,
// and both the query and the GetState succeed, however - GetState returns nil
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(nil, nil).Once()
md = m.Metadata("mychannel", "cc2", false)
md = m.Metadata("mychannel", "cc2")
assert.Nil(t, md)
assertLogged(t, recorder, "Chaincode cc2 isn't defined in channel mychannel")

// Scenario VI: A metadata retrieval is made and the chaincode is not in memory yet,
// and both the query and the GetState succeed, however - GetState returns a valid metadata
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil).Once()
md = m.Metadata("mychannel", "cc2", false)
md = m.Metadata("mychannel", "cc2")
assert.Equal(t, &chaincode.Metadata{
Name: "cc2",
Version: "1.0",
Expand All @@ -447,7 +447,7 @@ func TestMetadata(t *testing.T) {
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(protoutil.MarshalOrPanic(&peer.CollectionConfigPackage{}), nil).Once()
md = m.Metadata("mychannel", "cc1", true)
md = m.Metadata("mychannel", "cc1", "col1")
assert.Equal(t, &chaincode.Metadata{
Name: "cc1",
Version: "1.0",
Expand All @@ -463,7 +463,7 @@ func TestMetadata(t *testing.T) {
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(nil, errors.New("foo")).Once()
md = m.Metadata("mychannel", "cc1", true)
md = m.Metadata("mychannel", "cc1", "col1")
assert.Nil(t, md)
assertLogged(t, recorder, "Failed querying lscc namespace for cc1~collection: foo")
}
Expand Down
43 changes: 34 additions & 9 deletions core/chaincode/lifecycle/metadata_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ import (
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/common/policydsl"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
)

// LegacyMetadataProvider provides metadata for a lscc-defined chaincode
// on a specific channel.
type LegacyMetadataProvider interface {
Metadata(channel string, cc string, includeCollections bool) *chaincode.Metadata
Metadata(channel string, cc string, collections ...string) *chaincode.Metadata
}

// ChaincodeInfoProvider provides metadata for a _lifecycle-defined
Expand Down Expand Up @@ -105,28 +104,54 @@ func (mp *MetadataProvider) toSignaturePolicyEnvelope(channelID string, policyBy
}

// Metadata implements the metadata retriever support interface for service discovery
func (mp *MetadataProvider) Metadata(channel string, ccName string, includeCollections bool) *chaincode.Metadata {
func (mp *MetadataProvider) Metadata(channel string, ccName string, collections ...string) *chaincode.Metadata {
ccInfo, err := mp.ChaincodeInfoProvider.ChaincodeInfo(channel, ccName)
if err != nil {
logger.Debugf("chaincode '%s' on channel '%s' not defined in _lifecycle. requesting metadata from lscc", ccName, channel)
// fallback to legacy metadata via cclifecycle
return mp.LegacyMetadataProvider.Metadata(channel, ccName, includeCollections)
return mp.LegacyMetadataProvider.Metadata(channel, ccName, collections...)
}

spe, err := mp.toSignaturePolicyEnvelope(channel, ccInfo.Definition.ValidationInfo.ValidationParameter)
if err != nil {
logger.Errorf("could not convert policy for chaincode '%s' on channel '%s', err '%s'", ccName, channel, err)
spe = policydsl.MarshaledRejectAllPolicy
return nil
}

// 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
ccMetadata := &chaincode.Metadata{
Name: ccName,
Version: strconv.FormatInt(ccInfo.Definition.Sequence, 10),
Policy: spe,
CollectionsConfig: ccInfo.Definition.Collections,
Name: ccName,
Version: strconv.FormatInt(ccInfo.Definition.Sequence, 10),
Policy: spe,
CollectionPolicies: map[string][]byte{},
CollectionsConfig: ccInfo.Definition.Collections,
}

if ccInfo.Definition.Collections == nil {
return ccMetadata
}

// process any existing collection endorsement policies
for _, collectionName := range collections {
for _, conf := range ccInfo.Definition.Collections.Config {
staticCollConfig := conf.GetStaticCollectionConfig()
if staticCollConfig == nil {
continue
}
if staticCollConfig.Name == collectionName {
if staticCollConfig.EndorsementPolicy != nil {
ep := protoutil.MarshalOrPanic(staticCollConfig.EndorsementPolicy)
cspe, err := mp.toSignaturePolicyEnvelope(channel, ep)
if err != nil {
logger.Errorf("could not convert collection policy for chaincode '%s' collection '%s' on channel '%s', err '%s'", ccName, collectionName, channel, err)
return nil
}
ccMetadata.CollectionPolicies[collectionName] = cspe
}
}
}
}

return ccMetadata
Expand Down
Loading

0 comments on commit 131c9e9

Please sign in to comment.