Skip to content

Commit cc21093

Browse files
authored
feat(core): implement EIP-2935 ethereum#29465 ethereum#30924 (#2033)
1 parent 50210d9 commit cc21093

File tree

11 files changed

+323
-8
lines changed

11 files changed

+323
-8
lines changed

common/big.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package common
1818

19-
import "math/big"
19+
import (
20+
"math/big"
21+
22+
"github.com/holiman/uint256"
23+
)
2024

2125
// Common big integers often used
2226
var (
@@ -27,4 +31,6 @@ var (
2731
Big32 = big.NewInt(32)
2832
Big256 = big.NewInt(256)
2933
Big257 = big.NewInt(257)
34+
35+
U2560 = uint256.NewInt(0)
3036
)

core/chain_makers.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (b *BlockGen) OffsetTime(seconds int64) {
193193
if b.header.Time <= b.parent.Header().Time {
194194
panic("block time out of range")
195195
}
196-
chainReader := &fakeChainReader{config: b.config}
196+
chainReader := &fakeChainReader{config: b.config, engine: b.engine}
197197
b.header.Difficulty = b.engine.CalcDifficulty(chainReader, b.header.Time, b.parent.Header())
198198
}
199199

@@ -214,7 +214,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
214214
config = params.TestChainConfig
215215
}
216216
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
217-
chainReader := &fakeChainReader{config: config}
217+
chainReader := &fakeChainReader{config: config, engine: engine}
218218
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
219219
b := &BlockGen{i: i, parent: parent, chain: blocks, statedb: statedb, config: config, engine: engine}
220220
b.header = makeHeader(chainReader, parent, statedb, b.engine)
@@ -231,10 +231,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
231231
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
232232
misc.ApplyDAOHardFork(statedb)
233233
}
234-
// Execute any user modifications to the block and finalize it
234+
235+
if config.IsPrague(b.header.Number) {
236+
// EIP-2935
237+
blockContext := NewEVMBlockContext(b.header, chainReader, &b.header.Coinbase)
238+
evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, config, vm.Config{})
239+
ProcessParentBlockHash(b.header.ParentHash, evm, statedb)
240+
}
241+
242+
// Execute any user modifications to the block
235243
if gen != nil {
236244
gen(i, b)
237245
}
246+
238247
if b.engine != nil {
239248
// Finalize and seal the block
240249
block, _ := b.engine.Finalize(chainReader, b.header, statedb, statedb.Copy(), b.txs, b.uncles, b.receipts)
@@ -323,13 +332,15 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
323332

324333
type fakeChainReader struct {
325334
config *params.ChainConfig
335+
engine consensus.Engine
326336
}
327337

328338
// Config returns the chain configuration.
329339
func (cr *fakeChainReader) Config() *params.ChainConfig {
330340
return cr.config
331341
}
332342

343+
func (cr *fakeChainReader) Engine() consensus.Engine { return cr.engine }
333344
func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil }
334345
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil }
335346
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }

core/genesis.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,7 @@ func DefaultDevnetGenesisBlock() *Genesis {
465465
}
466466
}
467467

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

core/state_processor.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package core
1818

