feat(core): implement EIP-2935 #29465 #30924 (#2033)

This commit is contained in:
Daniel Liu 2026-02-12 11:31:26 +08:00 committed by GitHub
parent 50210d90e3
commit cc2109342c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 323 additions and 8 deletions

View file

@ -16,7 +16,11 @@
package common
import "math/big"
import (
"math/big"
"github.com/holiman/uint256"
)
// Common big integers often used
var (
@ -27,4 +31,6 @@ var (
Big32 = big.NewInt(32)
Big256 = big.NewInt(256)
Big257 = big.NewInt(257)
U2560 = uint256.NewInt(0)
)

View file

@ -193,7 +193,7 @@ func (b *BlockGen) OffsetTime(seconds int64) {
if b.header.Time <= b.parent.Header().Time {
panic("block time out of range")
}
chainReader := &fakeChainReader{config: b.config}
chainReader := &fakeChainReader{config: b.config, engine: b.engine}
b.header.Difficulty = b.engine.CalcDifficulty(chainReader, b.header.Time, b.parent.Header())
}
@ -214,7 +214,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
config = params.TestChainConfig
}
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
chainReader := &fakeChainReader{config: config}
chainReader := &fakeChainReader{config: config, engine: engine}
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, parent: parent, chain: blocks, statedb: statedb, config: config, engine: engine}
b.header = makeHeader(chainReader, parent, statedb, b.engine)
@ -231,10 +231,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
misc.ApplyDAOHardFork(statedb)
}
// Execute any user modifications to the block and finalize it
if config.IsPrague(b.header.Number) {
// EIP-2935
blockContext := NewEVMBlockContext(b.header, chainReader, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, config, vm.Config{})
ProcessParentBlockHash(b.header.ParentHash, evm, statedb)
}
// Execute any user modifications to the block
if gen != nil {
gen(i, b)
}
if b.engine != nil {
// Finalize and seal the block
block, _ := b.engine.Finalize(chainReader, b.header, statedb, statedb.Copy(), b.txs, b.uncles, b.receipts)
@ -323,6 +332,7 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
type fakeChainReader struct {
config *params.ChainConfig
engine consensus.Engine
}
// Config returns the chain configuration.
@ -330,6 +340,7 @@ func (cr *fakeChainReader) Config() *params.ChainConfig {
return cr.config
}
func (cr *fakeChainReader) Engine() consensus.Engine { return cr.engine }
func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil }
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }

View file

@ -465,8 +465,7 @@ func DefaultDevnetGenesisBlock() *Genesis {
}
}
// DeveloperGenesisBlock returns the 'geth --dev' genesis block. Note, this must
// be seeded with the
// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
// Override the default period to the user requested one
config := *params.AllXDPoSProtocolChanges
@ -489,6 +488,8 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
// Pre-deploy system contracts
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
},
}
}

View file

@ -17,6 +17,8 @@
package core
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"runtime"
@ -100,6 +102,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
signer := types.MakeSigner(p.config, blockNumber)
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
if p.config.IsPrague(block.Number()) {
ProcessParentBlockHash(block.ParentHash(), vmenv, tracingStateDB)
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
// check denylist txs after hf
@ -203,6 +209,10 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
signer := types.MakeSigner(p.config, blockNumber)
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
if p.config.IsPrague(block.Number()) {
ProcessParentBlockHash(block.ParentHash(), vmenv, tracingStateDB)
}
// Iterate over and process the individual transactions
receipts = make([]*types.Receipt, block.Transactions().Len())
for i, tx := range block.Transactions() {
@ -639,3 +649,95 @@ func InitSignerInTransactions(config *params.ChainConfig, header *types.Header,
}
wg.Wait()
}
// ProcessParentBlockHash writes the parent hash to the EIP-2935 history contract
// and enforces the expected code, with a one-time Prague backfill if missing.
func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb vm.StateDB) {
// Verify history contract code matches the expected bytecode
code := statedb.GetCode(params.HistoryStorageAddress)
if len(code) > 0 && !bytes.Equal(code, params.HistoryStorageCode) {
log.Error("History storage code mismatch",
"have", crypto.Keccak256Hash(code),
"want", crypto.Keccak256Hash(params.HistoryStorageCode),
)
panic("history storage code mismatch")
}
blockNumber := vmenv.Context.BlockNumber
if blockNumber == nil || !vmenv.ChainConfig().IsPrague(blockNumber) {
return
}
forkBlock := vmenv.ChainConfig().PragueBlock
if forkBlock == nil {
forkBlock = common.PragueBlock
}
if forkBlock == nil || blockNumber.Cmp(forkBlock) < 0 {
return
}
// Only deploy and backfill if the contract is missing at/after Prague activation.
if len(code) == 0 {
if !statedb.Exist(params.HistoryStorageAddress) {
statedb.CreateAccount(params.HistoryStorageAddress)
}
if statedb.GetNonce(params.HistoryStorageAddress) == 0 {
statedb.SetNonce(params.HistoryStorageAddress, 1)
}
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
if blockNumber.Sign() > 0 {
end := blockNumber.Uint64() - 1
start := end
if end+1 > params.HistoryServeWindow {
start = end + 1 - params.HistoryServeWindow
}
if forkBlock.Sign() > 0 {
forkStart := forkBlock.Uint64() - 1
if forkStart > start {
start = forkStart
}
}
for n := start; n <= end; n++ {
hash := vmenv.Context.GetHash(n)
if hash == (common.Hash{}) {
log.Debug("History backfill missing hash", "number", n)
continue
}
statedb.SetState(params.HistoryStorageAddress, historyStorageKey(n), hash)
}
}
}
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
}
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
}
}
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
To: &params.HistoryStorageAddress,
Data: prevHash.Bytes(),
}
vmenv.Reset(NewEVMTxContext(msg), statedb)
statedb.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
if err != nil {
panic(err)
}
statedb.Finalise(true)
}
func historyStorageKey(number uint64) common.Hash {
ringIndex := number % params.HistoryServeWindow
var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex)
return key
}

