Skip to content
54 changes: 54 additions & 0 deletions blockchain/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package blockchain

import (
"crypto/ecdsa"
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
Expand Down Expand Up @@ -2205,3 +2206,56 @@ func TestTransientStorageReset(t *testing.T) {
t.Fatalf("Unexpected dirty storage slot")
}
}

func TestProcessParentBlockHash(t *testing.T) {
var (
chainConfig = &params.ChainConfig{
ShanghaiCompatibleBlock: common.Big0, // Shanghai fork is necesasry because `params.HistoryStorageCode` contains `PUSH0(0x5f)` instruction
}
hashA = common.Hash{0x01}
hashB = common.Hash{0x02}
header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Time: common.Big0, BlockScore: common.Big0}
parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Time: common.Big0, BlockScore: common.Big0}
coinbase = common.Address{}
rules = params.Rules{}
db = state.NewDatabase(database.NewMemoryDBManager())
)
test := func(statedb *state.StateDB) {
if err := statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode); err != nil {
t.Error(err)
}
statedb.SetNonce(params.HistoryStorageAddress, 1)
statedb.IntermediateRoot(true)

vmContext := NewEVMBlockContext(header, nil, &coinbase)
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, &vm.Config{})
if err := ProcessParentBlockHash(header, evm, statedb, rules); err != nil {
t.Error(err)
}

vmContext = NewEVMBlockContext(parent, nil, &coinbase)
evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, &vm.Config{})
if err := ProcessParentBlockHash(parent, evm, statedb, rules); err != nil {
t.Error(err)
}

// 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.EmptyRootHashOriginal, db, nil, nil)
test(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)
}
2 changes: 2 additions & 0 deletions blockchain/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
b := &BlockGen{i: i, parent: parent, chain: blocks, chainReader: blockchain, statedb: stateDB, config: config, engine: engine}
b.header = makeHeader(b.chainReader, parent, stateDB, b.engine)

engine.Initialize(blockchain, b.header, stateDB)

// Execute any user modifications to the block and finalize it
if gen != nil {
gen(i, b)
Expand Down
37 changes: 37 additions & 0 deletions blockchain/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/kaiachain/kaia/blockchain/state"
"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/blockchain/vm"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/consensus"
"github.com/kaiachain/kaia/params"
)
Expand Down Expand Up @@ -74,6 +75,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
processStats ProcessStats
)

p.engine.Initialize(p.bc, header, statedb)

// Extract author from the header
author, _ := p.bc.Engine().Author(header) // Ignore error, we're past header validation

Expand All @@ -99,3 +102,37 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg

return receipts, allLogs, *usedGas, internalTxTraces, processStats, nil
}

// ProcessParentBlockHash stores the parent block hash in the history storage contract
// as per EIP-2935.
func ProcessParentBlockHash(header *types.Header, vmenv *vm.EVM, statedb vm.StateDB, rules params.Rules) error {
var (
from = params.SystemAddress
data = header.ParentHash.Bytes()
gasLimit = uint64(30_000_000)
)

intrinsicGas, err := types.IntrinsicGas(data, nil, false, rules)
if err != nil {
return err
}

msg := types.NewMessage(
from,
&params.HistoryStorageAddress,
0,
common.Big0,
gasLimit,
common.Big0,
data,
false,
intrinsicGas,
nil,
)

vmenv.Reset(NewEVMTxContext(msg, header, vmenv.ChainConfig()), statedb)
statedb.AddAddressToAccessList(params.HistoryStorageAddress)
vmenv.Call(vm.AccountRef(from), *msg.To(), msg.Data(), gasLimit, common.Big0)
statedb.Finalise(true, true)
return nil
}
2 changes: 2 additions & 0 deletions blockchain/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,6 @@ type StateDB interface {
GetTxHash() common.Hash

GetKey(address common.Address) accountkey.AccountKey

Finalise(bool, bool)
}
3 changes: 3 additions & 0 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro

func (c *Clique) InitSnapshot() {}

func (c *Clique) Initialize(chain consensus.ChainReader, header *types.Header, state *state.StateDB) {
}

