Skip to content

Commit 90b4c72

Browse files
committed
[FAB-2061] Gossip inter-org confidentiality - P2
In the previous commit, I adjusted the membership routing so that membership would only be disseminated between pairs of organizations. The only thing left is to deal with identity messages, which are disseminated using the pull mechanism. This commit adds a filtering capability to the pull engine, and to its enclosing object- the pull mediator. The filter acts on a context of a message (that contains info about the peer's credentials, hence it's organization) sent from the remote peer (helloMsg and requestMsg) and return a function that declares for each item (digest or item in the response) whether it should be sent to the remote peer. In the next commit, I shall write filtering logic to make it so that peers would gossip identities with destination orgs only if they are their own identities. Change-Id: I7996bafe5ce2962279c01e59c57868057cfd1d7e Signed-off-by: Yacov Manevich <[email protected]>
1 parent 077126e commit 90b4c72

File tree

7 files changed

+191
-23
lines changed

7 files changed

+191
-23
lines changed

gossip/gossip/algo/pull.go

+29-6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func SetResponseWaitTime(time time.Duration) {
6969
viper.Set("peer.gossip.responseWaitTime", time)
7070
}
7171

72+
// DigestFilter filters digests to be sent to a remote peer that
73+
// sent a hello or a request, based on its messages's context
74+
type DigestFilter func(context interface{}) func(digestItem string) bool
75+
7276
// PullAdapter is needed by the PullEngine in order to
7377
// send messages to the remote PullEngine instances.
7478
// The PullEngine expects to be invoked with
@@ -109,11 +113,12 @@ type PullEngine struct {
109113
lock sync.Mutex
110114
outgoingNONCES *util.Set
111115
incomingNONCES *util.Set
116+
digFilter DigestFilter
112117
}
113118

