Skip to content

Commit 820ee67

Browse files
author
Jason Yellick
committed
Orderer broadcast filtering framework
In order to allow reconfiguration messages to be supported by the orderer as well as to allow authentication of message signatures from clients, the ordering service will need to support limited filtering of broadcast messages as they arrive. This changeset defines a simple interface for doing so as well as a structure which supports applying many rules at once. The solo orderer is hooked into this framework, but for the moment only supports the existing behavior of 'Accept all non-empty messages', this will change once other more sophisticated filters are added. The hope is that this filtering framework can be re-used across ordering plugins, especially the component for verifying client signatures on incoming messages. https://jira.hyperledger.org/browse/FAB-591 Change-Id: I534330a1cb2b675c6f34e73079654ab3280520c0 Signed-off-by: Jason Yellick <[email protected]>
1 parent 038ea83 commit 820ee67

File tree

3 files changed

+230
-14
lines changed

3 files changed

+230
-14
lines changed

orderer/broadcastfilter/filter.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 broadcastfilter
18+
19+
import (
20+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
21+
)
22+
23+
// Action is used to express the output of a rule
24+
type Action int
25+
26+
const (
27+
// Accept indicates that the message should be processed
28+
Accept = iota
29+
// Reconfigure indicates that this message modifies this rule, and should therefore be processed in a batch by itself
30+
Reconfigure
31+
// Reject indicates that the message should not be processed
32+
Reject
33+
// Forward indicates that the rule could not determine the correct course of action
34+
Forward
35+
)
36+
37+
// Rule defines a filter function which accepts, rejects, or forwards (to the next rule) a BroadcastMessage
38+
type Rule interface {
39+
// Apply applies the rule to the given BroadcastMessage, replying with the Action to take for the message
40+
Apply(message *ab.BroadcastMessage) Action
41+
}
42+
43+
// EmptyRejectRule rejects empty messages
44+
var EmptyRejectRule = Rule(emptyRejectRule{})
45+
46+
type emptyRejectRule struct{}
47+
48+
func (a emptyRejectRule) Apply(message *ab.BroadcastMessage) Action {
49+
if message.Data == nil {
50+
return Reject
51+
}
52+
return Forward
53+
}
54+
55+
// AcceptRule always returns Accept as a result for Apply
56+
var AcceptRule = Rule(acceptRule{})
57+
58+
type acceptRule struct{}
59+
60+
func (a acceptRule) Apply(message *ab.BroadcastMessage) Action {
61+
return Accept
62+
}
63+
64+
// RuleSet is used to apply a collection of rules
65+
type RuleSet struct {
66+
rules []Rule
67+
}
68+
69+
// NewRuleSet creates a new RuleSet with the given ordered list of Rules
70+
func NewRuleSet(rules []Rule) *RuleSet {
71+
return &RuleSet{
72+
rules: rules,
73+
}
74+
}
75+
76+
// Apply applies the rules given for this set in order, returning the first non-Forward result and the Rule which generated it
77+
// or returning Forward, nil if no rules accept or reject it
78+
func (rs *RuleSet) Apply(message *ab.BroadcastMessage) (Action, Rule) {
79+
for _, rule := range rs.rules {
80+
action := rule.Apply(message)
81+
switch action {
82+
case Forward:
83+
continue
84+
default:
85+
return action, rule
86+
}
87+
}
88+
return Forward, nil
89+
}
+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 broadcastfilter
18+
19+
import (
20+
"testing"
21+
22+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
23+
)
24+
25+
var RejectRule = Rule(rejectRule{})
26+
27+
type rejectRule struct{}
28+
29+
func (r rejectRule) Apply(message *ab.BroadcastMessage) Action {
30+
return Reject
31+
}
32+
33+
var ForwardRule = Rule(forwardRule{})
34+
35+
type forwardRule struct{}
36+
37+
func (r forwardRule) Apply(message *ab.BroadcastMessage) Action {
38+
return Forward
39+
}
40+
41+
func TestEmptyRejectRule(t *testing.T) {
42+
rs := NewRuleSet([]Rule{EmptyRejectRule})
43+
result, rule := rs.Apply(&ab.BroadcastMessage{})
44+
if result != Reject {
45+
t.Fatalf("Should have rejected")
46+
}
47+
if rule != EmptyRejectRule {
48+
t.Fatalf("Rejected but not for the right rule")
49+
}
50+
result, _ = rs.Apply(&ab.BroadcastMessage{Data: []byte("fakedata")})
51+
if result != Forward {
52+
t.Fatalf("Should have forwarded")
53+
}
54+
}
55+
56+
func TestAcceptReject(t *testing.T) {
57+
rs := NewRuleSet([]Rule{AcceptRule, RejectRule})
58+
result, rule := rs.Apply(&ab.BroadcastMessage{})
59+
if result != Accept {
60+
t.Fatalf("Should have accepted")
61+
}
62+
if rule != AcceptRule {
63+
t.Fatalf("Accepted but not for the right rule")
64+
}
65+
}
66+
67+
func TestRejectAccept(t *testing.T) {
68+
rs := NewRuleSet([]Rule{RejectRule, AcceptRule})
69+
result, rule := rs.Apply(&ab.BroadcastMessage{})
70+
if result != Reject {
71+
t.Fatalf("Should have rejected")
72+
}
73+
if rule != RejectRule {
74+
t.Fatalf("Rejected but not for the right rule")
75+
}
76+
}
77+
78+
func TestForwardAccept(t *testing.T) {
79+
rs := NewRuleSet([]Rule{ForwardRule, AcceptRule})
80+
result, rule := rs.Apply(&ab.BroadcastMessage{})
81+
if result != Accept {
82+
t.Fatalf("Should have accepted")
83+
}
84+
if rule != AcceptRule {
85+
t.Fatalf("Accepted but not for the right rule")
86+
}
87+
}
88+
89+
func TestForward(t *testing.T) {
90+
rs := NewRuleSet([]Rule{ForwardRule})
91+
result, rule := rs.Apply(&ab.BroadcastMessage{})
92+
if result != Forward {
93+
t.Fatalf("Should have forwarded")
94+
}
95+
if rule != nil {
96+
t.Fatalf("Forwarded but rule is set")
97+
}
98+
}
99+
100+
func TestNoRule(t *testing.T) {
101+
rs := NewRuleSet([]Rule{})
102+
result, rule := rs.Apply(&ab.BroadcastMessage{})
103+
if result != Forward {
104+
t.Fatalf("Should have forwarded")
105+
}
106+
if rule != nil {
107+
t.Fatalf("Forwarded but rule is set")
108+
}
109+
}

