Skip to content

Commit f0159f1

Browse files
author
Marko Vukolic
committed
[FAB-477] optimize sbft quorum sizes
This changeset optimizes quorum sizes outside the classical config where N=3F+1. See comments in the code and in https://jira.hyperledger.org/browse/FAB-477. Unit test, also showing the advantage, added. Change-Id: I0b629ab90702f82baa9b169ef825c99d9739ffa2 Signed-off-by: Marko Vukolic <[email protected]>
1 parent f9b68d4 commit f0159f1

File tree

5 files changed

+84
-30
lines changed

5 files changed

+84
-30
lines changed

orderer/sbft/simplebft/commit.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package simplebft
1919
import "reflect"
2020

2121
func (s *SBFT) maybeSendCommit() {
22-
if s.cur.prepared || len(s.cur.prep) < s.noFaultyQuorum()-1 {
22+
if s.cur.prepared || len(s.cur.prep) < s.commonCaseQuorum()-1 {
2323
return
2424
}
2525
s.sendCommit()
@@ -52,7 +52,7 @@ func (s *SBFT) handleCommit(c *Subject, src uint64) {
5252
s.cancelViewChangeTimer()
5353

5454
//maybe mark as comitted
55-
if s.cur.committed || len(s.cur.commit) < s.noFaultyQuorum() {
55+
if s.cur.committed || len(s.cur.commit) < s.commonCaseQuorum() {
5656
return
5757
}
5858
s.cur.committed = true

orderer/sbft/simplebft/simplebft.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package simplebft
1818

1919
import (
2020
"fmt"
21+
"math"
2122
"reflect"
2223
"time"
2324

@@ -182,8 +183,18 @@ func (s *SBFT) nextView() uint64 {
182183
return s.view + 1
183184
}
184185

185-
func (s *SBFT) noFaultyQuorum() int {
186-
return int(s.config.N - s.config.F)
186+
func (s *SBFT) commonCaseQuorum() int {
187+
//When N=3F+1 this should be 2F+1 (N-F)
188+
//More generally, we need every two common case quorums of size X to intersect in at least F+1 orderers,
189+
//hence 2X>=N+F+1, or X is:
190+
return int(math.Ceil(float64(s.config.N+s.config.F+1) / float64(2)))
191+
}
192+
193+
func (s *SBFT) viewChangeQuorum() int {
194+
//When N=3F+1 this should be 2F+1 (N-F)
195+
//More generally, we need every view change quorum to intersect with every common case quorum at least F+1 orderers, hence:
196+
//Y >= N-X+F+1
197+
return int(s.config.N+s.config.F+1) - s.commonCaseQuorum()
187198
}
188199

189200
func (s *SBFT) oneCorrectQuorum() int {

orderer/sbft/simplebft/simplebft_test.go

+65-22
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ import (
2121
"testing"
2222
"time"
2323

24+
"math"
25+
2426
"github.com/golang/protobuf/proto"
2527
"github.com/op/go-logging"
2628
)
2729

30+
const lowN uint64 = 4 //keep lowN greater or equal to 4
31+
const highN uint64 = 10 //keep highN greater or equal to 10
32+
2833
var testLog = logging.MustGetLogger("test")
2934

3035
func init() {
@@ -69,7 +74,7 @@ func connectAll(sys *testSystem) {
6974

7075
func TestSBFT(t *testing.T) {
7176
skipInShortMode(t)
72-
N := uint64(4)
77+
N := lowN
7378
sys := newTestSystem(N)
7479
var repls []*SBFT
7580
var adapters []*testSystemAdapter
@@ -104,9 +109,47 @@ func TestSBFT(t *testing.T) {
104109
}
105110
}
106111

112+
func TestQuorumSizes(t *testing.T) {
113+
for N := uint64(1); N < 100; N++ {
114+
for f := uint64(0); f <= uint64(math.Floor(float64(N-1)/float64(3))); f++ {
115+
sys := newTestSystem(N)
116+
a := sys.NewAdapter(0)
117+
s, err := New(0, &Config{N: N, F: f, BatchDurationNsec: 2000000000, BatchSizeBytes: 10, RequestTimeoutNsec: 20000000000}, a)
118+
if err != nil {
119+
t.Fatal(err)
120+
}
121+
if uint64(2*s.commonCaseQuorum())-N < f+1 {
122+
t.Fatal("insufficient intersection of two common case quorums", "N = ", N, " F = ", f)
123+
}
124+
if uint64(s.commonCaseQuorum()+s.viewChangeQuorum())-N < f+1 {
125+
t.Fatal("insufficient intersection of common case and view change quorums", "N = ", N, " F = ", f)
126+
}
127+
if f < uint64(math.Floor(float64(N-1)/float64(3))) {
128+
//end test for unoptimized f
129+
continue
130+
}
131+
//test additionally when f is optimized
132+
switch int(math.Mod(float64(N), float64(3))) {
133+
case 1:
134+
if s.commonCaseQuorum() != int(N-f) || s.viewChangeQuorum() != int(N-f) {
135+
t.Fatal("quorum sizes are wrong in default case N mod 3 == 1")
136+
}
137+
case 2:
138+
if s.viewChangeQuorum() >= s.commonCaseQuorum() || s.viewChangeQuorum() >= int(N-f) {
139+
t.Fatal("view change quorums size not optimized when N mod 3 == 2")
140+
}
141+
case 3:
142+
if s.commonCaseQuorum() >= int(N-f) || s.viewChangeQuorum() >= int(N-f) {
143+
t.Fatal("quorum sizes not optimized when N mod 3 == 3")
144+
}
145+
}
146+
}
147+
}
148+
}
149+
107150
func TestSBFTDelayed(t *testing.T) {
108151
skipInShortMode(t)
109-
N := uint64(4)
152+
N := lowN
110153
sys := newTestSystem(N)
111154
var repls []*SBFT
112155
var adapters []*testSystemAdapter
@@ -177,7 +220,7 @@ func TestN1(t *testing.T) {
177220

178221
func TestMonotonicViews(t *testing.T) {
179222
skipInShortMode(t)
180-
N := uint64(4)
223+
N := lowN
181224
sys := newTestSystem(N)
182225
var repls []*SBFT
183226
var adapters []*testSystemAdapter
@@ -211,7 +254,7 @@ func TestMonotonicViews(t *testing.T) {
211254
}
212255
}
213256

214-
func TestByzPrimary(t *testing.T) {
257+
func TestByzPrimaryN4(t *testing.T) {
215258
skipInShortMode(t)
216259
N := uint64(4)
217260
sys := newTestSystem(N)
@@ -320,7 +363,7 @@ func TestNewPrimaryHandlingViewChange(t *testing.T) {
320363

321364
func TestByzPrimaryBullyingSingleReplica(t *testing.T) {
322365
skipInShortMode(t)
323-
N := uint64(10)
366+
N := highN
324367
sys := newTestSystem(N)
325368
var repls []*SBFT
326369
var adapters []*testSystemAdapter
@@ -369,7 +412,7 @@ func TestByzPrimaryBullyingSingleReplica(t *testing.T) {
369412

370413
func TestViewChange(t *testing.T) {
371414
skipInShortMode(t)
372-
N := uint64(4)
415+
N := lowN
373416
sys := newTestSystem(N)
374417
var repls []*SBFT
375418
var adapters []*testSystemAdapter
@@ -409,7 +452,7 @@ func TestViewChange(t *testing.T) {
409452

410453
func TestMsgReordering(t *testing.T) {
411454
skipInShortMode(t)
412-
N := uint64(4)
455+
N := lowN
413456
sys := newTestSystem(N)
414457
var repls []*SBFT
415458
var adapters []*testSystemAdapter
@@ -462,7 +505,7 @@ func TestMsgReordering(t *testing.T) {
462505

463506
func TestBacklogReordering(t *testing.T) {
464507
skipInShortMode(t)
465-
N := uint64(4)
508+
N := lowN
466509
sys := newTestSystem(N)
467510
var repls []*SBFT
468511
var adapters []*testSystemAdapter
@@ -515,7 +558,7 @@ func TestBacklogReordering(t *testing.T) {
515558

516559
func TestViewChangeWithRetransmission(t *testing.T) {
517560
skipInShortMode(t)
518-
N := uint64(4)
561+
N := lowN
519562
sys := newTestSystem(N)
520563
var repls []*SBFT
521564
var adapters []*testSystemAdapter
@@ -555,7 +598,7 @@ func TestViewChangeWithRetransmission(t *testing.T) {
555598

556599
func TestViewChangeXset(t *testing.T) {
557600
skipInShortMode(t)
558-
N := uint64(4)
601+
N := lowN
559602
sys := newTestSystem(N)
560603
var repls []*SBFT
561604
var adapters []*testSystemAdapter
@@ -632,7 +675,7 @@ func TestViewChangeXset(t *testing.T) {
632675

633676
func TestRestart(t *testing.T) {
634677
skipInShortMode(t)
635-
N := uint64(4)
678+
N := lowN
636679
sys := newTestSystem(N)
637680
var repls []*SBFT
638681
var adapters []*testSystemAdapter
@@ -686,7 +729,7 @@ func TestRestart(t *testing.T) {
686729

687730
func TestAbdicatingPrimary(t *testing.T) {
688731
skipInShortMode(t)
689-
N := uint64(4)
732+
N := lowN
690733
sys := newTestSystem(N)
691734
var repls []*SBFT
692735
var adapters []*testSystemAdapter
@@ -745,7 +788,7 @@ func TestAbdicatingPrimary(t *testing.T) {
745788

746789
func TestRestartAfterPrepare(t *testing.T) {
747790
skipInShortMode(t)
748-
N := uint64(4)
791+
N := lowN
749792
sys := newTestSystem(N)
750793
var repls []*SBFT
751794
var adapters []*testSystemAdapter
@@ -815,7 +858,7 @@ func TestRestartAfterPrepare(t *testing.T) {
815858

816859
func TestRestartAfterCommit(t *testing.T) {
817860
skipInShortMode(t)
818-
N := uint64(4)
861+
N := lowN
819862
sys := newTestSystem(N)
820863
var repls []*SBFT
821864
var adapters []*testSystemAdapter
@@ -885,7 +928,7 @@ func TestRestartAfterCommit(t *testing.T) {
885928

886929
func TestRestartAfterCheckpoint(t *testing.T) {
887930
skipInShortMode(t)
888-
N := uint64(4)
931+
N := lowN
889932
sys := newTestSystem(N)
890933
var repls []*SBFT
891934
var adapters []*testSystemAdapter
@@ -955,7 +998,7 @@ func TestRestartAfterCheckpoint(t *testing.T) {
955998

956999
func TestErroneousViewChange(t *testing.T) {
9571000
skipInShortMode(t)
958-
N := uint64(4)
1001+
N := lowN
9591002
sys := newTestSystem(N)
9601003
var repls []*SBFT
9611004
var adapters []*testSystemAdapter
@@ -1045,7 +1088,7 @@ func TestErroneousViewChange(t *testing.T) {
10451088

10461089
func TestRestartMissedViewChange(t *testing.T) {
10471090
skipInShortMode(t)
1048-
N := uint64(4)
1091+
N := lowN
10491092
sys := newTestSystem(N)
10501093
var repls []*SBFT
10511094
var adapters []*testSystemAdapter
@@ -1118,7 +1161,7 @@ func TestRestartMissedViewChange(t *testing.T) {
11181161

11191162
func TestFullBacklog(t *testing.T) {
11201163
skipInShortMode(t)
1121-
N := uint64(4)
1164+
N := lowN
11221165
sys := newTestSystem(N)
11231166
var repls []*SBFT
11241167
var adapters []*testSystemAdapter
@@ -1160,7 +1203,7 @@ func TestFullBacklog(t *testing.T) {
11601203

11611204
func TestHelloMsg(t *testing.T) {
11621205
skipInShortMode(t)
1163-
N := uint64(4)
1206+
N := lowN
11641207
sys := newTestSystemWOTimers(N)
11651208
var repls []*SBFT
11661209
var adapters []*testSystemAdapter
@@ -1227,7 +1270,7 @@ func TestHelloMsg(t *testing.T) {
12271270

12281271
func TestViewChangeTimer(t *testing.T) {
12291272
skipInShortMode(t)
1230-
N := uint64(4)
1273+
N := lowN
12311274
sys := newTestSystem(N)
12321275
var repls []*SBFT
12331276
var adapters []*testSystemAdapter
@@ -1311,7 +1354,7 @@ func TestViewChangeTimer(t *testing.T) {
13111354

13121355
func TestResendViewChange(t *testing.T) {
13131356
skipInShortMode(t)
1314-
N := uint64(4)
1357+
N := lowN
13151358
sys := newTestSystem(N)
13161359
var repls []*SBFT
13171360
var adapters []*testSystemAdapter
@@ -1371,7 +1414,7 @@ func TestResendViewChange(t *testing.T) {
13711414

13721415
func TestTenReplicasBombedWithRequests(t *testing.T) {
13731416
skipInShortMode(t)
1374-
N := uint64(10)
1417+
N := highN
13751418
requestNumber := 11
13761419
sys := newTestSystem(N)
13771420
var repls []*SBFT

orderer/sbft/simplebft/viewchange.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ func (s *SBFT) handleViewChange(svc *Signed, src uint64) {
111111
}
112112
}
113113

114-
if quorum == s.noFaultyQuorum() {
115-
log.Noticef("replica %d: received 2f+1 view change messages, starting view change timer", s.id)
114+
if quorum == s.viewChangeQuorum() {
115+
log.Noticef("replica %d: received view change quorum, starting view change timer", s.id)
116116
s.viewChangeTimer = s.sys.Timer(s.viewChangeTimeout, func() {
117117
s.viewChangeTimeout *= 2
118118
log.Noticef("replica %d: view change timed out, sending next", s.id)

orderer/sbft/simplebft/xset.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ nextm:
8383
}
8484
count += 1
8585
}
86-
if count < s.noFaultyQuorum() {
86+
if count < s.viewChangeQuorum() {
8787
continue
8888
}
8989
log.Debugf("replica %d: found %d replicas for Pset %d/%d", s.id, count, mtuple.Seq.Seq, mtuple.Seq.View)
@@ -128,7 +128,7 @@ nextm:
128128

129129
// B. otherwise select null request
130130
// We actually don't select a null request, but report the most recent batch instead.
131-
if emptycount >= s.noFaultyQuorum() {
131+
if emptycount >= s.viewChangeQuorum() {
132132
log.Debugf("replica %d: no pertinent requests found for %d", s.id, next)
133133
return nil, best, true
134134
}

0 commit comments

Comments
 (0)