Skip to content

Commit 983f923

Browse files
authored
core/forkid: implement the forkid EIP, announce via ENR (#19738)
* eth: chain config (genesis + fork) ENR entry * core/forkid, eth: protocol independent fork ID, update to CRC32 spec * core/forkid, eth: make forkid a struct, next uint64, enr struct, RLP * core/forkid: change forkhash rlp encoding from int to [4]byte * eth: fixup eth entry a bit and update it every block * eth: fix lint * eth: fix crash in ethclient tests
1 parent cc0f0e2 commit 983f923

File tree

11 files changed

+571
-96
lines changed

11 files changed

+571
-96
lines changed

core/blockchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2146,7 +2146,7 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
21462146
return bc.hc.GetHeaderByNumber(number)
21472147
}
21482148

2149-
// Config retrieves the blockchain's chain configuration.
2149+
// Config retrieves the chain's fork configuration.
21502150
func (bc *BlockChain) Config() *params.ChainConfig { return bc.chainConfig }
21512151

21522152
// Engine retrieves the blockchain's consensus engine.

core/forkid/forkid.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Copyright 2019 The go-ethereum Authors
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 forkid implements EIP-2124 (https://eips.ethereum.org/EIPS/eip-2124).
18+
package forkid
19+
20+
import (
21+
"encoding/binary"
22+
"errors"
23+
"hash/crc32"
24+
"math"
25+
"math/big"
26+
"reflect"
27+
"strings"
28+
29+
"github.com/ethereum/go-ethereum/common"
30+
"github.com/ethereum/go-ethereum/core"
31+
"github.com/ethereum/go-ethereum/log"
32+
"github.com/ethereum/go-ethereum/params"
33+
)
34+
35+
var (
36+
// ErrRemoteStale is returned by the validator if a remote fork checksum is a
37+
// subset of our already applied forks, but the announced next fork block is
38+
// not on our already passed chain.
39+
ErrRemoteStale = errors.New("remote needs update")
40+
41+
// ErrLocalIncompatibleOrStale is returned by the validator if a remote fork
42+
// checksum does not match any local checksum variation, signalling that the
43+
// two chains have diverged in the past at some point (possibly at genesis).
44+
ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update")
45+
)
46+
47+
// ID is a fork identifier as defined by EIP-2124.
48+
type ID struct {
49+
Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers
50+
Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known
51+
}
52+
53+
// NewID calculates the Ethereum fork ID from the chain config and head.
54+
func NewID(chain *core.BlockChain) ID {
55+
return newID(
56+
chain.Config(),
57+
chain.Genesis().Hash(),
58+
chain.CurrentHeader().Number.Uint64(),
59+
)
60+
}
61+
62+
// newID is the internal version of NewID, which takes extracted values as its
63+
// arguments instead of a chain. The reason is to allow testing the IDs without
64+
// having to simulate an entire blockchain.
65+
func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
66+
// Calculate the starting checksum from the genesis hash
67+
hash := crc32.ChecksumIEEE(genesis[:])
68+
69+
// Calculate the current fork checksum and the next fork block
70+
var next uint64
71+
for _, fork := range gatherForks(config) {
72+
if fork <= head {
73+
// Fork already passed, checksum the previous hash and the fork number
74+
hash = checksumUpdate(hash, fork)
75+
continue
76+
}
77+
next = fork
78+
break
79+
}
80+
return ID{Hash: checksumToBytes(hash), Next: next}
81+
}
82+
83+
// NewFilter creates an filter that returns if a fork ID should be rejected or not
84+
// based on the local chain's status.
85+
func NewFilter(chain *core.BlockChain) func(id ID) error {
86+
return newFilter(
87+
chain.Config(),
88+
chain.Genesis().Hash(),
89+
func() uint64 {
90+
return chain.CurrentHeader().Number.Uint64()
91+
},
92+
)
93+
}
94+
95+
// newFilter is the internal version of NewFilter, taking closures as its arguments
96+
// instead of a chain. The reason is to allow testing it without having to simulate
97+
// an entire blockchain.
98+
func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) func(id ID) error {
99+
// Calculate the all the valid fork hash and fork next combos
100+
var (
101+
forks = gatherForks(config)
102+
sums = make([][4]byte, len(forks)+1) // 0th is the genesis
103+
)
104+
hash := crc32.ChecksumIEEE(genesis[:])
105+
sums[0] = checksumToBytes(hash)
106+
for i, fork := range forks {
107+
hash = checksumUpdate(hash, fork)
108+
sums[i+1] = checksumToBytes(hash)
109+
}
110+
// Add two sentries to simplify the fork checks and don't require special
111+
// casing the last one.
112+
forks = append(forks, math.MaxUint64) // Last fork will never be passed
113+
114+
// Create a validator that will filter out incompatible chains
115+
return func(id ID) error {
116+
// Run the fork checksum validation ruleset:
117+
// 1. If local and remote FORK_CSUM matches, connect.
118+
// The two nodes are in the same fork state currently. They might know
119+
// of differing future forks, but that's not relevant until the fork
120+
// triggers (might be postponed, nodes might be updated to match).
121+
// 2. If the remote FORK_CSUM is a subset of the local past forks and the
122+
// remote FORK_NEXT matches with the locally following fork block number,
123+
// connect.
124+
// Remote node is currently syncing. It might eventually diverge from
125+
// us, but at this current point in time we don't have enough information.
126+
// 3. If the remote FORK_CSUM is a superset of the local past forks and can
127+
// be completed with locally known future forks, connect.
128+
// Local node is currently syncing. It might eventually diverge from
129+
// the remote, but at this current point in time we don't have enough
130+
// information.
131+
// 4. Reject in all other cases.
132+
head := headfn()
133+
for i, fork := range forks {
134+
// If our head is beyond this fork, continue to the next (we have a dummy
135+
// fork of maxuint64 as the last item to always fail this check eventually).
136+
if head > fork {
137+
continue
138+
}
139+
// Found the first unpassed fork block, check if our current state matches
140+
// the remote checksum (rule #1).
141+
if sums[i] == id.Hash {
142+
// Yay, fork checksum matched, ignore any upcoming fork
143+
return nil
144+
}
145+
// The local and remote nodes are in different forks currently, check if the
146+
// remote checksum is a subset of our local forks (rule #2).
147+
for j := 0; j < i; j++ {
148+
if sums[j] == id.Hash {
149+
// Remote checksum is a subset, validate based on the announced next fork
150+
if forks[j] != id.Next {
151+
return ErrRemoteStale
152+
}
153+
return nil
154+
}
155+
}
156+
// Remote chain is not a subset of our local one, check if it's a superset by
157+
// any chance, signalling that we're simply out of sync (rule #3).
158+
for j := i + 1; j < len(sums); j++ {
159+
if sums[j] == id.Hash {
160+
// Yay, remote checksum is a superset, ignore upcoming forks
161+
return nil
162+
}
163+
}
164+
// No exact, subset or superset match. We are on differing chains, reject.
165+
return ErrLocalIncompatibleOrStale
166+
}
167+
log.Error("Impossible fork ID validation", "id", id)
168+
return nil // Something's very wrong, accept rather than reject
169+
}
170+
}
171+
172+
// checksum calculates the IEEE CRC32 checksum of a block number.
173+
func checksum(fork uint64) uint32 {
174+
var blob [8]byte
175+
binary.BigEndian.PutUint64(blob[:], fork)
176+
return crc32.ChecksumIEEE(blob[:])
177+
}
178+
179+
// checksumUpdate calculates the next IEEE CRC32 checksum based on the previous
180+
// one and a fork block number (equivalent to CRC32(original-blob || fork)).
181+
func checksumUpdate(hash uint32, fork uint64) uint32 {
182+
var blob [8]byte
183+
binary.BigEndian.PutUint64(blob[:], fork)
184+
return crc32.Update(hash, crc32.IEEETable, blob[:])
185+
}
186+
187+
// checksumToBytes converts a uint32 checksum into a [4]byte array.
188+
func checksumToBytes(hash uint32) [4]byte {
189+
var blob [4]byte
190+
binary.BigEndian.PutUint32(blob[:], hash)
191+
return blob
192+
}
193+
194+
// gatherForks gathers all the known forks and creates a sorted list out of them.
195+
func gatherForks(config *params.ChainConfig) []uint64 {
196+
// Gather all the fork block numbers via reflection
197+
kind := reflect.TypeOf(params.ChainConfig{})
198+
conf := reflect.ValueOf(config).Elem()
199+
200+
var forks []uint64
201+
for i := 0; i < kind.NumField(); i++ {
202+
// Fetch the next field and skip non-fork rules
203+
field := kind.Field(i)
204+
if !strings.HasSuffix(field.Name, "Block") {
205+
continue
206+
}
207+
if field.Type != reflect.TypeOf(new(big.Int)) {
208+
continue
209+
}
210+
// Extract the fork rule block number and aggregate it
211+
rule := conf.Field(i).Interface().(*big.Int)
212+
if rule != nil {
213+
forks = append(forks, rule.Uint64())
214+
}
215+
}
216+
// Sort the fork block numbers to permit chronologival XOR
217+
for i := 0; i < len(forks); i++ {
218+
for j := i + 1; j < len(forks); j++ {
219+
if forks[i] > forks[j] {
220+
forks[i], forks[j] = forks[j], forks[i]
221+
}
222+
}
223+
}
224+
// Deduplicate block numbers applying multiple forks
225+
for i := 1; i < len(forks); i++ {
226+
if forks[i] == forks[i-1] {
227+
forks = append(forks[:i], forks[i+1:]...)
228+
i--
229+
}
230+
}
231+
// Skip any forks in block 0, that's the genesis ruleset
232+
if len(forks) > 0 && forks[0] == 0 {
233+
forks = forks[1:]
234+
}
235+
return forks
236+
}

0 commit comments

Comments
 (0)