Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion common/big.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

package common

import "math/big"
import (
"math/big"

"github.com/holiman/uint256"
)

// Common big integers often used
var (
Expand All @@ -27,4 +31,6 @@ var (
Big32 = big.NewInt(32)
Big256 = big.NewInt(256)
Big257 = big.NewInt(257)

U2560 = uint256.NewInt(0)
)
17 changes: 14 additions & 3 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -323,13 +332,15 @@ 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.
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 }
Expand Down
5 changes: 3 additions & 2 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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},
},
}
}
Expand Down
102 changes: 102 additions & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package core

import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"runtime"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
}
148 changes: 147 additions & 1 deletion core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package core

import (
"crypto/ecdsa"
"encoding/binary"
"math"
"math/big"
"testing"
Expand All @@ -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"
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)
}
Loading