// Finalize implements consensus.Engine and returns the final block.
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, receipts []*types.Receipt) (*types.Block, error) {
// No block rewards in PoA, so the state remains as is
Expand Down
3 changes: 3 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type Engine interface {
// rules of a particular engine. The changes are executed inline.
Prepare(chain ChainReader, header *types.Header) error

// Initialize runs any pre-transaction state modifications (e.g., EIP-2539)
Initialize(chain ChainReader, header *types.Header, state *state.StateDB)

// Finalize runs any post-transaction state modifications (e.g. block rewards)
// and assembles the final block.
// Note: The block header and state database might be updated to reflect any
Expand Down
3 changes: 3 additions & 0 deletions consensus/gxhash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,9 @@ func (gxhash *Gxhash) Prepare(chain consensus.ChainReader, header *types.Header)
return nil
}

func (gxhash *Gxhash) Initialize(chain consensus.ChainReader, header *types.Header, state *state.StateDB) {
}

// Finalize implements consensus.Engine, accumulating the block rewards,
// setting the final state and assembling the block.
func (gxhash *Gxhash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, receipts []*types.Receipt) (*types.Block, error) {
Expand Down
11 changes: 11 additions & 0 deletions consensus/istanbul/backend/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import (
"time"

lru "github.com/hashicorp/golang-lru"
"github.com/kaiachain/kaia/blockchain"
"github.com/kaiachain/kaia/blockchain/state"
"github.com/kaiachain/kaia/blockchain/system"
"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/blockchain/vm"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/consensus"
"github.com/kaiachain/kaia/consensus/istanbul"
Expand Down Expand Up @@ -488,6 +490,15 @@ func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) er
return nil
}

func (sb *backend) Initialize(chain consensus.ChainReader, header *types.Header, state *state.StateDB) {
// [EIP-2935] stores the parent block hash in the history storage contract
if chain.Config().IsPragueForkEnabled(header.Number) {
context := blockchain.NewEVMBlockContext(header, chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, state, chain.Config(), &vm.Config{})
blockchain.ProcessParentBlockHash(header, vmenv, state, chain.Config().Rules(header.Number))
}
}

// Finalize runs any post-transaction state modifications (e.g. block rewards)
// and assembles the final block.
//
Expand Down
12 changes: 12 additions & 0 deletions consensus/mocks/engine_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion node/cn/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common
if block == nil {
return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash)
}
_, _, _, statedb, release, err := api.cn.stateAtTransaction(block, txIndex, 0)
_, _, _, statedb, release, err := api.cn.stateAtTransaction(block, txIndex, 0, nil, true, false)
if err != nil {
return StorageRangeResult{}, err
}
Expand Down
4 changes: 2 additions & 2 deletions node/cn/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ func (b *CNAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, ree
return b.cn.stateAtBlock(block, reexec, base, readOnly, preferDisk)
}

func (b *CNAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.cn.stateAtTransaction(block, txIndex, reexec)
func (b *CNAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.cn.stateAtTransaction(block, txIndex, reexec, base, readOnly, preferDisk)
}

func (b *CNAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
Expand Down
7 changes: 5 additions & 2 deletions node/cn/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (cn *CN) stateAtBlock(block *types.Block, reexec uint64, base *state.StateD
}

// stateAtTransaction returns the execution environment of a certain transaction.
func (cn *CN) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, tracers.StateReleaseFunc, error) {
func (cn *CN) stateAtTransaction(block *types.Block, txIndex int, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, tracers.StateReleaseFunc, error) {
// Short circuit if it's genesis block.
if block.NumberU64() == 0 {
return nil, vm.BlockContext{}, vm.TxContext{}, nil, nil, errors.New("no transaction in genesis")
Expand All @@ -210,10 +210,13 @@ func (cn *CN) stateAtTransaction(block *types.Block, txIndex int, reexec uint64)
}
// Lookup the statedb of parent block from the live database,
// otherwise regenerate it on the flight.
statedb, release, err := cn.stateAtBlock(parent, reexec, nil, true, false)
statedb, release, err := cn.stateAtBlock(parent, reexec, base, readOnly, preferDisk)
if err != nil {
return nil, vm.BlockContext{}, vm.TxContext{}, nil, nil, err
}
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
cn.engine.Initialize(cn.blockchain, block.Header(), statedb)

if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, vm.BlockContext{}, vm.TxContext{}, statedb, release, nil
}
Expand Down
35 changes: 16 additions & 19 deletions node/cn/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type Backend interface {
// N.B: For executing transactions on block N, the required stateRoot is block N-1,
// so this method should be called with the parent.
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, StateReleaseFunc, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, StateReleaseFunc, error)
}

// CommonAPI contains
Expand Down Expand Up @@ -425,17 +425,12 @@ func (api *CommonAPI) traceChain(start, end *types.Block, config *TraceConfig, n
logged = time.Now()
logger.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin))
}
// Retrieve the parent block and target block for tracing.
block, err := api.blockByNumber(localctx, rpc.BlockNumber(number))
if err != nil {
failed = err
break
}
next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1))
if err != nil {
failed = err
break
}

