From f0159f1c23bb63e07947b3e96bf5e18aec11a325 Mon Sep 17 00:00:00 2001 From: Marko Vukolic Date: Wed, 21 Dec 2016 14:32:20 +0100 Subject: [PATCH] [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 --- orderer/sbft/simplebft/commit.go | 4 +- orderer/sbft/simplebft/simplebft.go | 15 +++- orderer/sbft/simplebft/simplebft_test.go | 87 ++++++++++++++++++------ orderer/sbft/simplebft/viewchange.go | 4 +- orderer/sbft/simplebft/xset.go | 4 +- 5 files changed, 84 insertions(+), 30 deletions(-) diff --git a/orderer/sbft/simplebft/commit.go b/orderer/sbft/simplebft/commit.go index 4d1a8721980..47f5df54aa9 100644 --- a/orderer/sbft/simplebft/commit.go +++ b/orderer/sbft/simplebft/commit.go @@ -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() @@ -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 diff --git a/orderer/sbft/simplebft/simplebft.go b/orderer/sbft/simplebft/simplebft.go index 2a40843108c..6f267628fae 100644 --- a/orderer/sbft/simplebft/simplebft.go +++ b/orderer/sbft/simplebft/simplebft.go @@ -18,6 +18,7 @@ package simplebft import ( "fmt" + "math" "reflect" "time" @@ -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 { diff --git a/orderer/sbft/simplebft/simplebft_test.go b/orderer/sbft/simplebft/simplebft_test.go index ea410dd9a0d..705bf9926d8 100644 --- a/orderer/sbft/simplebft/simplebft_test.go +++ b/orderer/sbft/simplebft/simplebft_test.go @@ -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() { @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/orderer/sbft/simplebft/viewchange.go b/orderer/sbft/simplebft/viewchange.go index 1d6996c4f53..249c1164132 100644 --- a/orderer/sbft/simplebft/viewchange.go +++ b/orderer/sbft/simplebft/viewchange.go @@ -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) diff --git a/orderer/sbft/simplebft/xset.go b/orderer/sbft/simplebft/xset.go index 628594a37fd..e1e33b93176 100644 --- a/orderer/sbft/simplebft/xset.go +++ b/orderer/sbft/simplebft/xset.go @@ -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) @@ -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 }