Skip to content

Commit

Permalink
[FAB-18527] Discovery supports state based endorsement queries (#2764)
Browse files Browse the repository at this point in the history
This commit adds support for state based endorsement queries to the discovery service.

When state based endorsement policies are passed in the chaincode call,
discovery now combines them and then requires their combinations to be satisfied as well
as the chaincode endorsement policies.

Support for chaincode calls with *only* state based endorsement is future work and not implemented
yet because of lack of a way to express this intent in the protobuf definition.

Change-Id: I005a6abca77061cd531888cdff544691b25d09ec
Signed-off-by: Yacov Manevich <yacovm@il.ibm.com>
  • Loading branch information
yacovm authored Jul 19, 2021
1 parent 84c1270 commit 9a922fd
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 3 deletions.
60 changes: 57 additions & 3 deletions discovery/endorsement/endorsement.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,21 +216,64 @@ func filterOutUnsatisfiedLayouts(endorsersByGroup map[string]*discovery.Peers, l
return filteredLayouts
}

func computeStateBasedPrincipalSets(chaincodes []*peer.ChaincodeCall, logger *flogging.FabricLogger) (inquire.ComparablePrincipalSets, error) {
var stateBasedCPS []inquire.ComparablePrincipalSets
for _, chaincode := range chaincodes {
if len(chaincode.KeyPolicies) == 0 {
continue
}

logger.Debugf("Chaincode call to %s is satisfied by %d state based policies of %v",
chaincode.Name, len(chaincode.KeyPolicies), chaincode.KeyPolicies)

for _, stateBasedPolicy := range chaincode.KeyPolicies {
var cmpsets inquire.ComparablePrincipalSets
stateBasedPolicy := inquire.NewInquireableSignaturePolicy(stateBasedPolicy)
for _, ps := range stateBasedPolicy.SatisfiedBy() {
cps := inquire.NewComparablePrincipalSet(ps)
if cps == nil {
return nil, errors.New("failed creating a comparable principal set for state based endorsement")
}
cmpsets = append(cmpsets, cps)
}
if len(cmpsets) == 0 {
return nil, errors.New("state based endorsement policy cannot be satisfied")
}
stateBasedCPS = append(stateBasedCPS, cmpsets)
}
}

if len(stateBasedCPS) > 0 {
stateBasedPrincipalSet, err := mergePrincipalSets(stateBasedCPS)
if err != nil {
return nil, errors.WithStack(err)
}

logger.Debugf("Merging state based policies: %v --> %v", stateBasedCPS, stateBasedPrincipalSet)

return stateBasedPrincipalSet, nil
}

logger.Debugf("No state based policies requested")

return nil, nil
}

func (ea *endorsementAnalyzer) computePrincipalSets(channelID common.ChannelID, interest *peer.ChaincodeInterest) (policies.PrincipalSets, error) {
sessionLogger := logger.With("channel", string(channelID))
var inquireablePolicies []policies.InquireablePolicy
var inquireablePoliciesForChaincodeAndCollections []policies.InquireablePolicy
for _, chaincode := range interest.Chaincodes {
policies := ea.PoliciesByChaincode(string(channelID), chaincode.Name, chaincode.CollectionNames...)
if len(policies) == 0 {
sessionLogger.Debug("Policy for chaincode '", chaincode, "'doesn't exist")
return nil, errors.New("policy not found")
}
inquireablePolicies = append(inquireablePolicies, policies...)
inquireablePoliciesForChaincodeAndCollections = append(inquireablePoliciesForChaincodeAndCollections, policies...)
}

var cpss []inquire.ComparablePrincipalSets

for _, policy := range inquireablePolicies {
for _, policy := range inquireablePoliciesForChaincodeAndCollections {
var cmpsets inquire.ComparablePrincipalSets
for _, ps := range policy.SatisfiedBy() {
cps := inquire.NewComparablePrincipalSet(ps)
Expand All @@ -245,11 +288,22 @@ func (ea *endorsementAnalyzer) computePrincipalSets(channelID common.ChannelID,
cpss = append(cpss, cmpsets)
}

stateBasedCPS, err := computeStateBasedPrincipalSets(interest.Chaincodes, sessionLogger)
if err != nil {
return nil, errors.WithStack(err)
}

if len(stateBasedCPS) > 0 {
cpss = append(cpss, stateBasedCPS)
}

cps, err := mergePrincipalSets(cpss)
if err != nil {
return nil, errors.WithStack(err)
}

sessionLogger.Debugf("Merging principal sets: %v --> %v", cpss, cps)

return cps.ToPrincipalSets(), nil
}

Expand Down
200 changes: 200 additions & 0 deletions discovery/endorsement/endorsement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"fmt"
"testing"

common2 "github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/common/policydsl"

"github.com/golang/protobuf/proto"
discoveryprotos "github.com/hyperledger/fabric-protos-go/discovery"
"github.com/hyperledger/fabric-protos-go/gossip"
Expand Down Expand Up @@ -551,6 +554,203 @@ func TestPeersForEndorsement(t *testing.T) {
peerIdentityString("p6"): {},
}, extractPeers(desc))
})

t.Run("Chaincode call with state based endorsement policy I", func(t *testing.T) {
// Scenario XIII: A chaincode call with a state based endorsement policy
// Total organizations are 0, 2, 4, 6, 10, 12
// and the endorsement policies of the chaincode is:
// cc1: OR(AND(0, 2), AND(6, 10))
// However the chaincode call is accompanied with a hint
// for a state based endorsement policy for organization 10
// Therefore, the result should be: 6, 10

chanPeers := peerSet{}
for _, id := range []int{0, 2, 4, 6, 10, 12} {
peer := newPeer(id).withChaincode("cc1", "1.0")
chanPeers = append(chanPeers, peer)
}

g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()

mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc1",
Version: "1.0",
}).Once()

pb := principalBuilder{}
cc1policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p2")).
newSet().addPrincipal(peerRole("p6")).addPrincipal(peerRole("p10")).buildPolicy()

pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()

analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
KeyPolicies: []*common2.SignaturePolicyEnvelope{
{
Identities: []*msp.MSPPrincipal{peerRole("p10")},
Rule: policydsl.SignedBy(0),
},
},
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 1)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
require.Equal(t, map[string]struct{}{
peerIdentityString("p6"): {},
peerIdentityString("p10"): {},
}, extractPeers(desc))
})

