Skip to content

Commit be875de

Browse files
markya0616alanchchen
authored andcommitted
Merge pull request #96 from getamis/feature/catchup_round_change
consensus/istanbul: catch up round before send round change
2 parents 74f9eec + 27f176b commit be875de

File tree

5 files changed

+158
-33
lines changed

5 files changed

+158
-33
lines changed

consensus/istanbul/core/core.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (c *core) commit() {
193193
}
194194

195195
if err := c.backend.Commit(proposal, signatures); err != nil {
196-
c.sendRoundChange()
196+
c.sendNextRoundChange()
197197
return
198198
}
199199
}
@@ -232,6 +232,7 @@ func (c *core) catchUpRound(view *istanbul.View) {
232232
}
233233
c.waitingForRoundChange = true
234234
c.current = newRoundState(view, c.valSet)
235+
c.roundChangeSet.Clear(view.Round)
235236
c.newRoundChangeTimer()
236237

237238
logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet)
@@ -266,7 +267,19 @@ func (c *core) newRoundChangeTimer() {
266267

267268
timeout := time.Duration(c.config.RequestTimeout) * time.Millisecond
268269
c.roundChangeTimer = time.AfterFunc(timeout, func() {
269-
c.sendRoundChange()
270+
// If we're not waiting for round change yet, we can try to catch up
271+
// the max round with F+1 round change message. We only need to catch up
272+
// if the max round is larger than current round.
273+
if !c.waitingForRoundChange {
274+
maxRound := c.roundChangeSet.MaxRound(c.valSet.F() + 1)
275+
if maxRound != nil && maxRound.Cmp(c.current.Round()) > 0 {
276+
c.sendRoundChange(maxRound)
277+
} else {
278+
c.sendNextRoundChange()
279+
}
280+
} else {
281+
c.sendNextRoundChange()
282+
}
270283
})
271284
}
272285

consensus/istanbul/core/preprepare.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
7070
// Verify the proposal we received
7171
if err := c.backend.Verify(preprepare.Proposal); err != nil {
7272
logger.Warn("Failed to verify proposal", "err", err)
73-
c.sendRoundChange()
73+
c.sendNextRoundChange()
7474
return err
7575
}
7676

consensus/istanbul/core/roundchange.go

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,26 @@ import (
2424
"github.com/ethereum/go-ethereum/consensus/istanbul"
2525
)
2626

