1358 lines
42 KiB
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)
|
|
}
|