orderer/solo/broadcast.go

+32-14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"time"
2121

2222
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
23+
"github.com/hyperledger/fabric/orderer/broadcastfilter"
2324
"github.com/hyperledger/fabric/orderer/rawledger"
2425
)
2526

@@ -28,6 +29,7 @@ type broadcastServer struct {
2829
batchSize int
2930
batchTimeout time.Duration
3031
rl rawledger.Writer
32+
filter *broadcastfilter.RuleSet
3133
sendChan chan *ab.BroadcastMessage
3234
exitChan chan struct{}
3335
}
@@ -44,6 +46,7 @@ func newPlainBroadcastServer(queueSize, batchSize int, batchTimeout time.Duratio
4446
batchSize: batchSize,
4547
batchTimeout: batchTimeout,
4648
rl: rl,
49+
filter: broadcastfilter.NewRuleSet([]broadcastfilter.Rule{broadcastfilter.EmptyRejectRule, broadcastfilter.AcceptRule}),
4750
sendChan: make(chan *ab.BroadcastMessage),
4851
exitChan: make(chan struct{}),
4952
}
@@ -62,11 +65,21 @@ outer:
6265
for {
6366
select {
6467
case msg := <-bs.sendChan:
65-
curBatch = append(curBatch, msg)
66-
if len(curBatch) < bs.batchSize {
67-
continue
68+
// The messages must be filtered a second time in case configuration has changed since the message was received
69+
action, _ := bs.filter.Apply(msg)
70+
switch action {
71+
case broadcastfilter.Accept:
72+
curBatch = append(curBatch, msg)
73+
if len(curBatch) < bs.batchSize {
74+
continue
75+
}
76+
logger.Debugf("Batch size met, creating block")
77+
case broadcastfilter.Forward:
78+
logger.Debugf("Ignoring message because it was not accepted by a filter")
79+
default:
80+
// TODO add support for other cases, unreachable for now
81+
logger.Fatalf("NOT IMPLEMENTED YET")
6882
}
69-
logger.Debugf("Batch size met, creating block")
7083
case <-timer:
7184
if len(curBatch) == 0 {
7285
continue outer
@@ -123,18 +136,23 @@ func (b *broadcaster) queueBroadcastMessages(srv ab.AtomicBroadcast_BroadcastSer
123136
return err
124137
}
125138

126-
if msg.Data == nil {
127-
err = srv.Send(&ab.BroadcastResponse{Status: ab.Status_BAD_REQUEST})
128-
if err != nil {
129-
return err
130-
}
131-
}
139+
action, _ := b.bs.filter.Apply(msg)
132140

133-
select {
134-
case b.queue <- msg:
135-
err = srv.Send(&ab.BroadcastResponse{Status: ab.Status_SUCCESS})
141+
switch action {
142+
case broadcastfilter.Accept:
143+
select {
144+
case b.queue <- msg:
145+
err = srv.Send(&ab.BroadcastResponse{ab.Status_SUCCESS})
146+
default:
147+
err = srv.Send(&ab.BroadcastResponse{ab.Status_SERVICE_UNAVAILABLE})
148+
}
149+
case broadcastfilter.Forward:
150+
fallthrough
151+
case broadcastfilter.Reject:
152+
err = srv.Send(&ab.BroadcastResponse{ab.Status_BAD_REQUEST})
136153
default:
137-
err = srv.Send(&ab.BroadcastResponse{Status: ab.Status_SERVICE_UNAVAILABLE})
154+
// TODO add support for other cases, unreachable for now
155+
logger.Fatalf("NOT IMPLEMENTED YET")
138156
}
139157

140158
if err != nil {

0 commit comments

Comments
 (0)