Skip to content

Commit

Permalink
sbft: rework new view null requests
Browse files Browse the repository at this point in the history
Previously we would use our own concept of LastBatch() for the null
request batch prevHash.  This was unsound and replicas that were in view
change for one or more requests could not verify the correctness of this
hash.  Instead we now base our choice of prevHash on the now properly
reported most recent checkpoint.

Change-Id: Ia6794113672d64700b05ed10cac8e81539428f5b
Signed-off-by: Simon Schubert <sis@zurich.ibm.com>
  • Loading branch information
corecode authored and Simon Schubert committed Nov 21, 2016
1 parent 061020b commit ab67f34
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 141 deletions.
2 changes: 1 addition & 1 deletion orderer/sbft/simplebft/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (s *SBFT) handleHello(h *Hello, src uint64) {
return
}

_, ok := s.makeXset(vcs)
_, _, ok := s.makeXset(vcs)
if !ok {
log.Warningf("invalid hello new view xset from %d", src)
return
Expand Down
59 changes: 30 additions & 29 deletions orderer/sbft/simplebft/newview.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package simplebft

import (
"bytes"
"fmt"
"reflect"
)
Expand All @@ -36,23 +37,20 @@ func (s *SBFT) maybeSendNewView() {
}
}

xset, ok := s.makeXset(vcs)
xset, newBatch, ok := s.makeXset(vcs)
if !ok {
log.Debug("xset not yet sufficient")
return
}

