go_study/fabric-main/discovery/endorsement/endorsement_test.go

1358 lines
42 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package endorsement
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"
"github.com/hyperledger/fabric-protos-go/msp"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/common/policies/inquire"
"github.com/hyperledger/fabric/gossip/api"
"github.com/hyperledger/fabric/gossip/common"
"github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var pkiID2MSPID = map[string]string{
"p0": "Org0MSP",
"p1": "Org1MSP",
"p2": "Org2MSP",
"p3": "Org3MSP",
"p4": "Org4MSP",
"p5": "Org5MSP",
"p6": "Org6MSP",
"p7": "Org7MSP",
"p8": "Org8MSP",
"p9": "Org9MSP",
"p10": "Org10MSP",
"p11": "Org11MSP",
"p12": "Org12MSP",
"p13": "Org13MSP",
"p14": "Org14MSP",
"p15": "Org15MSP",
}
func TestPeersForEndorsement(t *testing.T) {
extractPeers := func(desc *discoveryprotos.EndorsementDescriptor) map[string]struct{} {
res := map[string]struct{}{}
for _, endorsers := range desc.EndorsersByGroups {
for _, p := range endorsers.Peers {
res[string(p.Identity)] = struct{}{}
require.Equal(t, string(p.Identity), string(p.MembershipInfo.Payload))
require.Equal(t, string(p.Identity), string(p.StateInfo.Payload))
}
}
return res
}
cc := "chaincode"
g := &gossipMock{}
pf := &policyFetcherMock{}
ccWithMissingPolicy := "chaincodeWithMissingPolicy"
channel := common.ChannelID("test")
alivePeers := peerSet{
newPeer(0),
newPeer(2),
newPeer(4),
newPeer(6),
newPeer(8),
newPeer(10),
newPeer(11),
newPeer(12),
}
identities := identitySet(pkiID2MSPID)
chanPeers := peerSet{
newPeer(0).withChaincode(cc, "1.0"),
newPeer(3).withChaincode(cc, "1.0"),
newPeer(6).withChaincode(cc, "1.0"),
newPeer(9).withChaincode(cc, "1.0"),
newPeer(11).withChaincode(cc, "1.0"),
newPeer(12).withChaincode(cc, "1.0"),
}
g.On("Peers").Return(alivePeers.toMembers())
g.On("IdentityInfo").Return(identities)
// Scenario I: Policy isn't found
t.Run("PolicyNotFound", func(t *testing.T) {
pf.On("PoliciesByChaincode", ccWithMissingPolicy).Return(nil).Once()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: ccWithMissingPolicy,
},
},
})
require.Nil(t, desc)
require.Equal(t, "policy not found", err.Error())
})
t.Run("NotEnoughPeers", func(t *testing.T) {
// Scenario II: Policy is found but not enough peers to satisfy the policy.
// The policy requires a signature from:
// p1 and p6, or
// p11 x2 (twice), but we only have a single peer in the alive view for p11
pb := principalBuilder{}
policy := pb.newSet().addPrincipal(peerRole("p1")).addPrincipal(peerRole("p6")).
newSet().addPrincipal(peerRole("p11")).addPrincipal(peerRole("p11")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{Name: cc, Version: "1.0"}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
require.Nil(t, desc)
require.Equal(t, err.Error(), "no peer combination can satisfy the endorsement policy")
})
t.Run("DisjointViews", func(t *testing.T) {
pb := principalBuilder{}
// Scenario III: Policy is found and there are enough peers to satisfy
// only 1 type of principal combination: p0 and p6.
// However, the combination of a signature from p10 and p12
// cannot be satisfied because p10 is not in the channel view but only in the alive view
policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
newSet().addPrincipal(peerRole("p10")).addPrincipal(peerRole("p12")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
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("p0"): {},
peerIdentityString("p6"): {},
}, extractPeers(desc))
})
t.Run("MultipleCombinations", func(t *testing.T) {
// Scenario IV: Policy is found and there are enough peers to satisfy
// 2 principal combinations:
// p0 and p6, or
// p12 alone
pb := principalBuilder{}
policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
newSet().addPrincipal(peerRole("p12")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 2)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
require.Len(t, desc.Layouts[1].QuantitiesByGroup, 1)
require.Equal(t, map[string]struct{}{
peerIdentityString("p0"): {},
peerIdentityString("p6"): {},
peerIdentityString("p12"): {},
}, extractPeers(desc))
})
t.Run("WrongVersionInstalled", func(t *testing.T) {
// Scenario V: Policy is found, and there are enough peers to satisfy policy combinations,
// but all peers have the wrong version installed on them.
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.1",
}).Once()
pb := principalBuilder{}
policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
newSet().addPrincipal(peerRole("p12")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
require.Nil(t, desc)
require.Equal(t, "required chaincodes are not installed on sufficient peers", err.Error())
// Scenario VI: Policy is found, there are enough peers to satisfy policy combinations,
// but some peers have the wrong chaincode version, and some don't even have it installed.
chanPeers := peerSet{
newPeer(0).withChaincode(cc, "0.6"),
newPeer(3).withChaincode(cc, "1.0"),
newPeer(6).withChaincode(cc, "1.0"),
newPeer(9).withChaincode(cc, "1.0"),
newPeer(12),
}
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
}).Once()
desc, err = analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
require.Nil(t, desc)
require.Equal(t, "required chaincodes are not installed on sufficient peers", err.Error())
})
t.Run("NoChaincodeMetadataFromLedger", func(t *testing.T) {
// Scenario VII: Policy is found, there are enough peers to satisfy the policy,
// but the chaincode metadata cannot be fetched from the ledger.
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(nil).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
require.Nil(t, desc)
require.Equal(t, "No metadata was found for chaincode chaincode in channel test", err.Error())
})
t.Run("Collections", func(t *testing.T) {
// Scenario VIII: Policy is found and there are enough peers to satisfy
// 2 principal combinations: p0 and p6, or p12 alone.
// However, the query contains a collection which has a policy that permits only p0 and p12,
// and thus - the combination of p0 and p6 is filtered out and we're left with p12 only.
collectionOrgs := []*msp.MSPPrincipal{
peerRole("p0"),
peerRole("p12"),
}
col2principals := map[string][]*msp.MSPPrincipal{
"collection": collectionOrgs,
}
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
CollectionsConfig: buildCollectionConfig(col2principals),
}).Once()
pb := principalBuilder{}
policy := pb.newSet().addPrincipal(peerRole("p0")).
addPrincipal(peerRole("p6")).newSet().
addPrincipal(peerRole("p12")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
CollectionNames: []string{"collection"},
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 1)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 1)
require.Equal(t, map[string]struct{}{
peerIdentityString("p12"): {},
}, extractPeers(desc))
})
t.Run("Chaincode2Chaincode I", func(t *testing.T) {
// Scenario IX: A chaincode-to-chaincode query is made.
// Total organizations are 0, 2, 4, 6, 10, 12
// and the endorsement policies of the chaincodes are as follows:
// cc1: OR(AND(0, 2), AND(6, 10))
// cc2: AND(6, 10, 12)
// cc3: AND(4, 12)
// Therefore, the result should be: 4, 6, 10, 12
chanPeers := peerSet{}
for _, id := range []int{0, 2, 4, 6, 10, 12} {
peer := newPeer(id).withChaincode("cc1", "1.0").withChaincode("cc2", "1.0").withChaincode("cc3", "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()
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc2",
Version: "1.0",
}).Once()
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc3",
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()
cc2policy := pb.newSet().addPrincipal(peerRole("p6")).
addPrincipal(peerRole("p10")).addPrincipal(peerRole("p12")).buildPolicy()
pf.On("PoliciesByChaincode", "cc2").Return(cc2policy).Once()
cc3policy := pb.newSet().addPrincipal(peerRole("p4")).
addPrincipal(peerRole("p12")).buildPolicy()
pf.On("PoliciesByChaincode", "cc3").Return(cc3policy).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
},
{
Name: "cc2",
},
{
Name: "cc3",
},
},
})
require.NoError(t, err)
require.NotNil(t, desc)
require.Len(t, desc.Layouts, 1)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 4)
require.Equal(t, map[string]struct{}{
peerIdentityString("p4"): {},
peerIdentityString("p6"): {},
peerIdentityString("p10"): {},
peerIdentityString("p12"): {},
}, extractPeers(desc))
})
t.Run("Chaincode2Chaincode II", func(t *testing.T) {
// Scenario X: A chaincode-to-chaincode query is made.
// and the endorsement policies of the chaincodes are as follows:
// cc1: OR(0, 1)
// cc2: AND(0, 1)
// Therefore, the result should be: (0, 1)
cc1 := "cc1"
cc2 := "cc2"
chanPeers := peerSet{
newPeer(0).withChaincode(cc1, "1.0").withChaincode(cc2, "1.0"),
newPeer(1).withChaincode(cc1, "1.0").withChaincode(cc2, "1.0"),
}.toMembers()
alivePeers := peerSet{
newPeer(0),
newPeer(1),
}.toMembers()
g := &gossipMock{}
g.On("Peers").Return(alivePeers)
g.On("IdentityInfo").Return(identities)
g.On("PeersOfChannel").Return(chanPeers).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc1",
Version: "1.0",
})
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc2",
Version: "1.0",
})
pb := principalBuilder{}
cc1policy := pb.newSet().addPrincipal(peerRole("p0")).
newSet().addPrincipal(peerRole("p1")).buildPolicy()
pf.On("PoliciesByChaincode", "cc1").Return(cc1policy).Once()
cc2policy := pb.newSet().addPrincipal(peerRole("p0")).
addPrincipal(peerRole("p1")).buildPolicy()
pf.On("PoliciesByChaincode", "cc2").Return(cc2policy).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
},
{
Name: "cc2",
},
},
})
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("p0"): {},
peerIdentityString("p1"): {},
}, extractPeers(desc))
})
t.Run("Collection specific EP", func(t *testing.T) {
// Scenario XI: Policy is found and there are enough peers to satisfy
// 2 principal combinations: p0 and p6, or p12 alone.
// The collection has p0, p6, and p12 in it.
// The chaincode EP is (p0 and p6) or p12.
// However, the chaincode has a collection level EP that requires p6 and p12.
// Thus, the only combination that can satisfy would be p6 and p12.
collectionOrgs := []*msp.MSPPrincipal{
peerRole("p0"),
peerRole("p6"),
peerRole("p12"),
}
col2principals := map[string][]*msp.MSPPrincipal{
"collection": collectionOrgs,
}
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
CollectionsConfig: buildCollectionConfig(col2principals),
}).Once()
pb := principalBuilder{}
chaincodeEP := pb.newSet().addPrincipal(peerRole("p0")).
addPrincipal(peerRole("p6")).newSet().
addPrincipal(peerRole("p12")).buildPolicy()
collectionEP := pb.newSet().addPrincipal(peerRole("p6")).
addPrincipal(peerRole("p12")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
pf := &policyFetcherMock{}
pf.On("PoliciesByChaincode", cc).Return([]policies.InquireablePolicy{chaincodeEP, collectionEP}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
CollectionNames: []string{"collection"},
},
},
})
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("p12"): {},
}, extractPeers(desc))
})
t.Run("Private data blind write", func(t *testing.T) {
// Scenario XII: The collection has only p0 in it
// The chaincode EP is p6 or p0.
// The collection endorsement policy is p0 and p6.
// However p6 is not in the collection at all (only p0),
// so it doesn't have the pre-images.
// To that end, the client indicates that it's a blind write
// by turning on the "noPrivateRead" field in the request.
// This might seem like a pathological case, but it's
// effective because it is in the intersection of
// several use cases.
collectionOrgs := []*msp.MSPPrincipal{
peerRole("p0"),
}
col2principals := map[string][]*msp.MSPPrincipal{
"collection": collectionOrgs,
}
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
CollectionsConfig: buildCollectionConfig(col2principals),
}).Once()
pb := principalBuilder{}
chaincodeEP := pb.newSet().addPrincipal(peerRole("p0")).newSet(). // p0 or p6
addPrincipal(peerRole("p6")).buildPolicy()
collectionEP := pb.newSet().addPrincipal(peerRole("p0")). // p0 and p6
addPrincipal(peerRole("p6")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
pf := &policyFetcherMock{}
pf.On("PoliciesByChaincode", cc).Return([]policies.InquireablePolicy{chaincodeEP, collectionEP}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
CollectionNames: []string{"collection"},
NoPrivateReads: true, // This means a blind write
},
},
})
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("p0"): {},
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)
})
t.Run("Chaincode call with DisregardNamespacePolicy set but no key policies or collection policies present", func(t *testing.T) {
// Scenario XVI: A chaincode call with DisregardNamespacePolicy set
// Total organizations are 0, 2, 4, 6, 10, 12
// and there is a collection specified by the client but no collection policies exist
// We expect an error because since DisregardNamespacePolicy is specified, and no collection policies are defined,
// there is not a single endorsement policy to compute.
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",
DisregardNamespacePolicy: true,
},
},
})
require.EqualError(t, err, "requested to disregard chaincode cc1's policy but key and collection policies are missing,"+
" either disable DisregardNamespacePolicy or specify at least one key policy or at least one collection policy")
require.Nil(t, desc)
})
t.Run("Chaincode call with state based endorsement policy and no chaincode namespace policy", func(t *testing.T) {
// Scenario XVII: A chaincode call with a state based endorsement policy and DisregardNamespacePolicy set
// 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: {2, 6}
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",
DisregardNamespacePolicy: true,
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, 1)
require.Len(t, desc.Layouts[0].QuantitiesByGroup, 2)
require.Equal(t, map[string]struct{}{
peerIdentityString("p2"): {},
peerIdentityString("p6"): {},
}, extractPeers(desc))
})
t.Run("Chaincode call with collection endorsement policy and no namespace endorsement policy", func(t *testing.T) {
// Scenario XVIII: A chaincode call with collection endorsement policy and DisregardNamespacePolicy set
// The chaincode EP is OR(AND(0, 2), AND(6, 10))
// The collection endorsement policy is p0 and p2.
// Additionally, the client sets DisregardNamespacePolicy which makes
// discovery only use the collection policy and not the namespace policy.
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()
collectionOrgs := []*msp.MSPPrincipal{
peerRole("p0"),
peerRole("p2"),
}
col2principals := map[string][]*msp.MSPPrincipal{
"collection": collectionOrgs,
}
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: "cc1",
Version: "1.0",
CollectionsConfig: buildCollectionConfig(col2principals),
}).Once()
pb := principalBuilder{}
cc1policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p2")).
newSet().addPrincipal(peerRole("p6")).addPrincipal(peerRole("p10")).buildPolicy()
collectionEP := pb.newSet().addPrincipal(peerRole("p0")). // p0 and p6
addPrincipal(peerRole("p2")).buildPolicy()
pf.On("PoliciesByChaincode", "cc1").Return([]policies.InquireablePolicy{cc1policy, collectionEP}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "cc1",
DisregardNamespacePolicy: true,
CollectionNames: []string{"collection"},
},
},
})
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("p0"): {},
peerIdentityString("p2"): {},
}, extractPeers(desc))
})
t.Run("Identity based endorsement policy doesn't interfere with discovery", func(t *testing.T) {
// Scenario XIX: Policy is based on either identities or on organizations
// 2 principal combinations:
// p0 and p6 (organizations), or p7 (identity)
pb := principalBuilder{}
policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
newSet().addPrincipal(identity("p7")).buildPolicy()
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
mf := &metadataFetcher{}
mf.On("Metadata").Return(&chaincode.Metadata{
Name: cc,
Version: "1.0",
}).Once()
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
pf.On("PoliciesByChaincode", cc).Return(policy).Once()
desc, err := analyzer.PeersForEndorsement(channel, &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: cc,
},
},
})
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("p0"): {},
peerIdentityString("p6"): {},
}, extractPeers(desc))
})
}
func TestPeersAuthorizedByCriteria(t *testing.T) {
cc1 := "cc1"
cc2 := "cc2"
members := peerSet{
newPeer(0).withChaincode(cc1, "1.0"),
newPeer(3).withChaincode(cc1, "1.0"),
newPeer(6).withChaincode(cc1, "1.0"),
newPeer(9).withChaincode(cc1, "1.0"),
newPeer(12).withChaincode(cc1, "1.0"),
}.toMembers()
members2 := append(discovery.Members{}, members...)
members2 = append(members2, peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers()...)
members2 = append(members2, peerSet{newPeer(14).withChaincode(cc1, "1.1")}.toMembers()...)
members2 = append(members2, peerSet{newPeer(15).withChaincode(cc2, "1.0")}.toMembers()...)
alivePeers := peerSet{
newPeer(0),
newPeer(2),
newPeer(4),
newPeer(6),
newPeer(8),
newPeer(10),
newPeer(11),
newPeer(12),
newPeer(13),
newPeer(14),
newPeer(15),
}.toMembers()
identities := identitySet(pkiID2MSPID)
for _, tst := range []struct {
name string
arguments *peer.ChaincodeInterest
totalExistingMembers discovery.Members
metadata []*chaincode.Metadata
expected discovery.Members
}{
{
name: "Nil interest",
arguments: nil,
totalExistingMembers: members,
expected: members,
},
{
name: "Empty interest invocation chain",
arguments: &peer.ChaincodeInterest{},
totalExistingMembers: members,
expected: members,
},
{
name: "Chaincodes only installed on some peers",
arguments: &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{Name: cc1},
{Name: cc2},
},
},
totalExistingMembers: members2,
metadata: []*chaincode.Metadata{
{
Name: "cc1",
Version: "1.1",
},
{
Name: "cc2",
Version: "1.0",
},
},
expected: peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers(),
},
{
name: "Only some peers authorized by collection",
arguments: &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{Name: cc1, CollectionNames: []string{"collection"}},
},
},
totalExistingMembers: members,
metadata: []*chaincode.Metadata{
{
Name: cc1,
Version: "1.0",
CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
"collection": {
peerRole("p0"),
peerRole("p12"),
},
}),
},
{
Name: cc1,
Version: "1.0",
CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
"collection": {
peerRole("p3"),
peerRole("p9"),
},
}),
},
},
expected: peerSet{
newPeer(0).withChaincode(cc1, "1.0"),
newPeer(12).withChaincode(cc1, "1.0"),
}.toMembers(),
},
} {
t.Run(tst.name, func(t *testing.T) {
g := &gossipMock{}
pf := &policyFetcherMock{}
mf := &metadataFetcher{}
g.On("Peers").Return(alivePeers)
g.On("IdentityInfo").Return(identities)
g.On("PeersOfChannel").Return(tst.totalExistingMembers).Once()
for _, md := range tst.metadata {
mf.On("Metadata").Return(md).Once()
}
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
actualMembers, err := analyzer.PeersAuthorizedByCriteria(common.ChannelID("mychannel"), tst.arguments)
require.NoError(t, err)
require.Equal(t, tst.expected, actualMembers)
})
}
}
func TestPop(t *testing.T) {
slice := []inquire.ComparablePrincipalSets{{}, {}}
require.Len(t, slice, 2)
_, slice, err := popComparablePrincipalSets(slice)
require.NoError(t, err)
require.Len(t, slice, 1)
_, slice, err = popComparablePrincipalSets(slice)
require.NoError(t, err)
require.Len(t, slice, 0)
_, _, err = popComparablePrincipalSets(slice)
require.Error(t, err)
require.Equal(t, "no principal sets remained after filtering", err.Error())
}
func TestMergePrincipalSetsNilInput(t *testing.T) {
_, err := mergePrincipalSets(nil)
require.Error(t, err)
require.Equal(t, "no principal sets remained after filtering", err.Error())
}
func TestComputePrincipalSetsNoPolicies(t *testing.T) {
// Tests a hypothetical case where no chaincodes populate the chaincode interest.
interest := &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{},
}
ea := &endorsementAnalyzer{}
_, err := ea.computePrincipalSets(common.ChannelID("mychannel"), interest)
require.Error(t, err)
require.Contains(t, err.Error(), "no principal sets remained after filtering")
}
func TestLoadMetadataAndFiltersCollectionNotPresentInConfig(t *testing.T) {
interest := &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "mycc",
CollectionNames: []string{"bar"},
},
},
}
org1AndOrg2 := []*msp.MSPPrincipal{
orgPrincipal("Org1MSP"),
orgPrincipal("Org2MSP"),
}
col2principals := map[string][]*msp.MSPPrincipal{
"foo": org1AndOrg2,
}
config := buildCollectionConfig(col2principals)
mdf := &metadataFetcher{}
mdf.On("Metadata").Return(&chaincode.Metadata{
Name: "mycc",
CollectionsConfig: config,
Policy: []byte{1, 2, 3},
})
_, err := loadMetadataAndFilters(metadataAndFilterContext{
identityInfoByID: nil,
evaluator: nil,
chainID: common.ChannelID("mychannel"),
fetch: mdf,
interest: interest,
})
require.Equal(t, "collection bar doesn't exist in collection config for chaincode mycc", err.Error())
}
func TestLoadMetadataAndFiltersInvalidCollectionData(t *testing.T) {
interest := &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{
{
Name: "mycc",
CollectionNames: []string{"col1"},
},
},
}
mdf := &metadataFetcher{}
mdf.On("Metadata").Return(&chaincode.Metadata{
Name: "mycc",
CollectionsConfig: &peer.CollectionConfigPackage{},
Policy: []byte{1, 2, 3},
})
_, err := loadMetadataAndFilters(metadataAndFilterContext{
identityInfoByID: nil,
evaluator: nil,
chainID: common.ChannelID("mychannel"),
fetch: mdf,
interest: interest,
})
require.Error(t, err)
require.Contains(t, err.Error(), "collection col1 doesn't exist in collection config for chaincode mycc")
}
type peerSet []*peerInfo
func (p peerSet) toMembers() discovery.Members {
var members discovery.Members
for _, peer := range p {
members = append(members, peer.NetworkMember)
}
return members
}
func identitySet(pkiID2MSPID map[string]string) api.PeerIdentitySet {
var res api.PeerIdentitySet
for pkiID, mspID := range pkiID2MSPID {
sID := &msp.SerializedIdentity{
Mspid: pkiID2MSPID[pkiID],
IdBytes: []byte(pkiID),
}
res = append(res, api.PeerIdentityInfo{
Identity: api.PeerIdentityType(protoutil.MarshalOrPanic(sID)),
PKIId: common.PKIidType(pkiID),
Organization: api.OrgIdentityType(mspID),
})
}
return res
}
type peerInfo struct {
identity api.PeerIdentityType
pkiID common.PKIidType
discovery.NetworkMember
}
func peerIdentityString(id string) string {
return string(protoutil.MarshalOrPanic(&msp.SerializedIdentity{
Mspid: pkiID2MSPID[id],
IdBytes: []byte(id),
}))
}
func newPeer(i int) *peerInfo {
p := fmt.Sprintf("p%d", i)
identity := protoutil.MarshalOrPanic(&msp.SerializedIdentity{
Mspid: pkiID2MSPID[p],
IdBytes: []byte(p),
})
return &peerInfo{
pkiID: common.PKIidType(p),
identity: api.PeerIdentityType(identity),
NetworkMember: discovery.NetworkMember{
PKIid: common.PKIidType(p),
Endpoint: p,
InternalEndpoint: p,
Envelope: &gossip.Envelope{
Payload: []byte(identity),
},
},
}
}
func peerRole(pkiID string) *msp.MSPPrincipal {
return &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_ROLE,
Principal: protoutil.MarshalOrPanic(&msp.MSPRole{
MspIdentifier: pkiID2MSPID[pkiID],
Role: msp.MSPRole_PEER,
}),
}
}
func identity(pkiID string) *msp.MSPPrincipal {
return &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_IDENTITY,
Principal: protoutil.MarshalOrPanic(&msp.SerializedIdentity{
Mspid: pkiID2MSPID[pkiID],
IdBytes: []byte(pkiID),
}),
}
}
func (pi *peerInfo) withChaincode(name, version string) *peerInfo {
if pi.Properties == nil {
pi.Properties = &gossip.Properties{}
}
pi.Properties.Chaincodes = append(pi.Properties.Chaincodes, &gossip.Chaincode{
Name: name,
Version: version,
})
return pi
}
type gossipMock struct {
mock.Mock
}
func (g *gossipMock) IdentityInfo() api.PeerIdentitySet {
return g.Called().Get(0).(api.PeerIdentitySet)
}
func (g *gossipMock) PeersOfChannel(_ common.ChannelID) discovery.Members {
members := g.Called().Get(0)
return members.(discovery.Members)
}
func (g *gossipMock) Peers() discovery.Members {
members := g.Called().Get(0)
return members.(discovery.Members)
}
type policyFetcherMock struct {
mock.Mock
}
func (pf *policyFetcherMock) PoliciesByChaincode(channel string, chaincode string, collections ...string) []policies.InquireablePolicy {
arg := pf.Called(chaincode)
if arg.Get(0) == nil {
return nil
}
singlePolicy, isSinglePolicy := arg.Get(0).(policies.InquireablePolicy)
if isSinglePolicy {
return []policies.InquireablePolicy{singlePolicy}
}
return arg.Get(0).([]policies.InquireablePolicy)
}
type principalBuilder struct {
ip inquireablePolicy
}
func (pb *principalBuilder) buildPolicy() inquireablePolicy {
defer func() {
pb.ip = nil
}()
return pb.ip
}
func (pb *principalBuilder) newSet() *principalBuilder {
pb.ip = append(pb.ip, make(policies.PrincipalSet, 0))
return pb
}
func (pb *principalBuilder) addPrincipal(principal *msp.MSPPrincipal) *principalBuilder {
pb.ip[len(pb.ip)-1] = append(pb.ip[len(pb.ip)-1], principal)
return pb
}
type inquireablePolicy []policies.PrincipalSet
func (ip inquireablePolicy) SatisfiedBy() []policies.PrincipalSet {
return ip
}
type principalEvaluatorMock struct{}
func (pe *principalEvaluatorMock) SatisfiesPrincipal(_ string, identity []byte, principal *msp.MSPPrincipal) error {
sId := &msp.SerializedIdentity{}
if err := proto.Unmarshal(identity, sId); err != nil {
return err
}
if principal.PrincipalClassification == msp.MSPPrincipal_IDENTITY {
identityPrincipal := &msp.SerializedIdentity{}
if err := proto.Unmarshal(principal.Principal, identityPrincipal); err != nil {
return err
}
if proto.Equal(sId, identityPrincipal) {
return nil
}
return fmt.Errorf("identities do not match")
}
// Else, it's either an OU type or a role type, so we only classify by MSP ID
peerRole := &msp.MSPRole{}
if err := proto.Unmarshal(principal.Principal, peerRole); err != nil {
return err
}
if peerRole.MspIdentifier == sId.Mspid {
return nil
}
return errors.New("bingo")
}
type metadataFetcher struct {
mock.Mock
}
func (mf *metadataFetcher) Metadata(channel string, cc string, _ ...string) *chaincode.Metadata {
arg := mf.Called().Get(0)
if arg == nil {
return nil
}
return arg.(*chaincode.Metadata)
}