diff --git a/common/big.go b/common/big.go index 65d4377bf70c..cbb562a28ef8 100644 --- a/common/big.go +++ b/common/big.go @@ -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) ) diff --git a/core/chain_makers.go b/core/chain_makers.go index 3b0e667110ef..6d96386bd47d 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -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 } diff --git a/core/genesis.go b/core/genesis.go index d30326a74b4b..89be0a80e9ce 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -425,8 +425,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 @@ -449,6 +448,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}, }, } } diff --git a/core/state_processor.go b/core/state_processor.go index b8f11552d60b..7305b639661c 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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 black-list 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: ¶ms.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 +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 7d183d6e6a31..072864c0250f 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -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) +} diff --git a/docs/README.md b/docs/README.md index 70b21760b578..f888f74b426b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,3 +4,4 @@ - [Validator Contract](xdc/validator.md) - [Development environment](develop.md) - [Solidity](solidity.md) +- [Upgrade notes](upgrade.md) diff --git a/docs/upgrade.md b/docs/upgrade.md new file mode 100644 index 000000000000..b6aa6228c358 --- /dev/null +++ b/docs/upgrade.md @@ -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`. diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 34a9b2f7f695..d1805502b982 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -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 } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 28d59a493e25..25b0e8ccfd16 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -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 diff --git a/miner/worker.go b/miner/worker.go index 5fa6f217b4db..e86eab4db9ab 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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 diff --git a/params/protocol_params.go b/params/protocol_params.go index 687b02ed64f6..4fa317b750b7 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -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") +)