var batch *Batch
if xset.Digest != nil {
if reflect.DeepEqual(s.cur.subject.Digest, xset.Digest) {
batch = s.cur.preprep.Batch
} else {
log.Warningf("forfeiting primary - do not have request in store for %d %x", xset.Seq.Seq, xset.Digest)
xset = nil
}
if newBatch != nil {
batch = newBatch
} else if reflect.DeepEqual(s.cur.subject.Digest, xset.Digest) {
batch = s.cur.preprep.Batch
} else {
batch = s.makeBatch(xset.Seq.Seq, s.sys.LastBatch().Hash(), nil)
xset.Digest = batch.Hash()
log.Warningf("forfeiting primary - do not have request in store for %d %x", xset.Seq.Seq, xset.Digest)
xset = nil
}

nv := &NewView{
Expand All @@ -73,12 +71,13 @@ func (s *SBFT) checkNewViewSignatures(nv *NewView) ([]*ViewChange, error) {
vc := &ViewChange{}
err := s.checkSig(svc, vcsrc, vc)
if err == nil {
_, err = s.checkBatch(vc.Checkpoint, false, true)
if vc.View != nv.View {
err = fmt.Errorf("view does not match")
}
}
if err != nil {
return nil, err
return nil, fmt.Errorf("viewchange from %d: %s", vcsrc, err)
}
vcs = append(vcs, vc)
}
Expand All @@ -101,27 +100,31 @@ func (s *SBFT) handleNewView(nv *NewView, src uint64) {
if err != nil {
log.Warningf("invalid new view from %d: %s", src, err)
s.sendViewChange()
return
}

xset, ok := s.makeXset(vcs)
if xset.Digest == nil {
// null request special treatment
xset.Digest = s.makeBatch(nv.Xset.Seq.Seq, s.sys.LastBatch().Hash(), nil).Hash()
}

if !ok || !reflect.DeepEqual(nv.Xset, xset) {
log.Warningf("invalid new view from %d: xset incorrect: %v, %v", src, nv.Xset, xset)
if nv.Batch == nil {
log.Warningf("invalid new view from %d: no batch attached", src)
s.sendViewChange()
return
}

if nv.Batch == nil {
log.Warningf("invalid new view from %d: batch empty", src)
xset, newBatch, ok := s.makeXset(vcs)

if !ok || !reflect.DeepEqual(nv.Xset, xset) {
log.Warningf("invalid new view from %d: xset incorrect: %v, %v", src, nv.Xset, xset)
s.sendViewChange()
return
}

if !reflect.DeepEqual(hash(nv.Batch.Header), nv.Xset.Digest) {
if nv.Xset == nil {
if !bytes.Equal(nv.Batch.Hash(), newBatch.Hash()) {
log.Warningf("invalid new view from %d: new batch for null request does not match: %x, %x, %v",
src, nv.Batch.Hash(), newBatch.Hash(), nv)
s.sendViewChange()
return
}
} else if !bytes.Equal(nv.Batch.Hash(), nv.Xset.Digest) {
log.Warningf("invalid new view from %d: batch head hash does not match xset: %x, %x, %v",
src, hash(nv.Batch.Header), nv.Xset.Digest, nv)
s.sendViewChange()
Expand Down Expand Up @@ -151,17 +154,15 @@ func (s *SBFT) processNewView() {
return
}

nextSeq := s.nextSeq()
if *nv.Xset.Seq != nextSeq {
log.Infof("we are outdated")
return
}
s.activeView = true
s.discardBacklog(s.primaryID())

pp := &Preprepare{
Seq: nv.Xset.Seq,
Seq: &SeqView{Seq: nv.Batch.DecodeHeader().Seq, View: s.view},
Batch: nv.Batch,
}

s.activeView = true
s.handleCheckedPreprepare(pp)

s.processBacklog()
}
103 changes: 69 additions & 34 deletions orderer/sbft/simplebft/newview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,24 @@ func TestXsetNoByz(t *testing.T) {
Pset: nil,
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")},
&Subject{&SeqView{2, 2}, []byte("val2")}},
Executed: 1,
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Executed: 1,
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")},
&Subject{&SeqView{2, 2}, []byte("val2")}},
Executed: 1,
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
}

xset, ok := s.makeXset(vcs)
xset, _, ok := s.makeXset(vcs)
if !ok {
t.Fatal("no xset")
}
Expand All @@ -56,31 +56,66 @@ func TestXsetNoByz(t *testing.T) {
}
}

func TestXsetNoNew(t *testing.T) {
s := &SBFT{config: Config{N: 4, F: 1}, view: 3}
prev := s.makeBatch(2, []byte("prev"), nil)
vcs := []*ViewChange{
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Checkpoint: prev,
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Checkpoint: prev,
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Checkpoint: prev,
},
}

_, newBatch, ok := s.makeXset(vcs)
if !ok {
t.Fatal("no xset")
}

expect := s.makeBatch(3, prev.Hash(), nil)
if !reflect.DeepEqual(newBatch, expect) {
t.Errorf("batches don't match: %v, %v", newBatch.DecodeHeader(), expect.DecodeHeader())
}
}

func TestXsetByz0(t *testing.T) {
s := &SBFT{config: Config{N: 4, F: 1}, view: 3}
vcs := []*ViewChange{
&ViewChange{
View: 3,
Pset: nil,
Qset: nil,
Executed: 1,
View: 3,
Pset: nil,
Qset: nil,
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Executed: 1,
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")},
&Subject{&SeqView{2, 2}, []byte("val2")}},
Executed: 1,
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
}

xset, ok := s.makeXset(vcs)
xset, _, ok := s.makeXset(vcs)
if ok {
t.Error("should not have received an xset")
}
Expand All @@ -90,10 +125,10 @@ func TestXsetByz0(t *testing.T) {
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")},
&Subject{&SeqView{2, 2}, []byte("val2")}},
Executed: 2,
Checkpoint: s.makeBatch(2, []byte("prev"), nil),
})

xset, ok = s.makeXset(vcs)
xset, _, ok = s.makeXset(vcs)
if !ok {
t.Error("no xset")
}
Expand All @@ -106,39 +141,39 @@ func TestXsetByz2(t *testing.T) {
s := &SBFT{config: Config{N: 4, F: 1}, view: 3}
vcs := []*ViewChange{
&ViewChange{
View: 3,
Pset: nil,
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Executed: 1,
View: 3,
Pset: nil,
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Executed: 1,
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
&ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{2, 2}, []byte("val2")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")},
&Subject{&SeqView{2, 2}, []byte("val2")}},
Executed: 1,
Checkpoint: s.makeBatch(1, []byte("prev"), nil),
},
}

xset, ok := s.makeXset(vcs)
xset, _, ok := s.makeXset(vcs)
if ok {
t.Error("should not have received an xset")
}

vcs = append(vcs, &ViewChange{
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Executed: 2,
View: 3,
Pset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Qset: []*Subject{&Subject{&SeqView{1, 2}, []byte("val1")}},
Checkpoint: s.makeBatch(2, []byte("prev"), nil),
})

xset, ok = s.makeXset(vcs)
xset, _, ok = s.makeXset(vcs)
if !ok {
t.Error("no xset")
}
Expand Down

0 comments on commit ab67f34

Please sign in to comment.