From 74d66c7eb10fc7c83c0ff88c4b554f22069ca7c7 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Wed, 5 Jun 2019 08:01:41 +0200 Subject: [PATCH 01/10] changed to go modules for dependency resolution --- Gopkg.lock | 45 --------------------------------------------- Gopkg.toml | 42 ------------------------------------------ Makefile | 3 --- go.mod | 10 ++++++++++ go.sum | 15 +++++++++++++++ 5 files changed, 25 insertions(+), 90 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod create mode 100644 go.sum diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index d8adc82..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,45 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - name = "github.com/NebulousLabs/merkletree" - packages = ["."] - revision = "1db44fa75fb1036c6d7be59a5793be4b1fb8a5ab" - -[[projects]] - name = "github.com/davecgh/go-spew" - packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" - -[[projects]] - name = "github.com/klauspost/cpuid" - packages = ["."] - revision = "ae7887de9fa5d2db4eaa8174a7eff2c1ac00f2da" - version = "v1.1" - -[[projects]] - name = "github.com/klauspost/reedsolomon" - packages = ["."] - revision = "6bb6130ff6a76a904c1841707d65603aec9cc288" - version = "v1.6" - -[[projects]] - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - name = "github.com/stretchr/testify" - packages = ["assert"] - revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" - version = "v1.2.1" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "c7ccc0fa8beab06a021f9a8163c140efe84fbf406dccf7ff02420acec908c62c" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 1be2171..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,42 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/klauspost/reedsolomon" - version = "1.6.0" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.2.1" - -[prune] - go-tests = true - unused-packages = true - -[[constraint]] - branch = "master" - name = "github.com/cbergoon/merkletree" diff --git a/Makefile b/Makefile index 7197f62..a755502 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,2 @@ -deps: - @dep ensure - test: @go test ./... --cover \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ddaa570 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/anthdm/hbbft + +go 1.12 + +require ( + github.com/NebulousLabs/merkletree v0.0.0-20181203152040-08d5d54b07f5 + github.com/klauspost/cpuid v1.2.1 // indirect + github.com/klauspost/reedsolomon v1.9.2 + github.com/sirupsen/logrus v1.4.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..821fffe --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/NebulousLabs/merkletree v0.0.0-20181203152040-08d5d54b07f5 h1:pk9SclNGplPbF6YDIDKMhHh9SaUWcoxPkMr7zdu1hfk= +github.com/NebulousLabs/merkletree v0.0.0-20181203152040-08d5d54b07f5/go.mod h1:Cn056wBLKay+uIS9LJn7ymwhgC5mqbOtG6iOhEvyy4M= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18= +github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From c1594efc932c5f1198724d11d16aeba9f188cb7c Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Wed, 5 Jun 2019 08:03:17 +0200 Subject: [PATCH 02/10] added modules artifacts --- go.mod | 1 + go.sum | 3 +++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index ddaa570..af80e25 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,5 @@ require ( github.com/klauspost/cpuid v1.2.1 // indirect github.com/klauspost/reedsolomon v1.9.2 github.com/sirupsen/logrus v1.4.2 + github.com/stretchr/testify v1.2.2 ) diff --git a/go.sum b/go.sum index 821fffe..4fa9787 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,18 @@ github.com/NebulousLabs/merkletree v0.0.0-20181203152040-08d5d54b07f5 h1:pk9SclNGplPbF6YDIDKMhHh9SaUWcoxPkMr7zdu1hfk= github.com/NebulousLabs/merkletree v0.0.0-20181203152040-08d5d54b07f5/go.mod h1:Cn056wBLKay+uIS9LJn7ymwhgC5mqbOtG6iOhEvyy4M= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18= github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From cb751fd16c8f59e5f3ca290dec50ab281c3323dc Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 12 May 2021 14:53:36 +0300 Subject: [PATCH 03/10] Make it work with an out-of-order message delivery. --- Makefile | 9 +- acs.go | 30 +++-- acs_test.go | 86 ++++++++++++++ bba.go | 306 +++++++++++++++++++++++++++++++++--------------- bba_test.go | 93 ++++++++++++++- cc.go | 22 ++++ honey_badger.go | 6 +- rbc.go | 13 +- rbc_test.go | 97 +++++++++++++++ 9 files changed, 544 insertions(+), 118 deletions(-) create mode 100644 cc.go diff --git a/Makefile b/Makefile index a755502..5d2e7c8 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,7 @@ -test: - @go test ./... --cover \ No newline at end of file +test: + @go test ./... --cover + +test-randomized: + go test --count 10000 --run TestBBARandomized + go test --count 10000 --run TestRBCRandomized + go test --count 10000 --run TestACSRandomized diff --git a/acs.go b/acs.go index 8417a43..4414928 100644 --- a/acs.go +++ b/acs.go @@ -15,7 +15,7 @@ type ACSMessage struct { // ACS implements the Asynchronous Common Subset protocol. // ACS assumes a network of N nodes that send signed messages to each other. // There can be f faulty nodes where (3 * f < N). -// Each participating node proposes an element for inlcusion. The protocol +// Each participating node proposes an element for inclusion. The protocol // guarantees that all of the good nodes output the same set, consisting of // at least (N -f) of the proposed values. // @@ -23,8 +23,8 @@ type ACSMessage struct { // ACS creates a Broadcast algorithm for each of the participating nodes. // At least (N -f) of these will eventually output the element proposed by that // node. ACS will also create and BBA instance for each participating node, to -// decide whether that node's proposed element should be inlcuded in common set. -// Whenever an element is received via broadcast, we imput "true" into the +// decide whether that node's proposed element should be included in common set. +// Whenever an element is received via broadcast, we input "true" into the // corresponding BBA instance. When (N-f) BBA instances have decided true we // input false into the remaining ones, where we haven't provided input yet. // Once all BBA instances have decided, ACS returns the set of all proposed @@ -101,6 +101,12 @@ func NewACS(cfg Config) *ACS { return acs } +// Messages returns all the internal messages from the message que. Note that +// the que will be empty after invoking this method. +func (a *ACS) Messages() []MessageTuple { + return a.messageQue.messages() +} + // InputValue sets the input value for broadcast and returns an initial set of // Broadcast and ACS Messages to be broadcasted in the network. func (a *ACS) InputValue(val []byte) error { @@ -155,7 +161,7 @@ func (a *ACS) Output() map[uint64][]byte { func (a *ACS) Done() bool { agreementsDone := true for _, bba := range a.bbaInstances { - if !bba.done { + if !bba.Done() { agreementsDone = false } } @@ -184,7 +190,7 @@ func (a *ACS) inputValue(data []byte) error { } if output := rbc.Output(); output != nil { a.rbcResults[a.ID] = output - a.processAgreement(a.ID, func(bba *BBA) error { + return a.processAgreement(a.ID, func(bba *BBA) error { if bba.AcceptInput() { return bba.InputValue(true) } @@ -255,7 +261,10 @@ func (a *ACS) processAgreement(pid uint64, fun func(bba *BBA) error) error { if !ok { return fmt.Errorf("could not find bba instance for (%d)", pid) } - if bba.done { + if bba.Done() { + if !a.decided { + a.tryCompleteAgreement() + } return nil } if err := fun(bba); err != nil { @@ -287,6 +296,10 @@ func (a *ACS) processAgreement(pid uint64, fun func(bba *BBA) error) error { } } } + } + // Completion can be triggered either by the BBA or the RBC output, + // depending on which is completed first. Both variants are possible. + if _, ok := a.bbaResults[pid]; ok { a.tryCompleteAgreement() } return nil @@ -308,8 +321,9 @@ func (a *ACS) tryCompleteAgreement() { } bcResults := make(map[uint64][]byte) for _, id := range nodesThatProvidedTrue { - val, _ := a.rbcResults[id] - bcResults[id] = val + if val, bcOk := a.rbcResults[id]; bcOk { + bcResults[id] = val + } } if len(nodesThatProvidedTrue) == len(bcResults) { a.output = bcResults diff --git a/acs_test.go b/acs_test.go index db28df7..ca40bf1 100644 --- a/acs_test.go +++ b/acs_test.go @@ -1,10 +1,14 @@ package hbbft import ( + "bytes" + "fmt" + "math/rand" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Test ACS with 4 good nodes. The result should be that at least the output @@ -106,11 +110,93 @@ func TestACSOutputIsNilAfterConsuming(t *testing.T) { assert.Nil(t, acs.Output()) } +// This test checks, if messages sent in any order are still handled correctly. +// It is expected to run this test multiple times. +func TestACSRandomized(t *testing.T) { + if err := testACSRandomized(t); err != nil { + t.Fatalf("Failed, reason=%+v", err) + } +} +func testACSRandomized(t *testing.T) error { + var err error + var N, T = 7, 5 + + msgs := make([]*testMsg, 0) + nodes := make([]uint64, N) + for n := range nodes { + nodes[n] = uint64(n) + } + + cfg := make([]Config, N) + for i := range cfg { + cfg[i] = Config{ + N: N, + F: N - T, + ID: uint64(i), + Nodes: nodes, + BatchSize: 21254, // Should be unused. + } + + } + + acs := make([]*ACS, N) + for a := range acs { + acs[a] = NewACS(cfg[a]) + if err = acs[a].InputValue([]byte{1, 2, byte(a)}); err != nil { + return fmt.Errorf("Failed to process ACS.InputValue: %+v", err) + } + msgs = appendTestMsgs(acs[a].Messages(), nodes[a], msgs) + } + + // var done bool + for len(msgs) != 0 { + m := rand.Intn(len(msgs)) + msg := msgs[m] + + msgTo := msg.msg.To + if acsMsg, ok := msg.msg.Payload.(*ACSMessage); ok { + if err = acs[msgTo].HandleMessage(uint64(msg.from), acsMsg); err != nil { + return fmt.Errorf("Failed to ACS.HandleMessage: %+v", err) + } + } else { + return fmt.Errorf("Unexpected message type: %+v", msg.msg.Payload) + } + + // Remove the message from the buffer and append the new messages, if any. + msgs[m] = msgs[len(msgs)-1] + msgs = msgs[:len(msgs)-1] + msgs = appendTestMsgs(acs[msgTo].Messages(), nodes[msgTo], msgs) + } + + out0 := acs[0].Output() + for a := range acs { + require.True(t, acs[a].Done()) + if a == 0 { + continue + } + var outA map[uint64][]byte = acs[a].Output() + require.Equal(t, len(out0), len(outA)) + for i := range out0 { + require.Equal(t, bytes.Compare(out0[i], outA[i]), 0) + } + acs[a].stop() + } + return nil +} + type testMsg struct { from uint64 msg MessageTuple } +func appendTestMsgs(msgs []MessageTuple, senderID uint64, buf []*testMsg) []*testMsg { + output := buf[:] + for m := range msgs { + output = append(output, &testMsg{from: senderID, msg: msgs[m]}) + } + return output +} + func makeACSNetwork(n int) []*ACS { network := make([]*ACS, n) for i := 0; i < n; i++ { diff --git a/bba.go b/bba.go index caee0ba..016fbb0 100644 --- a/bba.go +++ b/bba.go @@ -33,31 +33,57 @@ type AuxRequest struct { Value bool } +// DoneRequest is not part of the HB protocol, but we use +// it here to terminate the protocol in a graceful way. +type DoneRequest struct{} + // BBA is the Binary Byzantine Agreement build from a common coin protocol. type BBA struct { // Config holds the BBA configuration. Config + + // Common Coin implementation to use. + commonCoin CommonCoin + // Current epoch. epoch uint32 - // Bval requests we accepted this epoch. + + // Bval requests we accepted this epoch (received from a quorum). binValues []bool - // sentBvals are the binary values this instance sent. + + // sentBvals are the binary values this instance sent in this epoch. sentBvals []bool - // recvBval is a mapping of the sender and the receveived binary value. - recvBval map[uint64]bool - // recvAux is a mapping of the sender and the receveived Aux value. + + // recvBval is a mapping of the sender and the received binary value. + // We need to collect both values, because each node can send several + // different bval messages in a single round. + recvBvalT map[uint64]bool + recvBvalF map[uint64]bool + + // recvAux is a mapping of the sender and the received Aux value. recvAux map[uint64]bool + + // recvDone contains received info, on which epoch which peer has decided. + recvDone map[uint64]uint32 + // Whether this bba is terminated or not. + // It can have an output, but should be still running, + // because it has to help other peers to decide. done bool - // output and estimated of the bba protocol. This can be either nil or a - // boolean. + + // output and estimated of the bba protocol. + // This can be either nil or a boolean. + // The decision is kept permanently, while the output is cleared on first fetch. output, estimated, decision interface{} + //delayedMessages are messages that are received by a node that is already - // in a later epoch. These messages will be qued an handled the next epoch. - delayedMessages []delayedMessage + // in a later epoch. These messages will be queued an handled the next epoch. + delayedMessages []*delayedMessage + // For all the external access, like Done, Output, etc. lock sync.RWMutex - // Que of AgreementMessages that need to be broadcasted after each received + + // Queue of AgreementMessages that need to be broadcasted after each received // message. messages []*AgreementMessage @@ -73,17 +99,26 @@ func NewBBA(cfg Config) *BBA { if cfg.F == 0 { cfg.F = (cfg.N - 1) / 3 } + var cc CommonCoin + if cfg.CommonCoin != nil { + cc = cfg.CommonCoin + } else { + cc = NewFakeCoin() // Use it by default to avoid breaking changes in the API. + } bba := &BBA{ Config: cfg, - recvBval: make(map[uint64]bool), + commonCoin: cc, + recvBvalT: make(map[uint64]bool), + recvBvalF: make(map[uint64]bool), recvAux: make(map[uint64]bool), + recvDone: make(map[uint64]uint32), sentBvals: []bool{}, binValues: []bool{}, closeCh: make(chan struct{}), inputCh: make(chan bbaInputTuple), messageCh: make(chan bbaMessageTuple), messages: []*AgreementMessage{}, - delayedMessages: []delayedMessage{}, + delayedMessages: []*delayedMessage{}, } go bba.run() return bba @@ -120,8 +155,8 @@ func (b *BBA) InputValue(val bool) error { return <-t.err } -// HandleMessage will process the given rpc message. The caller is resposible to -// make sure only RPC messages are passed that are elligible for the BBA protocol. +// HandleMessage will process the given rpc message. The caller is responsible to +// make sure only RPC messages are passed that are eligible for the BBA protocol. func (b *BBA) HandleMessage(senderID uint64, msg *AgreementMessage) error { b.msgCount++ t := bbaMessageTuple{ @@ -133,7 +168,7 @@ func (b *BBA) HandleMessage(senderID uint64, msg *AgreementMessage) error { return <-t.err } -// AcceptInput returns true whether this bba instance is elligable for accepting +// AcceptInput returns true whether this bba instance is eligable for accepting // a new input value. func (b *BBA) AcceptInput() bool { return b.epoch == 0 && b.estimated == nil @@ -143,7 +178,9 @@ func (b *BBA) AcceptInput() bool { // then it will return the output else nil. Note that after consuming the output // its will be set to nil forever. func (b *BBA) Output() interface{} { - if b.output != nil { + b.lock.Lock() + defer b.lock.Unlock() + if b.done && b.output != nil { out := b.output b.output = nil return out @@ -155,23 +192,31 @@ func (b *BBA) Output() interface{} { // processing a protocol message. After calling this method the que will // be empty. Hence calling Messages can only occur once in a single roundtrip. func (b *BBA) Messages() []*AgreementMessage { - b.lock.RLock() - msgs := b.messages - b.lock.RUnlock() - b.lock.Lock() defer b.lock.Unlock() + msgs := b.messages b.messages = []*AgreementMessage{} return msgs } +// addMessage adds single message to be broadcasted. +// The actual recipients are set in the ACS part. func (b *BBA) addMessage(msg *AgreementMessage) { b.lock.Lock() defer b.lock.Unlock() b.messages = append(b.messages, msg) } -func (b *BBA) stop() { +// Done indicates, if the process is already decided and can be terminated. +// It can be so that the BBA has decided, but needs to support other peers to decide. +func (b *BBA) Done() bool { + b.lock.Lock() + defer b.lock.Unlock() + return b.done +} + +// Stop the BBA thread. +func (b *BBA) Stop() { close(b.closeCh) } @@ -182,10 +227,14 @@ func (b *BBA) run() { select { case <-b.closeCh: return - case t := <-b.inputCh: - t.err <- b.inputValue(t.value) - case t := <-b.messageCh: - t.err <- b.handleMessage(t.senderID, t.msg) + case t, ok := <-b.inputCh: + if ok { + t.err <- b.inputValue(t.value) + } + case t, ok := <-b.messageCh: + if ok { + t.err <- b.handleMessage(t.senderID, t.msg) + } } } } @@ -193,19 +242,21 @@ func (b *BBA) run() { // inputValue will set the given val as the initial value to be proposed in the // Agreement. func (b *BBA) inputValue(val bool) error { - // Make sure we are in the first epoch round. + // Make sure we are in the first epoch and the value is not estimated yet. if b.epoch != 0 || b.estimated != nil { return nil } b.estimated = val - b.sentBvals = append(b.sentBvals, val) - b.addMessage(NewAgreementMessage(int(b.epoch), &BvalRequest{val})) - return b.handleBvalRequest(b.ID, val) + return b.sendBval(val) } -// handleMessage will process the given rpc message. The caller is resposible to -// make sure only RPC messages are passed that are elligible for the BBA protocol. +// handleMessage will process the given rpc message. The caller is responsible to +// make sure only RPC messages are passed that are eligible for the BBA protocol. func (b *BBA) handleMessage(senderID uint64, msg *AgreementMessage) error { + if _, ok := msg.Message.(*DoneRequest); ok { + // We handle done messages from all the epochs. + return b.handleDoneRequest(senderID, uint32(msg.Epoch)) + } if b.done { return nil } @@ -217,9 +268,9 @@ func (b *BBA) handleMessage(senderID uint64, msg *AgreementMessage) error { ) return nil } - // Messages from later epochs will be qued and processed later. + // Messages from later epochs will be queued and processed later. if msg.Epoch > int(b.epoch) { - b.delayedMessages = append(b.delayedMessages, delayedMessage{senderID, msg}) + b.delayedMessages = append(b.delayedMessages, &delayedMessage{senderID, msg}) return nil } @@ -236,64 +287,71 @@ func (b *BBA) handleMessage(senderID uint64, msg *AgreementMessage) error { // handleBvalRequest processes the received binary value and fills up the // message que if there are any messages that need to be broadcasted. func (b *BBA) handleBvalRequest(senderID uint64, val bool) error { - b.lock.Lock() - b.recvBval[senderID] = val - b.lock.Unlock() - lenBval := b.countBvals(val) + b.addRecvBval(senderID, val) + lenBval := b.countRecvBvals(val) // When receiving n bval(b) messages from 2f+1 nodes: inputs := inputs u {b} - if lenBval == 2*b.F+1 { + // The set binValues can increase over time (more that a single element). + if lenBval > 2*b.F { wasEmptyBinValues := len(b.binValues) == 0 - b.binValues = append(b.binValues, val) + b.addBinValue(val) // If inputs > 0 broadcast output(b) and handle the output ourselfs. // Wait until binValues > 0, then broadcast AUX(b). The AUX(b) broadcast - // may only occure once per epoch. + // may only occur once per epoch. if wasEmptyBinValues { b.addMessage(NewAgreementMessage(int(b.epoch), &AuxRequest{val})) b.handleAuxRequest(b.ID, val) } - return nil } // When receiving input(b) messages from f + 1 nodes, if inputs(b) is not // been sent yet broadcast input(b) and handle the input ourselfs. - if lenBval == b.F+1 && !b.hasSentBval(val) { - b.sentBvals = append(b.sentBvals, val) - b.addMessage(NewAgreementMessage(int(b.epoch), &BvalRequest{val})) - return b.handleBvalRequest(b.ID, val) + if lenBval > b.F && !b.hasSentBval(val) { + return b.sendBval(val) } - return nil + + // It is possible that we have the needed aux messages already, + // therefore we need to try to make a decision. + return b.tryOutputAgreement() } func (b *BBA) handleAuxRequest(senderID uint64, val bool) error { - b.lock.Lock() + if _, ok := b.recvAux[senderID]; ok { + // Only a single aux can be received from a peer. + return fmt.Errorf("aux already received, recvNode=%v, epoch=%v, aux=%+v, new %v->%v", b.ID, b.epoch, b.recvAux, senderID, val) + } b.recvAux[senderID] = val - b.lock.Unlock() - b.tryOutputAgreement() + return b.tryOutputAgreement() +} + +func (b *BBA) handleDoneRequest(senderID uint64, doneInEpoch uint32) error { + b.recvDone[senderID] = doneInEpoch + if b.canMarkDoneNow() { + b.done = true + } return nil } // tryOutputAgreement waits until at least (N - f) output messages received, // once the (N - f) messages are received, make a common coin and uses it to // compute the next decision estimate and output the optional decision value. -func (b *BBA) tryOutputAgreement() { +func (b *BBA) tryOutputAgreement() error { if len(b.binValues) == 0 { - return + return nil } // Wait longer till eventually receive (N - F) aux messages. - lenOutputs, values := b.countOutputs() + lenOutputs, values := b.countGoodAux() if lenOutputs < b.N-b.F { - return + return nil } - // TODO: implement a real common coin algorithm. - coin := b.epoch%2 == 0 + coin := b.commonCoin.FlipCoin(b.epoch) // Continue the BBA until both: // - a value b is output in some epoch r // - the value (coin r) = b for some round r' > r - if b.done || b.decision != nil && b.decision.(bool) == coin { + if b.done || (b.decision != nil && b.decision.(bool) == coin) { b.done = true - return + return nil } log.Debugf( @@ -304,30 +362,46 @@ func (b *BBA) tryOutputAgreement() { // Start the next epoch. b.advanceEpoch() - if len(values) != 1 { - b.estimated = coin - } else { + if len(values) == 1 { b.estimated = values[0] - // Output may be set only once. - if b.decision == nil && values[0] == coin { + if b.decision == nil && values[0] == coin { // Output may be set only once. b.output = values[0] b.decision = values[0] log.Debugf("id (%d) outputed a decision (%v) after (%d) msgs", b.ID, values[0], b.msgCount) b.msgCount = 0 + b.sendDone() } + } else { + b.estimated = coin + } + if err := b.sendBval(b.estimated.(bool)); err != nil { + return err } - estimated := b.estimated.(bool) - b.sentBvals = append(b.sentBvals, estimated) - b.addMessage(NewAgreementMessage(int(b.epoch), &BvalRequest{estimated})) - // handle the delayed messages. - for _, que := range b.delayedMessages { - if err := b.handleMessage(que.sid, que.msg); err != nil { - // TODO: Handle this error properly. - log.Warn(err) + // Handle the delayed messages. They can be re-added to the delayed list, + // if their epoch is still in the future (in the handleMessage function). + deliveryCandidates := b.delayedMessages + b.delayedMessages = []*delayedMessage{} + for _, delayed := range deliveryCandidates { + if err := b.handleMessage(delayed.sid, delayed.msg); err != nil { + return err } } - b.delayedMessages = []delayedMessage{} + return nil +} + +func (b *BBA) sendBval(val bool) error { + b.sentBvals = append(b.sentBvals, val) + b.addMessage(NewAgreementMessage(int(b.epoch), &BvalRequest{val})) + return b.handleBvalRequest(b.ID, val) +} + +func (b *BBA) sendDone() error { + if _, ok := b.recvDone[b.ID]; ok { + return nil + } + b.addMessage(NewAgreementMessage(int(b.epoch), &DoneRequest{})) + return b.handleDoneRequest(b.ID, b.epoch) } // advanceEpoch will reset all the values that are bound to an epoch and increments @@ -336,37 +410,57 @@ func (b *BBA) advanceEpoch() { b.binValues = []bool{} b.sentBvals = []bool{} b.recvAux = make(map[uint64]bool) - b.recvBval = make(map[uint64]bool) + b.recvBvalT = make(map[uint64]bool) + b.recvBvalF = make(map[uint64]bool) b.epoch++ } -// countOutputs returns the number of received (aux) messages, the corresponding +// countGoodAux returns the number of received (aux) messages, the corresponding // values that where also in our inputs. -func (b *BBA) countOutputs() (int, []bool) { - m := map[bool]int{} - for i, val := range b.recvAux { - m[val] = int(i) - } - vals := []bool{} - for _, val := range b.binValues { - if _, ok := m[val]; ok { - vals = append(vals, val) +func (b *BBA) countGoodAux() (int, []bool) { + // Collect aux messages, that were received with values also present in binValues. + goodAux := map[uint64]bool{} + for i := range b.recvAux { + for _, binVal := range b.binValues { + if b.recvAux[i] == binVal { + goodAux[i] = binVal + } } } - return len(b.recvAux), vals + // Take only the values present in the goodAux + values := []bool{} + for _, auxVal := range goodAux { + found := false + for _, val := range values { + if auxVal == val { + found = true + break + } + } + if !found { + values = append(values, auxVal) + } + } + return len(goodAux), values } -// countBvals counts all the received Bval inputs matching b. -func (b *BBA) countBvals(ok bool) int { - b.lock.RLock() - defer b.lock.RUnlock() - n := 0 - for _, val := range b.recvBval { - if val == ok { - n++ - } +// addRecvBval marks the specified BVAL_r(val) as received. +// The same node can send multiple values, we track them separatelly. +func (b *BBA) addRecvBval(senderID uint64, val bool) { + if val { + b.recvBvalT[senderID] = val + } else { + b.recvBvalF[senderID] = val + } +} + +// countRecvBvals counts all the received Bval inputs matching b. +func (b *BBA) countRecvBvals(val bool) int { + if val { + return len(b.recvBvalT) + } else { + return len(b.recvBvalF) } - return n } // hasSentBval return true if we already sent out the given value. @@ -378,3 +472,29 @@ func (b *BBA) hasSentBval(val bool) bool { } return false } + +func (b *BBA) addBinValue(val bool) { + for _, bv := range b.binValues { + if bv == val { + return + } + } + b.binValues = append(b.binValues, val) +} + +// If others (more than F) have decided in previous epochs, then we are +// among the others, who decided in a subsequent round, therefore we don't +// need to wait for more epochs to close the process. +func (b *BBA) canMarkDoneNow() bool { + if _, ok := b.recvDone[b.ID]; !ok { + // We have not decided yet, can't close the process. + return false + } + count := 0 + for _, e := range b.recvDone { + if e < b.recvDone[b.ID] { + count++ + } + } + return count > b.F +} diff --git a/bba_test.go b/bba_test.go index 9f6cb59..7e7902d 100644 --- a/bba_test.go +++ b/bba_test.go @@ -1,16 +1,19 @@ package hbbft import ( + "fmt" + "math/rand" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Testing BBA should cover all of the following specifications. // // 1. If a correct node outputs the value (b), then every good node outputs (b). // 2. If all good nodes receive input, then every good node outputs a value. -// 3. If any good node ouputs value (b), then at least one good ndoe receives (b) +// 3. If any good node outputs value (b), then at least one good node receives (b) // as input. // func TestAllNodesFaultyAgreement(t *testing.T) { @@ -44,7 +47,7 @@ func TestBBAStepByStep(t *testing.T) { assert.Nil(t, bba.InputValue(true)) assert.Equal(t, 1, len(bba.sentBvals)) assert.True(t, bba.sentBvals[0]) - assert.True(t, bba.recvBval[0]) // we are id (0) + assert.True(t, bba.recvBvalT[0]) // we are id (0) msgs := bba.Messages() assert.Equal(t, 1, len(msgs)) assert.IsType(t, &BvalRequest{}, msgs[0].Message) @@ -52,13 +55,13 @@ func TestBBAStepByStep(t *testing.T) { // Sent input from node 1 bba.handleBvalRequest(uint64(1), true) - assert.True(t, bba.recvBval[1]) + assert.True(t, bba.recvBvalT[1]) // Sent input from node 2 // The algorithm decribes that after receiving (N - f) bval messages we // broadcast AUX(b) bba.handleBvalRequest(uint64(2), true) - assert.True(t, bba.recvBval[2]) + assert.True(t, bba.recvBvalT[2]) msg := bba.Messages() assert.Equal(t, 1, len(msg)) assert.IsType(t, &AuxRequest{}, msg[0].Message) @@ -82,7 +85,8 @@ func TestNewBBA(t *testing.T) { cfg := Config{N: 4} bba := NewBBA(cfg) assert.Equal(t, 0, len(bba.binValues)) - assert.Equal(t, 0, len(bba.recvBval)) + assert.Equal(t, 0, len(bba.recvBvalT)) + assert.Equal(t, 0, len(bba.recvBvalF)) assert.Equal(t, 0, len(bba.recvAux)) assert.Equal(t, 0, len(bba.sentBvals)) assert.Equal(t, uint32(0), bba.epoch) @@ -179,7 +183,6 @@ func makeBBAInstances(n int) []*BBA { bbas := make([]*BBA, n) for i := 0; i < n; i++ { bbas[i] = NewBBA(Config{N: n, ID: uint64(i)}) - go bbas[i].run() } return bbas } @@ -189,3 +192,81 @@ type testAgreementMessage struct { to uint64 msg *AgreementMessage } + +func appendTestAgreementMessages(msgs []*AgreementMessage, senderID uint64, nodes []uint64, buf []*testAgreementMessage) []*testAgreementMessage { + output := buf[:] + for n := range nodes { + if nodes[n] != senderID { + for m := range msgs { + output = append(output, &testAgreementMessage{from: senderID, to: nodes[n], msg: msgs[m]}) + } + } + } + return output +} + +// This test should be run repeatedly for some time. +func TestBBARandomized(t *testing.T) { + if err := testBBARandomized(t); err != nil { + t.Fatalf("Failed, reason=%+v", err) + } +} +func testBBARandomized(t *testing.T) error { + var err error + var N, T = 7, 5 + + msgs := make([]*testAgreementMessage, 0) + nodes := make([]uint64, N) + for n := range nodes { + nodes[n] = uint64(n) + } + + cfg := make([]Config, N) + for i := range cfg { + cfg[i] = Config{ + N: N, + F: N - T, + ID: uint64(i), + Nodes: nodes, + BatchSize: 21254, // Should be unused. + } + } + + inputs := make([]bool, N) + for i := range inputs { + inputs[i] = rand.Int()%2 == 0 + } + + bba := make([]*BBA, N) + for i := range bba { + bba[i] = NewBBA(cfg[i]) + if err = bba[i].InputValue(inputs[i]); err != nil { + return fmt.Errorf("Failed to process BBA.InputValue: %v", err) + } + msgs = appendTestAgreementMessages(bba[i].Messages(), nodes[i], nodes, msgs) + } + + for len(msgs) != 0 { + m := rand.Intn(len(msgs)) + msg := msgs[m] + msgTo := msg.to + if err = bba[msgTo].HandleMessage(msg.from, msg.msg); err != nil { + return fmt.Errorf("Failed to BBA.HandleMessage: %v", err) + } + + // Remove the message from the buffer and add the new messages. + msgs[m] = msgs[len(msgs)-1] + msgs = msgs[:len(msgs)-1] + msgs = appendTestAgreementMessages(bba[msgTo].Messages(), nodes[msgTo], nodes, msgs) + } + + out0 := bba[0].Output().(bool) + for i := range bba { + require.True(t, bba[i].done) + if i != 0 { + require.Equal(t, bba[i].Output(), out0) + } + bba[i].Stop() + } + return nil +} diff --git a/cc.go b/cc.go new file mode 100644 index 0000000..5b5de92 --- /dev/null +++ b/cc.go @@ -0,0 +1,22 @@ +package hbbft + +// CommonCoin is an interface to be provided by the real CC implementation. +type CommonCoin interface { + FlipCoin(epoch uint32) bool +} + +// fakeCoin is a trivial incorrect implementation of the common coin interface. +// It is used here just to avoid dependencies to particular crypto libraries. +type fakeCoin struct{} + +func NewFakeCoin() CommonCoin { + return &fakeCoin{} +} + +// Let's return the same coin for 2 times in a row. +// That can lead to faster consensus, because the next round is successful +// when the coin is the same as the first one with some decisions. +// It is beneficial to start such a sequence with [true, true, ...]. +func (fc *fakeCoin) FlipCoin(epoch uint32) bool { + return (epoch/2)%2 == 0 +} diff --git a/honey_badger.go b/honey_badger.go index cdf999f..6a1d9f8 100644 --- a/honey_badger.go +++ b/honey_badger.go @@ -28,8 +28,10 @@ type Config struct { ID uint64 // Identifiers of the participating nodes. Nodes []uint64 - // Maximum number of transactions that will be comitted in one epoch. + // Maximum number of transactions that will be committed in one epoch. BatchSize int + // Common Coin to use. + CommonCoin CommonCoin } // HoneyBadger represents the top-level protocol of the hbbft consensus. @@ -214,7 +216,7 @@ func (hb *HoneyBadger) removeOldEpochs(epoch uint64) { continue } for _, t := range acs.bbaInstances { - t.stop() + t.Stop() } for _, t := range acs.rbcInstances { t.stop() diff --git a/rbc.go b/rbc.go index 7bf705d..914a4f1 100644 --- a/rbc.go +++ b/rbc.go @@ -20,9 +20,8 @@ type BroadcastMessage struct { // ProofRequest holds the RootHash along with the Shard of the erasure encoded // payload. type ProofRequest struct { - RootHash []byte - // Proof[0] will containt the actual data. - Proof [][]byte + RootHash []byte + Proof [][]byte // Proof[0] will containt the actual data. Index, Leaves int } @@ -52,9 +51,9 @@ type RBC struct { // The reedsolomon encoder to encode the proposed value into shards. enc reedsolomon.Encoder // recvReadys is a mapping between the sender and the root hash that was - // inluded in the ReadyRequest. + // included in the ReadyRequest. recvReadys map[uint64][]byte - // revcEchos is a mapping between the sender and the EchoRequest. + // recvEchos is a mapping between the sender and the EchoRequest. recvEchos map[uint64]*EchoRequest // Number of the parity and data shards that will be used for erasure encoding // the given value. @@ -137,8 +136,8 @@ func (r *RBC) InputValue(data []byte) ([]*BroadcastMessage, error) { } // HandleMessage will process the given rpc message and will return a possible -// outcome. The caller is resposible to make sure only RPC messages are passed -// that are elligible for the RBC protocol. +// outcome. The caller is responsible to make sure only RPC messages are passed +// that are eligible for the RBC protocol. func (r *RBC) HandleMessage(senderID uint64, msg *BroadcastMessage) error { t := rbcMessageTuple{ senderID: senderID, diff --git a/rbc_test.go b/rbc_test.go index eff6df0..2497b31 100644 --- a/rbc_test.go +++ b/rbc_test.go @@ -1,13 +1,17 @@ package hbbft import ( + "bytes" + "fmt" "log" + "math/rand" "sync" "testing" "github.com/klauspost/reedsolomon" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Test RBC where 1 node will not provide its value. We use 4 nodes that will @@ -155,6 +159,99 @@ func TestMakeProofRequests(t *testing.T) { } } +// This test should be run repeatedly for some time. +func TestRBCRandomized(t *testing.T) { + if err := testRBCRandomized(t); err != nil { + t.Fatalf("Failed, reason=%+v", err) + } +} +func testRBCRandomized(t *testing.T) error { + var err error + var N, T = 7, 5 + + msgs := make([]*testRBCMsg, 0) + nodes := make([]uint64, N) + for n := range nodes { + nodes[n] = uint64(n) + } + + cfg := make([]Config, N) + for i := range cfg { + cfg[i] = Config{ + N: N, + F: N - T, + ID: uint64(i), + Nodes: nodes, + BatchSize: 21254, // Should be unused. + } + } + + var input [10]byte + rand.Read(input[:]) + + rbc := make([]*RBC, N) + proposerID := uint64(rand.Intn(N)) + for i := range rbc { + rbc[i] = NewRBC(cfg[i], proposerID) + } + var inMsgs []*BroadcastMessage + if inMsgs, err = rbc[proposerID].InputValue(input[:]); err != nil { + return fmt.Errorf("Failed to process RBC.InputValue: %v", err) + } + msgs = appendTestBBAMsgsExplicit(inMsgs, proposerID, nodes, msgs) + + for len(msgs) != 0 { + m := rand.Intn(len(msgs)) + msg := msgs[m] + msgTo := msg.to + if err = rbc[msgTo].HandleMessage(msg.from, msg.msg); err != nil { + return fmt.Errorf("Failed to RBC.HandleMessage: %v", err) + } + + // Remove the message from the buffer and add the new messages. + msgs[m] = msgs[len(msgs)-1] + msgs = msgs[:len(msgs)-1] + msgs = appendTestBBAMsgsBroadcast(rbc[msgTo].Messages(), msgTo, nodes, msgs) + } + + for i := range rbc { + out := rbc[i].Output() + require.NotNil(t, out) + require.Equal(t, bytes.Compare(input[:], out[:len(input)]), 0) // RBC adds zeros to the end. + rbc[i].stop() + } + return nil +} + +type testRBCMsg struct { + from uint64 + to uint64 + msg *BroadcastMessage +} + +func appendTestBBAMsgsExplicit(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { + output := buf[:] + msgPos := 0 + for n := range nodes { + if nodes[n] != senderID { + output = append(output, &testRBCMsg{from: senderID, to: nodes[n], msg: msgs[msgPos]}) + msgPos++ + } + } + return output +} +func appendTestBBAMsgsBroadcast(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { + output := buf[:] + for n := range nodes { + if nodes[n] != senderID { + for m := range msgs { + output = append(output, &testRBCMsg{from: senderID, to: nodes[n], msg: msgs[m]}) + } + } + } + return output +} + type bcResult struct { nodeID uint64 value []byte From 666ab58831845ffd4590432e4749e54d8c5481c9 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Mon, 17 May 2021 18:53:09 +0300 Subject: [PATCH 04/10] ACS needs to wait for RBC as well. --- acs.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/acs.go b/acs.go index 4414928..6fe4089 100644 --- a/acs.go +++ b/acs.go @@ -157,12 +157,17 @@ func (a *ACS) Output() map[uint64][]byte { } // Done returns true whether ACS has completed its agreements and cleared its -// messageQue. +// messageQue. It is possible that the BBA part will decide before the RBC. func (a *ACS) Done() bool { agreementsDone := true - for _, bba := range a.bbaInstances { + for i, bba := range a.bbaInstances { if !bba.Done() { agreementsDone = false + break + } + if a.rbcResults[i] == nil { + agreementsDone = false + break } } return agreementsDone && a.messageQue.len() == 0 From c897be85c569f227ec47786e65b1493535231607 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Mon, 17 May 2021 23:09:04 +0300 Subject: [PATCH 05/10] Make ACS::Stop() independent of the HB part. --- acs.go | 8 +++++++- acs_test.go | 2 +- honey_badger.go | 8 +------- rbc.go | 2 +- rbc_test.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/acs.go b/acs.go index 6fe4089..9e14892 100644 --- a/acs.go +++ b/acs.go @@ -205,8 +205,14 @@ func (a *ACS) inputValue(data []byte) error { return nil } -func (a *ACS) stop() { +func (a *ACS) Stop() { close(a.closeCh) + for _, rbc := range a.rbcInstances { + rbc.Stop() + } + for _, bba := range a.bbaInstances { + bba.Stop() + } } func (a *ACS) run() { diff --git a/acs_test.go b/acs_test.go index ca40bf1..f107ec0 100644 --- a/acs_test.go +++ b/acs_test.go @@ -179,7 +179,7 @@ func testACSRandomized(t *testing.T) error { for i := range out0 { require.Equal(t, bytes.Compare(out0[i], outA[i]), 0) } - acs[a].stop() + acs[a].Stop() } return nil } diff --git a/honey_badger.go b/honey_badger.go index 6a1d9f8..e82c60e 100644 --- a/honey_badger.go +++ b/honey_badger.go @@ -215,13 +215,7 @@ func (hb *HoneyBadger) removeOldEpochs(epoch uint64) { if i >= hb.epoch-1 { continue } - for _, t := range acs.bbaInstances { - t.Stop() - } - for _, t := range acs.rbcInstances { - t.stop() - } - acs.stop() + acs.Stop() delete(hb.acsInstances, i) } } diff --git a/rbc.go b/rbc.go index 914a4f1..61f3df3 100644 --- a/rbc.go +++ b/rbc.go @@ -148,7 +148,7 @@ func (r *RBC) HandleMessage(senderID uint64, msg *BroadcastMessage) error { return <-t.err } -func (r *RBC) stop() { +func (r *RBC) Stop() { close(r.closeCh) } diff --git a/rbc_test.go b/rbc_test.go index 2497b31..4dffacb 100644 --- a/rbc_test.go +++ b/rbc_test.go @@ -218,7 +218,7 @@ func testRBCRandomized(t *testing.T) error { out := rbc[i].Output() require.NotNil(t, out) require.Equal(t, bytes.Compare(input[:], out[:len(input)]), 0) // RBC adds zeros to the end. - rbc[i].stop() + rbc[i].Stop() } return nil } From 258e54d010fbf15c2a117f44acca6a204a8cf238 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Mon, 12 Jul 2021 19:49:15 +0300 Subject: [PATCH 06/10] CC should be for each BBA separately. --- acs.go | 2 +- bba.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++----- bba_test.go | 10 +++---- cc.go | 23 +++++++++++----- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/acs.go b/acs.go index 9e14892..b63c74e 100644 --- a/acs.go +++ b/acs.go @@ -95,7 +95,7 @@ func NewACS(cfg Config) *ACS { // Create all the instances for the participating nodes for _, id := range cfg.Nodes { acs.rbcInstances[id] = NewRBC(cfg, id) - acs.bbaInstances[id] = NewBBA(cfg) + acs.bbaInstances[id] = NewBBA(cfg, id) } go acs.run() return acs diff --git a/bba.go b/bba.go index 016fbb0..73d55ed 100644 --- a/bba.go +++ b/bba.go @@ -33,6 +33,13 @@ type AuxRequest struct { Value bool } +// CCRequest is not part of the HB protocol. We use it +// to interact with the CC asynchronously, to avoid blocking +// and to have a uniform interface from the user of the BBA. +type CCRequest struct { + Payload interface{} +} + // DoneRequest is not part of the HB protocol, but we use // it here to terminate the protocol in a graceful way. type DoneRequest struct{} @@ -43,7 +50,9 @@ type BBA struct { Config // Common Coin implementation to use. - commonCoin CommonCoin + commonCoin CommonCoin + commonCoinAsked bool + commonCoinValue *bool // Current epoch. epoch uint32 @@ -95,7 +104,7 @@ type BBA struct { } // NewBBA returns a new instance of the Binary Byzantine Agreement. -func NewBBA(cfg Config) *BBA { +func NewBBA(cfg Config, nodeID uint64) *BBA { if cfg.F == 0 { cfg.F = (cfg.N - 1) / 3 } @@ -107,7 +116,9 @@ func NewBBA(cfg Config) *BBA { } bba := &BBA{ Config: cfg, - commonCoin: cc, + commonCoin: cc.ForNodeID(nodeID), + commonCoinAsked: false, + commonCoinValue: nil, recvBvalT: make(map[uint64]bool), recvBvalF: make(map[uint64]bool), recvAux: make(map[uint64]bool), @@ -279,6 +290,8 @@ func (b *BBA) handleMessage(senderID uint64, msg *AgreementMessage) error { return b.handleBvalRequest(senderID, t.Value) case *AuxRequest: return b.handleAuxRequest(senderID, t.Value) + case *CCRequest: + return b.handleCCRequest(t.Payload) default: return fmt.Errorf("unknown BBA message received: %v", t) } @@ -287,6 +300,9 @@ func (b *BBA) handleMessage(senderID uint64, msg *AgreementMessage) error { // handleBvalRequest processes the received binary value and fills up the // message que if there are any messages that need to be broadcasted. func (b *BBA) handleBvalRequest(senderID uint64, val bool) error { + if b.commonCoinAsked { + return nil + } b.addRecvBval(senderID, val) lenBval := b.countRecvBvals(val) @@ -300,7 +316,9 @@ func (b *BBA) handleBvalRequest(senderID uint64, val bool) error { // may only occur once per epoch. if wasEmptyBinValues { b.addMessage(NewAgreementMessage(int(b.epoch), &AuxRequest{val})) - b.handleAuxRequest(b.ID, val) + if err := b.handleAuxRequest(b.ID, val); err != nil { + return err + } } } // When receiving input(b) messages from f + 1 nodes, if inputs(b) is not @@ -315,6 +333,9 @@ func (b *BBA) handleBvalRequest(senderID uint64, val bool) error { } func (b *BBA) handleAuxRequest(senderID uint64, val bool) error { + if b.commonCoinAsked { + return nil + } if _, ok := b.recvAux[senderID]; ok { // Only a single aux can be received from a peer. return fmt.Errorf("aux already received, recvNode=%v, epoch=%v, aux=%+v, new %v->%v", b.ID, b.epoch, b.recvAux, senderID, val) @@ -323,6 +344,20 @@ func (b *BBA) handleAuxRequest(senderID uint64, val bool) error { return b.tryOutputAgreement() } +func (b *BBA) handleCCRequest(payload interface{}) error { + coin, outPayloads, err := b.commonCoin.HandleRequest(b.epoch, payload) + if err != nil { + return err + } + if err := b.sendCC(outPayloads); err != nil { + return err + } + if b.commonCoinValue == nil { + b.commonCoinValue = coin + } + return b.tryOutputAgreement() +} + func (b *BBA) handleDoneRequest(senderID uint64, doneInEpoch uint32) error { b.recvDone[senderID] = doneInEpoch if b.canMarkDoneNow() { @@ -344,7 +379,24 @@ func (b *BBA) tryOutputAgreement() error { return nil } - coin := b.commonCoin.FlipCoin(b.epoch) + if !b.commonCoinAsked { + maybeCoin, ccPayloads, err := b.commonCoin.StartCoinFlip(b.epoch) + if err != nil { + return err + } + if err := b.sendCC(ccPayloads); err != nil { + return err + } + b.commonCoinAsked = true + if b.commonCoinValue == nil { + b.commonCoinValue = maybeCoin + } + } + if b.commonCoinValue == nil { + // Still waiting for the common coin. + return nil + } + coin := *b.commonCoinValue // Continue the BBA until both: // - a value b is output in some epoch r @@ -369,7 +421,9 @@ func (b *BBA) tryOutputAgreement() error { b.decision = values[0] log.Debugf("id (%d) outputed a decision (%v) after (%d) msgs", b.ID, values[0], b.msgCount) b.msgCount = 0 - b.sendDone() + if err := b.sendDone(); err != nil { + return err + } } } else { b.estimated = coin @@ -396,6 +450,13 @@ func (b *BBA) sendBval(val bool) error { return b.handleBvalRequest(b.ID, val) } +func (b *BBA) sendCC(outPayloads []interface{}) error { + for _, outPayload := range outPayloads { + b.addMessage(NewAgreementMessage(int(b.epoch), &CCRequest{Payload: outPayload})) + } + return nil +} + func (b *BBA) sendDone() error { if _, ok := b.recvDone[b.ID]; ok { return nil @@ -412,6 +473,8 @@ func (b *BBA) advanceEpoch() { b.recvAux = make(map[uint64]bool) b.recvBvalT = make(map[uint64]bool) b.recvBvalF = make(map[uint64]bool) + b.commonCoinAsked = false + b.commonCoinValue = nil b.epoch++ } diff --git a/bba_test.go b/bba_test.go index 7e7902d..9e9b580 100644 --- a/bba_test.go +++ b/bba_test.go @@ -41,7 +41,7 @@ func TestAgreementGoodNodes(t *testing.T) { } func TestBBAStepByStep(t *testing.T) { - bba := NewBBA(Config{N: 4, ID: 0}) + bba := NewBBA(Config{N: 4, ID: 0}, 0) // Set our input value. assert.Nil(t, bba.InputValue(true)) @@ -83,7 +83,7 @@ func TestBBAStepByStep(t *testing.T) { func TestNewBBA(t *testing.T) { cfg := Config{N: 4} - bba := NewBBA(cfg) + bba := NewBBA(cfg, 0) assert.Equal(t, 0, len(bba.binValues)) assert.Equal(t, 0, len(bba.recvBvalT)) assert.Equal(t, 0, len(bba.recvBvalF)) @@ -96,7 +96,7 @@ func TestNewBBA(t *testing.T) { func TestAdvanceEpochInBBA(t *testing.T) { cfg := Config{N: 4} - bba := NewBBA(cfg) + bba := NewBBA(cfg, 0) bba.epoch = 8 bba.binValues = []bool{false, true, true} bba.sentBvals = []bool{false, true} @@ -182,7 +182,7 @@ func excludeID(ids []uint64, id uint64) []uint64 { func makeBBAInstances(n int) []*BBA { bbas := make([]*BBA, n) for i := 0; i < n; i++ { - bbas[i] = NewBBA(Config{N: n, ID: uint64(i)}) + bbas[i] = NewBBA(Config{N: n, ID: uint64(i)}, uint64(i)) } return bbas } @@ -239,7 +239,7 @@ func testBBARandomized(t *testing.T) error { bba := make([]*BBA, N) for i := range bba { - bba[i] = NewBBA(cfg[i]) + bba[i] = NewBBA(cfg[i], nodes[i]) if err = bba[i].InputValue(inputs[i]); err != nil { return fmt.Errorf("Failed to process BBA.InputValue: %v", err) } diff --git a/cc.go b/cc.go index 5b5de92..250d46a 100644 --- a/cc.go +++ b/cc.go @@ -2,21 +2,30 @@ package hbbft // CommonCoin is an interface to be provided by the real CC implementation. type CommonCoin interface { - FlipCoin(epoch uint32) bool + ForNodeID(nodeID uint64) CommonCoin + HandleRequest(epoch uint32, payload interface{}) (*bool, []interface{}, error) + StartCoinFlip(epoch uint32) (*bool, []interface{}, error) } // fakeCoin is a trivial incorrect implementation of the common coin interface. // It is used here just to avoid dependencies to particular crypto libraries. type fakeCoin struct{} -func NewFakeCoin() CommonCoin { - return &fakeCoin{} -} - // Let's return the same coin for 2 times in a row. // That can lead to faster consensus, because the next round is successful // when the coin is the same as the first one with some decisions. // It is beneficial to start such a sequence with [true, true, ...]. -func (fc *fakeCoin) FlipCoin(epoch uint32) bool { - return (epoch/2)%2 == 0 +func NewFakeCoin() CommonCoin { + return &fakeCoin{} +} + +func (fc *fakeCoin) ForNodeID(nodeID uint64) CommonCoin { + return &fakeCoin{} +} +func (fc *fakeCoin) HandleRequest(epoch uint32, payload interface{}) (*bool, []interface{}, error) { + panic("HandleRequest is not used in this implementation") +} +func (fc *fakeCoin) StartCoinFlip(epoch uint32) (*bool, []interface{}, error) { + coin := (epoch/2)%2 == 0 + return &coin, []interface{}{}, nil } From cf21a85e1c0d905a252b5cb80a0c46fdf0714dfa Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Thu, 15 Jul 2021 14:48:43 +0300 Subject: [PATCH 07/10] RBC fixes. Was able to mark as decided, while not setting the output. --- acs.go | 9 ++++++--- rbc.go | 24 ++++++++++++++++-------- rbc_test.go | 22 +++++++++++----------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/acs.go b/acs.go index b63c74e..554a264 100644 --- a/acs.go +++ b/acs.go @@ -64,9 +64,7 @@ type ( } acsInputResponse struct { - rbcMessages []*BroadcastMessage - acsMessages []*ACSMessage - err error + err error } acsInputTuple struct { @@ -101,6 +99,11 @@ func NewACS(cfg Config) *ACS { return acs } +// DebugInfo returns internal state. Should be used for debugging only. +func (a *ACS) DebugInfo() (map[uint64]*RBC, map[uint64]*BBA, map[uint64][]byte, map[uint64]bool, []MessageTuple) { // TODO: ... + return a.rbcInstances, a.bbaInstances, a.rbcResults, a.bbaResults, a.messageQue.que +} + // Messages returns all the internal messages from the message que. Note that // the que will be empty after invoking this method. func (a *ACS) Messages() []MessageTuple { diff --git a/rbc.go b/rbc.go index 61f3df3..a85a835 100644 --- a/rbc.go +++ b/rbc.go @@ -258,8 +258,8 @@ func (r *RBC) handleEchoRequest(senderID uint64, req *EchoRequest) error { return fmt.Errorf( "received invalid proof from (%d) my id (%d)", senderID, r.ID) } - r.recvEchos[senderID] = req + if r.readySent || r.countEchos(req.RootHash) < r.N-r.F { return r.tryDecodeValue(req.RootHash) } @@ -282,10 +282,11 @@ func (r *RBC) handleReadyRequest(senderID uint64, req *ReadyRequest) error { } r.recvReadys[senderID] = req.RootHash - if r.countReadys(req.RootHash) == r.F+1 && !r.readySent { + if !r.readySent && r.countReadys(req.RootHash) == r.F+1 { r.readySent = true ready := &ReadyRequest{req.RootHash} r.messages = append(r.messages, &BroadcastMessage{ready}) + return r.handleReadyRequest(r.ID, ready) } return r.tryDecodeValue(req.RootHash) } @@ -298,7 +299,6 @@ func (r *RBC) tryDecodeValue(hash []byte) error { } // At this point we can decode the shards. First we create a new slice of // only sortable proof values. - r.outputDecoded = true var prfs proofs for _, echo := range r.recvEchos { prfs = append(prfs, echo.ProofRequest) @@ -311,13 +311,17 @@ func (r *RBC) tryDecodeValue(hash []byte) error { shards[p.Index] = p.Proof[0] } if err := r.enc.Reconstruct(shards); err != nil { - return nil + if err == reedsolomon.ErrTooFewShards { + return nil + } + return err } var value []byte for _, data := range shards[:r.numDataShards] { value = append(value, data...) } r.output = value + r.outputDecoded = true return nil } @@ -325,7 +329,7 @@ func (r *RBC) tryDecodeValue(hash []byte) error { func (r *RBC) countEchos(hash []byte) int { n := 0 for _, e := range r.recvEchos { - if bytes.Compare(hash, e.RootHash) == 0 { + if bytes.Equal(hash, e.RootHash) { n++ } } @@ -336,7 +340,7 @@ func (r *RBC) countEchos(hash []byte) int { func (r *RBC) countReadys(hash []byte) int { n := 0 for _, h := range r.recvReadys { - if bytes.Compare(hash, h) == 0 { + if bytes.Equal(hash, h) { n++ } } @@ -349,7 +353,9 @@ func makeProofRequests(shards [][]byte) ([]*ProofRequest, error) { reqs := make([]*ProofRequest, len(shards)) for i := 0; i < len(reqs); i++ { tree := merkletree.New(sha256.New()) - tree.SetIndex(uint64(i)) + if err := tree.SetIndex(uint64(i)); err != nil { + return nil, err + } for i := 0; i < len(shards); i++ { tree.Push(shards[i]) } @@ -370,7 +376,9 @@ func makeBroadcastMessages(shards [][]byte) ([]*BroadcastMessage, error) { msgs := make([]*BroadcastMessage, len(shards)) for i := 0; i < len(msgs); i++ { tree := merkletree.New(sha256.New()) - tree.SetIndex(uint64(i)) + if err := tree.SetIndex(uint64(i)); err != nil { + return nil, err + } for i := 0; i < len(shards); i++ { tree.Push(shards[i]) } diff --git a/rbc_test.go b/rbc_test.go index 4dffacb..1eeb948 100644 --- a/rbc_test.go +++ b/rbc_test.go @@ -118,7 +118,7 @@ func TestRBCOutputIsNilAfterConsuming(t *testing.T) { func TestRBCMessagesIsEmptyAfterConsuming(t *testing.T) { rbc := NewRBC(Config{N: 4}, 0) - rbc.messages = []*BroadcastMessage{&BroadcastMessage{}} + rbc.messages = []*BroadcastMessage{{}} assert.Equal(t, 1, len(rbc.Messages())) assert.Equal(t, 0, len(rbc.Messages())) } @@ -186,7 +186,7 @@ func testRBCRandomized(t *testing.T) error { } } - var input [10]byte + var input [10000]byte rand.Read(input[:]) rbc := make([]*RBC, N) @@ -198,8 +198,7 @@ func testRBCRandomized(t *testing.T) error { if inMsgs, err = rbc[proposerID].InputValue(input[:]); err != nil { return fmt.Errorf("Failed to process RBC.InputValue: %v", err) } - msgs = appendTestBBAMsgsExplicit(inMsgs, proposerID, nodes, msgs) - + msgs = appendTestRBCMsgsExplicit(inMsgs, proposerID, nodes, msgs) for len(msgs) != 0 { m := rand.Intn(len(msgs)) msg := msgs[m] @@ -211,7 +210,7 @@ func testRBCRandomized(t *testing.T) error { // Remove the message from the buffer and add the new messages. msgs[m] = msgs[len(msgs)-1] msgs = msgs[:len(msgs)-1] - msgs = appendTestBBAMsgsBroadcast(rbc[msgTo].Messages(), msgTo, nodes, msgs) + msgs = appendTestRBCMsgsBroadcast(rbc[msgTo].Messages(), msgTo, nodes, msgs) } for i := range rbc { @@ -229,7 +228,7 @@ type testRBCMsg struct { msg *BroadcastMessage } -func appendTestBBAMsgsExplicit(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { +func appendTestRBCMsgsExplicit(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { output := buf[:] msgPos := 0 for n := range nodes { @@ -240,7 +239,7 @@ func appendTestBBAMsgsExplicit(msgs []*BroadcastMessage, senderID uint64, nodes } return output } -func appendTestBBAMsgsBroadcast(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { +func appendTestRBCMsgsBroadcast(msgs []*BroadcastMessage, senderID uint64, nodes []uint64, buf []*testRBCMsg) []*testRBCMsg { output := buf[:] for n := range nodes { if nodes[n] != senderID { @@ -285,11 +284,13 @@ func (e *testRBCEngine) run() { continue } for _, msg := range e.rbc.Messages() { - e.transport.Broadcast(e.rbc.ID, msg) + if err := e.transport.Broadcast(e.rbc.ID, msg); err != nil { + panic(err) + } } if output := e.rbc.Output(); output != nil { // Faulty node will refuse to send its produced output, causing - // potential disturb of conensus liveness. + // potential disturb of consensus liveness. if e.faulty { continue } @@ -311,8 +312,7 @@ func (e *testRBCEngine) inputValue(data []byte) error { for i := 0; i < len(reqs); i++ { msgs[i] = reqs[i] } - e.transport.SendProofMessages(e.rbc.ID, msgs) - return nil + return e.transport.SendProofMessages(e.rbc.ID, msgs) } func makeRBCNodes(n, pid int, resCh chan bcResult) []*testRBCEngine { From 8601c509bea6f66590086f59f312ccc72bcc6bdc Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 11 Aug 2021 15:58:34 +0300 Subject: [PATCH 08/10] ACS: Done condition fixed. --- acs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acs.go b/acs.go index 554a264..b754c3e 100644 --- a/acs.go +++ b/acs.go @@ -168,7 +168,7 @@ func (a *ACS) Done() bool { agreementsDone = false break } - if a.rbcResults[i] == nil { + if a.bbaResults[i] && a.rbcResults[i] == nil { agreementsDone = false break } From b949585b7515cf3339948ff513ec5dd2bf63e58a Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Tue, 24 Aug 2021 11:34:59 +0300 Subject: [PATCH 09/10] Make it work with 0 Date: Tue, 23 Aug 2022 12:50:40 +0300 Subject: [PATCH 10/10] F=0 is possible configuration use -1 for a default. --- README.md | 8 +++++--- acs.go | 2 +- acs_test.go | 5 +++-- bba.go | 2 +- bba_test.go | 8 ++++---- bench/main.go | 1 + honey_badger_test.go | 2 ++ rbc.go | 2 +- rbc_test.go | 6 ++++-- simulation/main.go | 1 + 10 files changed, 23 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e960ba8..dc657bb 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,18 @@ make test Create a new instance of HoneyBadger. ```golang -// Create a Config struct with your prefered settings. +// Create a Config struct with your preferred settings. cfg := hbbft.Config{ // The number of nodes in the network. N: 4, + // Number of tolerated faulty nodes, use -1 to take max possible. + F: -1, // Identifier of this node. ID: 101, // Identifiers of the participating nodes. Nodes: uint64{67, 1, 99, 101}, - // The prefered batch size. If BatchSize is empty, an ideal batch size will - // be choosen for you. + // The preferred batch size. If BatchSize is empty, an ideal batch size will + // be chosen for you. BatchSize: 100, } diff --git a/acs.go b/acs.go index b754c3e..7af4864 100644 --- a/acs.go +++ b/acs.go @@ -76,7 +76,7 @@ type ( // NewACS returns a new ACS instance configured with the given Config and node // ids. func NewACS(cfg Config) *ACS { - if cfg.F == 0 { + if cfg.F == -1 { cfg.F = (cfg.N - 1) / 3 } acs := &ACS{ diff --git a/acs_test.go b/acs_test.go index f107ec0..6a5dcc2 100644 --- a/acs_test.go +++ b/acs_test.go @@ -82,6 +82,7 @@ func TestNewACS(t *testing.T) { nodes = []uint64{0, 1, 2, 3} acs = NewACS(Config{ N: len(nodes), + F: -1, // Use default. ID: id, Nodes: nodes, }) @@ -101,7 +102,7 @@ func TestNewACS(t *testing.T) { } func TestACSOutputIsNilAfterConsuming(t *testing.T) { - acs := NewACS(Config{N: 4}) + acs := NewACS(Config{N: 4, F: -1}) // Use default for F. output := map[uint64][]byte{ 1: []byte("this is it"), } @@ -200,7 +201,7 @@ func appendTestMsgs(msgs []MessageTuple, senderID uint64, buf []*testMsg) []*tes func makeACSNetwork(n int) []*ACS { network := make([]*ACS, n) for i := 0; i < n; i++ { - network[i] = NewACS(Config{N: n, ID: uint64(i), Nodes: makeids(n)}) + network[i] = NewACS(Config{N: n, F: -1, ID: uint64(i), Nodes: makeids(n)}) // Use default for F. go network[i].run() } return network diff --git a/bba.go b/bba.go index 73d55ed..7a12270 100644 --- a/bba.go +++ b/bba.go @@ -105,7 +105,7 @@ type BBA struct { // NewBBA returns a new instance of the Binary Byzantine Agreement. func NewBBA(cfg Config, nodeID uint64) *BBA { - if cfg.F == 0 { + if cfg.F == -1 { cfg.F = (cfg.N - 1) / 3 } var cc CommonCoin diff --git a/bba_test.go b/bba_test.go index 9e9b580..8347b93 100644 --- a/bba_test.go +++ b/bba_test.go @@ -41,7 +41,7 @@ func TestAgreementGoodNodes(t *testing.T) { } func TestBBAStepByStep(t *testing.T) { - bba := NewBBA(Config{N: 4, ID: 0}, 0) + bba := NewBBA(Config{N: 4, F: -1, ID: 0}, 0) // Set our input value. assert.Nil(t, bba.InputValue(true)) @@ -82,7 +82,7 @@ func TestBBAStepByStep(t *testing.T) { } func TestNewBBA(t *testing.T) { - cfg := Config{N: 4} + cfg := Config{N: 4, F: -1} bba := NewBBA(cfg, 0) assert.Equal(t, 0, len(bba.binValues)) assert.Equal(t, 0, len(bba.recvBvalT)) @@ -95,7 +95,7 @@ func TestNewBBA(t *testing.T) { } func TestAdvanceEpochInBBA(t *testing.T) { - cfg := Config{N: 4} + cfg := Config{N: 4, F: -1} bba := NewBBA(cfg, 0) bba.epoch = 8 bba.binValues = []bool{false, true, true} @@ -182,7 +182,7 @@ func excludeID(ids []uint64, id uint64) []uint64 { func makeBBAInstances(n int) []*BBA { bbas := make([]*BBA, n) for i := 0; i < n; i++ { - bbas[i] = NewBBA(Config{N: n, ID: uint64(i)}, uint64(i)) + bbas[i] = NewBBA(Config{N: n, F: -1, ID: uint64(i)}, uint64(i)) } return bbas } diff --git a/bench/main.go b/bench/main.go index 84bcf27..1337f9a 100644 --- a/bench/main.go +++ b/bench/main.go @@ -70,6 +70,7 @@ func makeNodes(n, ntx, txsize, batchSize int) []*hbbft.HoneyBadger { for i := 0; i < n; i++ { cfg := hbbft.Config{ N: n, + F: -1, ID: uint64(i), Nodes: makeids(n), BatchSize: batchSize, diff --git a/honey_badger_test.go b/honey_badger_test.go index cd6b9a8..2a81c95 100644 --- a/honey_badger_test.go +++ b/honey_badger_test.go @@ -11,6 +11,7 @@ import ( func TestEngineAddTransaction(t *testing.T) { cfg := Config{ N: 6, + F: -1, ID: 0, Nodes: []uint64{0, 1, 2, 3, 4, 5, 6}, } @@ -75,6 +76,7 @@ func makeTestNodes(n int) []*testNode { cfg := Config{ ID: uint64(i), N: len(transports), + F: -1, Nodes: makeids(n), } nodes[i] = newTestNode(NewHoneyBadger(cfg), tr) diff --git a/rbc.go b/rbc.go index d8faa7b..1e9bcc7 100644 --- a/rbc.go +++ b/rbc.go @@ -93,7 +93,7 @@ type ( // NewRBC returns a new instance of the ReliableBroadcast configured // with the given config func NewRBC(cfg Config, proposerID uint64) *RBC { - if cfg.F == 0 { + if cfg.F == -1 { cfg.F = (cfg.N - 1) / 3 } parityShards := 2 * cfg.F diff --git a/rbc_test.go b/rbc_test.go index 1eeb948..1b103ce 100644 --- a/rbc_test.go +++ b/rbc_test.go @@ -77,6 +77,7 @@ func TestRBC4GoodNodes(t *testing.T) { func TestRBCInputValue(t *testing.T) { rbc := NewRBC(Config{ N: 4, + F: -1, }, 0) reqs, err := rbc.InputValue([]byte("this is a test string")) assert.Nil(t, err) @@ -109,7 +110,7 @@ func TestNewReliableBroadcast(t *testing.T) { } func TestRBCOutputIsNilAfterConsuming(t *testing.T) { - rbc := NewRBC(Config{N: 4}, 0) + rbc := NewRBC(Config{N: 4, F: -1}, 0) output := []byte("a") rbc.output = output assert.Equal(t, output, rbc.Output()) @@ -117,7 +118,7 @@ func TestRBCOutputIsNilAfterConsuming(t *testing.T) { } func TestRBCMessagesIsEmptyAfterConsuming(t *testing.T) { - rbc := NewRBC(Config{N: 4}, 0) + rbc := NewRBC(Config{N: 4, F: -1}, 0) rbc.messages = []*BroadcastMessage{{}} assert.Equal(t, 1, len(rbc.Messages())) assert.Equal(t, 0, len(rbc.Messages())) @@ -324,6 +325,7 @@ func makeRBCNodes(n, pid int, resCh chan bcResult) []*testRBCEngine { cfg := Config{ ID: uint64(i), N: len(transports), + F: -1, } nodes[i] = newTestRBCEngine(resCh, NewRBC(cfg, uint64(pid)), tr) go nodes[i].run() diff --git a/simulation/main.go b/simulation/main.go index a1dc63d..9c0230c 100644 --- a/simulation/main.go +++ b/simulation/main.go @@ -84,6 +84,7 @@ type Server struct { func newServer(id uint64, tr hbbft.Transport, nodes []uint64) *Server { hb := hbbft.NewHoneyBadger(hbbft.Config{ N: len(nodes), + F: -1, ID: id, Nodes: nodes, BatchSize: batchSize,