114-
// NewPullEngine creates an instance of a PullEngine with a certain sleep time
115-
// between pull initiations
116-
func NewPullEngine(participant PullAdapter, sleepTime time.Duration) *PullEngine {
119+
// NewPullEngineWithFilter creates an instance of a PullEngine with a certain sleep time
120+
// between pull initiations, and uses the given filters when sending digests and responses
121+
func NewPullEngineWithFilter(participant PullAdapter, sleepTime time.Duration, df DigestFilter) *PullEngine {
117122
engine := &PullEngine{
118123
PullAdapter: participant,
119124
stopFlag: int32(0),
@@ -125,6 +130,7 @@ func NewPullEngine(participant PullAdapter, sleepTime time.Duration) *PullEngine
125130
acceptingResponses: int32(0),
126131
incomingNONCES: util.NewSet(),
127132
outgoingNONCES: util.NewSet(),
133+
digFilter: df,
128134
}
129135

130136
go func() {
@@ -140,8 +146,19 @@ func NewPullEngine(participant PullAdapter, sleepTime time.Duration) *PullEngine
140146
return engine
141147
}
142148

149+
// NewPullEngine creates an instance of a PullEngine with a certain sleep time
150+
// between pull initiations
151+
func NewPullEngine(participant PullAdapter, sleepTime time.Duration) *PullEngine {
152+
acceptAllFilter := func(_ interface{}) func(string) bool {
153+
return func(_ string) bool {
154+
return true
155+
}
156+
}
157+
return NewPullEngineWithFilter(participant, sleepTime, acceptAllFilter)
158+
}
159+
143160
func (engine *PullEngine) toDie() bool {
144-
return (atomic.LoadInt32(&(engine.stopFlag)) == int32(1))
161+
return atomic.LoadInt32(&(engine.stopFlag)) == int32(1)
145162
}
146163

147164
func (engine *PullEngine) acceptResponses() {
@@ -275,8 +292,13 @@ func (engine *PullEngine) OnHello(nonce uint64, context interface{}) {
275292

276293
a := engine.state.ToArray()
277294
digest := make([]string, len(a))
295+
filter := engine.digFilter(context)
278296
for i, item := range a {
279-
digest[i] = item.(string)
297+
dig := item.(string)
298+
if !filter(dig) {
299+
continue
300+
}
301+
digest[i] = dig
280302
}
281303
engine.SendDigest(digest, nonce, context)
282304
}
@@ -288,9 +310,10 @@ func (engine *PullEngine) OnReq(items []string, nonce uint64, context interface{
288310
}
289311
engine.lock.Lock()
290312

313+
filter := engine.digFilter(context)
291314
var items2Send []string
292315
for _, item := range items {
293-
if engine.state.Exists(item) {
316+
if engine.state.Exists(item) && filter(item) {
294317
items2Send = append(items2Send, item)
295318
}
296319
}

gossip/gossip/algo/pull_test.go

+48-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ limitations under the License.
1717
package algo
1818

1919
import (
20+
"fmt"
21+
"strconv"
2022
"strings"
2123
"sync"
24+
"sync/atomic"
2225
"testing"
2326
"time"
2427

25-
"fmt"
26-
"sync/atomic"
27-
2828
"github.com/hyperledger/fabric/gossip/util"
2929
"github.com/spf13/viper"
3030
"github.com/stretchr/testify/assert"
@@ -501,6 +501,51 @@ func TestSpread(t *testing.T) {
501501
}
502502
}
503503
lock.Unlock()
504+
}
505+
506+
func TestFilter(t *testing.T) {
507+
t.Parallel()
508+
// Scenario: 3 instances, items [0-5] are found only in the first instance, the other 2 have none.
509+
// and also the first instance only gives the 2nd instance even items, and odd items to the 3rd.
510+
// also, instances 2 and 3 don't know each other.
511+
// Expected outcome: inst2 has only even items, and inst3 has only odd items
512+
peers := make(map[string]*pullTestInstance)
513+
inst1 := newPushPullTestInstance("p1", peers)
514+
inst2 := newPushPullTestInstance("p2", peers)
515+
inst3 := newPushPullTestInstance("p3", peers)
516+
defer inst1.stop()
517+
defer inst2.stop()
518+
defer inst3.stop()
519+
520+
inst1.PullEngine.digFilter = func(context interface{}) func(digestItem string) bool {
521+
return func(digestItem string) bool {
522+
n, _ := strconv.ParseInt(digestItem, 10, 64)
523+
if context == "p2" {
524+
return n%2 == 0
525+
}
526+
return n%2 == 1
527+
}
528+
}
529+
530+
inst1.Add("0", "1", "2", "3", "4", "5")
531+
inst2.setNextPeerSelection([]string{"p1"})
532+
inst3.setNextPeerSelection([]string{"p1"})
533+
534+
time.Sleep(time.Second * 2)
535+
536+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "0", Strcmp) != -1)
537+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "1", Strcmp) == -1)
538+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "2", Strcmp) != -1)
539+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "3", Strcmp) == -1)
540+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "4", Strcmp) != -1)
541+
assert.True(t, util.IndexInSlice(inst2.state.ToArray(), "5", Strcmp) == -1)
542+
543+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "0", Strcmp) == -1)
544+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "1", Strcmp) != -1)
545+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "2", Strcmp) == -1)
546+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "3", Strcmp) != -1)
547+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "4", Strcmp) == -1)
548+
assert.True(t, util.IndexInSlice(inst3.state.ToArray(), "5", Strcmp) != -1)
504549

505550
}
506551

gossip/gossip/certstore_test.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,17 @@ func testCertificateUpdate(t *testing.T, updateFactory func(uint64) proto.Receiv
120120
sender := &senderMock{}
121121
memberSvc := &membershipSvcMock{}
122122
memberSvc.On("GetMembership").Return([]discovery.NetworkMember{{PKIid: []byte("bla bla"), Endpoint: "localhost:5611"}})
123+
adapter := pull.PullAdapter{
124+
Sndr: sender,
125+
MemSvc: memberSvc,
126+
IdExtractor: func(msg *proto.SignedGossipMessage) string {
127+
return string(msg.GetPeerIdentity().PkiId)
128+
},
129+
MsgCons: func(msg *proto.SignedGossipMessage) {
123130

124-
pullMediator := pull.NewPullMediator(config,
125-
sender,
126-
memberSvc,
127-
func(msg *proto.SignedGossipMessage) string { return string(msg.GetPeerIdentity().PkiId) },
128-
func(msg *proto.SignedGossipMessage) {})
131+
},
132+
}
133+
pullMediator := pull.NewPullMediator(config, adapter)
129134
certStore := newCertStore(&pullerMock{
130135
Mediator: pullMediator,
131136
}, identity.NewIdentityMapper(&naiveCryptoService{}), api.PeerIdentityType("SELF"), &naiveCryptoService{})

gossip/gossip/channel/channel.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,13 @@ func (gc *gossipChannel) createBlockPuller() pull.Mediator {
268268
}
269269
gc.DeMultiplex(msg)
270270
}
271-
return pull.NewPullMediator(conf, gc, gc.memFilter, seqNumFromMsg, blockConsumer)
271+
adapter := pull.PullAdapter{
272+
Sndr: gc,
273+
MemSvc: gc.memFilter,
274+
IdExtractor: seqNumFromMsg,
275+
MsgCons: blockConsumer,
276+
}
277+
return pull.NewPullMediator(conf, adapter)
272278
}
273279