View file

@ -18,6 +18,7 @@ package core
import (
"crypto/ecdsa"
"encoding/binary"
"math"
"math/big"
"testing"
@ -26,10 +27,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/trie"
"github.com/holiman/uint256"
@ -318,7 +321,7 @@ func GenerateBadBlock(t *testing.T, parent *types.Block, engine consensus.Engine
header := &types.Header{
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
Difficulty: engine.CalcDifficulty(&fakeChainReader{config: config, engine: engine}, parent.Time()+10, &types.Header{
Number: parent.Number(),
Time: parent.Time(),
Difficulty: parent.Difficulty(),
@ -489,3 +492,146 @@ func TestApplyTransactionWithEVMTracer(t *testing.T) {
})
}
}
func TestProcessParentBlockHash(t *testing.T) {
var (
chainConfig = params.MergedTestChainConfig
hashA = common.Hash{0x01}
hashB = common.Hash{0x02}
header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Difficulty: big.NewInt(0)}
parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Difficulty: big.NewInt(0)}
coinbase = common.Address{}
)
test := func(statedb *state.StateDB) {
statedb.SetNonce(params.HistoryStorageAddress, 1)
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
statedb.IntermediateRoot(true)
vmContext := NewEVMBlockContext(header, nil, &coinbase)
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, nil, chainConfig, vm.Config{})
ProcessParentBlockHash(header.ParentHash, evm, statedb)
vmContext = NewEVMBlockContext(parent, nil, &coinbase)
evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, nil, chainConfig, vm.Config{})
ProcessParentBlockHash(parent.ParentHash, evm, statedb)
// make sure that the state is correct
if have := getParentBlockHash(statedb, 1); have != hashA {
t.Errorf("want parent hash %v, have %v", hashA, have)
}
if have := getParentBlockHash(statedb, 0); have != hashB {
t.Errorf("want parent hash %v, have %v", hashB, have)
}
}
t.Run("MPT", func(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
test(statedb)
})
}
func TestProcessParentBlockHashPragueGuard(t *testing.T) {
config := *params.MergedTestChainConfig
config.PragueBlock = big.NewInt(10)
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
blockNumber := big.NewInt(5)
random := common.Hash{}
blockContext := vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: func(uint64) common.Hash { return common.Hash{} },
Coinbase: common.Address{},
BlockNumber: blockNumber,
Time: 0,
Difficulty: big.NewInt(0),
GasLimit: 0,
BaseFee: nil,
Random: &random,
}
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
if code := statedb.GetCode(params.HistoryStorageAddress); len(code) != 0 {
t.Fatalf("unexpected history contract code predeploy: %x", code)
}
if have := getParentBlockHash(statedb, 0); have != (common.Hash{}) {
t.Fatalf("expected empty history slot, have %v", have)
}
}
func TestProcessParentBlockHashBackfillMissingHistory(t *testing.T) {
config := *params.MergedTestChainConfig
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
blockNumber := big.NewInt(int64(params.HistoryServeWindow + 1))
available := map[uint64]common.Hash{
1: {0x11},
100: {0x22},
}
random := common.Hash{}
blockContext := vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: func(n uint64) common.Hash {
if hash, ok := available[n]; ok {
return hash
}
return common.Hash{}
},
Coinbase: common.Address{},
BlockNumber: blockNumber,
Time: 0,
Difficulty: big.NewInt(0),
GasLimit: 0,
BaseFee: nil,
Random: &random,
}
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
if have := getParentBlockHash(statedb, 1); have != available[1] {
t.Fatalf("expected hash at slot 1, have %v", have)
}
if have := getParentBlockHash(statedb, 100); have != available[100] {
t.Fatalf("expected hash at slot 100, have %v", have)
}
if have := getParentBlockHash(statedb, 2); have != (common.Hash{}) {
t.Fatalf("expected empty history slot, have %v", have)
}
}
func TestProcessParentBlockHashCodeMismatchPanics(t *testing.T) {
config := *params.MergedTestChainConfig
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
statedb.SetCode(params.HistoryStorageAddress, []byte{0x01})
blockNumber := big.NewInt(1)
random := common.Hash{}
blockContext := vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: func(uint64) common.Hash { return common.Hash{} },
Coinbase: common.Address{},
BlockNumber: blockNumber,
Time: 0,
Difficulty: big.NewInt(0),
GasLimit: 0,
BaseFee: nil,
Random: &random,
}
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
defer func() {
if recover() == nil {
t.Fatal("expected panic on history storage code mismatch")
}
}()
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
}
func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash {
ringIndex := number % params.HistoryServeWindow
var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex)
return statedb.GetState(params.HistoryStorageAddress, key)
}

