diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 59294ba17..77f484045 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -24,6 +24,7 @@ package blockchain import ( "crypto/ecdsa" + "encoding/binary" "encoding/json" "fmt" "math/big" @@ -2205,3 +2206,56 @@ func TestTransientStorageReset(t *testing.T) { t.Fatalf("Unexpected dirty storage slot") } } + +func TestProcessParentBlockHash(t *testing.T) { + var ( + chainConfig = ¶ms.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) +} diff --git a/blockchain/chain_makers.go b/blockchain/chain_makers.go index f8265cbdc..081233cb6 100644 --- a/blockchain/chain_makers.go +++ b/blockchain/chain_makers.go @@ -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) diff --git a/blockchain/state_processor.go b/blockchain/state_processor.go index 081aa19c9..80a362b3d 100644 --- a/blockchain/state_processor.go +++ b/blockchain/state_processor.go @@ -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" ) @@ -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 @@ -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, + ¶ms.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 +} diff --git a/blockchain/vm/interface.go b/blockchain/vm/interface.go index 563ce69b3..4e1b8e576 100644 --- a/blockchain/vm/interface.go +++ b/blockchain/vm/interface.go @@ -104,4 +104,6 @@ type StateDB interface { GetTxHash() common.Hash GetKey(address common.Address) accountkey.AccountKey + + Finalise(bool, bool) } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 059782d5c..f44640a70 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -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 diff --git a/consensus/consensus.go b/consensus/consensus.go index 2cf07a629..e11981714 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -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 diff --git a/consensus/gxhash/consensus.go b/consensus/gxhash/consensus.go index 524a4efc6..8223c2a2f 100644 --- a/consensus/gxhash/consensus.go +++ b/consensus/gxhash/consensus.go @@ -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) { diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go index cef70842a..7999dd7b6 100644 --- a/consensus/istanbul/backend/engine.go +++ b/consensus/istanbul/backend/engine.go @@ -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" @@ -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. // diff --git a/consensus/mocks/engine_mock.go b/consensus/mocks/engine_mock.go index b186e5090..ee7ca50cb 100644 --- a/consensus/mocks/engine_mock.go +++ b/consensus/mocks/engine_mock.go @@ -167,6 +167,18 @@ func (mr *MockEngineMockRecorder) InitSnapshot() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitSnapshot", reflect.TypeOf((*MockEngine)(nil).InitSnapshot)) } +// Initialize mocks base method. +func (m *MockEngine) Initialize(arg0 consensus.ChainReader, arg1 *types.Header, arg2 *state.StateDB) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Initialize", arg0, arg1, arg2) +} + +// Initialize indicates an expected call of Initialize. +func (mr *MockEngineMockRecorder) Initialize(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockEngine)(nil).Initialize), arg0, arg1, arg2) +} + // Prepare mocks base method. func (m *MockEngine) Prepare(arg0 consensus.ChainReader, arg1 *types.Header) error { m.ctrl.T.Helper() diff --git a/node/cn/api.go b/node/cn/api.go index 23548a8bb..15de359cb 100644 --- a/node/cn/api.go +++ b/node/cn/api.go @@ -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 } diff --git a/node/cn/api_backend.go b/node/cn/api_backend.go index d5091f35a..63880ba48 100644 --- a/node/cn/api_backend.go +++ b/node/cn/api_backend.go @@ -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) { diff --git a/node/cn/state_accessor.go b/node/cn/state_accessor.go index 23e41d9d8..e24162be9 100644 --- a/node/cn/state_accessor.go +++ b/node/cn/state_accessor.go @@ -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") @@ -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 } diff --git a/node/cn/tracers/api.go b/node/cn/tracers/api.go index f3a54d929..2c2a3235f 100644 --- a/node/cn/tracers/api.go +++ b/node/cn/tracers/api.go @@ -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 @@ -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 @@ -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 @@ -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 } @@ -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) @@ -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()} @@ -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 } @@ -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()) @@ -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 @@ -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 } @@ -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 diff --git a/node/cn/tracers/api_test.go b/node/cn/tracers/api_test.go index 0a808ddde..76e04f629 100644 --- a/node/cn/tracers/api_test.go +++ b/node/cn/tracers/api_test.go @@ -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 diff --git a/params/protocol_params.go b/params/protocol_params.go index 9241708fa..c24b4e4f4 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -26,6 +26,8 @@ import ( "fmt" "math/big" "time" + + "github.com/kaiachain/kaia/common" ) var TargetGasLimit = GenesisGasLimit // The artificial target @@ -198,6 +200,8 @@ const ( // ZeroBaseFee exists for supporting Ethereum compatible data structure. ZeroBaseFee uint64 = 0 + + HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. ) const ( @@ -219,6 +223,13 @@ var ( GenesisBlockScore = big.NewInt(131072) // BlockScore of the Genesis block. MinimumBlockScore = big.NewInt(131072) // The minimum that the blockscore may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether blockscore should go up or not. + + // SystemAddress is where the system-transaction is sent from as per EIP-4788 + SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + + // EIP-2935 - Serve historical block hashes from state + HistoryStorageAddress = common.HexToAddress("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e") + HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500") ) // Parameters for execution time limit diff --git a/reward/supply_manager_test.go b/reward/supply_manager_test.go index 79135b5bb..0f6fbbbb4 100644 --- a/reward/supply_manager_test.go +++ b/reward/supply_manager_test.go @@ -808,6 +808,9 @@ func (s *supplyTestEngine) Prepare(chain consensus.ChainReader, header *types.He return nil } +func (s *supplyTestEngine) Initialize(chain consensus.ChainReader, header *types.Header, state *state.StateDB) { +} + // Simplfied version of istanbul Finalize for testing native token distribution. func (s *supplyTestEngine) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, receipts []*types.Receipt) (*types.Block, error) { header.BlockScore = common.Big1 diff --git a/work/worker.go b/work/worker.go index ff6bc7d25..7ed4e721c 100644 --- a/work/worker.go +++ b/work/worker.go @@ -573,6 +573,8 @@ func (self *worker) commitNewWork() { self.current.stateMu.Lock() defer self.current.stateMu.Unlock() + self.engine.Initialize(self.chain, header, self.current.state) + // Create the current work task work := self.current if self.nodetype == common.CONSENSUSNODE {