Skip to content

Commit d4ae30d

Browse files
authored
Merge pull request #127 from getamis/feature/add-block-lock
consensus/istanbul: add block lock
2 parents 265828f + 09ea540 commit d4ae30d

15 files changed

+405
-27
lines changed

consensus/istanbul/backend.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package istanbul
1818

1919
import (
20+
"math/big"
2021
"time"
2122

2223
"github.com/ethereum/go-ethereum/common"
@@ -54,4 +55,13 @@ type Backend interface {
5455
// CheckSignature verifies the signature by checking if it's signed by
5556
// the given validator
5657
CheckSignature(data []byte, addr common.Address, sig []byte) error
58+
59+
// HasBlock checks if the combination of the given hash and height matches any existing blocks
60+
HasBlock(hash common.Hash, number *big.Int) bool
61+
62+
// GetProposer returns the proposer of the given block height
63+
GetProposer(number uint64) common.Address
64+
65+
// ParentValidators returns the validator set of the given proposal's parent block
66+
ParentValidators(proposal Proposal) ValidatorSet
5767
}

consensus/istanbul/backend/backend.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package backend
1818

1919
import (
2020
"crypto/ecdsa"
21+
"math/big"
2122
"sync"
2223
"time"
2324

@@ -92,11 +93,7 @@ func (sb *backend) Address() common.Address {
9293

9394
// Validators implements istanbul.Backend.Validators
9495
func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet {
95-
snap, err := sb.snapshot(sb.chain, proposal.Number().Uint64(), proposal.Hash(), nil)
96-
if err != nil {
97-
return validator.NewSet(nil, sb.config.ProposerPolicy)
98-
}
99-
return snap.ValSet
96+
return sb.getValidators(proposal.Number().Uint64(), proposal.Hash())
10097
}
10198

10299
// Broadcast implements istanbul.Backend.Send
@@ -220,3 +217,33 @@ func (sb *backend) CheckSignature(data []byte, address common.Address, sig []byt
220217
}
221218
return nil
222219
}
220+
221+
// HasBlock implements istanbul.Backend.HashBlock
222+
func (sb *backend) HasBlock(hash common.Hash, number *big.Int) bool {
223+
return sb.chain.GetHeader(hash, number.Uint64()) != nil
224+
}
225+
226+
// GetProposer implements istanbul.Backend.GetProposer
227+
func (sb *backend) GetProposer(number uint64) common.Address {
228+
if h := sb.chain.GetHeaderByNumber(number); h != nil {
229+
a, _ := sb.Author(h)
230+
return a
231+
}
232+
return common.Address{}
233+
}
234+
235+
// ParentValidators implements istanbul.Backend.GetParentValidators
236+
func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet {
237+
if block, ok := proposal.(*types.Block); ok {
238+
return sb.getValidators(block.Number().Uint64()-1, block.ParentHash())
239+
}
240+
return validator.NewSet(nil, sb.config.ProposerPolicy)
241+
}
242+
243+
func (sb *backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet {
244+
snap, err := sb.snapshot(sb.chain, number, hash, nil)
245+
if err != nil {
246+
return validator.NewSet(nil, sb.config.ProposerPolicy)
247+
}
248+
return snap.ValSet
249+
}

consensus/istanbul/backend/backend_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,44 @@ func TestCommit(t *testing.T) {
177177
}
178178
}
179179

180+
func TestHasBlock(t *testing.T) {
181+
chain, engine := newBlockChain(1)
182+
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
183+
finalBlock, _ := engine.Seal(chain, block, nil)
184+
chain.InsertChain(types.Blocks{finalBlock})
185+
if engine.HasBlock(block.Hash(), finalBlock.Number()) {
186+
t.Errorf("error mismatch: have true, want false")
187+
}
188+
if !engine.HasBlock(finalBlock.Hash(), finalBlock.Number()) {
189+
t.Errorf("error mismatch: have false, want true")
190+
}
191+
}
192+
193+
func TestGetProposer(t *testing.T) {
194+
chain, engine := newBlockChain(1)
195+
block := makeBlock(chain, engine, chain.Genesis())
196+
chain.InsertChain(types.Blocks{block})
197+
expected := engine.GetProposer(1)
198+
actual := engine.Address()
199+
if actual != expected {
200+
t.Errorf("proposer mismatch: have %v, want %v", actual.Hex(), expected.Hex())
201+
}
202+
}
203+
204+
func TestParentValidators(t *testing.T) {
205+
chain, engine := newBlockChain(1)
206+
block := makeBlock(chain, engine, chain.Genesis())
207+
chain.InsertChain(types.Blocks{block})
208+
expected := engine.Validators(block).List()
209+
//Block without seal will make empty validator set
210+
block = makeBlockWithoutSeal(chain, engine, block)
211+
chain.InsertChain(types.Blocks{block})
212+
actual := engine.ParentValidators(block).List()
213+
if len(expected) != len(actual) || expected[0] != actual[0] {
214+
t.Errorf("validator set mismatch: have %v, want %v", actual, expected)
215+
}
216+
}
217+
180218
/**
181219
* SimpleBackend
182220
* Private key: bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1

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()
@@ -294,7 +294,7 @@ func testProcessBacklog(t *testing.T, msg *message) {
294294
current: newRoundState(&istanbul.View{
295295
Sequence: big.NewInt(1),
296296
Round: big.NewInt(0),
297-
}, newTestValidatorSet(4)),
297+
}, newTestValidatorSet(4), common.Hash{}, nil),
298298
}
299299
c.subscribeEvents()
300300
defer c.unsubscribeEvents()

consensus/istanbul/core/commit.go

Lines changed: 16 additions & 1 deletion
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)
@@ -60,6 +73,8 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error {
6073
// If we already have a proposal, we may have chance to speed up the consensus process
6174
// 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 LockBlock here since state can skip Prepared state and jump directly to 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: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func (c *core) commit() {
170170
}
171171

172172
if err := c.backend.Commit(proposal, committedSeals); err != nil {
173+
c.current.UnlockHash() //Unlock block when insertion fails
173174
c.sendNextRoundChange()
174175
return
175176
}
@@ -188,13 +189,21 @@ func (c *core) startNewRound(newView *istanbul.View, roundChange bool) {
188189
// Clear invalid round change messages
189190
c.roundChangeSet = newRoundChangeSet(c.valSet)
190191
// New snapshot for new round
191-
c.current = newRoundState(newView, c.valSet)
192+
c.updateRoundState(newView, c.valSet, roundChange)
192193
// Calculate new proposer
193194
c.valSet.CalcProposer(c.lastProposer, newView.Round.Uint64())
194195
c.waitingForRoundChange = false
195196
c.setState(StateAcceptRequest)
196197
if roundChange && c.isProposer() {
197-
c.backend.NextRound()
198+
// If it is locked, propose the old proposal
199+
if c.current != nil && c.current.IsHashLocked() {
200+
r := &istanbul.Request{
201+
Proposal: c.current.Proposal(), //c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState
202+
}
203+
c.sendPreprepare(r)
204+
} else {
205+
c.backend.NextRound()
206+
}
198207
}
199208
c.newRoundChangeTimer()
200209

@@ -208,13 +217,25 @@ func (c *core) catchUpRound(view *istanbul.View) {
208217
c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64())
209218
}
210219
c.waitingForRoundChange = true
211-
c.current = newRoundState(view, c.valSet)
220+
221+
//Needs to keep block lock for round catching up
222+
c.updateRoundState(view, c.valSet, true)
212223
c.roundChangeSet.Clear(view.Round)
213224
c.newRoundChangeTimer()
214225

215226
logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet)
216227
}
217228

229+
// updateRoundState updates round state by checking if locking block is necessary
230+
func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) {
231+
// Lock only if both roundChange is true and it is locked
232+
if roundChange && c.current != nil && c.current.IsHashLocked() {
233+
c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare)
234+
} else {
235+
c.current = newRoundState(view, validatorSet, common.Hash{}, nil)
236+
}
237+
}
238+
218239
func (c *core) setState(state State) {
219240
if c.state != state {
220241
c.state = state

consensus/istanbul/core/prepare.go

Lines changed: 5 additions & 2 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 Preprepare step.
5254
if err := c.verifyPrepare(prepare, src); err != nil {
5355
return err
5456
}
5557

5658
c.acceptPrepare(msg, src)
5759

58-
// Change to StatePrepared if we've received enough prepare messages
60+
// Change to StatePrepared if we've received enough prepare messages or it is locked
5961
// and we are in earlier state before StatePrepared
60-
if c.current.Prepares.Size() > 2*c.valSet.F() && c.state.Cmp(StatePrepared) < 0 {
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: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,21 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
5656
}
5757

5858
// Ensure we have the same view with the preprepare message
59+
// If it is old message, see if we need to broadcast COMMIT
5960
if err := c.checkMessage(msgPreprepare, preprepare.View); err != nil {
61+
if err == errOldMessage {
62+
// Get validator set for the given proposal
63+
valSet := c.backend.ParentValidators(preprepare.Proposal).Copy()
64+
previousProposer := c.backend.GetProposer(preprepare.Proposal.Number().Uint64() - 1)
65+
valSet.CalcProposer(previousProposer, preprepare.View.Round.Uint64())
66+
// Broadcast COMMIT if it is an existing block
67+
// 1. The proposer needs to be a proposer matches the given (Sequence + Round)
68+
// 2. The given block must exist
69+
if valSet.IsProposer(src.Address()) && c.backend.HasBlock(preprepare.Proposal.Hash(), preprepare.Proposal.Number()) {
70+
c.sendCommitForOldBlock(preprepare.View, preprepare.Proposal.Hash())
71+
return nil
72+
}
73+
}
6074
return err
6175
}
6276

@@ -84,10 +98,27 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
8498
return err
8599
}
86100

101+
// Here is about to accept the preprepare
87102
if c.state == StateAcceptRequest {
88-
c.acceptPreprepare(preprepare)
89-
c.setState(StatePreprepared)
90-
c.sendPrepare()
103+
// If it is locked, it can only process on the locked block
104+
// Otherwise, broadcast PREPARE and enter Prepared state
105+
if c.current.IsHashLocked() {
106+
// Broadcast COMMIT directly if the proposal matches the locked block
107+
// Otherwise, send ROUND CHANGE
108+
if preprepare.Proposal.Hash() == c.current.GetLockedHash() {
109+
// Broadcast COMMIT and enters Prepared state directly
110+
c.acceptPreprepare(preprepare)
111+
c.setState(StatePrepared)
112+
c.sendCommit()
113+
} else {
114+
// Send round change
115+
c.sendNextRoundChange()
116+
}
117+
} else {
118+
c.acceptPreprepare(preprepare)
119+
c.setState(StatePreprepared)
120+
c.sendPrepare()
121+
}
91122
}
92123

93124
return nil

0 commit comments

Comments
 (0)