Skip to content

Commit fdf2f7a

Browse files
committed
[FAB-872] Gossip multiChannel support
This commit links gossip/channel To the gossip implementation, and: 1) Adds routing logic to the gossip dissemination function that disseminates messages according to the message channel and whether it should be routed only in the organization or not. 2) Changes the gossip.Gossip API to accomodate to multi-channel changes 3) Changes the gossip tests to accomodate to multi-channel environment 4) Adds tests that ensure that peers do NOT get blocks of channels they haven't joined them with a JoinChannel invocation. 5) Adds a test that disseminates blocks from all peers to all peers. 6) Since I have lots of tests now, made them run in parallel, to save time Change-Id: Ibe6ad886def11e18f2be70b80dd19e4e8395b16f Signed-off-by: Yacov Manevich <[email protected]>
1 parent a5b12f2 commit fdf2f7a

File tree

15 files changed

+1112
-310
lines changed

15 files changed

+1112
-310
lines changed

core/committer/noopssinglechain/client.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/hyperledger/fabric/core/committer/txvalidator"
2626
"github.com/hyperledger/fabric/core/peer"
2727
"github.com/hyperledger/fabric/events/producer"
28+
gossipcommon "github.com/hyperledger/fabric/gossip/common"
2829
gossip_proto "github.com/hyperledger/fabric/gossip/proto"
2930
"github.com/hyperledger/fabric/gossip/service"
3031
"github.com/hyperledger/fabric/protos/common"
@@ -195,7 +196,7 @@ func (d *DeliverService) readUntilClose() {
195196
logger.Debug("Validating block, chainID", d.chainID)
196197
validator.Validate(t.Block)
197198

198-
numberOfPeers := len(service.GetGossipService().GetPeers())
199+
numberOfPeers := len(service.GetGossipService().PeersOfChannel(gossipcommon.ChainID(d.chainID)))
199200
// Create payload with a block received
200201
payload := createPayload(seqNum, t.Block)
201202
// Use payload to create gossip message

gossip/filter/filter.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package filter
18+
19+
import (
20+
"github.com/hyperledger/fabric/gossip/comm"
21+
"github.com/hyperledger/fabric/gossip/discovery"
22+
"github.com/hyperledger/fabric/gossip/util"
23+
)
24+
25+
// RoutingFilter defines a predicate on a NetworkMember
26+
// It is used to assert whether a given NetworkMember should be
27+
// selected for be given a message
28+
type RoutingFilter func(discovery.NetworkMember) bool
29+
30+
// CombineRoutingFilters returns the logical AND of given routing filters
31+
func CombineRoutingFilters(filters ...RoutingFilter) RoutingFilter {
32+
return func(member discovery.NetworkMember) bool {
33+
for _, filter := range filters {
34+
if !filter(member) {
35+
return false
36+
}
37+
}
38+
return true
39+
}
40+
}
41+
42+
// SelectPeers returns a slice of peers that match a list of routing filters
43+
func SelectPeers(k int, peerPool []discovery.NetworkMember, filters ...RoutingFilter) []*comm.RemotePeer {
44+
var indices []int
45+
if len(peerPool) <= k {
46+
indices = make([]int, len(peerPool))
47+
for i := 0; i < len(peerPool); i++ {
48+
indices[i] = i
49+
}
50+
} else {
51+
indices = util.GetRandomIndices(k, len(peerPool)-1)
52+
}
53+
54+
var remotePeers []*comm.RemotePeer
55+
for _, index := range indices {
56+
peer := peerPool[index]
57+
if CombineRoutingFilters(filters ...)(peer) {
58+
remotePeers = append(remotePeers, &comm.RemotePeer{PKIID: peer.PKIid, Endpoint: peer.Endpoint})
59+
}
60+
61+
}
62+
return remotePeers
63+
}

gossip/gossip/batcher.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package gossip
1818

1919
import (
20+
"fmt"
2021
"sync"
2122
"sync/atomic"
2223
"time"
@@ -44,6 +45,10 @@ type batchingEmitter interface {
4445
// latency: the maximum delay that each message can be stored without being forwarded
4546
// cb: a callback that is called in order for the forwarding to take place
4647
func newBatchingEmitter(iterations, burstSize int, latency time.Duration, cb emitBatchCallback) batchingEmitter {
48+
if iterations < 0 {
49+
panic(fmt.Errorf("Got a negative iterations number"))
50+
}
51+
4752
p := &batchingEmitterImpl{
4853
cb: cb,
4954
delay: latency,
@@ -54,7 +59,10 @@ func newBatchingEmitter(iterations, burstSize int, latency time.Duration, cb emi
5459
stopFlag: int32(0),
5560
}
5661

57-
go p.periodicEmit()
62+
if iterations != 0 {
63+
go p.periodicEmit()
64+
}
65+
5866
return p
5967
}
6068

@@ -126,6 +134,9 @@ func (p *batchingEmitterImpl) Size() int {
126134
}
127135

128136
func (p *batchingEmitterImpl) Add(message interface{}) {
137+
if p.iterations == 0 {
138+
return
139+
}
129140
p.lock.Lock()
130141
defer p.lock.Unlock()
131142

gossip/gossip/certstore_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ func (m *membershipSvcMock) GetMembership() []discovery.NetworkMember {
8181
}
8282

8383
func TestCertStoreBadSignature(t *testing.T) {
84-
t.Parallel()
8584
badSignature := func(nonce uint64) comm.ReceivedMessage {
8685
return createUpdateMessage(nonce, createBadlySignedUpdateMessage())
8786
}
@@ -90,7 +89,6 @@ func TestCertStoreBadSignature(t *testing.T) {
9089
}
9190

9291
func TestCertStoreMismatchedIdentity(t *testing.T) {
93-
t.Parallel()
9492
mismatchedIdentity := func(nonce uint64) comm.ReceivedMessage {
9593
return createUpdateMessage(nonce, createMismatchedUpdateMessage())
9694
}
@@ -99,7 +97,6 @@ func TestCertStoreMismatchedIdentity(t *testing.T) {
9997
}
10098

10199
func TestCertStoreShouldSucceed(t *testing.T) {
102-
t.Parallel()
103100
totallyFineIdentity := func(nonce uint64) comm.ReceivedMessage {
104101
return createUpdateMessage(nonce, createValidUpdateMessage())
105102
}
@@ -129,6 +126,8 @@ func testCertificateUpdate(t *testing.T, updateFactory func(uint64) comm.Receive
129126
Mediator: pullMediator,
130127
}, identity.NewIdentityMapper(&naiveCryptoService{}), api.PeerIdentityType("SELF"), &naiveCryptoService{})
131128

129+
defer pullMediator.Stop()
130+
132131
wg := sync.WaitGroup{}
133132
wg.Add(1)
134133
sentHello := false

gossip/gossip/channel/channel.go

+10-30
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/hyperledger/fabric/gossip/comm"
2828
"github.com/hyperledger/fabric/gossip/common"
2929
"github.com/hyperledger/fabric/gossip/discovery"
30+
"github.com/hyperledger/fabric/gossip/filter"
3031
"github.com/hyperledger/fabric/gossip/gossip/msgstore"
3132
"github.com/hyperledger/fabric/gossip/gossip/pull"
3233
"github.com/hyperledger/fabric/gossip/proto"
@@ -163,7 +164,7 @@ func NewGossipChannel(mcs api.MessageCryptoService, chainID common.ChainID, adap
163164
gc.blocksPuller.Remove(m.(*proto.GossipMessage))
164165
})
165166

166-
gc.stateInfoMsgStore = msgstore.NewMessageStore(comparator, func(m interface{}) {})
167+
gc.stateInfoMsgStore = NewStateInfoMessageStore()
167168
gc.blocksPuller = gc.createBlockPuller()
168169

169170
gc.ConfigureChannel(joinMsg)
@@ -219,7 +220,10 @@ func (gc *gossipChannel) GetPeers() []discovery.NetworkMember {
219220

220221
func (gc *gossipChannel) requestStateInfo() {
221222
req := gc.createStateInfoRequest()
222-
endpoints := selectPeers(gc.GetConf().PullPeerNum, gc.GetMembership(), gc.IsMemberInChan)
223+
endpoints := filter.SelectPeers(gc.GetConf().PullPeerNum, gc.GetMembership(), gc.IsSubscribed)
224+
if len(endpoints) == 0 {
225+
endpoints = filter.SelectPeers(gc.GetConf().PullPeerNum, gc.GetMembership(), gc.IsMemberInChan)
226+
}
223227
gc.Send(req, endpoints...)
224228
}
225229

@@ -531,31 +535,7 @@ func (gc *gossipChannel) UpdateStateInfo(msg *proto.GossipMessage) {
531535
atomic.StoreInt32(&gc.shouldGossipStateInfo, int32(1))
532536
}
533537

534-
// selectPeers returns a slice of peers that match a list of routing filters
535-
func selectPeers(k int, peerPool []discovery.NetworkMember, filters ...func(discovery.NetworkMember) bool) []*comm.RemotePeer {
536-
var indices []int
537-
if len(peerPool) < k {
538-
indices = make([]int, len(peerPool))
539-
for i := 0; i < len(peerPool); i++ {
540-
indices[i] = i
541-
}
542-
} else {
543-
indices = util.GetRandomIndices(k, len(peerPool)-1)
544-
}
545-
546-
var remotePeers []*comm.RemotePeer
547-
for _, index := range indices {
548-
peer := peerPool[index]
549-
passesFilters := true
550-
for _, filter := range filters {
551-
if !filter(peer) {
552-
passesFilters = false
553-
}
554-
}
555-
if passesFilters {
556-
remotePeers = append(remotePeers, &comm.RemotePeer{PKIID: peer.PKIid, Endpoint: peer.Endpoint})
557-
}
558-
559-
}
560-
return remotePeers
561-
}
538+
// NewStateInfoMessageStore returns a MessageStore
539+
func NewStateInfoMessageStore() msgstore.MessageStore {
540+
return msgstore.NewMessageStore(proto.NewGossipMessageComparator(0), func(m interface{}) {})
541+
}

gossip/gossip/chanstate.go

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package gossip
18+
19+
import (
20+
"sync"
21+
"sync/atomic"
22+
"time"
23+
24+
"github.com/hyperledger/fabric/gossip/api"
25+
"github.com/hyperledger/fabric/gossip/comm"
26+
"github.com/hyperledger/fabric/gossip/common"
27+
"github.com/hyperledger/fabric/gossip/discovery"
28+
"github.com/hyperledger/fabric/gossip/gossip/channel"
29+
"github.com/hyperledger/fabric/gossip/gossip/msgstore"
30+
"github.com/hyperledger/fabric/gossip/gossip/pull"
31+
"github.com/hyperledger/fabric/gossip/proto"
32+
"github.com/hyperledger/fabric/gossip/util"
33+
)
34+
35+
type channelState struct {
36+
stopping int32
37+
sync.RWMutex
38+
channels map[string]channel.GossipChannel
39+
g *gossipServiceImpl
40+
}
41+
42+
func (cs *channelState) stop() {
43+
if cs.isStopping() {
44+
return
45+
}
46+
atomic.StoreInt32(&cs.stopping, int32(1))
47+
cs.Lock()
48+
defer cs.Unlock()
49+
for _, gc := range cs.channels {
50+
gc.Stop()
51+
}
52+
}
53+
54+
func (cs *channelState) isStopping() bool {
55+
return atomic.LoadInt32(&cs.stopping) == int32(1)
56+
}
57+
58+
func (cs *channelState) getGossipChannelByChainID(chainID common.ChainID) channel.GossipChannel {
59+
if cs.isStopping() {
60+
return nil
61+
}
62+
cs.Lock()
63+
defer cs.Unlock()
64+
return cs.channels[string(chainID)]
65+
}
66+
67+
func (cs *channelState) joinChannel(joinMsg api.JoinChannelMessage, chainID common.ChainID) {
68+
if cs.isStopping() {
69+
return
70+
}
71+
cs.Lock()
72+
defer cs.Unlock()
73+
if gc, exists := cs.channels[string(chainID)]; !exists {
74+
cs.channels[string(chainID)] = channel.NewGossipChannel(cs.g.mcs, chainID, &gossipAdapterImpl{gossipServiceImpl: cs.g, Discovery: cs.g.disc}, joinMsg)
75+
} else {
76+
gc.ConfigureChannel(joinMsg)
77+
}
78+
}
79+
80+
type gossipAdapterImpl struct {
81+
*gossipServiceImpl
82+
discovery.Discovery
83+
}
84+
85+
func (ga *gossipAdapterImpl) GetConf() channel.Config {
86+
return channel.Config{
87+
ID: ga.conf.ID,
88+
MaxBlockCountToStore: ga.conf.MaxBlockCountToStore,
89+
PublishStateInfoInterval: ga.conf.PublishStateInfoInterval,
90+
PullInterval: ga.conf.PullInterval,
91+
PullPeerNum: ga.conf.PullPeerNum,
92+
RequestStateInfoInterval: ga.conf.RequestStateInfoInterval,
93+
}
94+
}
95+
96+
// Gossip gossips a message
97+
func (ga *gossipAdapterImpl) Gossip(msg *proto.GossipMessage) {
98+
ga.gossipServiceImpl.emitter.Add(msg)
99+
}
100+
101+
// ValidateStateInfoMessage returns error if a message isn't valid
102+
// nil otherwise
103+
func (ga *gossipAdapterImpl) ValidateStateInfoMessage(msg *proto.GossipMessage) error {
104+
return ga.gossipServiceImpl.validateStateInfoMsg(msg.GetStateInfo())
105+
}
106+
107+
// OrgByPeerIdentity extracts the organization identifier from a peer's identity
108+
func (ga *gossipAdapterImpl) OrgByPeerIdentity(identity api.PeerIdentityType) api.OrgIdentityType {
109+
return ga.gossipServiceImpl.secAdvisor.OrgByPeerIdentity(identity)
110+
}
111+
112+
// GetOrgOfPeer returns the organization identifier of a certain peer
113+
func (ga *gossipAdapterImpl) GetOrgOfPeer(PKIID common.PKIidType) api.OrgIdentityType {
114+
return ga.gossipServiceImpl.getOrgOfPeer(PKIID)
115+
}
116+
117+
// Adapter enables the gossipChannel
118+
// to communicate with gossipServiceImpl.
119+
120+
// Adapter connects a GossipChannel to the gossip implementation
121+
type Adapter interface {
122+
123+
// GetConf returns the configuration
124+
// of the GossipChannel
125+
GetConf() Config
126+
127+
// Gossip gossips a message
128+
Gossip(*proto.GossipMessage)
129+
130+
// DeMultiplex publishes a message to subscribers
131+
DeMultiplex(interface{})
132+
133+
// GetMembership returns the peers that are considered alive
134+
GetMembership() []discovery.NetworkMember
135+
136+
// Send sends a message to a list of peers
137+
Send(msg *proto.GossipMessage, peers ...*comm.RemotePeer)
138+
139+
// ValidateStateInfoMessage returns error if a message isn't valid
140+
// nil otherwise
141+
ValidateStateInfoMessage(*proto.GossipMessage) error
142+
143+
// OrgByPeerIdentity extracts the organization identifier from a peer's identity
144+
OrgByPeerIdentity(identity api.PeerIdentityType) api.OrgIdentityType
145+
146+
// GetOrgOfPeer returns the organization identifier of a certain peer
147+
GetOrgOfPeer(common.PKIidType) api.OrgIdentityType
148+
}
149+
150+
type gossipChannel struct {
151+
Adapter
152+
sync.RWMutex
153+
shouldGossipStateInfo int32
154+
stopChan chan struct{}
155+
stateInfoMsg *proto.GossipMessage
156+
orgs []api.OrgIdentityType
157+
joinMsg api.JoinChannelMessage
158+
blockMsgStore msgstore.MessageStore
159+
stateInfoMsgStore msgstore.MessageStore
160+
chainID common.ChainID
161+
blocksPuller pull.Mediator
162+
logger *util.Logger
163+
stateInfoPublishScheduler *time.Ticker
164+
stateInfoRequestScheduler *time.Ticker
165+
}

0 commit comments

Comments
 (0)