View file

@ -4,3 +4,4 @@
- [Validator Contract](xdc/validator.md)
- [Development environment](develop.md)
- [Solidity](solidity.md)
- [Upgrade notes](upgrade.md)

8
docs/upgrade.md Normal file
View file

@ -0,0 +1,8 @@
# Upgrade Notes
## Prague / EIP-2935
- The history storage contract is predeployed only in the developer genesis.
- For other networks, the contract is deployed at Prague activation by the system call during block processing and state access.
- Nodes upgrading after Prague will perform a one-time backfill of recent parent hashes into the ring buffer. If historical headers are pruned or unavailable, missing slots are skipped and only available hashes are filled.
- If you maintain a custom genesis and want predeployment, add an account entry for `HistoryStorageAddress` with `Nonce: 1` and `Code: HistoryStorageCode`.

View file

@ -206,6 +206,12 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
if err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
if eth.blockchain.Config().IsPrague(block.Number()) {
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, nil, eth.blockchain.Config(), vm.Config{})
core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
}
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, vm.BlockContext{}, statedb, release, nil
}

View file

@ -379,6 +379,12 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
failed = err
break
}
// Insert parent hash in history contract.
if api.backend.ChainConfig().IsPrague(next.Number()) {
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, nil, api.backend.ChainConfig(), vm.Config{})
core.ProcessParentBlockHash(next.ParentHash(), vmenv, statedb)
}
// Clean out any pending release functions of trace state. Note this
// step must be done after constructing tracing state, because the
// tracing state of block next depends on the parent state and construction
@ -513,6 +519,9 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
)
if chainConfig.IsPrague(block.Number()) {
core.ProcessParentBlockHash(block.ParentHash(), vm.NewEVM(vmctx, vm.TxContext{}, statedb, nil, chainConfig, vm.Config{}), statedb)
}
feeCapacity := statedb.GetTRC21FeeCapacityFromState()
for i, tx := range block.Transactions() {
if err := ctx.Err(); err != nil {
@ -593,6 +602,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
results = make([]*txTraceResult, len(txs))
)
if api.backend.ChainConfig().IsPrague(block.Number()) {
vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, nil, api.backend.ChainConfig(), vm.Config{})
core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
}
feeCapacity := statedb.GetTRC21FeeCapacityFromState()
for i, tx := range txs {
var balance *big.Int

View file

@ -787,6 +787,11 @@ func (w *worker) commitNewWork() {
if common.TIPSigning.Cmp(header.Number) == 0 {
work.state.DeleteAddress(common.BlockSignersBinary)
}
if w.config.IsPrague(header.Number) {
context := core.NewEVMBlockContext(header, w.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, w.current.state, nil, w.config, vm.Config{})
core.ProcessParentBlockHash(header.ParentHash, vmenv, w.current.state)
}
// won't grasp txs at checkpoint
var (
txs *transactionsByPriceAndNonce

View file

@ -16,7 +16,11 @@
package params
import "math/big"
import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
)
const (
GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations.
@ -160,6 +164,8 @@ const (
Bn256PairingBaseGasIstanbul uint64 = 45000 // Base price for an elliptic curve pairing check
Bn256PairingPerPointGasByzantium uint64 = 80000 // Byzantium per-point price for an elliptic curve pairing check
Bn256PairingPerPointGasIstanbul uint64 = 34000 // Per-point price for an elliptic curve pairing check
HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935.
)
var (
@ -170,3 +176,13 @@ var (
TargetGasLimit uint64 = XDCGenesisGasLimit // The artificial target
)
// System contracts.
var (
// SystemAddress is where the system-transaction is sent from as per EIP-2935
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// EIP-2935 - Serve historical block hashes from state
HistoryStorageAddress = common.HexToAddress("0x0000F90827F1C53a10cb7A02335B175320002935")
HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500")
)