Skip to content

Commit 38f82a8

Browse files
committed
consensus/istanbul: add block lock
1 parent 98a18ad commit 38f82a8

File tree

12 files changed

+295
-53
lines changed

12 files changed

+295
-53
lines changed

consensus/istanbul/core/backlog_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestCheckMessage(t *testing.T) {
3737
current: newRoundState(&istanbul.View{
3838
Sequence: big.NewInt(1),
3939
Round: big.NewInt(0),
40-
}, newTestValidatorSet(4)),
40+
}, newTestValidatorSet(4), common.Hash{}, nil),
4141
}
4242

4343
// invalid view format
@@ -209,7 +209,7 @@ func TestProcessFutureBacklog(t *testing.T) {
209209
current: newRoundState(&istanbul.View{
210210
Sequence: big.NewInt(1),
211211
Round: big.NewInt(0),
212-
}, newTestValidatorSet(4)),
212+
}, newTestValidatorSet(4), common.Hash{}, nil),
213213
state: StateAcceptRequest,
214214
}
215215
c.subscribeEvents()
@@ -297,7 +297,7 @@ func testProcessBacklog(t *testing.T, msg *message) {
297297
current: newRoundState(&istanbul.View{
298298
Sequence: big.NewInt(1),
299299
Round: big.NewInt(0),
300-
}, newTestValidatorSet(4)),
300+
}, newTestValidatorSet(4), common.Hash{}, nil),
301301
}
302302
c.subscribeEvents()
303303
defer c.unsubscribeEvents()

consensus/istanbul/core/commit.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,26 @@ package core
1919
import (
2020
"reflect"
2121

22+
"github.com/ethereum/go-ethereum/common"
2223
"github.com/ethereum/go-ethereum/consensus/istanbul"
2324
)
2425

2526
func (c *core) sendCommit() {
27+
sub := c.current.Subject()
28+
c.broadcastCommit(sub)
29+
}
30+
31+
func (c *core) sendCommitForOldBlock(view *istanbul.View, digest common.Hash) {
32+
sub := &istanbul.Subject{
33+
View: view,
34+
Digest: digest,
35+
}
36+
c.broadcastCommit(sub)
37+
}
38+
39+
func (c *core) broadcastCommit(sub *istanbul.Subject) {
2640
logger := c.logger.New("state", c.state)
2741

28-
sub := c.current.Subject()
2942
encodedSubject, err := Encode(sub)
3043
if err != nil {
3144
logger.Error("Failed to encode", "subject", sub)
@@ -55,11 +68,13 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error {
5568

5669
c.acceptCommit(msg, src)
5770

58-
// Commit the proposal once we have enough commit messages and we are not in StateCommitted.
71+
// Commit the proposal once we have enough COMMIT messages and we are not in the Committed state.
5972
//
6073
// If we already have a proposal, we may have chance to speed up the consensus process
61-
// by committing the proposal without prepare messages.
74+
// by committing the proposal without PREPARE messages.
6275
if c.current.Commits.Size() > 2*c.valSet.F() && c.state.Cmp(StateCommitted) < 0 {
76+
// Still need to call LockHash here since state can skip Prepared state and jump directly to the Committed state.
77+
c.current.LockHash()
6378
c.commit()
6479
}
6580

consensus/istanbul/core/commit_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ OUTER:
178178
if err != test.expectedErr {
179179
t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
180180
}
181+
if r0.current.IsHashLocked() {
182+
t.Errorf("block should not be locked")
183+
}
181184
continue OUTER
182185
}
183186
}
@@ -191,7 +194,9 @@ OUTER:
191194
if r0.current.Commits.Size() > 2*r0.valSet.F() {
192195
t.Errorf("the size of commit messages should be less than %v", 2*r0.valSet.F()+1)
193196
}
194-
197+
if r0.current.IsHashLocked() {
198+
t.Errorf("block should not be locked")
199+
}
195200
continue
196201
}
197202

@@ -214,6 +219,9 @@ OUTER:
214219
if signedCount <= 2*r0.valSet.F() {
215220
t.Errorf("the expected signed count should be larger than %v, but got %v", 2*r0.valSet.F(), signedCount)
216221
}
222+
if !r0.current.IsHashLocked() {
223+
t.Errorf("block should be locked")
224+
}
217225
}
218226
}
219227

consensus/istanbul/core/core.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/ethereum/go-ethereum/common"
2727
"github.com/ethereum/go-ethereum/consensus/istanbul"
28+
"github.com/ethereum/go-ethereum/core/types"
2829
"github.com/ethereum/go-ethereum/event"
2930
"github.com/ethereum/go-ethereum/log"
3031
metrics "github.com/ethereum/go-ethereum/metrics"
@@ -171,6 +172,7 @@ func (c *core) commit() {
171172
}
172173

