github.com/yimialmonte/fabric@v2.1.1+incompatible/discovery/service_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package discovery
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"reflect"
    14  	"testing"
    15  
    16  	"github.com/golang/protobuf/proto"
    17  	"github.com/hyperledger/fabric-protos-go/discovery"
    18  	"github.com/hyperledger/fabric-protos-go/gossip"
    19  	"github.com/hyperledger/fabric/gossip/api"
    20  	gcommon "github.com/hyperledger/fabric/gossip/common"
    21  	gdisc "github.com/hyperledger/fabric/gossip/discovery"
    22  	"github.com/hyperledger/fabric/gossip/protoext"
    23  	"github.com/hyperledger/fabric/protoutil"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/mock"
    26  )
    27  
    28  func TestConfig(t *testing.T) {
    29  	for _, trueOfFalse := range []bool{true, false} {
    30  		conf := Config{
    31  			AuthCacheEnabled:             trueOfFalse,
    32  			AuthCachePurgeRetentionRatio: 0.5,
    33  			AuthCacheMaxSize:             42,
    34  		}
    35  		service := NewService(conf, &mockSupport{})
    36  		assert.Equal(t, trueOfFalse, service.auth.conf.enabled)
    37  		assert.Equal(t, 42, service.auth.conf.maxCacheSize)
    38  		assert.Equal(t, 0.5, service.auth.conf.purgeRetentionRatio)
    39  	}
    40  }
    41  
    42  func TestService(t *testing.T) {
    43  	conf := Config{
    44  		AuthCacheEnabled: true,
    45  	}
    46  	ctx := context.Background()
    47  	req := &discovery.Request{
    48  		Authentication: &discovery.AuthInfo{
    49  			ClientIdentity: []byte{1, 2, 3},
    50  		},
    51  		Queries: []*discovery.Query{
    52  			{
    53  				Channel: "noneExistentChannel",
    54  			},
    55  		},
    56  	}
    57  	mockSup := &mockSupport{}
    58  	mockSup.On("ChannelExists", "noneExistentChannel").Return(false)
    59  	mockSup.On("ChannelExists", "channelWithAccessDenied").Return(true)
    60  	mockSup.On("ChannelExists", "channelWithAccessGranted").Return(true)
    61  	mockSup.On("ChannelExists", "channelWithSomeProblem").Return(true)
    62  	mockSup.On("EligibleForService", "channelWithAccessDenied", mock.Anything).Return(errors.New("foo"))
    63  	mockSup.On("EligibleForService", "channelWithAccessGranted", mock.Anything).Return(nil)
    64  	mockSup.On("EligibleForService", "channelWithSomeProblem", mock.Anything).Return(nil)
    65  	ed1 := &discovery.EndorsementDescriptor{
    66  		Chaincode: "cc1",
    67  	}
    68  	ed2 := &discovery.EndorsementDescriptor{
    69  		Chaincode: "cc2",
    70  	}
    71  	ed3 := &discovery.EndorsementDescriptor{
    72  		Chaincode: "cc3",
    73  	}
    74  	mockSup.On("PeersForEndorsement", "unknownCC").Return(nil, errors.New("unknown chaincode"))
    75  	mockSup.On("PeersForEndorsement", "cc1").Return(ed1, nil)
    76  	mockSup.On("PeersForEndorsement", "cc2").Return(ed2, nil)
    77  	mockSup.On("PeersForEndorsement", "cc3").Return(ed3, nil)
    78  
    79  	service := NewService(conf, mockSup)
    80  
    81  	// Scenario I: Channel does not exist
    82  	resp, err := service.Discover(ctx, toSignedRequest(req))
    83  	assert.NoError(t, err)
    84  	assert.Equal(t, wrapResult(&discovery.Error{Content: "access denied"}), resp)
    85  
    86  	// Scenario II: Channel does not exist
    87  	req.Queries[0].Channel = "channelWithAccessDenied"
    88  	resp, err = service.Discover(ctx, toSignedRequest(req))
    89  	assert.NoError(t, err)
    90  	assert.Equal(t, wrapResult(&discovery.Error{Content: "access denied"}), resp)
    91  
    92  	// Scenario III: Request with nil query
    93  	req.Queries[0].Channel = "channelWithAccessGranted"
    94  	req.Queries[0].Query = nil
    95  	resp, err = service.Discover(ctx, toSignedRequest(req))
    96  	assert.NoError(t, err)
    97  	assert.Contains(t, resp.Results[0].GetError().Content, "unknown or missing request type")
    98  
    99  	// Scenario IV: Request payload is invalid
   100  	signedRequest := toSignedRequest(req)
   101  	// Corrupt the payload by appending a zero byte at its end
   102  	signedRequest.Payload = append(signedRequest.Payload, 0)
   103  	resp, err = service.Discover(ctx, signedRequest)
   104  	assert.Nil(t, resp)
   105  	assert.Contains(t, err.Error(), "failed parsing request")
   106  
   107  	// Scenario V: Request a CC query with no chaincodes at all
   108  	req.Queries[0].Query = &discovery.Query_CcQuery{
   109  		CcQuery: &discovery.ChaincodeQuery{
   110  			Interests: []*discovery.ChaincodeInterest{
   111  				{},
   112  			},
   113  		},
   114  	}
   115  	resp, err = service.Discover(ctx, toSignedRequest(req))
   116  	assert.NoError(t, err)
   117  	assert.Contains(t, resp.Results[0].GetError().Content, "chaincode interest must contain at least one chaincode")
   118  
   119  	// Scenario VI: Request a CC query with no interests at all
   120  	req.Queries[0].Query = &discovery.Query_CcQuery{
   121  		CcQuery: &discovery.ChaincodeQuery{
   122  			Interests: []*discovery.ChaincodeInterest{}},
   123  	}
   124  	resp, err = service.Discover(ctx, toSignedRequest(req))
   125  	assert.NoError(t, err)
   126  	assert.Contains(t, resp.Results[0].GetError().Content, "chaincode query must have at least one chaincode interest")
   127  
   128  	// Scenario VII: Request a CC query with a chaincode name that is empty
   129  	req.Queries[0].Query = &discovery.Query_CcQuery{
   130  		CcQuery: &discovery.ChaincodeQuery{
   131  			Interests: []*discovery.ChaincodeInterest{{
   132  				Chaincodes: []*discovery.ChaincodeCall{{
   133  					Name: "",
   134  				}},
   135  			}}},
   136  	}
   137  	resp, err = service.Discover(ctx, toSignedRequest(req))
   138  	assert.NoError(t, err)
   139  	assert.Contains(t, resp.Results[0].GetError().Content, "chaincode name in interest cannot be empty")
   140  
   141  	// Scenario VIII: Request with a CC query where one chaincode is unavailable
   142  	req.Queries[0].Query = &discovery.Query_CcQuery{
   143  		CcQuery: &discovery.ChaincodeQuery{
   144  			Interests: []*discovery.ChaincodeInterest{
   145  				{
   146  					Chaincodes: []*discovery.ChaincodeCall{{Name: "unknownCC"}},
   147  				},
   148  				{
   149  					Chaincodes: []*discovery.ChaincodeCall{{Name: "cc1"}},
   150  				},
   151  			},
   152  		},
   153  	}
   154  
   155  	resp, err = service.Discover(ctx, toSignedRequest(req))
   156  	assert.NoError(t, err)
   157  	assert.Contains(t, resp.Results[0].GetError().Content, "failed constructing descriptor")
   158  	assert.Contains(t, resp.Results[0].GetError().Content, "unknownCC")
   159  
   160  	// Scenario IX: Request with a CC query where all are available
   161  	req.Queries[0].Query = &discovery.Query_CcQuery{
   162  		CcQuery: &discovery.ChaincodeQuery{
   163  			Interests: []*discovery.ChaincodeInterest{
   164  				{
   165  					Chaincodes: []*discovery.ChaincodeCall{{Name: "cc1"}},
   166  				},
   167  				{
   168  					Chaincodes: []*discovery.ChaincodeCall{{Name: "cc2"}},
   169  				},
   170  				{
   171  					Chaincodes: []*discovery.ChaincodeCall{{Name: "cc3"}},
   172  				},
   173  			},
   174  		},
   175  	}
   176  	resp, err = service.Discover(ctx, toSignedRequest(req))
   177  	assert.NoError(t, err)
   178  	expected := wrapResult(&discovery.ChaincodeQueryResult{
   179  		Content: []*discovery.EndorsementDescriptor{ed1, ed2, ed3},
   180  	})
   181  	assert.Equal(t, expected, resp)
   182  
   183  	// Scenario X: Request with a config query
   184  	mockSup.On("Config", mock.Anything).Return(nil, errors.New("failed fetching config")).Once()
   185  	req.Queries[0].Query = &discovery.Query_ConfigQuery{
   186  		ConfigQuery: &discovery.ConfigQuery{},
   187  	}
   188  	resp, err = service.Discover(ctx, toSignedRequest(req))
   189  	assert.NoError(t, err)
   190  	assert.Contains(t, resp.Results[0].GetError().Content, "failed fetching config for channel channelWithAccessGranted")
   191  
   192  	// Scenario XI: Request with a config query
   193  	mockSup.On("Config", mock.Anything).Return(&discovery.ConfigResult{}, nil).Once()
   194  	req.Queries[0].Query = &discovery.Query_ConfigQuery{
   195  		ConfigQuery: &discovery.ConfigQuery{},
   196  	}
   197  	resp, err = service.Discover(ctx, toSignedRequest(req))
   198  	assert.NoError(t, err)
   199  	assert.NotNil(t, resp.Results[0].GetConfigResult())
   200  
   201  	// Scenario XII: Request with a membership query
   202  	// Peers in membership view: { p0, p1, p2, p3}
   203  	// Peers in channel view: {p1, p2, p4}
   204  	// So that means that the returned peers for the channel should be the intersection
   205  	// which is: {p1, p2}, but the returned peers for the local query should be
   206  	// simply the membership view.
   207  	peersInMembershipView := gdisc.Members{
   208  		aliveMsg(0), aliveMsg(1), aliveMsg(2), aliveMsg(3),
   209  	}
   210  	peersInChannelView := gdisc.Members{
   211  		stateInfoMsg(1), stateInfoMsg(2), stateInfoMsg(4),
   212  	}
   213  	// EligibleForService for an "empty" channel
   214  	mockSup.On("EligibleForService", "", mock.Anything).Return(nil).Once()
   215  	mockSup.On("PeersAuthorizedByCriteria", gcommon.ChannelID("channelWithAccessGranted")).Return(peersInChannelView, nil).Once()
   216  	mockSup.On("PeersAuthorizedByCriteria", gcommon.ChannelID("channelWithSomeProblem")).Return(nil, errors.New("an error occurred")).Once()
   217  	mockSup.On("Peers").Return(peersInMembershipView).Twice()
   218  	mockSup.On("IdentityInfo").Return(api.PeerIdentitySet{
   219  		idInfo(0, "O2"), idInfo(1, "O2"), idInfo(2, "O3"),
   220  		idInfo(3, "O3"), idInfo(4, "O3"),
   221  	}).Twice()
   222  
   223  	req.Queries = []*discovery.Query{
   224  		{
   225  			Channel: "channelWithAccessGranted",
   226  			Query: &discovery.Query_PeerQuery{
   227  				PeerQuery: &discovery.PeerMembershipQuery{},
   228  			},
   229  		},
   230  		{
   231  			Query: &discovery.Query_LocalPeers{
   232  				LocalPeers: &discovery.LocalPeerQuery{},
   233  			},
   234  		},
   235  		{
   236  			Channel: "channelWithSomeProblem",
   237  			Query: &discovery.Query_PeerQuery{
   238  				PeerQuery: &discovery.PeerMembershipQuery{
   239  					Filter: &discovery.ChaincodeInterest{},
   240  				},
   241  			},
   242  		},
   243  	}
   244  	resp, err = service.Discover(ctx, toSignedRequest(req))
   245  	expectedChannelResponse := &discovery.PeerMembershipResult{
   246  		PeersByOrg: map[string]*discovery.Peers{
   247  			"O2": {
   248  				Peers: []*discovery.Peer{
   249  					{
   250  						Identity:       idInfo(1, "O2").Identity,
   251  						StateInfo:      stateInfoMsg(1).Envelope,
   252  						MembershipInfo: aliveMsg(1).Envelope,
   253  					},
   254  				},
   255  			},
   256  			"O3": {
   257  				Peers: []*discovery.Peer{
   258  					{
   259  						Identity:       idInfo(2, "O3").Identity,
   260  						StateInfo:      stateInfoMsg(2).Envelope,
   261  						MembershipInfo: aliveMsg(2).Envelope,
   262  					},
   263  				},
   264  			},
   265  		},
   266  	}
   267  	expectedLocalResponse := &discovery.PeerMembershipResult{
   268  		PeersByOrg: map[string]*discovery.Peers{
   269  			"O2": {
   270  				Peers: []*discovery.Peer{
   271  					{
   272  						Identity:       idInfo(0, "O2").Identity,
   273  						MembershipInfo: aliveMsg(0).Envelope,
   274  					},
   275  					{
   276  						Identity:       idInfo(1, "O2").Identity,
   277  						MembershipInfo: aliveMsg(1).Envelope,
   278  					},
   279  				},
   280  			},
   281  			"O3": {
   282  				Peers: []*discovery.Peer{
   283  					{
   284  						Identity:       idInfo(2, "O3").Identity,
   285  						MembershipInfo: aliveMsg(2).Envelope,
   286  					},
   287  					{
   288  						Identity:       idInfo(3, "O3").Identity,
   289  						MembershipInfo: aliveMsg(3).Envelope,
   290  					},
   291  				},
   292  			},
   293  		},
   294  	}
   295  
   296  	assert.Len(t, resp.Results, 3)
   297  	assert.Len(t, resp.Results[0].GetMembers().PeersByOrg, 2)
   298  	assert.Len(t, resp.Results[1].GetMembers().PeersByOrg, 2)
   299  	assert.Equal(t, "an error occurred", resp.Results[2].GetError().Content)
   300  
   301  	for org, responsePeers := range resp.Results[0].GetMembers().PeersByOrg {
   302  		err := peers(expectedChannelResponse.PeersByOrg[org].Peers).compare(peers(responsePeers.Peers))
   303  		assert.NoError(t, err)
   304  	}
   305  	for org, responsePeers := range resp.Results[1].GetMembers().PeersByOrg {
   306  		err := peers(expectedLocalResponse.PeersByOrg[org].Peers).compare(peers(responsePeers.Peers))
   307  		assert.NoError(t, err)
   308  	}
   309  
   310  	// Scenario XIII: The client is eligible for channel queries but not for channel-less
   311  	// since it's not an admin. It sends a query for a channel-less query but puts a channel in the query.
   312  	// It should fail because channel-less query types cannot have a channel configured in them.
   313  	req.Queries = []*discovery.Query{
   314  		{
   315  			Channel: "channelWithAccessGranted",
   316  			Query: &discovery.Query_LocalPeers{
   317  				LocalPeers: &discovery.LocalPeerQuery{},
   318  			},
   319  		},
   320  	}
   321  	resp, err = service.Discover(ctx, toSignedRequest(req))
   322  	assert.NoError(t, err)
   323  	assert.Contains(t, resp.Results[0].GetError().Content, "unknown or missing request type")
   324  }
   325  
   326  func TestValidateStructure(t *testing.T) {
   327  	extractHash := func(ctx context.Context) []byte {
   328  		return nil
   329  	}
   330  	// Scenarios I-V without TLS, scenarios VI onwards TLS
   331  
   332  	// Scenario I: Nil request
   333  	res, err := validateStructure(context.Background(), nil, false, extractHash)
   334  	assert.Nil(t, res)
   335  	assert.Equal(t, "nil request", err.Error())
   336  
   337  	// Scenario II: Malformed envelope
   338  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   339  		Payload: []byte{1, 2, 3},
   340  	}, false, extractHash)
   341  	assert.Nil(t, res)
   342  	assert.Contains(t, err.Error(), "failed parsing request")
   343  
   344  	// Scenario III: Empty request
   345  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{}, false, extractHash)
   346  	assert.Nil(t, res)
   347  	assert.Equal(t, "access denied, no authentication info in request", err.Error())
   348  
   349  	// Scenario IV: request without a client identity
   350  	req := &discovery.Request{
   351  		Authentication: &discovery.AuthInfo{},
   352  	}
   353  	b, _ := proto.Marshal(req)
   354  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   355  		Payload: b,
   356  	}, false, extractHash)
   357  	assert.Nil(t, res)
   358  	assert.Equal(t, "access denied, client identity wasn't supplied", err.Error())
   359  
   360  	// Scenario V: request with a client identity, should succeed because no TLS is used
   361  	req = &discovery.Request{
   362  		Authentication: &discovery.AuthInfo{
   363  			ClientIdentity: []byte{1, 2, 3},
   364  		},
   365  	}
   366  	b, _ = proto.Marshal(req)
   367  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   368  		Payload: b,
   369  	}, false, extractHash)
   370  	assert.NoError(t, err)
   371  	// Ensure returned request is as before serialization to bytes
   372  	assert.True(t, proto.Equal(req, res))
   373  
   374  	// Scenario VI: request with a client identity but with TLS enabled but client doesn't send a TLS cert
   375  	req = &discovery.Request{
   376  		Authentication: &discovery.AuthInfo{
   377  			ClientIdentity: []byte{1, 2, 3},
   378  		},
   379  	}
   380  	b, _ = proto.Marshal(req)
   381  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   382  		Payload: b,
   383  	}, true, extractHash)
   384  	assert.Nil(t, res)
   385  	assert.Equal(t, "client didn't send a TLS certificate", err.Error())
   386  
   387  	// Scenario VII: request with a client identity and with TLS enabled but the TLS cert hash doesn't match
   388  	// the computed one
   389  	extractHash = func(ctx context.Context) []byte {
   390  		return []byte{1, 2}
   391  	}
   392  	req = &discovery.Request{
   393  		Authentication: &discovery.AuthInfo{
   394  			ClientIdentity:    []byte{1, 2, 3},
   395  			ClientTlsCertHash: []byte{1, 2, 3},
   396  		},
   397  	}
   398  	b, _ = proto.Marshal(req)
   399  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   400  		Payload: b,
   401  	}, true, extractHash)
   402  	assert.Nil(t, res)
   403  	assert.Equal(t, "client claimed TLS hash doesn't match computed TLS hash from gRPC stream", err.Error())
   404  
   405  	// Scenario VIII: request with a client identity and with TLS enabled and the TLS cert hash doesn't match
   406  	// the computed one
   407  	extractHash = func(ctx context.Context) []byte {
   408  		return []byte{1, 2, 3}
   409  	}
   410  	req = &discovery.Request{
   411  		Authentication: &discovery.AuthInfo{
   412  			ClientIdentity:    []byte{1, 2, 3},
   413  			ClientTlsCertHash: []byte{1, 2, 3},
   414  		},
   415  	}
   416  	b, _ = proto.Marshal(req)
   417  	res, err = validateStructure(context.Background(), &discovery.SignedRequest{
   418  		Payload: b,
   419  	}, true, extractHash)
   420  }
   421  
   422  func TestValidateCCQuery(t *testing.T) {
   423  	err := validateCCQuery(&discovery.ChaincodeQuery{
   424  		Interests: []*discovery.ChaincodeInterest{
   425  			nil,
   426  		},
   427  	})
   428  	assert.Equal(t, "chaincode interest is nil", err.Error())
   429  }
   430  
   431  func wrapResult(responses ...interface{}) *discovery.Response {
   432  	response := &discovery.Response{}
   433  	for _, res := range responses {
   434  		response.Results = append(response.Results, wrapQueryResult(res))
   435  	}
   436  	return response
   437  }
   438  
   439  func wrapQueryResult(res interface{}) *discovery.QueryResult {
   440  	if err, isErr := res.(*discovery.Error); isErr {
   441  		return &discovery.QueryResult{
   442  			Result: &discovery.QueryResult_Error{
   443  				Error: err,
   444  			},
   445  		}
   446  	}
   447  	if ccRes, isCCQuery := res.(*discovery.ChaincodeQueryResult); isCCQuery {
   448  		return &discovery.QueryResult{
   449  			Result: &discovery.QueryResult_CcQueryRes{
   450  				CcQueryRes: ccRes,
   451  			},
   452  		}
   453  	}
   454  	if membRes, isMembershipQuery := res.(*discovery.PeerMembershipResult); isMembershipQuery {
   455  		return &discovery.QueryResult{
   456  			Result: &discovery.QueryResult_Members{
   457  				Members: membRes,
   458  			},
   459  		}
   460  	}
   461  	if confRes, isConfQuery := res.(*discovery.ConfigResult); isConfQuery {
   462  		return &discovery.QueryResult{
   463  			Result: &discovery.QueryResult_ConfigResult{
   464  				ConfigResult: confRes,
   465  			},
   466  		}
   467  	}
   468  	panic(fmt.Sprint("invalid type:", reflect.TypeOf(res)))
   469  }
   470  
   471  func toSignedRequest(req *discovery.Request) *discovery.SignedRequest {
   472  	b, _ := proto.Marshal(req)
   473  	return &discovery.SignedRequest{
   474  		Payload: b,
   475  	}
   476  }
   477  
   478  type mockSupport struct {
   479  	mock.Mock
   480  }
   481  
   482  func (ms *mockSupport) ConfigSequence(channel string) uint64 {
   483  	return 0
   484  }
   485  
   486  func (ms *mockSupport) IdentityInfo() api.PeerIdentitySet {
   487  	return ms.Called().Get(0).(api.PeerIdentitySet)
   488  }
   489  
   490  func (ms *mockSupport) ChannelExists(channel string) bool {
   491  	return ms.Called(channel).Get(0).(bool)
   492  }
   493  
   494  func (ms *mockSupport) PeersOfChannel(channel gcommon.ChannelID) gdisc.Members {
   495  	panic("not implemented")
   496  }
   497  
   498  func (ms *mockSupport) Peers() gdisc.Members {
   499  	return ms.Called().Get(0).(gdisc.Members)
   500  }
   501  
   502  func (ms *mockSupport) PeersForEndorsement(channel gcommon.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
   503  	cc := interest.Chaincodes[0].Name
   504  	args := ms.Called(cc)
   505  	if args.Get(0) == nil {
   506  		return nil, args.Error(1)
   507  	}
   508  	return args.Get(0).(*discovery.EndorsementDescriptor), args.Error(1)
   509  }
   510  
   511  func (ms *mockSupport) PeersAuthorizedByCriteria(chainID gcommon.ChannelID, interest *discovery.ChaincodeInterest) (gdisc.Members, error) {
   512  	args := ms.Called(chainID)
   513  	if args.Error(1) != nil {
   514  		return nil, args.Error(1)
   515  	}
   516  	return args.Get(0).(gdisc.Members), args.Error(1)
   517  }
   518  
   519  func (*mockSupport) Chaincodes(id gcommon.ChannelID) []*gossip.Chaincode {
   520  	panic("implement me")
   521  }
   522  
   523  func (ms *mockSupport) EligibleForService(channel string, data protoutil.SignedData) error {
   524  	return ms.Called(channel, data).Error(0)
   525  }
   526  
   527  func (ms *mockSupport) Config(channel string) (*discovery.ConfigResult, error) {
   528  	args := ms.Called(channel)
   529  	if args.Get(0) == nil {
   530  		return nil, args.Error(1)
   531  	}
   532  	return args.Get(0).(*discovery.ConfigResult), args.Error(1)
   533  }
   534  
   535  func idInfo(id int, org string) api.PeerIdentityInfo {
   536  	endpoint := fmt.Sprintf("p%d", id)
   537  	return api.PeerIdentityInfo{
   538  		PKIId:        gcommon.PKIidType(endpoint),
   539  		Organization: api.OrgIdentityType(org),
   540  		Identity:     api.PeerIdentityType(endpoint),
   541  	}
   542  }
   543  
   544  func stateInfoMsg(id int) gdisc.NetworkMember {
   545  	endpoint := fmt.Sprintf("p%d", id)
   546  	pkiID := gcommon.PKIidType(endpoint)
   547  	si := &gossip.StateInfo{
   548  		PkiId: pkiID,
   549  	}
   550  	gm := &gossip.GossipMessage{
   551  		Content: &gossip.GossipMessage_StateInfo{
   552  			StateInfo: si,
   553  		},
   554  	}
   555  	sm, _ := protoext.NoopSign(gm)
   556  	return gdisc.NetworkMember{
   557  		PKIid:    pkiID,
   558  		Envelope: sm.Envelope,
   559  	}
   560  }
   561  
   562  func aliveMsg(id int) gdisc.NetworkMember {
   563  	endpoint := fmt.Sprintf("p%d", id)
   564  	pkiID := gcommon.PKIidType(endpoint)
   565  	am := &gossip.AliveMessage{
   566  		Membership: &gossip.Member{
   567  			PkiId:    pkiID,
   568  			Endpoint: endpoint,
   569  		},
   570  	}
   571  	gm := &gossip.GossipMessage{
   572  		Content: &gossip.GossipMessage_AliveMsg{
   573  			AliveMsg: am,
   574  		},
   575  	}
   576  	sm, _ := protoext.NoopSign(gm)
   577  	return gdisc.NetworkMember{
   578  		PKIid:    pkiID,
   579  		Endpoint: endpoint,
   580  		Envelope: sm.Envelope,
   581  	}
   582  }
   583  
   584  type peers []*discovery.Peer
   585  
   586  func (ps peers) exists(p *discovery.Peer) error {
   587  	var found bool
   588  	for _, q := range ps {
   589  		if reflect.DeepEqual(*p, *q) {
   590  			found = true
   591  			break
   592  		}
   593  	}
   594  	if !found {
   595  		return fmt.Errorf("%v wasn't found in %v", ps, p)
   596  	}
   597  	return nil
   598  }
   599  
   600  func (ps peers) compare(otherPeers peers) error {
   601  	if len(ps) != len(otherPeers) {
   602  		return fmt.Errorf("size mismatch: %d, %d", len(ps), len(otherPeers))
   603  	}
   604  
   605  	for _, p := range otherPeers {
   606  		if err := ps.exists(p); err != nil {
   607  			return err
   608  		}
   609  	}
   610  
   611  	for _, p := range ps {
   612  		if err := otherPeers.exists(p); err != nil {
   613  			return err
   614  		}
   615  	}
   616  	return nil
   617  }