274280
// IsMemberInChan checks whether the given member is eligible to be in the channel

gossip/gossip/gossip_impl.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,13 @@ func (g *gossipServiceImpl) createCertStorePuller() pull.Mediator {
929929
g.logger.Info("Learned of a new certificate:", idMsg.Cert)
930930

931931
}
932-
return pull.NewPullMediator(conf, g.comm, g.disc, pkiIDFromMsg, certConsumer)
932+
adapter := pull.PullAdapter{
933+
Sndr: g.comm,
934+
MemSvc: g.disc,
935+
IdExtractor: pkiIDFromMsg,
936+
MsgCons: certConsumer,
937+
}
938+
return pull.NewPullMediator(conf, adapter)
933939
}
934940

935941
func (g *gossipServiceImpl) createStateInfoMsg(metadata []byte, chainID common.ChainID) (*proto.SignedGossipMessage, error) {

gossip/gossip/pull/pullstore.go

+42-6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ type MembershipService interface {
5555
GetMembership() []discovery.NetworkMember
5656
}
5757

58+
// DigestFilter filters digests to be sent to a remote peer, that
59+
// sent a hello with the following message
60+
type DigestFilter func(helloMsg proto.ReceivedMessage) func(digestItem string) bool
61+
62+
// byContext converts this DigestFilter to an algo.DigestFilter
63+
func (df DigestFilter) byContext() algo.DigestFilter {
64+
return func(context interface{}) func(digestItem string) bool {
65+
return func(digestItem string) bool {
66+
return df(context.(proto.ReceivedMessage))(digestItem)
67+
}
68+
}
69+
}
70+
5871
// PullConfig defines the configuration of the pull mediator
5972
type PullConfig struct {
6073
ID string
@@ -65,6 +78,16 @@ type PullConfig struct {
6578
MsgType proto.PullMsgType
6679
}
6780

81+
// PullAdapter defines methods of the pullStore to interact
82+
// with various modules of gossip
83+
type PullAdapter struct {
84+
Sndr Sender
85+
MemSvc MembershipService
86+
IdExtractor proto.IdentifierExtractor
87+
MsgCons proto.MsgConsumer
88+
DigFilter DigestFilter
89+
}
90+
6891
// Mediator is a component wrap a PullEngine and provides the methods
6992
// it needs to perform pull synchronization.
7093
// The specialization of a pull mediator to a certain type of message is
@@ -103,18 +126,31 @@ type pullMediatorImpl struct {
103126
}
104127

105128
// NewPullMediator returns a new Mediator
106-
func NewPullMediator(config PullConfig, sndr Sender, memSvc MembershipService, idExtractor proto.IdentifierExtractor, msgCons proto.MsgConsumer) Mediator {
129+
func NewPullMediator(config PullConfig, adapter PullAdapter) Mediator {
130+
digFilter := adapter.DigFilter
131+
132+
acceptAllFilter := func(_ proto.ReceivedMessage) func(string) bool {
133+
return func(_ string) bool {
134+
return true
135+
}
136+
}
137+
138+
if digFilter == nil {
139+
digFilter = acceptAllFilter
140+
}
141+
107142
p := &pullMediatorImpl{
108-
msgCons: msgCons,
143+
msgCons: adapter.MsgCons,
109144
msgType2Hook: make(map[PullMsgType][]MessageHook),
110-
idExtractor: idExtractor,
145+
idExtractor: adapter.IdExtractor,
111146
config: config,
112147
logger: util.GetLogger(util.LoggingPullModule, config.ID),
113148
itemID2Msg: make(map[string]*proto.SignedGossipMessage),
114-
memBvc: memSvc,
115-
Sender: sndr,
149+
memBvc: adapter.MemSvc,
150+
Sender: adapter.Sndr,
116151
}
117-
p.engine = algo.NewPullEngine(p, config.PullInterval)
152+
153+
p.engine = algo.NewPullEngineWithFilter(p, config.PullInterval, digFilter.byContext())
118154
return p
119155
}
120156

gossip/gossip/pull/pullstore_test.go

+48-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package pull
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"sync/atomic"
2223
"testing"
2324
"time"
@@ -106,6 +107,10 @@ func (p *pullInstance) wrapPullMsg(msg *proto.SignedGossipMessage) proto.Receive
106107
}
107108

108109
func createPullInstance(endpoint string, peer2PullInst map[string]*pullInstance) *pullInstance {
110+
return createPullInstanceWithFilters(endpoint, peer2PullInst, nil)
111+
}
112+
113+
func createPullInstanceWithFilters(endpoint string, peer2PullInst map[string]*pullInstance, df DigestFilter) *pullInstance {
109114
inst := &pullInstance{
110115
items: util.NewSet(),
111116
stopChan: make(chan struct{}),
@@ -137,7 +142,14 @@ func createPullInstance(endpoint string, peer2PullInst map[string]*pullInstance)
137142
blockConsumer := func(msg *proto.SignedGossipMessage) {
138143
inst.items.Add(msg.GetDataMsg().Payload.SeqNum)
139144
}
140-
inst.mediator = NewPullMediator(conf, inst, inst, seqNumFromMsg, blockConsumer)
145+
adapter := PullAdapter{
146+
Sndr: inst,
147+
MemSvc: inst,
148+
IdExtractor: seqNumFromMsg,
149+
MsgCons: blockConsumer,
150+
DigFilter: df,
151+
}
152+
inst.mediator = NewPullMediator(conf, adapter)
141153
go func() {
142154
for {
143155
select {
@@ -182,6 +194,41 @@ func TestRegisterMsgHook(t *testing.T) {
182194

183195
}
184196

197+
func TestFilter(t *testing.T) {
198+
t.Parallel()
199+
peer2pullInst := make(map[string]*pullInstance)
200+
201+
eq := func(a interface{}, b interface{}) bool {
202+
return a == b
203+
}
204+
df := func(msg proto.ReceivedMessage) func(string) bool {
205+
if msg.GetGossipMessage().IsDataReq() {
206+
req := msg.GetGossipMessage().GetDataReq()
207+
return func(item string) bool {
208+
return util.IndexInSlice(req.Digests, item, eq) != -1
209+
}
210+
}
211+
return func(digestItem string) bool {
212+
n, _ := strconv.ParseInt(digestItem, 10, 64)
213+
return n%2 == 0
214+
}
215+
}
216+
inst1 := createPullInstanceWithFilters("localhost:5611", peer2pullInst, df)
217+
inst2 := createPullInstance("localhost:5612", peer2pullInst)
218+
defer inst1.stop()
219+
defer inst2.stop()
220+
221+
inst1.mediator.Add(dataMsg(0))
222+
inst1.mediator.Add(dataMsg(1))
223+
inst1.mediator.Add(dataMsg(2))
224+
inst1.mediator.Add(dataMsg(3))
225+
226+
waitUntilOrFail(t, func() bool { return inst2.items.Exists(uint64(0)) })
227+
waitUntilOrFail(t, func() bool { return inst2.items.Exists(uint64(2)) })
228+
assert.False(t, inst2.items.Exists(uint64(1)))
229+
assert.False(t, inst2.items.Exists(uint64(3)))
230+
}
231+
185232
func TestAddAndRemove(t *testing.T) {
186233
t.Parallel()
187234
peer2pullInst := make(map[string]*pullInstance)

0 commit comments

Comments
 (0)