Skip to content

Commit

Permalink
[FAB-477] optimize sbft quorum sizes
Browse files Browse the repository at this point in the history
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 <mvu@zurich.ibm.com>
  • Loading branch information
Marko Vukolic committed Dec 22, 2016
1 parent f9b68d4 commit f0159f1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 30 deletions.
4 changes: 2 additions & 2 deletions orderer/sbft/simplebft/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package simplebft
import "reflect"

func (s *SBFT) maybeSendCommit() {
if s.cur.prepared || len(s.cur.prep) < s.noFaultyQuorum()-1 {
if s.cur.prepared || len(s.cur.prep) < s.commonCaseQuorum()-1 {
return
}
s.sendCommit()
Expand Down Expand Up @@ -52,7 +52,7 @@ func (s *SBFT) handleCommit(c *Subject, src uint64) {
s.cancelViewChangeTimer()

//maybe mark as comitted
if s.cur.committed || len(s.cur.commit) < s.noFaultyQuorum() {
if s.cur.committed || len(s.cur.commit) < s.commonCaseQuorum() {
return
}
s.cur.committed = true
Expand Down
15 changes: 13 additions & 2 deletions orderer/sbft/simplebft/simplebft.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package simplebft

import (
"fmt"
"math"
"reflect"
"time"

Expand Down Expand Up @@ -182,8 +183,18 @@ func (s *SBFT) nextView() uint64 {
return s.view + 1
}

func (s *SBFT) noFaultyQuorum() int {
return int(s.config.N - s.config.F)
func (s *SBFT) commonCaseQuorum() int {
//When N=3F+1 this should be 2F+1 (N-F)
//More generally, we need every two common case quorums of size X to intersect in at least F+1 orderers,
//hence 2X>=N+F+1, or X is:
return int(math.Ceil(float64(s.config.N+s.config.F+1) / float64(2)))
}

func (s *SBFT) viewChangeQuorum() int {
//When N=3F+1 this should be 2F+1 (N-F)
//More generally, we need every view change quorum to intersect with every common case quorum at least F+1 orderers, hence:
//Y >= N-X+F+1
return int(s.config.N+s.config.F+1) - s.commonCaseQuorum()
}

func (s *SBFT) oneCorrectQuorum() int {
Expand Down
87 changes: 65 additions & 22 deletions orderer/sbft/simplebft/simplebft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ import (
"testing"
"time"

"math"

"github.com/golang/protobuf/proto"
"github.com/op/go-logging"
)

const lowN uint64 = 4 //keep lowN greater or equal to 4
const highN uint64 = 10 //keep highN greater or equal to 10

var testLog = logging.MustGetLogger("test")

func init() {
Expand Down Expand Up @@ -69,7 +74,7 @@ func connectAll(sys *testSystem) {

func TestSBFT(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -104,9 +109,47 @@ func TestSBFT(t *testing.T) {
}
}

func TestQuorumSizes(t *testing.T) {
for N := uint64(1); N < 100; N++ {
for f := uint64(0); f <= uint64(math.Floor(float64(N-1)/float64(3))); f++ {
sys := newTestSystem(N)
a := sys.NewAdapter(0)
s, err := New(0, &Config{N: N, F: f, BatchDurationNsec: 2000000000, BatchSizeBytes: 10, RequestTimeoutNsec: 20000000000}, a)
if err != nil {
t.Fatal(err)
}
if uint64(2*s.commonCaseQuorum())-N < f+1 {
t.Fatal("insufficient intersection of two common case quorums", "N = ", N, " F = ", f)
}
if uint64(s.commonCaseQuorum()+s.viewChangeQuorum())-N < f+1 {
t.Fatal("insufficient intersection of common case and view change quorums", "N = ", N, " F = ", f)
}
if f < uint64(math.Floor(float64(N-1)/float64(3))) {
//end test for unoptimized f
continue
}
//test additionally when f is optimized
switch int(math.Mod(float64(N), float64(3))) {
case 1:
if s.commonCaseQuorum() != int(N-f) || s.viewChangeQuorum() != int(N-f) {
t.Fatal("quorum sizes are wrong in default case N mod 3 == 1")
}
case 2:
if s.viewChangeQuorum() >= s.commonCaseQuorum() || s.viewChangeQuorum() >= int(N-f) {
t.Fatal("view change quorums size not optimized when N mod 3 == 2")
}
case 3:
if s.commonCaseQuorum() >= int(N-f) || s.viewChangeQuorum() >= int(N-f) {
t.Fatal("quorum sizes not optimized when N mod 3 == 3")
}
}
}
}
}

func TestSBFTDelayed(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -177,7 +220,7 @@ func TestN1(t *testing.T) {

func TestMonotonicViews(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -211,7 +254,7 @@ func TestMonotonicViews(t *testing.T) {
}
}

func TestByzPrimary(t *testing.T) {
func TestByzPrimaryN4(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
sys := newTestSystem(N)
Expand Down Expand Up @@ -320,7 +363,7 @@ func TestNewPrimaryHandlingViewChange(t *testing.T) {

func TestByzPrimaryBullyingSingleReplica(t *testing.T) {
skipInShortMode(t)
N := uint64(10)
N := highN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -369,7 +412,7 @@ func TestByzPrimaryBullyingSingleReplica(t *testing.T) {

func TestViewChange(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -409,7 +452,7 @@ func TestViewChange(t *testing.T) {

func TestMsgReordering(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -462,7 +505,7 @@ func TestMsgReordering(t *testing.T) {

func TestBacklogReordering(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -515,7 +558,7 @@ func TestBacklogReordering(t *testing.T) {

func TestViewChangeWithRetransmission(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -555,7 +598,7 @@ func TestViewChangeWithRetransmission(t *testing.T) {

func TestViewChangeXset(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -632,7 +675,7 @@ func TestViewChangeXset(t *testing.T) {

func TestRestart(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -686,7 +729,7 @@ func TestRestart(t *testing.T) {

func TestAbdicatingPrimary(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -745,7 +788,7 @@ func TestAbdicatingPrimary(t *testing.T) {

func TestRestartAfterPrepare(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -815,7 +858,7 @@ func TestRestartAfterPrepare(t *testing.T) {

func TestRestartAfterCommit(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -885,7 +928,7 @@ func TestRestartAfterCommit(t *testing.T) {

func TestRestartAfterCheckpoint(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -955,7 +998,7 @@ func TestRestartAfterCheckpoint(t *testing.T) {

func TestErroneousViewChange(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1045,7 +1088,7 @@ func TestErroneousViewChange(t *testing.T) {

func TestRestartMissedViewChange(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1118,7 +1161,7 @@ func TestRestartMissedViewChange(t *testing.T) {

func TestFullBacklog(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1160,7 +1203,7 @@ func TestFullBacklog(t *testing.T) {

func TestHelloMsg(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystemWOTimers(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1227,7 +1270,7 @@ func TestHelloMsg(t *testing.T) {

func TestViewChangeTimer(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1311,7 +1354,7 @@ func TestViewChangeTimer(t *testing.T) {

func TestResendViewChange(t *testing.T) {
skipInShortMode(t)
N := uint64(4)
N := lowN
sys := newTestSystem(N)
var repls []*SBFT
var adapters []*testSystemAdapter
Expand Down Expand Up @@ -1371,7 +1414,7 @@ func TestResendViewChange(t *testing.T) {

func TestTenReplicasBombedWithRequests(t *testing.T) {
skipInShortMode(t)
N := uint64(10)
N := highN
requestNumber := 11
sys := newTestSystem(N)
var repls []*SBFT
Expand Down
4 changes: 2 additions & 2 deletions orderer/sbft/simplebft/viewchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ func (s *SBFT) handleViewChange(svc *Signed, src uint64) {
}
}

if quorum == s.noFaultyQuorum() {
log.Noticef("replica %d: received 2f+1 view change messages, starting view change timer", s.id)
if quorum == s.viewChangeQuorum() {
log.Noticef("replica %d: received view change quorum, starting view change timer", s.id)
s.viewChangeTimer = s.sys.Timer(s.viewChangeTimeout, func() {
s.viewChangeTimeout *= 2
log.Noticef("replica %d: view change timed out, sending next", s.id)
Expand Down
4 changes: 2 additions & 2 deletions orderer/sbft/simplebft/xset.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ nextm:
}
count += 1
}
if count < s.noFaultyQuorum() {
if count < s.viewChangeQuorum() {
continue
}
log.Debugf("replica %d: found %d replicas for Pset %d/%d", s.id, count, mtuple.Seq.Seq, mtuple.Seq.View)
Expand Down Expand Up @@ -128,7 +128,7 @@ nextm:

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

0 comments on commit f0159f1

Please sign in to comment.