1919
import (
20+
"bytes"
21+
"encoding/binary"
2022
"fmt"
2123
"math/big"
2224
"runtime"
@@ -100,6 +102,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
100102
signer := types.MakeSigner(p.config, blockNumber)
101103
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
102104

105+
if p.config.IsPrague(block.Number()) {
106+
ProcessParentBlockHash(block.ParentHash(), vmenv, tracingStateDB)
107+
}
108+
103109
// Iterate over and process the individual transactions
104110
for i, tx := range block.Transactions() {
105111
// check denylist txs after hf
@@ -203,6 +209,10 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
203209
signer := types.MakeSigner(p.config, blockNumber)
204210
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
205211

212+
if p.config.IsPrague(block.Number()) {
213+
ProcessParentBlockHash(block.ParentHash(), vmenv, tracingStateDB)
214+
}
215+
206216
// Iterate over and process the individual transactions
207217
receipts = make([]*types.Receipt, block.Transactions().Len())
208218
for i, tx := range block.Transactions() {
@@ -639,3 +649,95 @@ func InitSignerInTransactions(config *params.ChainConfig, header *types.Header,
639649
}
640650
wg.Wait()
641651
}
652+
653+
// ProcessParentBlockHash writes the parent hash to the EIP-2935 history contract
654+
// and enforces the expected code, with a one-time Prague backfill if missing.
655+
func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb vm.StateDB) {
656+
// Verify history contract code matches the expected bytecode
657+
code := statedb.GetCode(params.HistoryStorageAddress)
658+
if len(code) > 0 && !bytes.Equal(code, params.HistoryStorageCode) {
659+
log.Error("History storage code mismatch",
660+
"have", crypto.Keccak256Hash(code),
661+
"want", crypto.Keccak256Hash(params.HistoryStorageCode),
662+
)
663+
panic("history storage code mismatch")
664+
}
665+
666+
blockNumber := vmenv.Context.BlockNumber
667+
if blockNumber == nil || !vmenv.ChainConfig().IsPrague(blockNumber) {
668+
return
669+
}
670+
forkBlock := vmenv.ChainConfig().PragueBlock
671+
if forkBlock == nil {
672+
forkBlock = common.PragueBlock
673+
}
674+
if forkBlock == nil || blockNumber.Cmp(forkBlock) < 0 {
675+
return
676+
}
677+
678+
// Only deploy and backfill if the contract is missing at/after Prague activation.
679+
if len(code) == 0 {
680+
if !statedb.Exist(params.HistoryStorageAddress) {
681+
statedb.CreateAccount(params.HistoryStorageAddress)
682+
}
683+
if statedb.GetNonce(params.HistoryStorageAddress) == 0 {
684+
statedb.SetNonce(params.HistoryStorageAddress, 1)
685+
}
686+
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
687+
688+
if blockNumber.Sign() > 0 {
689+
end := blockNumber.Uint64() - 1
690+
start := end
691+
if end+1 > params.HistoryServeWindow {
692+
start = end + 1 - params.HistoryServeWindow
693+
}
694+
if forkBlock.Sign() > 0 {
695+
forkStart := forkBlock.Uint64() - 1
696+
if forkStart > start {
697+
start = forkStart
698+
}
699+
}
700+
for n := start; n <= end; n++ {
701+
hash := vmenv.Context.GetHash(n)
702+
if hash == (common.Hash{}) {
703+
log.Debug("History backfill missing hash", "number", n)
704+
continue
705+
}
706+
statedb.SetState(params.HistoryStorageAddress, historyStorageKey(n), hash)
707+
}
708+
}
709+
}
710+
711+
if tracer := vmenv.Config.Tracer; tracer != nil {
712+
if tracer.OnSystemCallStart != nil {
713+
tracer.OnSystemCallStart()
714+
}
715+
if tracer.OnSystemCallEnd != nil {
716+
defer tracer.OnSystemCallEnd()
717+
}
718+
}
719+
720+
msg := &Message{
721+
From: params.SystemAddress,
722+
GasLimit: 30_000_000,
723+
GasPrice: common.Big0,
724+
GasFeeCap: common.Big0,
725+
GasTipCap: common.Big0,
726+
To: &params.HistoryStorageAddress,
727+
Data: prevHash.Bytes(),
728+
}
729+
vmenv.Reset(NewEVMTxContext(msg), statedb)
730+
statedb.AddAddressToAccessList(params.HistoryStorageAddress)
731+
_, _, err := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
732+
if err != nil {
733+
panic(err)
734+
}
735+
statedb.Finalise(true)
736+
}
737+
738+
func historyStorageKey(number uint64) common.Hash {
739+
ringIndex := number % params.HistoryServeWindow
740+
var key common.Hash
741+
binary.BigEndian.PutUint64(key[24:], ringIndex)
742+
return key
743+
}

core/state_processor_test.go

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package core
1818

1919
import (
2020
"crypto/ecdsa"
21+
"encoding/binary"
2122
"math"
2223
"math/big"
2324
"testing"
@@ -26,10 +27,12 @@ import (
2627
"github.com/XinFinOrg/XDPoSChain/consensus"
2728
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
2829
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
30+
"github.com/XinFinOrg/XDPoSChain/core/state"
2931
"github.com/XinFinOrg/XDPoSChain/core/tracing"
3032
"github.com/XinFinOrg/XDPoSChain/core/types"
3133
"github.com/XinFinOrg/XDPoSChain/core/vm"
3234
"github.com/XinFinOrg/XDPoSChain/crypto"
35+
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
3336
"github.com/XinFinOrg/XDPoSChain/params"
3437
"github.com/XinFinOrg/XDPoSChain/trie"
3538
"github.com/holiman/uint256"
@@ -318,7 +321,7 @@ func GenerateBadBlock(t *testing.T, parent *types.Block, engine consensus.Engine
318321
header := &types.Header{
319322
ParentHash: parent.Hash(),
320323
Coinbase: parent.Coinbase(),
321-
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
324+
Difficulty: engine.CalcDifficulty(&fakeChainReader{config: config, engine: engine}, parent.Time()+10, &types.Header{
322325
Number: parent.Number(),
323326
Time: parent.Time(),
324327
Difficulty: parent.Difficulty(),
@@ -489,3 +492,146 @@ func TestApplyTransactionWithEVMTracer(t *testing.T) {
489492
})
490493
}
491494
}
495+
496+
func TestProcessParentBlockHash(t *testing.T) {
497+
var (
498+
chainConfig = params.MergedTestChainConfig
499+
hashA = common.Hash{0x01}
500+
hashB = common.Hash{0x02}
501+
header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Difficulty: big.NewInt(0)}
502+
parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Difficulty: big.NewInt(0)}
503+
coinbase = common.Address{}
504+
)
505+
test := func(statedb *state.StateDB) {
506+
statedb.SetNonce(params.HistoryStorageAddress, 1)
507+
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
508+
statedb.IntermediateRoot(true)
509+
510+
vmContext := NewEVMBlockContext(header, nil, &coinbase)
511+
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, nil, chainConfig, vm.Config{})
512+
ProcessParentBlockHash(header.ParentHash, evm, statedb)
513+
514+
vmContext = NewEVMBlockContext(parent, nil, &coinbase)
515+
evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, nil, chainConfig, vm.Config{})
516+
ProcessParentBlockHash(parent.ParentHash, evm, statedb)
517+
518+
// make sure that the state is correct
519+
if have := getParentBlockHash(statedb, 1); have != hashA {
520+
t.Errorf("want parent hash %v, have %v", hashA, have)
521+
}
522+
if have := getParentBlockHash(statedb, 0); have != hashB {
523+
t.Errorf("want parent hash %v, have %v", hashB, have)
524+
}
525+
}
526+
t.Run("MPT", func(t *testing.T) {
527+
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
528+
test(statedb)
529+
})
530+
}
531+
532+
func TestProcessParentBlockHashPragueGuard(t *testing.T) {
533+
config := *params.MergedTestChainConfig
534+
config.PragueBlock = big.NewInt(10)
535+
536+
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
537+
blockNumber := big.NewInt(5)
538+
random := common.Hash{}
539+
blockContext := vm.BlockContext{
540+
CanTransfer: CanTransfer,
541+
Transfer: Transfer,
542+
GetHash: func(uint64) common.Hash { return common.Hash{} },
543+
Coinbase: common.Address{},
544+
BlockNumber: blockNumber,
545+
Time: 0,
546+
Difficulty: big.NewInt(0),
547+
GasLimit: 0,
548+
BaseFee: nil,
549+
Random: &random,
550+
}
551+
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
552+
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
553+
554+
if code := statedb.GetCode(params.HistoryStorageAddress); len(code) != 0 {
555+
t.Fatalf("unexpected history contract code predeploy: %x", code)
556+
}
557+
if have := getParentBlockHash(statedb, 0); have != (common.Hash{}) {
558+
t.Fatalf("expected empty history slot, have %v", have)
559+
}
560+
}
561+
562+
func TestProcessParentBlockHashBackfillMissingHistory(t *testing.T) {
563+
config := *params.MergedTestChainConfig
564+
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
565+
blockNumber := big.NewInt(int64(params.HistoryServeWindow + 1))
566+
available := map[uint64]common.Hash{
567+
1: {0x11},
568+
100: {0x22},
569+
}
570+
571+
random := common.Hash{}
572+
blockContext := vm.BlockContext{
573+
CanTransfer: CanTransfer,
574+
Transfer: Transfer,
575+
GetHash: func(n uint64) common.Hash {
576+
if hash, ok := available[n]; ok {
577+
return hash
578+
}
579+
return common.Hash{}
580+
},
581+
Coinbase: common.Address{},
582+
BlockNumber: blockNumber,
583+
Time: 0,
584+
Difficulty: big.NewInt(0),
585+
GasLimit: 0,
586+
BaseFee: nil,
587+
Random: &random,
588+
}
589+
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
590+
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
591+
592+
if have := getParentBlockHash(statedb, 1); have != available[1] {
593+
t.Fatalf("expected hash at slot 1, have %v", have)
594+
}
595+
if have := getParentBlockHash(statedb, 100); have != available[100] {
596+
t.Fatalf("expected hash at slot 100, have %v", have)
597+
}
598+
if have := getParentBlockHash(statedb, 2); have != (common.Hash{}) {
599+
t.Fatalf("expected empty history slot, have %v", have)
600+
}
601+
}
602+
603+
func TestProcessParentBlockHashCodeMismatchPanics(t *testing.T) {
604+
config := *params.MergedTestChainConfig
605+
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())))
606+
statedb.SetCode(params.HistoryStorageAddress, []byte{0x01})
607+
608+
blockNumber := big.NewInt(1)
609+
random := common.Hash{}
610+
blockContext := vm.BlockContext{
611+
CanTransfer: CanTransfer,
612+
Transfer: Transfer,
613+
GetHash: func(uint64) common.Hash { return common.Hash{} },
614+
Coinbase: common.Address{},
615+
BlockNumber: blockNumber,
616+
Time: 0,
617+
Difficulty: big.NewInt(0),
618+
GasLimit: 0,
619+
BaseFee: nil,
620+
Random: &random,
621+
}
622+
evmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, nil, &config, vm.Config{})
623+
624+
defer func() {
625+
if recover() == nil {
626+
t.Fatal("expected panic on history storage code mismatch")
627+
}
628+
}()
629+
ProcessParentBlockHash(common.Hash{0x01}, evmenv, statedb)
630+
}
631+
632+
func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash {
633+
ringIndex := number % params.HistoryServeWindow
634+
var key common.Hash
635+
binary.BigEndian.PutUint64(key[24:], ringIndex)
636+
return statedb.GetState(params.HistoryStorageAddress, key)
637+
}

0 commit comments

Comments
 (0)