Skip to content

Commit d12b1a9

Browse files
consensus/beacon: check that only the latest pow block is valid ttd block (#25187)
* consensus/beacon: check that only the latest pow block is valid ttd block * consensus/beacon: move verification to async function * consensus/beacon: fix verifyTerminalPoWBlock, add test cases * consensus/beacon: cosmetic changes * consensus/beacon: apply karalabe's fixes
1 parent c2070f8 commit d12b1a9

File tree

4 files changed

+182
-2
lines changed

4 files changed

+182
-2
lines changed

consensus/beacon/consensus.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
112112
break
113113
}
114114
}
115+
115116
// All the headers have passed the transition point, use new rules.
116117
if len(preHeaders) == 0 {
117118
return beacon.verifyHeaders(chain, headers, nil)
118119
}
120+
119121
// The transition point exists in the middle, separate the headers
120122
// into two batches and apply different verification rules for them.
121123
var (
@@ -130,6 +132,14 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
130132
oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders, preSeals)
131133
newDone, newResult = beacon.verifyHeaders(chain, postHeaders, preHeaders[len(preHeaders)-1])
132134
)
135+
// Verify that pre-merge headers don't overflow the TTD
136+
if index, err := verifyTerminalPoWBlock(chain, preHeaders); err != nil {
137+
// Mark all subsequent pow headers with the error.
138+
for i := index; i < len(preHeaders); i++ {
139+
errors[i], done[i] = err, true
140+
}
141+
}
142+
// Collect the results
133143
for {
134144
for ; done[out]; out++ {
135145
results <- errors[out]
@@ -139,7 +149,9 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
139149
}
140150
select {
141151
case err := <-oldResult:
142-
errors[old], done[old] = err, true
152+
if !done[old] { // skip TTD-verified failures
153+
errors[old], done[old] = err, true
154+
}
143155
old++
144156
case err := <-newResult:
145157
errors[new], done[new] = err, true
@@ -154,6 +166,32 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
154166
return abort, results
155167
}
156168

169+
// verifyTerminalPoWBlock verifies that the preHeaders confirm to the specification
170+
// wrt. their total difficulty.
171+
// It expects:
172+
// - preHeaders to be at least 1 element
173+
// - the parent of the header element to be stored in the chain correctly
174+
// - the preHeaders to have a set difficulty
175+
// - the last element to be the terminal block
176+
func verifyTerminalPoWBlock(chain consensus.ChainHeaderReader, preHeaders []*types.Header) (int, error) {
177+
td := chain.GetTd(preHeaders[0].ParentHash, preHeaders[0].Number.Uint64()-1)
178+
if td == nil {
179+
return 0, consensus.ErrUnknownAncestor
180+
}
181+
// Check that all blocks before the last one are below the TTD
182+
for i, head := range preHeaders {
183+
if td.Cmp(chain.Config().TerminalTotalDifficulty) >= 0 {
184+
return i, consensus.ErrInvalidTerminalBlock
185+
}
186+
td.Add(td, head.Difficulty)
187+
}
188+
// Check that the last block is the terminal block
189+
if td.Cmp(chain.Config().TerminalTotalDifficulty) < 0 {
190+
return len(preHeaders) - 1, consensus.ErrInvalidTerminalBlock
191+
}
192+
return 0, nil
193+
}
194+
157195
// VerifyUncles verifies that the given block's uncles conform to the consensus
158196
// rules of the Ethereum consensus engine.
159197
func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {

consensus/beacon/consensus_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package beacon
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/consensus"
10+
"github.com/ethereum/go-ethereum/core/types"
11+
"github.com/ethereum/go-ethereum/params"
12+
)
13+
14+
type mockChain struct {
15+
config *params.ChainConfig
16+
tds map[uint64]*big.Int
17+
}
18+
19+
func newMockChain() *mockChain {
20+
return &mockChain{
21+
config: new(params.ChainConfig),
22+
tds: make(map[uint64]*big.Int),
23+
}
24+
}
25+
26+
func (m *mockChain) Config() *params.ChainConfig {
27+
return m.config
28+
}
29+
30+
func (m *mockChain) CurrentHeader() *types.Header { panic("not implemented") }
31+
32+
func (m *mockChain) GetHeader(hash common.Hash, number uint64) *types.Header {
33+
panic("not implemented")
34+
}
35+
36+
func (m *mockChain) GetHeaderByNumber(number uint64) *types.Header { panic("not implemented") }
37+
38+
func (m *mockChain) GetHeaderByHash(hash common.Hash) *types.Header { panic("not implemented") }
39+
40+
func (m *mockChain) GetTd(hash common.Hash, number uint64) *big.Int {
41+
num, ok := m.tds[number]
42+
if ok {
43+
return new(big.Int).Set(num)
44+
}
45+
return nil
46+
}
47+
48+
func TestVerifyTerminalBlock(t *testing.T) {
49+
chain := newMockChain()
50+
chain.tds[0] = big.NewInt(10)
51+
chain.config.TerminalTotalDifficulty = big.NewInt(50)
52+
53+
tests := []struct {
54+
preHeaders []*types.Header
55+
ttd *big.Int
56+
err error
57+
index int
58+
}{
59+
// valid ttd
60+
{
61+
preHeaders: []*types.Header{
62+
{Number: big.NewInt(1), Difficulty: big.NewInt(10)},
63+
{Number: big.NewInt(2), Difficulty: big.NewInt(10)},
64+
{Number: big.NewInt(3), Difficulty: big.NewInt(10)},
65+
{Number: big.NewInt(4), Difficulty: big.NewInt(10)},
66+
},
67+
ttd: big.NewInt(50),
68+
},
69+
// last block doesn't reach ttd
70+
{
71+
preHeaders: []*types.Header{
72+
{Number: big.NewInt(1), Difficulty: big.NewInt(10)},
73+
{Number: big.NewInt(2), Difficulty: big.NewInt(10)},
74+
{Number: big.NewInt(3), Difficulty: big.NewInt(10)},
75+
{Number: big.NewInt(4), Difficulty: big.NewInt(9)},
76+
},
77+
ttd: big.NewInt(50),
78+
err: consensus.ErrInvalidTerminalBlock,
79+
index: 3,
80+
},
81+
// two blocks reach ttd
82+
{
83+
preHeaders: []*types.Header{
84+
{Number: big.NewInt(1), Difficulty: big.NewInt(10)},
85+
{Number: big.NewInt(2), Difficulty: big.NewInt(10)},
86+
{Number: big.NewInt(3), Difficulty: big.NewInt(20)},
87+
{Number: big.NewInt(4), Difficulty: big.NewInt(10)},
88+
},
89+
ttd: big.NewInt(50),
90+
err: consensus.ErrInvalidTerminalBlock,
91+
index: 3,
92+
},
93+
// three blocks reach ttd
94+
{
95+
preHeaders: []*types.Header{
96+
{Number: big.NewInt(1), Difficulty: big.NewInt(10)},
97+
{Number: big.NewInt(2), Difficulty: big.NewInt(10)},
98+
{Number: big.NewInt(3), Difficulty: big.NewInt(20)},
99+
{Number: big.NewInt(4), Difficulty: big.NewInt(10)},
100+
{Number: big.NewInt(4), Difficulty: big.NewInt(10)},
101+
},
102+
ttd: big.NewInt(50),
103+
err: consensus.ErrInvalidTerminalBlock,
104+
index: 3,
105+
},
106+
// parent reached ttd
107+
{
108+
preHeaders: []*types.Header{
109+
{Number: big.NewInt(1), Difficulty: big.NewInt(10)},
110+
},
111+
ttd: big.NewInt(9),
112+
err: consensus.ErrInvalidTerminalBlock,
113+
index: 0,
114+
},
115+
// unknown parent
116+
{
117+
preHeaders: []*types.Header{
118+
{Number: big.NewInt(4), Difficulty: big.NewInt(10)},
119+
},
120+
ttd: big.NewInt(9),
121+
err: consensus.ErrUnknownAncestor,
122+
index: 0,
123+
},
124+
}
125+
126+
for i, test := range tests {
127+
fmt.Printf("Test: %v\n", i)
128+
chain.config.TerminalTotalDifficulty = test.ttd
129+
index, err := verifyTerminalPoWBlock(chain, test.preHeaders)
130+
if err != test.err {
131+
t.Fatalf("Invalid error encountered, expected %v got %v", test.err, err)
132+
}
133+
if index != test.index {
134+
t.Fatalf("Invalid index, expected %v got %v", test.index, index)
135+
}
136+
}
137+
}

consensus/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ var (
3434
// ErrInvalidNumber is returned if a block's number doesn't equal its parent's
3535
// plus one.
3636
ErrInvalidNumber = errors.New("invalid block number")
37+
38+
// ErrInvalidTerminalBlock is returned if a block is invalid wrt. the terminal
39+
// total difficulty.
40+
ErrInvalidTerminalBlock = errors.New("invalid terminal block")
3741
)

core/block_validator_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
107107
Alloc: map[common.Address]GenesisAccount{
108108
addr: {Balance: big.NewInt(1)},
109109
},
110-
BaseFee: big.NewInt(params.InitialBaseFee),
110+
BaseFee: big.NewInt(params.InitialBaseFee),
111+
Difficulty: new(big.Int),
111112
}
112113
copy(genspec.ExtraData[32:], addr[:])
113114
genesis := genspec.MustCommit(testdb)

0 commit comments

Comments
 (0)