// Prepare the statedb for tracing. Don't use the live database for
// tracing to avoid persisting state junks into the database. Switch
// over to `preferDisk` mode only if the memory usage exceeds the
Expand All @@ -446,11 +441,12 @@ func (api *CommonAPI) traceChain(start, end *types.Block, config *TraceConfig, n
s1, s2, s3 := statedb.Database().TrieDB().Size()
preferDisk = s1+s2+s3 > defaultTracechainMemLimit
}
statedb, release, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk)
_, _, _, statedb, release, err = api.backend.StateAtTransaction(localctx, next, 0, reexec, statedb, false, preferDisk)
if err != nil {
failed = err
break
}

// Clean out any pending derefs. Note this step must be done after
// constructing tracing state, because the tracing state of block
// next depends on the parent state and construction may fail if
Expand Down Expand Up @@ -623,17 +619,12 @@ func (api *CommonAPI) traceBlock(ctx context.Context, block *types.Block, config
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
}
// Create the parent state database
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
if err != nil {
return nil, err
}
reexec := defaultTraceReexec
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}

statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
_, _, _, statedb, release, err := api.backend.StateAtTransaction(ctx, block, 0, reexec, nil, true, false)
if err != nil {
return nil, err
}
Expand All @@ -647,7 +638,11 @@ func (api *CommonAPI) traceBlock(ctx context.Context, block *types.Block, config

pend = new(sync.WaitGroup)
jobs = make(chan *txTraceTask, len(txs))

header = block.Header()
blockCtx = blockchain.NewEVMBlockContext(header, newChainContext(ctx, api.backend), nil)
)

threads := runtime.NumCPU()
if threads > len(txs) {
threads = len(txs)
Expand All @@ -667,7 +662,6 @@ func (api *CommonAPI) traceBlock(ctx context.Context, block *types.Block, config
}

txCtx := blockchain.NewEVMTxContext(msg, block.Header(), api.backend.ChainConfig())
blockCtx := blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil)
res, err := api.traceTx(ctx, msg, blockCtx, txCtx, task.statedb, config)
if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
Expand Down Expand Up @@ -732,7 +726,7 @@ func (api *CommonAPI) standardTraceBlockToFile(ctx context.Context, block *types
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
_, _, _, statedb, release, err := api.backend.StateAtTransaction(ctx, parent, 0, reexec, nil, true, false)
if err != nil {
return nil, err
}
Expand All @@ -751,6 +745,9 @@ func (api *CommonAPI) standardTraceBlockToFile(ctx context.Context, block *types
}
logConfig.Debug = true

header := block.Header()
blockCtx := blockchain.NewEVMBlockContext(header, newChainContext(ctx, api.backend), nil)

// Execute transaction, either tracing all or just the requested one
var (
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
Expand All @@ -765,8 +762,7 @@ func (api *CommonAPI) standardTraceBlockToFile(ctx context.Context, block *types
}

var (
txCtx = blockchain.NewEVMTxContext(msg, block.Header(), api.backend.ChainConfig())
blockCtx = blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil)
txCtx = blockchain.NewEVMTxContext(msg, block.Header(), api.backend.ChainConfig())

vmConf vm.Config
dump *os.File
Expand Down Expand Up @@ -850,7 +846,7 @@ func (api *CommonAPI) TraceTransaction(ctx context.Context, hash common.Hash, co
if err != nil {
return nil, err
}
msg, blockCtx, txCtx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec)
msg, blockCtx, txCtx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec, nil, true, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -891,6 +887,7 @@ func (api *CommonAPI) TraceCall(ctx context.Context, args kaiaapi.CallArgs, bloc
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}

statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion node/cn/tracers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reex
return statedb, release, nil
}

func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, StateReleaseFunc, error) {
func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, StateReleaseFunc, error) {
parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, vm.BlockContext{}, vm.TxContext{}, nil, nil, errBlockNotFound
Expand Down
Loading