Skip to content

Commit 576146c

Browse files
committed
parlia: use parent snapshot for finalized quorum
Use the parent block's snapshot instead of the current block's snapshot when computing the validator quorum for fast finality. The current block's snapshot may reflect epoch changes (validator set transitions) that have not yet taken effect, leading to incorrect quorum thresholds.
1 parent 7b836ed commit 576146c

2 files changed

Lines changed: 182 additions & 1 deletion

File tree

consensus/parlia/finality_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright 2017 The bnb-chain Authors
2+
// This file is part of the bnb-chain library.
3+
//
4+
// The bnb-chain 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 bnb-chain 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 bnb-chain library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package parlia
18+
19+
import (
20+
"math/big"
21+
"testing"
22+
23+
"github.com/ethereum/go-ethereum/common"
24+
"github.com/ethereum/go-ethereum/consensus"
25+
"github.com/ethereum/go-ethereum/core/types"
26+
"github.com/ethereum/go-ethereum/params"
27+
)
28+
29+
type finalizedHeaderChain struct {
30+
cfg *params.ChainConfig
31+
current *types.Header
32+
byHash map[common.Hash]*types.Header
33+
byNumber map[uint64]*types.Header
34+
}
35+
36+
func (c *finalizedHeaderChain) Config() *params.ChainConfig {
37+
return c.cfg
38+
}
39+
40+
func (c *finalizedHeaderChain) CurrentHeader() *types.Header {
41+
return c.current
42+
}
43+
44+
func (c *finalizedHeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header {
45+
header := c.byHash[hash]
46+
if header != nil && header.Number.Uint64() == number {
47+
return header
48+
}
49+
return nil
50+
}
51+
52+
func (c *finalizedHeaderChain) GetHeaderByNumber(number uint64) *types.Header {
53+
return c.byNumber[number]
54+
}
55+
56+
func (c *finalizedHeaderChain) GetHeaderByHash(hash common.Hash) *types.Header {
57+
return c.byHash[hash]
58+
}
59+
60+
func (c *finalizedHeaderChain) GenesisHeader() *types.Header {
61+
return c.byNumber[0]
62+
}
63+
64+
func (c *finalizedHeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
65+
return big.NewInt(0)
66+
}
67+
68+
func (c *finalizedHeaderChain) GetHighestVerifiedHeader() *types.Header {
69+
return c.current
70+
}
71+
72+
func (c *finalizedHeaderChain) GetVerifiedBlockByHash(hash common.Hash) *types.Header {
73+
return c.byHash[hash]
74+
}
75+
76+
func (c *finalizedHeaderChain) ChasingHead() *types.Header {
77+
return c.current
78+
}
79+
80+
type fixedVotePool struct {
81+
n int
82+
}
83+
84+
func (v *fixedVotePool) FetchVotesByBlockHash(targetBlockHash common.Hash, sourceBlockNum uint64) []*types.VoteEnvelope {
85+
votes := make([]*types.VoteEnvelope, v.n)
86+
for i := range votes {
87+
votes[i] = &types.VoteEnvelope{}
88+
}
89+
return votes
90+
}
91+
92+
func makeValidatorSet(size int) map[common.Address]*ValidatorInfo {
93+
validators := make(map[common.Address]*ValidatorInfo, size)
94+
for i := 0; i < size; i++ {
95+
addr := common.BigToAddress(big.NewInt(int64(i + 1)))
96+
validators[addr] = &ValidatorInfo{}
97+
}
98+
return validators
99+
}
100+
101+
func TestGetFinalizedHeaderUsesParentSnapshotForFastFinalityQuorum(t *testing.T) {
102+
cfg := &params.ChainConfig{
103+
ChainID: big.NewInt(56),
104+
PlatoBlock: big.NewInt(0),
105+
Parlia: &params.ParliaConfig{},
106+
}
107+
108+
genesis := &types.Header{
109+
Number: big.NewInt(0),
110+
}
111+
targetParent := &types.Header{
112+
Number: big.NewInt(10),
113+
ParentHash: common.Hash{},
114+
}
115+
finalized := &types.Header{
116+
Number: big.NewInt(9),
117+
ParentHash: genesis.Hash(),
118+
}
119+
targetParent.ParentHash = finalized.Hash()
120+
target := &types.Header{
121+
Number: big.NewInt(11),
122+
ParentHash: targetParent.Hash(),
123+
}
124+
125+
chain := &finalizedHeaderChain{
126+
cfg: cfg,
127+
current: target,
128+
byHash: map[common.Hash]*types.Header{
129+
genesis.Hash(): genesis,
130+
targetParent.Hash(): targetParent,
131+
finalized.Hash(): finalized,
132+
target.Hash(): target,
133+
},
134+
byNumber: map[uint64]*types.Header{
135+
0: genesis,
136+
9: finalized,
137+
10: targetParent,
138+
11: target,
139+
},
140+
}
141+
142+
parentSnap := &Snapshot{
143+
config: cfg.Parlia,
144+
Number: 10,
145+
Hash: targetParent.Hash(),
146+
Validators: makeValidatorSet(4),
147+
Attestation: &types.VoteData{SourceNumber: 9, SourceHash: finalized.Hash(), TargetNumber: 9, TargetHash: finalized.Hash()},
148+
Recents: make(map[uint64]common.Address),
149+
RecentForkHashes: make(map[uint64]string),
150+
}
151+
headerSnap := &Snapshot{
152+
config: cfg.Parlia,
153+
Number: 11,
154+
Hash: target.Hash(),
155+
Validators: makeValidatorSet(2),
156+
Attestation: &types.VoteData{SourceNumber: 9, SourceHash: finalized.Hash(), TargetNumber: 10, TargetHash: targetParent.Hash()},
157+
Recents: make(map[uint64]common.Address),
158+
RecentForkHashes: make(map[uint64]string),
159+
}
160+
161+
engine := New(cfg, nil, nil, genesis.Hash())
162+
engine.recentSnaps.Add(targetParent.Hash(), parentSnap)
163+
engine.recentSnaps.Add(target.Hash(), headerSnap)
164+
engine.VotePool = &fixedVotePool{n: 2}
165+
166+
finalizedHeader := engine.GetFinalizedHeader(chain, target)
167+
if finalizedHeader == nil {
168+
t.Fatal("expected finalized header, got nil")
169+
}
170+
if finalizedHeader.Number.Uint64() != finalized.Number.Uint64() {
171+
t.Fatalf("expected fallback finalized block %d, got %d", finalized.Number.Uint64(), finalizedHeader.Number.Uint64())
172+
}
173+
}
174+
175+
var _ consensus.ChainHeaderReader = (*finalizedHeaderChain)(nil)

consensus/parlia/parlia.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2302,9 +2302,15 @@ func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *t
23022302
// Try to check if currentJustifiedNumber can become finalized by checking VotePool.
23032303
// We only need to check currentJustifiedNumber + 1, since currentJustifiedNumber is already the latest justified.
23042304
if p.VotePool != nil && currentJustifiedNumber == header.Number.Uint64()-1 {
2305+
parentSnap, err := p.snapshot(chain, header.Number.Uint64()-1, header.ParentHash, nil)
2306+
if err != nil {
2307+
log.Error("Failed to get parent snapshot for finality check",
2308+
"error", err, "blockNumber", header.Number.Uint64()-1, "blockHash", header.ParentHash)
2309+
return chain.GetHeader(snap.Attestation.SourceHash, snap.Attestation.SourceNumber)
2310+
}
23052311
// Check if the next block (direct child) has reached quorum in VotePool
23062312
votes := p.VotePool.FetchVotesByBlockHash(header.Hash(), currentJustifiedNumber)
2307-
quorum := cmath.CeilDiv(len(snap.Validators)*2, 3)
2313+
quorum := cmath.CeilDiv(len(parentSnap.Validators)*2, 3)
23082314

23092315
if len(votes) >= quorum {
23102316
return chain.GetHeader(currentJustifiedHash, currentJustifiedNumber)

0 commit comments

Comments
 (0)