Skip to content

Commit dc5fc64

Browse files
author
Marko Vukolic
committed
fix sbft consensus violation after attack
A correct replica would blindly put together a checkpointed batch with its locally preprepared one for the same sequence number. However, even correct replicas need to check if the payloads are the same, as Byzantine primary might had previously polluted a correct replica with a different preprepare. Added a test that was causing consensus violation in the previous code (now passing). Change-Id: I5417730cc95d05c619f445178fa93ecfe5043a70 Signed-off-by: Marko Vukolic <[email protected]>
1 parent 784d260 commit dc5fc64

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

orderer/sbft/simplebft/newview.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,17 @@ func (s *SBFT) handleNewView(nv *NewView, src uint64) {
146146
s.view = nv.View
147147
s.discardBacklog(s.primaryID())
148148

149-
// maybe deliver using xset
149+
// maybe deliver previous batch
150150
if s.sys.LastBatch().DecodeHeader().Seq < prevBatch.DecodeHeader().Seq {
151151
if prevBatch.DecodeHeader().Seq == s.cur.subject.Seq.Seq {
152152
// we just received a signature set for a request which we preprepared, but never delivered.
153-
prevBatch.Payloads = s.cur.preprep.Batch.Payloads
153+
// check first if the locally preprepared request matches the signature set
154+
if !reflect.DeepEqual(prevBatch.DecodeHeader().DataHash, s.cur.preprep.Batch.DecodeHeader().DataHash) {
155+
log.Warningf("replica %d: [seq %d] request checkpointed in a previous view does not match locally preprepared one, delivering batch without payload", s.id, s.cur.subject.Seq.Seq)
156+
} else {
157+
log.Debugf("replica %d: [seq %d] request checkpointed in a previous view with matching preprepare, completing and delivering the batch with payload", s.id, s.cur.subject.Seq.Seq)
158+
prevBatch.Payloads = s.cur.preprep.Batch.Payloads
159+
}
154160
}
155161
s.deliverBatch(prevBatch)
156162
}

orderer/sbft/simplebft/simplebft_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,58 @@ func TestByzPrimary(t *testing.T) {
266266
}
267267
}
268268

269+
func TestNewPrimaryHandlingViewChange(t *testing.T) {
270+
skipInShortMode(t)
271+
N := uint64(7)
272+
sys := newTestSystem(N)
273+
var repls []*SBFT
274+
var adapters []*testSystemAdapter
275+
for i := uint64(0); i < N; i++ {
276+
a := sys.NewAdapter(i)
277+
s, err := New(i, &Config{N: N, F: 2, BatchDurationNsec: 2000000000, BatchSizeBytes: 1, RequestTimeoutNsec: 20000000000}, a)
278+
if err != nil {
279+
t.Fatal(err)
280+
}
281+
repls = append(repls, s)
282+
adapters = append(adapters, a)
283+
}
284+
285+
r1 := []byte{1, 2, 3}
286+
r2 := []byte{5, 6, 7}
287+
288+
// change preprepare to 2-6
289+
sys.filterFn = func(e testElem) (testElem, bool) {
290+
if msg, ok := e.ev.(*testMsgEvent); ok {
291+
if pp := msg.msg.GetPreprepare(); pp != nil && msg.src == 0 && msg.dst >= 2 {
292+
pp := *pp
293+
batch := *pp.Batch
294+
batch.Payloads = [][]byte{r2}
295+
pp.Batch = &batch
296+
h := merkleHashData(batch.Payloads)
297+
bh := &BatchHeader{}
298+
proto.Unmarshal(pp.Batch.Header, bh)
299+
bh.DataHash = h
300+
bhraw, _ := proto.Marshal(bh)
301+
pp.Batch.Header = bhraw
302+
msg.msg = &Msg{&Msg_Preprepare{&pp}}
303+
}
304+
}
305+
return e, true
306+
}
307+
308+
connectAll(sys)
309+
repls[0].Request(r1)
310+
sys.Run()
311+
for _, a := range adapters {
312+
if len(a.batches) < 1 {
313+
t.Fatal("expected execution of at least one batch")
314+
}
315+
if a.batches[0].Payloads != nil && !reflect.DeepEqual(adapters[2].batches[0].Payloads, a.batches[0].Payloads) {
316+
t.Error("consensus violated on first batch at replica", a.id)
317+
}
318+
}
319+
}
320+
269321
func TestByzPrimaryBullyingSingleReplica(t *testing.T) {
270322
skipInShortMode(t)
271323
N := uint64(10)

0 commit comments

Comments
 (0)