t.Run("Chaincode call with state based endorsement policy II", func(t *testing.T) {
// Scenario XIV: A chaincode call with a state based endorsement policy
// Total organizations are 0, 2, 4, 6, 10, 12
// and the endorsement policies of the chaincode is:
// cc1: OR(AND(0, 2), AND(6, 10))
// However the chaincode call is accompanied with a hint
// for a state based endorsement policy for organization 12
// Therefore, the result should be: {0, 2, 12} or {6, 10, 12}

chanPeers := peerSet{}
for _, id := range []int{0, 2, 4, 6, 10, 12} {
peer := newPeer(id).withChaincode("cc1", "1.0")
chanPeers = append(chanPeers, peer)
}

g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()

mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc1",
Version: "1.0",
}).Once()

pb := principalBuilder{}
cc1policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p2")).
newSet().addPrincipal(peerRole("p6")).addPrincipal(peerRole("p10")).buildPolicy()

pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()

analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
KeyPolicies: []*common2.SignaturePolicyEnvelope{
{
Identities: []*msp.MSPPrincipal{peerRole("p12")},
Rule: policydsl.SignedBy(0),
},
},
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 2)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 3)
require.Len(t, desc.Layouts[1].QuantitiesByGroup, 3)
require.Equal(t, map[string]struct{}{
peerIdentityString("p0"): {},
peerIdentityString("p2"): {},
peerIdentityString("p6"): {},
peerIdentityString("p10"): {},
peerIdentityString("p12"): {},
}, extractPeers(desc))
// Find ID of org 12

// Ensure org 12 (and no other org) is found in both layouts
var intersectionSize int
for g1 := range desc.Layouts[0].QuantitiesByGroup {
for g2 := range desc.Layouts[1].QuantitiesByGroup {
if g1 == g2 {
require.Equal(t, intersectionSize, 0)
intersectionSize++
require.Equal(t, peerIdentityString("p12"), string(desc.EndorsersByGroups[g1].Peers[0].Identity))
}
}
}
})

t.Run("Chaincode call with state based endorsement policy III", func(t *testing.T) {
// Scenario XV: A chaincode call with a state based endorsement policy
// Total organizations are 0, 2, 4, 6, 10, 12
// and the endorsement policies of the chaincode is:
// cc1: OR(AND(0, 2), AND(6, 10))
// However the chaincode call is accompanied with a hint
// for a state based endorsement policy for both organizations 2 and 6
// Therefore, the result should be: {0, 2, 6} or {2, 6, 10}

chanPeers := peerSet{}
for _, id := range []int{0, 2, 4, 6, 10, 12} {
peer := newPeer(id).withChaincode("cc1", "1.0")
chanPeers = append(chanPeers, peer)
}

g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()

mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc1",
Version: "1.0",
}).Once()

pb := principalBuilder{}
cc1policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p2")).
newSet().addPrincipal(peerRole("p6")).addPrincipal(peerRole("p10")).buildPolicy()

pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()

analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
KeyPolicies: []*common2.SignaturePolicyEnvelope{
{
Identities: []*msp.MSPPrincipal{peerRole("p2")},
Rule: policydsl.SignedBy(0),
},
{
Identities: []*msp.MSPPrincipal{peerRole("p6")},
Rule: policydsl.SignedBy(0),
},
},
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 2)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 3)
require.Equal(t, map[string]struct{}{
peerIdentityString("p0"): {},
peerIdentityString("p2"): {},
peerIdentityString("p6"): {},
peerIdentityString("p10"): {},
}, extractPeers(desc))

// Ensure orgs 2, 6 are found in both layouts
intersection := make(map[string]struct{})
for g1 := range desc.Layouts[0].QuantitiesByGroup {
for g2 := range desc.Layouts[1].QuantitiesByGroup {
if g1 == g2 {
intersection[string(desc.EndorsersByGroups[g1].Peers[0].Identity)] = struct{}{}
}
}
}

require.Equal(t, map[string]struct{}{
peerIdentityString("p2"): {},
peerIdentityString("p6"): {},
}, intersection)
})
}

func TestPeersAuthorizedByCriteria(t *testing.T) {
Expand Down

0 comments on commit 9a922fd

Please sign in to comment.