core/forkid: correctly compute forkid when timestamp fork is activated in genesis (#27895)

This changes the forkID calculation to ignore time-based forks that occurred before the
genesis block. It's supposed to be done this way because the spec says:

> If a chain is configured to start with a non-Frontier ruleset already in its genesis, that is NOT considered a fork.
This commit is contained in:
lightclient 2023-08-16 15:31:02 -06:00 committed by GitHub
parent a3e35414b7
commit 32fde3f838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 76 additions and 16 deletions

View File

@ -77,7 +77,7 @@ func (c *Chain) RootAt(height int) common.Hash {
// ForkID gets the fork id of the chain. // ForkID gets the fork id of the chain.
func (c *Chain) ForkID() forkid.ID { func (c *Chain) ForkID() forkid.ID {
return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len()), c.blocks[0].Time()) return forkid.NewID(c.chainConfig, c.blocks[0], uint64(c.Len()), c.blocks[0].Time())
} }
// Shorten returns a copy chain of a desired height from the imported // Shorten returns a copy chain of a desired height from the imported

View File

@ -73,9 +73,9 @@ type ID struct {
type Filter func(id ID) error type Filter func(id ID) error
// NewID calculates the Ethereum fork ID from the chain config, genesis hash, head and time. // NewID calculates the Ethereum fork ID from the chain config, genesis hash, head and time.
func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) ID { func NewID(config *params.ChainConfig, genesis *types.Block, head, time uint64) ID {
// Calculate the starting checksum from the genesis hash // Calculate the starting checksum from the genesis hash
hash := crc32.ChecksumIEEE(genesis[:]) hash := crc32.ChecksumIEEE(genesis.Hash().Bytes())
// Calculate the current fork checksum and the next fork block // Calculate the current fork checksum and the next fork block
forksByBlock, forksByTime := gatherForks(config) forksByBlock, forksByTime := gatherForks(config)
@ -88,6 +88,10 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) I
return ID{Hash: checksumToBytes(hash), Next: fork} return ID{Hash: checksumToBytes(hash), Next: fork}
} }
for _, fork := range forksByTime { for _, fork := range forksByTime {
if fork <= genesis.Time() {
// Fork active in genesis, skip in forkid calculation
continue
}
if fork <= time { if fork <= time {
// Fork already passed, checksum the previous hash and fork timestamp // Fork already passed, checksum the previous hash and fork timestamp
hash = checksumUpdate(hash, fork) hash = checksumUpdate(hash, fork)
@ -104,7 +108,7 @@ func NewIDWithChain(chain Blockchain) ID {
return NewID( return NewID(
chain.Config(), chain.Config(),
chain.Genesis().Hash(), chain.Genesis(),
head.Number.Uint64(), head.Number.Uint64(),
head.Time, head.Time,
) )

View File

@ -18,10 +18,14 @@ package forkid
import ( import (
"bytes" "bytes"
"hash/crc32"
"math" "math"
"math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -36,13 +40,13 @@ func TestCreation(t *testing.T) {
} }
tests := []struct { tests := []struct {
config *params.ChainConfig config *params.ChainConfig
genesis common.Hash genesis *types.Block
cases []testcase cases []testcase
}{ }{
// Mainnet test cases // Mainnet test cases
{ {
params.MainnetChainConfig, params.MainnetChainConfig,
params.MainnetGenesisHash, core.DefaultGenesisBlock().ToBlock(),
[]testcase{ []testcase{
{0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
{1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
@ -77,7 +81,7 @@ func TestCreation(t *testing.T) {
// Goerli test cases // Goerli test cases
{ {
params.GoerliChainConfig, params.GoerliChainConfig,
params.GoerliGenesisHash, core.DefaultGoerliGenesisBlock().ToBlock(),
[]testcase{ []testcase{
{0, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {0, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block
{1561650, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block {1561650, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block
@ -94,7 +98,7 @@ func TestCreation(t *testing.T) {
// Sepolia test cases // Sepolia test cases
{ {
params.SepoliaChainConfig, params.SepoliaChainConfig,
params.SepoliaGenesisHash, core.DefaultSepoliaGenesisBlock().ToBlock(),
[]testcase{ []testcase{
{0, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin and first London block {0, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin and first London block
{1735370, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Last London block {1735370, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Last London block
@ -382,3 +386,55 @@ func TestEncoding(t *testing.T) {
} }
} }
} }
// Tests that time-based forks which are active at genesis are not included in
// forkid hash.
func TestTimeBasedForkInGenesis(t *testing.T) {
var (
time = uint64(1690475657)
genesis = types.NewBlockWithHeader(&types.Header{Time: time})
forkidHash = checksumToBytes(crc32.ChecksumIEEE(genesis.Hash().Bytes()))
config = func(shanghai, cancun uint64) *params.ChainConfig {
return &params.ChainConfig{
ChainID: big.NewInt(1337),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
MergeNetsplitBlock: big.NewInt(0),
ShanghaiTime: &shanghai,
CancunTime: &cancun,
Ethash: new(params.EthashConfig),
}
}
)
tests := []struct {
config *params.ChainConfig
want ID
}{
// Shanghai active before genesis, skip
{config(time-1, time+1), ID{Hash: forkidHash, Next: time + 1}},
// Shanghai active at genesis, skip
{config(time, time+1), ID{Hash: forkidHash, Next: time + 1}},
// Shanghai not active, skip
{config(time+1, time+2), ID{Hash: forkidHash, Next: time + 1}},
}
for _, tt := range tests {
if have := NewID(tt.config, genesis, 0, time); have != tt.want {
t.Fatalf("incorrect forkid hash: have %x, want %x", have, tt.want)
}
}
}

View File

@ -355,7 +355,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
number = head.Number.Uint64() number = head.Number.Uint64()
td = h.chain.GetTd(hash, number) td = h.chain.GetTd(hash, number)
) )
forkID := forkid.NewID(h.chain.Config(), genesis.Hash(), number, head.Time) forkID := forkid.NewID(h.chain.Config(), genesis, number, head.Time)
if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil {
peer.Log().Debug("Ethereum handshake failed", "err", err) peer.Log().Debug("Ethereum handshake failed", "err", err)
return err return err

View File

@ -61,6 +61,6 @@ func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) {
func currentENREntry(chain *core.BlockChain) *enrEntry { func currentENREntry(chain *core.BlockChain) *enrEntry {
head := chain.CurrentHeader() head := chain.CurrentHeader()
return &enrEntry{ return &enrEntry{
ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), head.Number.Uint64(), head.Time), ForkID: forkid.NewID(chain.Config(), chain.Genesis(), head.Number.Uint64(), head.Time),
} }
} }

View File

@ -40,7 +40,7 @@ func testHandshake(t *testing.T, protocol uint) {
genesis = backend.chain.Genesis() genesis = backend.chain.Genesis()
head = backend.chain.CurrentBlock() head = backend.chain.CurrentBlock()
td = backend.chain.GetTd(head.Hash(), head.Number.Uint64()) td = backend.chain.GetTd(head.Hash(), head.Number.Uint64())
forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time)
) )
tests := []struct { tests := []struct {
code uint64 code uint64

View File

@ -69,7 +69,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error {
p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
// Execute the LES handshake // Execute the LES handshake
forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time) forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.BlockChain().Genesis(), h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time)
if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil { if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil {
p.Log().Debug("Light Ethereum handshake failed", "err", err) p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err return err

View File

@ -124,8 +124,8 @@ func TestHandshake(t *testing.T) {
genesis = common.HexToHash("cafebabe") genesis = common.HexToHash("cafebabe")
chain1, chain2 = &fakeChain{}, &fakeChain{} chain1, chain2 = &fakeChain{}, &fakeChain{}
forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time) forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time)
forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time) forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time)
filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2)
) )

View File

@ -116,7 +116,7 @@ func (h *serverHandler) handle(p *clientPeer) error {
hash = head.Hash() hash = head.Hash()
number = head.Number.Uint64() number = head.Number.Uint64()
td = h.blockchain.GetTd(hash, number) td = h.blockchain.GetTd(hash, number)
forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), number, head.Time) forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis(), number, head.Time)
) )
if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil { if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil {
p.Log().Debug("Light Ethereum handshake failed", "err", err) p.Log().Debug("Light Ethereum handshake failed", "err", err)

View File

@ -499,7 +499,7 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t
head = server.handler.blockchain.CurrentHeader() head = server.handler.blockchain.CurrentHeader()
td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64())
) )
forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time) forkID := forkid.NewID(server.handler.blockchain.Config(), genesis, head.Number.Uint64(), head.Time)
tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID)
// Ensure the connection is established or exits when any error occurs // Ensure the connection is established or exits when any error occurs