173174
if err := c.backend.Commit(proposal, committedSeals); err != nil {
175+
c.current.UnlockHash() //Unlock block when insertion fails
174176
c.sendNextRoundChange()
175177
return
176178
}
@@ -203,13 +205,21 @@ func (c *core) startNewRound(newView *istanbul.View, lastProposal istanbul.Propo
203205
// Clear invalid ROUND CHANGE messages
204206
c.roundChangeSet = newRoundChangeSet(c.valSet)
205207
// New snapshot for new round
206-
c.current = newRoundState(newView, c.valSet)
208+
c.updateRoundState(newView, c.valSet, roundChange)
207209
// Calculate new proposer
208210
c.valSet.CalcProposer(c.lastProposer, newView.Round.Uint64())
209211
c.waitingForRoundChange = false
210212
c.setState(StateAcceptRequest)
211213
if roundChange && c.isProposer() {
212-
c.backend.NextRound()
214+
// If it is locked, propose the old proposal
215+
if c.current != nil && c.current.IsHashLocked() {
216+
r := &istanbul.Request{
217+
Proposal: c.current.Proposal(), //c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState
218+
}
219+
c.sendPreprepare(r)
220+
} else {
221+
c.backend.NextRound()
222+
}
213223
}
214224
c.newRoundChangeTimer()
215225

@@ -223,13 +233,25 @@ func (c *core) catchUpRound(view *istanbul.View) {
223233
c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64())
224234
}
225235
c.waitingForRoundChange = true
226-
c.current = newRoundState(view, c.valSet)
236+
237+
// Need to keep block locked for round catching up
238+
c.updateRoundState(view, c.valSet, true)
227239
c.roundChangeSet.Clear(view.Round)
228240
c.newRoundChangeTimer()
229241

230242
logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet)
231243
}
232244

245+
// updateRoundState updates round state by checking if locking block is necessary
246+
func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) {
247+
// Lock only if both roundChange is true and it is locked
248+
if roundChange && c.current != nil && c.current.IsHashLocked() {
249+
c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare)
250+
} else {
251+
c.current = newRoundState(view, validatorSet, common.Hash{}, nil)
252+
}
253+
}
254+
233255
func (c *core) setState(state State) {
234256
if c.state != state {
235257
c.state = state

consensus/istanbul/core/prepare.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,18 @@ func (c *core) handlePrepare(msg *message, src istanbul.Validator) error {
4949
return err
5050
}
5151

52+
// If it is locked, it can only process on the locked block.
53+
// Passing verifyPrepare and checkMessage implies it is processing on the locked block since it was verified in the Preprepared state.
5254
if err := c.verifyPrepare(prepare, src); err != nil {
5355
return err
5456
}
5557

5658
c.acceptPrepare(msg, src)
5759

58-
// Change to Prepared state if we've received enough PREPARE messages
59-
// and we are in earlier state before Prepared state
60-
if c.current.Prepares.Size() > 2*c.valSet.F() && c.state.Cmp(StatePrepared) < 0 {
60+
// Change to Prepared state if we've received enough PREPARE messages or it is locked
61+
// and we are in earlier state before Prepared state.
62+
if (c.current.IsHashLocked() || c.current.Prepares.Size() > 2*c.valSet.F()) && c.state.Cmp(StatePrepared) < 0 {
63+
c.current.LockHash()
6164
c.setState(StatePrepared)
6265
c.sendCommit()
6366
}

consensus/istanbul/core/prepare_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ OUTER:
201201
if err != test.expectedErr {
202202
t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
203203
}
204+
if r0.current.IsHashLocked() {
205+
t.Errorf("block should not be locked")
206+
}
204207
continue OUTER
205208
}
206209
}
@@ -214,6 +217,9 @@ OUTER:
214217
if r0.current.Prepares.Size() > 2*r0.valSet.F() {
215218
t.Errorf("the size of PREPARE messages should be less than %v", 2*r0.valSet.F()+1)
216219
}
220+
if r0.current.IsHashLocked() {
221+
t.Errorf("block should not be locked")
222+
}
217223

218224
continue
219225
}
@@ -246,6 +252,9 @@ OUTER:
246252
if !reflect.DeepEqual(m, expectedSubject) {
247253
t.Errorf("subject mismatch: have %v, want %v", m, expectedSubject)
248254
}
255+
if !r0.current.IsHashLocked() {
256+
t.Errorf("block should be locked")
257+
}
249258
}
250259
}
251260

consensus/istanbul/core/preprepare.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,17 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
8686

8787
// Here is about to accept the PRE-PREPARE
8888
if c.state == StateAcceptRequest {
89-
c.acceptPreprepare(preprepare)
90-
c.setState(StatePreprepared)
91-
c.sendPrepare()
89+
// Send ROUND CHANGE if the locked proposal and the received proposal are different
90+
if c.current.IsHashLocked() && preprepare.Proposal.Hash() != c.current.GetLockedHash() {
91+
c.sendNextRoundChange()
92+
} else {
93+
// Either
94+
// 1. the locked proposal and the received proposal match
95+
// 2. we have no locked proposal
96+
c.acceptPreprepare(preprepare)
97+
c.setState(StatePreprepared)
98+
c.sendPrepare()
99+
}
92100
}
93101

94102
return nil

0 commit comments

Comments
 (0)