Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: implement EIP-2935 #121

Merged
merged 11 commits into from
Nov 7, 2024
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 {
blukat29 marked this conversation as resolved.
Show resolved Hide resolved
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)
blukat29 marked this conversation as resolved.
Show resolved Hide resolved
}
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)
ian0371 marked this conversation as resolved.
Show resolved Hide resolved

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
blukat29 marked this conversation as resolved.
Show resolved Hide resolved
}
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)
ian0371 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading