github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/discovery/service_test.go (about)

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