27-
func (c *core) sendRoundChange() {
27+
// sendNextRoundChange sends the round change message with current round + 1
28+
func (c *core) sendNextRoundChange() {
29+
cv := c.currentView()
30+
c.sendRoundChange(new(big.Int).Add(cv.Round, common.Big1))
31+
}
32+
33+
// sendRoundChange sends the round change message with the given round
34+
func (c *core) sendRoundChange(round *big.Int) {
2835
logger := c.logger.New("state", c.state)
2936
logger.Trace("sendRoundChange")
3037

3138
cv := c.currentView()
39+
if cv.Round.Cmp(round) >= 0 {
40+
logger.Error("Cannot send out the round change", "current round", cv.Round, "target round", round)
41+
return
42+
}
43+
3244
c.catchUpRound(&istanbul.View{
3345
// The round number we'd like to transfer to.
34-
Round: new(big.Int).Add(cv.Round, common.Big1),
46+
Round: new(big.Int).Set(round),
3547
Sequence: new(big.Int).Set(cv.Sequence),
3648
})
3749

@@ -82,10 +94,7 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error {
8294

8395
// Add the round change message to its message set and return how many
8496
// messages we've got with the same round number and sequence number.
85-
num, err := c.roundChangeSet.Add(&istanbul.View{
86-
Round: new(big.Int).Set(rc.Round),
87-
Sequence: new(big.Int).Set(rc.Sequence),
88-
}, msg)
97+
num, err := c.roundChangeSet.Add(rc.Round, msg)
8998
if err != nil {
9099
logger.Warn("Failed to add round change message", "from", src, "msg", msg, "err", err)
91100
return err
@@ -94,13 +103,9 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error {
94103
// Once we received f+1 round change messages, those messages form a weak certificate.
95104
// If our round number is smaller than the certificate's round number, we would
96105
// try to catch up the round number.
97-
if num == int(c.valSet.F()+1) {
106+
if c.waitingForRoundChange && num == int(c.valSet.F()+1) {
98107
if cv.Round.Cmp(rc.Round) < 0 {
99-
c.catchUpRound(&istanbul.View{
100-
Round: new(big.Int).Sub(rc.Round, common.Big1),
101-
Sequence: new(big.Int).Set(rc.Sequence),
102-
})
103-
c.sendRoundChange()
108+
c.sendRoundChange(rc.Round)
104109
}
105110
}
106111

@@ -120,49 +125,59 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error {
120125
func newRoundChangeSet(valSet istanbul.ValidatorSet) *roundChangeSet {
121126
return &roundChangeSet{
122127
validatorSet: valSet,
123-
roundChanges: make(map[common.Hash]*messageSet),
128+
roundChanges: make(map[uint64]*messageSet),
124129
mu: new(sync.Mutex),
125130
}
126131
}
127132

128133
type roundChangeSet struct {
129134
validatorSet istanbul.ValidatorSet
130-
roundChanges map[common.Hash]*messageSet
135+
roundChanges map[uint64]*messageSet
131136
mu *sync.Mutex
132137
}
133138

134-
func (rcs *roundChangeSet) Add(v *istanbul.View, msg *message) (int, error) {
139+
// Add adds the round and message into round change set
140+
func (rcs *roundChangeSet) Add(r *big.Int, msg *message) (int, error) {
135141
rcs.mu.Lock()
136142
defer rcs.mu.Unlock()
137143

138-
h := istanbul.RLPHash(v)
139-
if rcs.roundChanges[h] == nil {
140-
rcs.roundChanges[h] = newMessageSet(rcs.validatorSet)
144+
round := r.Uint64()
145+
if rcs.roundChanges[round] == nil {
146+
rcs.roundChanges[round] = newMessageSet(rcs.validatorSet)
141147
}
142-
err := rcs.roundChanges[h].Add(msg)
148+
err := rcs.roundChanges[round].Add(msg)
143149
if err != nil {
144150
return 0, err
145151
}
146-
return rcs.roundChanges[h].Size(), nil
152+
return rcs.roundChanges[round].Size(), nil
147153
}
148154

149-
func (rcs *roundChangeSet) Clear(v *istanbul.View) {
155+
// Clear deletes the messages with smaller round
156+
func (rcs *roundChangeSet) Clear(round *big.Int) {
150157
rcs.mu.Lock()
151158
defer rcs.mu.Unlock()
152159

153160
for k, rms := range rcs.roundChanges {
154-
if len(rms.Values()) == 0 {
161+
if len(rms.Values()) == 0 || k < round.Uint64() {
155162
delete(rcs.roundChanges, k)
156163
}
164+
}
165+
}
166+
167+
// MaxRound returns the max round which the number of messages is equal or larger than num
168+
func (rcs *roundChangeSet) MaxRound(num int) *big.Int {
169+
rcs.mu.Lock()
170+
defer rcs.mu.Unlock()
157171

158-
var rc *roundChange
159-
if err := rms.Values()[0].Decode(&rc); err != nil {
172+
var maxRound *big.Int
173+
for k, rms := range rcs.roundChanges {
174+
if rms.Size() < num {
160175
continue
161176
}
162-
163-
if rc.Sequence.Cmp(v.Sequence) < 0 ||
164-
(rc.Sequence.Cmp(v.Sequence) == 0 && rc.Round.Cmp(v.Round) < 0) {
165-
delete(rcs.roundChanges, k)
177+
r := big.NewInt(int64(k))
178+
if maxRound == nil || maxRound.Cmp(r) < 0 {
179+
maxRound = r
166180
}
167181
}
182+
return maxRound
168183
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2017 AMIS Technologies
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package core
18+
19+
import (
20+
"math/big"
21+
"testing"
22+
23+
"github.com/ethereum/go-ethereum/common"
24+
"github.com/ethereum/go-ethereum/consensus/istanbul"
25+
"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
26+
)
27+
28+
func TestRoundChangeSet(t *testing.T) {
29+
vset := validator.NewSet(generateValidators(4), istanbul.RoundRobin)
30+
rc := newRoundChangeSet(vset)
31+
32+
view := &istanbul.View{
33+
Sequence: big.NewInt(1),
34+
Round: big.NewInt(1),
35+
}
36+
r := &roundChange{
37+
Round: view.Round,
38+
Sequence: view.Sequence,
39+
Digest: common.Hash{},
40+
}
41+
m, _ := Encode(r)
42+
43+
// Test Add()
44+
// Add message from all validators
45+
for i, v := range vset.List() {
46+
msg := &message{
47+
Code: msgRoundChange,
48+
Msg: m,
49+
Address: v.Address(),
50+
}
51+
rc.Add(view.Round, msg)
52+
if rc.roundChanges[view.Round.Uint64()].Size() != i+1 {
53+
t.Errorf("unexpected round change message size, got: %v, expected: %v",
54+
rc.roundChanges[view.Round.Uint64()].Size(), i+1)
55+
}
56+
}
57+
58+
// Add message again from all validators, but the size should be the same
59+
for _, v := range vset.List() {
60+
msg := &message{
61+
Code: msgRoundChange,
62+
Msg: m,
63+
Address: v.Address(),
64+
}
65+
rc.Add(view.Round, msg)
66+
if rc.roundChanges[view.Round.Uint64()].Size() != vset.Size() {
67+
t.Errorf("unexpected round change message size, got: %v, expected: %v",
68+
rc.roundChanges[view.Round.Uint64()].Size(), vset.Size())
69+
}
70+
}
71+
72+
// Test MaxRound()
73+
for i := 0; i < 10; i++ {
74+
maxRound := rc.MaxRound(i)
75+
if i <= vset.Size() {
76+
if maxRound == nil || maxRound.Cmp(view.Round) != 0 {
77+
t.Errorf("unexpected max round, got: %v, expected: %v", maxRound, view.Round)
78+
}
79+
} else if maxRound != nil {
80+
t.Errorf("max round should be nil, but got: %v", maxRound)
81+
82+
}
83+
}
84+
85+
// Test Clear()
86+
for i := int64(0); i < 2; i++ {
87+
rc.Clear(big.NewInt(i))
88+
if rc.roundChanges[view.Round.Uint64()].Size() != vset.Size() {
89+
t.Errorf("unexpected round change message size, got: %v, expected: %v",
90+
rc.roundChanges[view.Round.Uint64()].Size(), vset.Size())
91+
}
92+
}
93+
rc.Clear(big.NewInt(2))
94+
if rc.roundChanges[view.Round.Uint64()] != nil {
95+
t.Errorf("round change set should be nil, but got: %v",
96+
rc.roundChanges[view.Round.Uint64()])
97+
}
98+
}

consensus/istanbul/validator/default.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717
package validator
1818

1919
import (
20+
"math"
2021
"reflect"
2122
"sort"
2223
"sync"
2324

24-
"math"
25-
2625
"github.com/ethereum/go-ethereum/common"
2726
"github.com/ethereum/go-ethereum/consensus/istanbul"
2827
)

0 commit comments

Comments
 (0)