Commit c37715b5 authored by Christopher Ferris's avatar Christopher Ferris Committed by Gerrit Code Review
Browse files

Merge "[FAB-10111] cc2cc and collection support in go client"

parents 5636636c 6bb5127a
......@@ -49,7 +49,9 @@ type ChannelResponse interface {
// The selection is based on the given selection hints:
// PrioritySelector: Determines which endorsers are selected over others
// ExclusionFilter: Determines which endorsers are not selected
Endorsers(cc string, ps PrioritySelector, ef ExclusionFilter) (Endorsers, error)
// The given InvocationChain specifies the chaincode calls (along with collections)
// that the client passed during the construction of the request
Endorsers(invocationChain InvocationChain, ps PrioritySelector, ef ExclusionFilter) (Endorsers, error)
}
// LocalResponse aggregates responses for a channel-less scope
......
......@@ -9,6 +9,7 @@ package discovery
import (
"bytes"
"context"
"encoding/json"
"math/rand"
"time"
......@@ -34,8 +35,9 @@ type Client struct {
// NewRequest creates a new request
func NewRequest() *Request {
r := &Request{
queryMapping: make(map[discovery.QueryType]map[string]int),
Request: &discovery.Request{},
invocationChainMapping: make(map[int][]InvocationChain),
queryMapping: make(map[discovery.QueryType]map[string]int),
Request: &discovery.Request{},
}
// pre-populate types
for _, queryType := range configTypes {
......@@ -48,8 +50,10 @@ func NewRequest() *Request {
type Request struct {
lastChannel string
lastIndex int
// map from query type to channel (or channel + chaincode) to expected index in response
// map from query type to channel to expected index in response
queryMapping map[discovery.QueryType]map[string]int
// map from expected index in response to invocation chains
invocationChainMapping map[int][]InvocationChain
*discovery.Request
}
......@@ -68,22 +72,29 @@ func (req *Request) AddConfigQuery() *Request {
}
// AddEndorsersQuery adds to the request a query for given chaincodes
func (req *Request) AddEndorsersQuery(chaincodes ...string) *Request {
// interests are the chaincode interests that the client wants to query for.
// All interests for a given channel should be supplied in an aggregated slice
func (req *Request) AddEndorsersQuery(interests ...*discovery.ChaincodeInterest) (*Request, error) {
if err := validateInterests(interests...); err != nil {
return nil, err
}
ch := req.lastChannel
q := &discovery.Query_CcQuery{
CcQuery: &discovery.ChaincodeQuery{},
}
for _, cc := range chaincodes {
q.CcQuery.Interests = append(q.CcQuery.Interests, &discovery.ChaincodeInterest{
Chaincodes: []*discovery.ChaincodeCall{{Name: cc}},
})
CcQuery: &discovery.ChaincodeQuery{
Interests: interests,
},
}
req.Queries = append(req.Queries, &discovery.Query{
Channel: ch,
Query: q,
})
var invocationChains []InvocationChain
for _, interest := range interests {
invocationChains = append(invocationChains, interest.Chaincodes)
}
req.addChaincodeQueryMapping(invocationChains)
req.addQueryMapping(discovery.ChaincodeQueryType, ch)
return req
return req, nil
}
// AddLocalPeersQuery adds to the request a local peer query
......@@ -118,6 +129,10 @@ func (req *Request) OfChannel(ch string) *Request {
return req
}
func (req *Request) addChaincodeQueryMapping(invocationChains []InvocationChain) {
req.invocationChainMapping[req.lastIndex] = invocationChains
}
func (req *Request) addQueryMapping(queryType discovery.QueryType, key string) {
req.queryMapping[queryType][key] = req.lastIndex
req.lastIndex++
......@@ -165,7 +180,7 @@ func (c *Client) Send(ctx context.Context, req *Request, auth *discovery.AuthInf
if n := len(resp.Results); n != req.lastIndex {
return nil, errors.Errorf("Sent %d queries but received %d responses back", req.lastIndex, n)
}
return computeResponse(req.queryMapping, resp)
return req.computeResponse(resp)
}
type resultOrError interface {
......@@ -224,7 +239,7 @@ func (cr *channelResponse) Peers() ([]*Peer, error) {
return parsePeers(discovery.PeerMembershipQueryType, cr.response, cr.channel)
}
func (cr *channelResponse) Endorsers(cc string, ps PrioritySelector, ef ExclusionFilter) (Endorsers, error) {
func (cr *channelResponse) Endorsers(invocationChain InvocationChain, ps PrioritySelector, ef ExclusionFilter) (Endorsers, error) {
// If we have a key that has no chaincode field,
// it means it's an error returned from the service
if err, exists := cr.response[key{
......@@ -236,9 +251,9 @@ func (cr *channelResponse) Endorsers(cc string, ps PrioritySelector, ef Exclusio
// Else, the service returned a response that isn't an error
res, exists := cr.response[key{
queryType: discovery.ChaincodeQueryType,
channel: cr.channel,
chaincode: cc,
queryType: discovery.ChaincodeQueryType,
channel: cr.channel,
invocationChain: invocationChain.String(),
}]
if !exists {
......@@ -290,20 +305,20 @@ func (resp response) ForChannel(ch string) ChannelResponse {
}
type key struct {
queryType discovery.QueryType
channel string
chaincode string
queryType discovery.QueryType
channel string
invocationChain string
}
func computeResponse(queryMapping map[discovery.QueryType]map[string]int, r *discovery.Response) (response, error) {
func (req *Request) computeResponse(r *discovery.Response) (response, error) {
var err error
resp := make(response)
for configType, channel2index := range queryMapping {
for configType, channel2index := range req.queryMapping {
switch configType {
case discovery.ConfigQueryType:
err = resp.mapConfig(channel2index, r)
case discovery.ChaincodeQueryType:
err = resp.mapEndorsers(channel2index, r)
err = resp.mapEndorsers(channel2index, r, req.queryMapping, req.invocationChainMapping)
case discovery.PeerMembershipQueryType:
err = resp.mapPeerMembership(channel2index, r, discovery.PeerMembershipQueryType)
case discovery.LocalMembershipQueryType:
......@@ -400,36 +415,46 @@ func isStateInfoExpected(qt discovery.QueryType) bool {
return qt != discovery.LocalMembershipQueryType
}
func (resp response) mapEndorsers(channel2index map[string]int, r *discovery.Response) error {
func (resp response) mapEndorsers(
channel2index map[string]int,
r *discovery.Response,
queryMapping map[discovery.QueryType]map[string]int,
chaincodeQueryMapping map[int][]InvocationChain) error {
for ch, index := range channel2index {
ccQueryRes, err := r.EndorsersAt(index)
if ccQueryRes == nil && err == nil {
return errors.Errorf("expected QueryResult of either ChaincodeQueryResult or Error but got %v instead", r.Results[index])
}
key := key{
queryType: discovery.ChaincodeQueryType,
channel: ch,
}
if err != nil {
key := key{
queryType: discovery.ChaincodeQueryType,
channel: ch,
}
resp[key] = errors.New(err.Content)
continue
}
if err := resp.mapEndorsersOfChannel(ccQueryRes, ch); err != nil {
if err := resp.mapEndorsersOfChannel(ccQueryRes, ch, chaincodeQueryMapping[index]); err != nil {
return errors.Wrapf(err, "failed assembling endorsers of channel %s", ch)
}
}
return nil
}
func (resp response) mapEndorsersOfChannel(ccRs *discovery.ChaincodeQueryResult, channel string) error {
for _, desc := range ccRs.Content {
func (resp response) mapEndorsersOfChannel(ccRs *discovery.ChaincodeQueryResult, channel string, invocationChain []InvocationChain) error {
if len(ccRs.Content) < len(invocationChain) {
return errors.Errorf("expected %d endorsement descriptors but got only %d", len(invocationChain), len(ccRs.Content))
}
for i, desc := range ccRs.Content {
expectedCCName := invocationChain[i][0].Name
if desc.Chaincode != expectedCCName {
return errors.Errorf("expected chaincode %s but got endorsement descriptor for %s", expectedCCName, desc.Chaincode)
}
key := key{
queryType: discovery.ChaincodeQueryType,
channel: channel,
chaincode: desc.Chaincode,
queryType: discovery.ChaincodeQueryType,
channel: channel,
invocationChain: invocationChain[i].String(),
}
descriptor, err := resp.createEndorsementDescriptor(desc, channel)
......@@ -544,3 +569,40 @@ func validateStateInfoMessage(message *gossip.SignedGossipMessage) error {
}
return nil
}
func validateInterests(interests ...*discovery.ChaincodeInterest) error {
if len(interests) == 0 {
return errors.New("no chaincode interests given")
}
for _, interest := range interests {
if interest == nil {
return errors.New("chaincode interest is nil")
}
if err := InvocationChain(interest.Chaincodes).ValidateInvocationChain(); err != nil {
return err
}
}
return nil
}
// InvocationChain aggregates ChaincodeCalls
type InvocationChain []*discovery.ChaincodeCall
// String returns a string representation of this invocation chain
func (ic InvocationChain) String() string {
s, _ := json.Marshal(ic)
return string(s)
}
// ValidateInvocationChain validates the InvocationChain's structure
func (ic InvocationChain) ValidateInvocationChain() error {
if len(ic) == 0 {
return errors.New("invocation chain should not be empty")
}
for _, cc := range ic {
if cc.Name == "" {
return errors.New("chaincode name should not be empty")
}
}
return nil
}
......@@ -48,6 +48,10 @@ var (
{"A", "B"}, {"C"}, {"A", "D"},
}
orgCombinationsThatSatisfyPolicy2 = [][]string{
{"B", "D"},
}
expectedOrgCombinations = []map[string]struct{}{
{
"A": {},
......@@ -62,13 +66,26 @@ var (
},
}
expectedOrgCombinations2 = []map[string]struct{}{
{
"B": {},
"C": {},
"D": {},
},
}
cc = &gossip.Chaincode{
Name: "mycc",
Version: "1.0",
}
cc2 = &gossip.Chaincode{
Name: "mycc2",
Version: "1.0",
}
propertiesWithChaincodes = &gossip.Properties{
Chaincodes: []*gossip.Chaincode{cc},
Chaincodes: []*gossip.Chaincode{cc, cc2},
}
expectedConf = &discovery.ConfigResult{
......@@ -85,14 +102,14 @@ var (
}
channelPeersWithChaincodes = discovery3.Members{
newPeer(0, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(1, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(2, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(3, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(4, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(5, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(6, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(7, stateInfoMessage(cc), propertiesWithChaincodes).NetworkMember,
newPeer(0, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(1, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(2, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(3, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(4, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(5, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(6, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
newPeer(7, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
}
channelPeersWithoutChaincodes = discovery3.Members{
......@@ -254,17 +271,34 @@ func createDiscoveryService(sup *mockSupport) discovery.DiscoveryServer {
pe := &principalEvaluator{}
pf := &policyFetcher{}
sig, _ := cauthdsl.FromString("OR(AND('A.member', 'B.member'), 'C.member', AND('A.member', 'D.member'))")
polBytes, _ := proto.Marshal(sig)
mdf.On("Metadata").Return(&chaincode.Metadata{
sigPol, _ := cauthdsl.FromString("OR(AND('A.member', 'B.member'), 'C.member', AND('A.member', 'D.member'))")
polBytes, _ := proto.Marshal(sigPol)
mdf.On("Metadata", "mycc").Return(&chaincode.Metadata{
Policy: polBytes,
Name: "mycc",
Version: "1.0",
Id: []byte{1, 2, 3},
})
pf.On("PolicyByChaincode").Return(&inquireablePolicy{
pf.On("PolicyByChaincode", "mycc").Return(&inquireablePolicy{
orgCombinations: orgCombinationsThatSatisfyPolicy,
})
sigPol, _ = cauthdsl.FromString("AND('B.member', 'C.member')")
polBytes, _ = proto.Marshal(sigPol)
mdf.On("Metadata", "mycc2").Return(&chaincode.Metadata{
Policy: polBytes,
Name: "mycc2",
Version: "1.0",
Id: []byte{1, 2, 3},
CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
"col": {memberPrincipal("B"), memberPrincipal("C"), memberPrincipal("D")},
}),
})
pf.On("PolicyByChaincode", "mycc2").Return(&inquireablePolicy{
orgCombinations: orgCombinationsThatSatisfyPolicy2,
})
sup.On("Config", "mychannel").Return(expectedConf)
sup.On("Peers").Return(membershipPeers)
sup.endorsementAnalyzer = endorsement.NewEndorsementAnalyzer(sup, pf, pe, mdf)
......@@ -298,63 +332,96 @@ func TestClient(t *testing.T) {
sup.On("PeersOfChannel").Return(channelPeersWithoutChaincodes).Times(2)
req := NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc").AddPeersQuery().AddConfigQuery().AddLocalPeersQuery()
req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddLocalPeersQuery().AddEndorsersQuery(interest("mycc"))
r, err := cl.Send(ctx, req, authInfo)
assert.NoError(t, err)
// Check behavior for channels that we didn't query for.
fakeChannel := r.ForChannel("fakeChannel")
peers, err := fakeChannel.Peers()
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, peers)
t.Run("Channel mismatch", func(t *testing.T) {
// Check behavior for channels that we didn't query for.
fakeChannel := r.ForChannel("fakeChannel")
peers, err := fakeChannel.Peers()
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, peers)
endorsers, err := fakeChannel.Endorsers("mycc", NoPriorities, NoExclusion)
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, endorsers)
endorsers, err := fakeChannel.Endorsers(ccCall("mycc"), NoPriorities, NoExclusion)
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, endorsers)
conf, err := fakeChannel.Config()
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, conf)
conf, err := fakeChannel.Config()
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, conf)
})
// Check response for the correct channel
mychannel := r.ForChannel("mychannel")
conf, err = mychannel.Config()
assert.NoError(t, err)
assert.Equal(t, expectedConf.Msps, conf.Msps)
assert.Equal(t, expectedConf.Orderers, conf.Orderers)
peers, err = mychannel.Peers()
assert.NoError(t, err)
// We should see all peers as provided above
assert.Len(t, peers, 8)
// Check response for peers when doing a local query
peers, err = r.ForLocal().Peers()
assert.NoError(t, err)
assert.Len(t, peers, 8)
t.Run("Peer membership query", func(t *testing.T) {
// Check response for the correct channel
mychannel := r.ForChannel("mychannel")
conf, err := mychannel.Config()
assert.NoError(t, err)
assert.Equal(t, expectedConf.Msps, conf.Msps)
assert.Equal(t, expectedConf.Orderers, conf.Orderers)
peers, err := mychannel.Peers()
assert.NoError(t, err)
// We should see all peers as provided above
assert.Len(t, peers, 8)
// Check response for peers when doing a local query
peers, err = r.ForLocal().Peers()
assert.NoError(t, err)
assert.Len(t, peers, 8)
})
endorsers, err = mychannel.Endorsers("mycc", NoPriorities, NoExclusion)
// However, since we didn't provide any chaincodes to these peers - the server shouldn't
// be able to construct the descriptor.
// Just check that the appropriate error is returned, and nothing crashes.
assert.Contains(t, err.Error(), "failed constructing descriptor for chaincode")
assert.Nil(t, endorsers)
t.Run("Endorser query without chaincode installed", func(t *testing.T) {
mychannel := r.ForChannel("mychannel")
endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoPriorities, NoExclusion)
// However, since we didn't provide any chaincodes to these peers - the server shouldn't
// be able to construct the descriptor.
// Just check that the appropriate error is returned, and nothing crashes.
assert.Contains(t, err.Error(), "failed constructing descriptor for chaincode")
assert.Nil(t, endorsers)
})
// Next, we check the case when the peers publish chaincode for themselves.
sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Times(2)
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc").AddPeersQuery()
r, err = cl.Send(ctx, req, authInfo)
assert.NoError(t, err)
t.Run("Endorser query with chaincodes installed", func(t *testing.T) {
// Next, we check the case when the peers publish chaincode for themselves.
sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Times(2)
req = NewRequest()
req.OfChannel("mychannel").AddPeersQuery().AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, authInfo)
assert.NoError(t, err)
mychannel = r.ForChannel("mychannel")
peers, err = mychannel.Peers()
assert.NoError(t, err)
assert.Len(t, peers, 8)
mychannel := r.ForChannel("mychannel")
peers, err := mychannel.Peers()
assert.NoError(t, err)
assert.Len(t, peers, 8)
// We should get a valid endorsement descriptor from the service
endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoPriorities, NoExclusion)
assert.NoError(t, err)
// The combinations of endorsers should be in the expected combinations
assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers))
})
t.Run("Endorser query with cc2cc and collections", func(t *testing.T) {
sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Twice()
req = NewRequest()
myccOnly := ccCall("mycc")
myccAndmycc2 := ccCall("mycc", "mycc2")
myccAndmycc2[1].CollectionNames = append(myccAndmycc2[1].CollectionNames, "col")
req.OfChannel("mychannel").AddEndorsersQuery(cc2ccInterests(myccAndmycc2, myccOnly)...)
r, err = cl.Send(ctx, req, authInfo)
assert.NoError(t, err)
mychannel := r.ForChannel("mychannel")
// Check the endorsers for the non cc2cc call
endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoPriorities, NoExclusion)
assert.NoError(t, err)
assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers))
// Check the endorsers for the cc2cc call with collections
call := ccCall("mycc", "mycc2")
call[1].CollectionNames = append(call[1].CollectionNames, "col")
endorsers, err = mychannel.Endorsers(call, NoPriorities, NoExclusion)
assert.NoError(t, err)
assert.Contains(t, expectedOrgCombinations2, getMSPs(endorsers))
})
// We should get a valid endorsement descriptor from the service
endorsers, err = mychannel.Endorsers("mycc", NoPriorities, NoExclusion)
assert.NoError(t, err)
// The combinations of endorsers should be in the expected combinations
assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers))
}
func TestUnableToSign(t *testing.T) {
......@@ -413,7 +480,7 @@ func TestBadResponses(t *testing.T) {
// Scenario I: discovery service sends back an error
svc.On("Discover").Return(nil, errors.New("foo")).Once()
req := NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc").AddPeersQuery().AddConfigQuery()
req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc"))
r, err := cl.Send(ctx, req, auth)
assert.Contains(t, err.Error(), "foo")
assert.Nil(t, r)
......@@ -421,7 +488,7 @@ func TestBadResponses(t *testing.T) {
// Scenario II: discovery service sends back an empty response
svc.On("Discover").Return(&discovery.Response{}, nil).Once()
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc").AddPeersQuery().AddConfigQuery()
req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, auth)
assert.Equal(t, "Sent 3 queries but received 0 responses back", err.Error())
assert.Nil(t, r)
......@@ -443,13 +510,10 @@ func TestBadResponses(t *testing.T) {
},
}, nil).Once()
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc")
req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, auth)
assert.NoError(t, err)
mychannel := r.ForChannel("mychannel")
endorsers, err := mychannel.Endorsers("mycc", NoPriorities, NoExclusion)
assert.Nil(t, endorsers)
assert.Equal(t, ErrNotFound, err)
assert.Nil(t, r)
assert.Contains(t, err.Error(), "expected chaincode mycc but got endorsement descriptor for notmycc")
// Scenario IV: discovery service sends back a layout that has empty envelopes
svc.On("Discover").Return(&discovery.Response{
......@@ -460,7 +524,7 @@ func TestBadResponses(t *testing.T) {
},
}, nil).Once()
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc")
req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, auth)
assert.Contains(t, err.Error(), "received empty envelope(s) for endorsers for chaincode mycc")
assert.Nil(t, r)
......@@ -475,11 +539,11 @@ func TestBadResponses(t *testing.T) {
},
}, nil).Once()
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc")
req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, auth)
assert.NoError(t, err)
mychannel = r.ForChannel("mychannel")
endorsers, err = mychannel.Endorsers("mycc", NoPriorities, NoExclusion)
mychannel := r.ForChannel("mychannel")
endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoPriorities, NoExclusion)
assert.Nil(t, endorsers)
assert.Contains(t, err.Error(), "no endorsement combination can be satisfied")
......@@ -492,12 +556,28 @@ func TestBadResponses(t *testing.T) {
},
}, nil).Once()
req = NewRequest()
req.OfChannel("mychannel").AddEndorsersQuery("mycc")
req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc"))
r, err = cl.Send(ctx, req, auth)
assert.Contains(t, err.Error(), "group B isn't mapped to endorsers, but exists in a layout")
assert.Empty(t, r)
}
func TestAddEndorsersQueryInvalidInput(t *testing.T) {
_, err := NewRequest().AddEndorsersQuery()
assert.Contains(t, err.Error(), "no chaincode interests given")
_, err = NewRequest().AddEndorsersQuery(nil)
assert.Contains(t, err.Error(), "chaincode interest is nil")
_, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{})
assert.Contains(t, err.Error(), "invocation chain should not be empty")
_, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{
Chaincodes: []*discovery.ChaincodeCall{{}},
})
assert.Contains(t, err.Error(), "chaincode name should not be empty")
}
func TestValidateAliveMessage(t *testing.T) {
am := aliveMessage(1)
msg, _ := am.ToGossipMessage()
......@@ -543,6 +623,20 @@ func TestValidateStateInfoMessage(t *testing.T) {
assert.Equal(t, "message isn't a stateInfo message", err.Error())
}
func TestString(t *testing.T) {
var ic InvocationChain
ic = append(ic, &discovery.ChaincodeCall{
Name: "foo",
CollectionNames: []string{"c1", "c2"},
})
ic = append(ic, &discovery.ChaincodeCall{
Name: "bar",
CollectionNames: []string{"c3", "c4"},
})
expected := `[{"name":"foo","collection_names":["c1","c2"]},{"name":"bar","collection_names":["c3","c4"]}]`
assert.Equal(t, expected, ic.String())
}
func getMSP(peer *Peer) string {