diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index d2053943bb54703f041a6fb1dcb87caa510ed72c..6c545cff39bd06f0e83046432123b1a0eef73ac9 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -66,9 +66,9 @@ var ( // ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, // DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender type SimulatedBackend struct { - m *stages.MockSentry - getHeader func(hash common.Hash, number uint64) *types.Header - checkTEVM func(common.Hash) (bool, error) + m *stages.MockSentry + getHeader func(hash common.Hash, number uint64) *types.Header + contractHasTEVM func(common.Hash) (bool, error) mu sync.Mutex prependBlock *types.Block @@ -105,7 +105,7 @@ func NewSimulatedBackendWithConfig(alloc core.GenesisAlloc, config *params.Chain return h }, } - backend.checkTEVM = ethdb.GetCheckTEVM(olddb.NewObjectDatabase(m.DB)) + backend.contractHasTEVM = ethdb.GetHasTEVM(olddb.NewObjectDatabase(m.DB)) backend.events = filters.NewEventSystem(&filterBackend{m.DB, backend}) backend.emptyPendingBlock() return backend @@ -654,7 +654,7 @@ func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg msg := callMsg{call} txContext := core.NewEVMTxContext(msg) - evmContext := core.NewEVMBlockContext(block.Header(), b.getHeader, b.m.Engine, nil, b.checkTEVM) + evmContext := core.NewEVMBlockContext(block.Header(), b.getHeader, b.m.Engine, nil, b.contractHasTEVM) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. vmEnv := vm.NewEVM(evmContext, txContext, statedb, b.m.ChainConfig, vm.Config{}) @@ -687,7 +687,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx types.Transac &b.pendingHeader.Coinbase, b.gasPool, b.pendingState, state.NewNoopWriter(), b.pendingHeader, tx, - &b.pendingHeader.GasUsed, vm.Config{}, b.checkTEVM); err != nil { + &b.pendingHeader.GasUsed, vm.Config{}, b.contractHasTEVM); err != nil { return err } //fmt.Printf("==== Start producing block %d\n", (b.prependBlock.NumberU64() + 1)) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 5d2cc74e0b115a1efedc2cc6020c264859ca6217..0a9d075b4abe381a4aecc6f27745d5728f1eed48 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -127,15 +127,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, ) gaspool.AddGas(pre.Env.GasLimit) vmContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: pre.Env.Coinbase, - BlockNumber: pre.Env.Number, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, - Time: pre.Env.Timestamp, - Difficulty: pre.Env.Difficulty, - GasLimit: pre.Env.GasLimit, - GetHash: getHash, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: pre.Env.Coinbase, + BlockNumber: pre.Env.Number, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, + Time: pre.Env.Timestamp, + Difficulty: pre.Env.Difficulty, + GasLimit: pre.Env.GasLimit, + GetHash: getHash, } // If currentBaseFee is defined, add it to the vmContext. if pre.Env.BaseFee != nil { diff --git a/cmd/hack/hack.go b/cmd/hack/hack.go index 77a21acf81c6f0c29d08e0edfa53448d63f5da0e..954b73c29698ea2a351f56d4edca7403f62329e0 100644 --- a/cmd/hack/hack.go +++ b/cmd/hack/hack.go @@ -2187,7 +2187,8 @@ func scanReceipts(chaindata string, block uint64) error { intraBlockState := state.New(dbstate) getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - receipts1, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, nil /* checkTEVM */, block, vmConfig) + contractHasTEVM := ethdb.GetHasTEVM(tx) + receipts1, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, contractHasTEVM, block, vmConfig) if err1 != nil { return err1 } @@ -2219,7 +2220,7 @@ func scanReceipts(chaindata string, block uint64) error { } func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWriter state.StateWriter, - chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, checkTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { + chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, contractHasTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { header := block.Header() vmConfig.TraceJumpDest = true engine := ethash.NewFullFaker() @@ -2232,7 +2233,7 @@ func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWrit rules := chainConfig.Rules(block.NumberU64()) for i, tx := range block.Transactions() { ibs.Prepare(tx.Hash(), block.Hash(), i) - receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, checkTEVM) + receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, contractHasTEVM) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%x] failed: %v", i, tx.Hash(), err) } diff --git a/cmd/rpcdaemon/commands/debug_api.go b/cmd/rpcdaemon/commands/debug_api.go index 6eb4c56c9f0bfc76acc4b137ee68050d97a70bc9..8902cd4b2d83b5693c7274a5c02a2b3c3a230153 100644 --- a/cmd/rpcdaemon/commands/debug_api.go +++ b/cmd/rpcdaemon/commands/debug_api.go @@ -72,8 +72,8 @@ func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash co getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - checkTEVM := ethdb.GetCheckTEVM(tx) - _, _, _, _, stateReader, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, checkTEVM, ethash.NewFaker(), tx, blockHash, txIndex) + contractHasTEVM := ethdb.GetHasTEVM(tx) + _, _, _, _, stateReader, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, blockHash, txIndex) if err != nil { return StorageRangeResult{}, err } @@ -235,8 +235,8 @@ func (api *PrivateDebugAPIImpl) AccountAt(ctx context.Context, blockHash common. getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - checkTEVM := ethdb.GetCheckTEVM(tx) - _, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, checkTEVM, ethash.NewFaker(), tx, blockHash, txIndex) + contractHasTEVM := ethdb.GetHasTEVM(tx) + _, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, blockHash, txIndex) if err != nil { return nil, err } diff --git a/cmd/rpcdaemon/commands/eth_block.go b/cmd/rpcdaemon/commands/eth_block.go index c126520956b8348c6c978b07f7c55fe85d5c2362..092bff555621f54d4785e87866410e514936bab7 100644 --- a/cmd/rpcdaemon/commands/eth_block.go +++ b/cmd/rpcdaemon/commands/eth_block.go @@ -14,6 +14,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" "github.com/ledgerwatch/erigon/turbo/rpchelper" @@ -91,7 +92,7 @@ func (api *APIImpl) CallBundle(ctx context.Context, txHashes []common.Hash, stat return nil, err } - blockCtx, txCtx := transactions.GetEvmContext(firstMsg, header, stateBlockNumberOrHash.RequireCanonical, tx) + blockCtx, txCtx := transactions.GetEvmContext(firstMsg, header, stateBlockNumberOrHash.RequireCanonical, tx, ethdb.GetHasTEVM(tx)) evm := vm.NewEVM(blockCtx, txCtx, st, chainConfig, vm.Config{Debug: false}) timeoutMilliSeconds := int64(5000) diff --git a/cmd/rpcdaemon/commands/eth_call.go b/cmd/rpcdaemon/commands/eth_call.go index 03e9608dbb68e6142d27faa68a2f15ad1936102b..32e7a66cf215f2fd1ada5a1c4ac5b93a8f259ac6 100644 --- a/cmd/rpcdaemon/commands/eth_call.go +++ b/cmd/rpcdaemon/commands/eth_call.go @@ -14,6 +14,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/internal/ethapi" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rpc" @@ -38,7 +39,9 @@ func (api *APIImpl) Call(ctx context.Context, args ethapi.CallArgs, blockNrOrHas args.Gas = (*hexutil.Uint64)(&api.GasCap) } - result, err := transactions.DoCall(ctx, args, tx, blockNrOrHash, overrides, api.GasCap, chainConfig, api.filters) + contractHasTEVM := ethdb.GetHasTEVM(tx) + + result, err := transactions.DoCall(ctx, args, tx, blockNrOrHash, overrides, api.GasCap, chainConfig, api.filters, contractHasTEVM) if err != nil { return nil, err } @@ -163,11 +166,13 @@ func (api *APIImpl) EstimateGas(ctx context.Context, args ethapi.CallArgs, block return 0, err } + contractHasTEVM := ethdb.GetHasTEVM(dbtx) + // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := transactions.DoCall(ctx, args, dbtx, rpc.BlockNumberOrHash{BlockNumber: &lastBlockNum}, nil, api.GasCap, chainConfig, api.filters) + result, err := transactions.DoCall(ctx, args, dbtx, rpc.BlockNumberOrHash{BlockNumber: &lastBlockNum}, nil, api.GasCap, chainConfig, api.filters, contractHasTEVM) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { // Special case, raise gas limit diff --git a/cmd/rpcdaemon/commands/eth_receipts.go b/cmd/rpcdaemon/commands/eth_receipts.go index edfd25be0b7c2f91b0585d56227759efccf7757b..8e3991befa8e2e55caf0bf61758c43b1c5bd330c 100644 --- a/cmd/rpcdaemon/commands/eth_receipts.go +++ b/cmd/rpcdaemon/commands/eth_receipts.go @@ -37,8 +37,8 @@ func getReceipts(ctx context.Context, tx kv.Tx, chainConfig *params.ChainConfig, getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - checkTEVM := ethdb.GetCheckTEVM(tx) - _, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, checkTEVM, ethash.NewFaker(), tx, block.Hash(), 0) + contractHasTEVM := ethdb.GetHasTEVM(tx) + _, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, block.Hash(), 0) if err != nil { return nil, err } @@ -48,7 +48,7 @@ func getReceipts(ctx context.Context, tx kv.Tx, chainConfig *params.ChainConfig, var usedGas = new(uint64) for i, txn := range block.Transactions() { ibs.Prepare(txn.Hash(), block.Hash(), i) - receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, ethash.NewFaker(), nil, gp, ibs, state.NewNoopWriter(), block.Header(), txn, usedGas, vm.Config{}, checkTEVM) + receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, ethash.NewFaker(), nil, gp, ibs, state.NewNoopWriter(), block.Header(), txn, usedGas, vm.Config{}, contractHasTEVM) if err != nil { return nil, err } diff --git a/cmd/rpcdaemon/commands/trace_adhoc.go b/cmd/rpcdaemon/commands/trace_adhoc.go index b652e150cf1d065d4cbd7937f20828ef4dc2a605..70cba2bb915f649fb2d6b8feb7d7829bea1e6b4c 100644 --- a/cmd/rpcdaemon/commands/trace_adhoc.go +++ b/cmd/rpcdaemon/commands/trace_adhoc.go @@ -22,6 +22,7 @@ import ( "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/stack" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/rpchelper" "github.com/ledgerwatch/erigon/turbo/shards" @@ -902,7 +903,7 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp return nil, err } - blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx) + blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx, ethdb.GetHasTEVM(tx)) blockCtx.GasLimit = math.MaxUint64 blockCtx.MaxGasLimit = true @@ -1107,7 +1108,7 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []type } // Get a new instance of the EVM. - blockCtx, txCtx := transactions.GetEvmContext(msg, header, parentNrOrHash.RequireCanonical, dbtx) + blockCtx, txCtx := transactions.GetEvmContext(msg, header, parentNrOrHash.RequireCanonical, dbtx, ethdb.GetHasTEVM(dbtx)) if useParent { blockCtx.GasLimit = math.MaxUint64 blockCtx.MaxGasLimit = true diff --git a/cmd/rpcdaemon/commands/tracing.go b/cmd/rpcdaemon/commands/tracing.go index 87f4f928aaeaedd8254b9651a4914e89f26562a0..d9dce5a2c3ea62e70d3a8b02ee6abc22c3046be6 100644 --- a/cmd/rpcdaemon/commands/tracing.go +++ b/cmd/rpcdaemon/commands/tracing.go @@ -12,6 +12,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/eth/tracers" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/internal/ethapi" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/rpchelper" @@ -53,7 +54,8 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - msg, blockCtx, txCtx, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, nil /* checkTEVM */, ethash.NewFaker(), tx, blockHash, txIndex) + contractHasTEVM := ethdb.GetHasTEVM(tx) + msg, blockCtx, txCtx, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, blockHash, txIndex) if err != nil { stream.WriteNil() return err @@ -106,7 +108,8 @@ func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallA if err != nil { return err } - blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, dbtx) + + blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, dbtx, ethdb.GetHasTEVM(dbtx)) // Trace the transaction and return return transactions.TraceTx(ctx, msg, blockCtx, txCtx, ibs, config, chainConfig, stream) } diff --git a/cmd/snapshots/debug/debug_test.go b/cmd/snapshots/debug/debug_test.go index cb5e510e49dfdfb28b833d2ed7f644825b430c9d..a5ea362db2461e81f0f5f20579c8377eae1521c1 100644 --- a/cmd/snapshots/debug/debug_test.go +++ b/cmd/snapshots/debug/debug_test.go @@ -121,9 +121,9 @@ func TestMatreshkaStream(t *testing.T) { t.Fatal(err, currentBlock) } - checkTEVM := ethdb.GetCheckTEVM(tx) + contractHasTEVM := ethdb.GetHasTEVM(tx) - _, err = core.ExecuteBlockEphemerally(chainConfig, &vm.Config{NoReceipts: true}, getHeader, ethash.NewFaker(), block, stateReaderWriter, stateReaderWriter, nil, nil, checkTEVM) + _, err = core.ExecuteBlockEphemerally(chainConfig, &vm.Config{NoReceipts: true}, getHeader, ethash.NewFaker(), block, stateReaderWriter, stateReaderWriter, nil, nil, contractHasTEVM) if err != nil { t.Fatal(err, currentBlock) } diff --git a/cmd/state/commands/check_change_sets.go b/cmd/state/commands/check_change_sets.go index c2cd22839f1d0c20584b3dbbd4dfb6edab4410dc..acbaf1a50e6a5b7daa5d16f4859a289c289b45b6 100644 --- a/cmd/state/commands/check_change_sets.go +++ b/cmd/state/commands/check_change_sets.go @@ -21,6 +21,7 @@ import ( "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/log/v3" "github.com/spf13/cobra" ) @@ -138,7 +139,8 @@ func CheckChangeSets(genesis *core.Genesis, logger log.Logger, blockNum uint64, } getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(rwtx, hash, number) } - receipts, err1 := runBlock(intraBlockState, noOpWriter, blockWriter, chainConfig, getHeader, nil /* checkTEVM */, block, vmConfig) + contractHasTEVM := ethdb.GetHasTEVM(rwtx) + receipts, err1 := runBlock(intraBlockState, noOpWriter, blockWriter, chainConfig, getHeader, contractHasTEVM, block, vmConfig) if err1 != nil { return err1 } diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go index ed0fec840cc0fa9a5f15c171a6a19f1eb24922e0..379140c96d6af2cfa80f4ec294cca7c7172a3a8f 100644 --- a/cmd/state/commands/opcode_tracer.go +++ b/cmd/state/commands/opcode_tracer.go @@ -25,6 +25,7 @@ import ( "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/stack" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/log/v3" "github.com/spf13/cobra" @@ -549,7 +550,8 @@ func OpcodeTracer(genesis *core.Genesis, blockNum uint64, chaindata string, numB intraBlockState.SetTracer(ot) getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(historyTx, hash, number) } - receipts, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, nil /* checkTEVM */, block, vmConfig) + contractHasTEVM := ethdb.GetHasTEVM(historyTx) + receipts, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, contractHasTEVM, block, vmConfig) if err1 != nil { return err1 } @@ -664,7 +666,7 @@ func check(e error) { } func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWriter state.StateWriter, - chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, checkTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { + chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, contractHasTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { header := block.Header() vmConfig.TraceJumpDest = true engine := ethash.NewFullFaker() @@ -677,7 +679,7 @@ func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWrit rules := chainConfig.Rules(block.NumberU64()) for i, tx := range block.Transactions() { ibs.Prepare(tx.Hash(), block.Hash(), i) - receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, checkTEVM) + receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, contractHasTEVM) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%x] failed: %v", i, tx.Hash(), err) } diff --git a/core/blockchain.go b/core/blockchain.go index 133d2ad3dbcaf238e3e042475c41e3af166f793b..b8f99ad8550e2e5bf51566fe62a21ac3df925983 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -97,7 +97,7 @@ func ExecuteBlockEphemerally( stateWriter state.WriterWithChangeSets, epochReader consensus.EpochReader, chainReader consensus.ChainHeaderReader, - checkTEVM func(codeHash common.Hash) (bool, error), + contractHasTEVM func(codeHash common.Hash) (bool, error), ) (types.Receipts, error) { defer blockExecutionTimer.UpdateDuration(time.Now()) block.Uncles() @@ -127,7 +127,7 @@ func ExecuteBlockEphemerally( writeTrace = true } - receipt, _, err := ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, noop, header, tx, usedGas, *vmConfig, checkTEVM) + receipt, _, err := ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, noop, header, tx, usedGas, *vmConfig, contractHasTEVM) if writeTrace { w, err1 := os.Create(fmt.Sprintf("txtrace_%x.txt", tx.Hash())) if err1 != nil { diff --git a/core/chain_makers.go b/core/chain_makers.go index 9cfac15cfd30e29dde6cfa9eb7c2867befc41eaa..1d722a5e2b9a4f561abd32c550b0f87ea5c11bcc 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -109,8 +109,8 @@ func (b *BlockGen) AddTxWithChain(getHeader func(hash common.Hash, number uint64 b.SetCoinbase(common.Address{}) } b.ibs.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) - checkTEVM := func(_ common.Hash) (bool, error) { return false, nil } - receipt, _, err := ApplyTransaction(b.config, getHeader, engine, &b.header.Coinbase, b.gasPool, b.ibs, state.NewNoopWriter(), b.header, tx, &b.header.GasUsed, vm.Config{}, checkTEVM) + contractHasTEVM := func(_ common.Hash) (bool, error) { return false, nil } + receipt, _, err := ApplyTransaction(b.config, getHeader, engine, &b.header.Coinbase, b.gasPool, b.ibs, state.NewNoopWriter(), b.header, tx, &b.header.GasUsed, vm.Config{}, contractHasTEVM) if err != nil { panic(err) } @@ -123,8 +123,8 @@ func (b *BlockGen) AddFailedTxWithChain(getHeader func(hash common.Hash, number b.SetCoinbase(common.Address{}) } b.ibs.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) - checkTEVM := func(common.Hash) (bool, error) { return false, nil } - receipt, _, err := ApplyTransaction(b.config, getHeader, engine, &b.header.Coinbase, b.gasPool, b.ibs, state.NewNoopWriter(), b.header, tx, &b.header.GasUsed, vm.Config{}, checkTEVM) + contractHasTEVM := func(common.Hash) (bool, error) { return false, nil } + receipt, _, err := ApplyTransaction(b.config, getHeader, engine, &b.header.Coinbase, b.gasPool, b.ibs, state.NewNoopWriter(), b.header, tx, &b.header.GasUsed, vm.Config{}, contractHasTEVM) _ = err // accept failed transactions b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) diff --git a/core/evm.go b/core/evm.go index b0e847ab094d4dd6a8a007b3b4eb43ef9173604a..716f037e57042b625b39f0432f39363a7bc6cbdd 100644 --- a/core/evm.go +++ b/core/evm.go @@ -28,7 +28,7 @@ import ( ) // NewEVMBlockContext creates a new context for use in the EVM. -func NewEVMBlockContext(header *types.Header, getHeader func(hash common.Hash, number uint64) *types.Header, engine consensus.Engine, author *common.Address, checkTEVM func(contractHash common.Hash) (bool, error)) vm.BlockContext { +func NewEVMBlockContext(header *types.Header, getHeader func(hash common.Hash, number uint64) *types.Header, engine consensus.Engine, author *common.Address, contractHasTEVM func(contractHash common.Hash) (bool, error)) vm.BlockContext { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address if author == nil { @@ -43,23 +43,22 @@ func NewEVMBlockContext(header *types.Header, getHeader func(hash common.Hash, n panic(fmt.Errorf("header.BaseFee higher than 2^256-1")) } } - - if checkTEVM == nil { - checkTEVM = func(_ common.Hash) (bool, error) { + if contractHasTEVM == nil { + contractHasTEVM = func(_ common.Hash) (bool, error) { return false, nil } } return vm.BlockContext{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, getHeader), - Coinbase: beneficiary, - BlockNumber: header.Number.Uint64(), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - BaseFee: &baseFee, - GasLimit: header.GasLimit, - CheckTEVM: checkTEVM, + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, getHeader), + Coinbase: beneficiary, + BlockNumber: header.Number.Uint64(), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: &baseFee, + GasLimit: header.GasLimit, + ContractHasTEVM: contractHasTEVM, } } diff --git a/core/state_processor.go b/core/state_processor.go index 29cbe3c777907f8a58ab03bb383d57254223c775..0a2fdca0a52226e011baad612476363a9be71a52 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -139,9 +139,9 @@ func applyTransaction(config *params.ChainConfig, gp *GasPool, statedb *state.In // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, engine consensus.Engine, author *common.Address, gp *GasPool, ibs *state.IntraBlockState, stateWriter state.StateWriter, header *types.Header, tx types.Transaction, usedGas *uint64, cfg vm.Config, checkTEVM func(contractHash common.Hash) (bool, error)) (*types.Receipt, []byte, error) { +func ApplyTransaction(config *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, engine consensus.Engine, author *common.Address, gp *GasPool, ibs *state.IntraBlockState, stateWriter state.StateWriter, header *types.Header, tx types.Transaction, usedGas *uint64, cfg vm.Config, contractHasTEVM func(contractHash common.Hash) (bool, error)) (*types.Receipt, []byte, error) { // Create a new context to be used in the EVM environment - blockContext := NewEVMBlockContext(header, getHeader, engine, author, checkTEVM) + blockContext := NewEVMBlockContext(header, getHeader, engine, author, contractHasTEVM) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, ibs, config, cfg) // Add addresses to access list if applicable // about the transaction and calling mechanisms. diff --git a/core/vm/evm.go b/core/vm/evm.go index ffaa046b221647be81a694802256a681f0c32146..7f1893001743b0e0e60092231b61f8ad8317da5e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -23,7 +23,6 @@ import ( "time" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/u256" "github.com/ledgerwatch/erigon/crypto" @@ -62,10 +61,21 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { + callback, err := selectInterpreter(evm, contract) + if err != nil { + return nil, err + } + + defer callback() + + return evm.interpreter.Run(contract, input, readOnly) +} + +func selectInterpreter(evm *EVM, contract *Contract) (func(), error) { interpreter := evm.interpreter - defer func() { + callback := func() { evm.interpreter = interpreter - }() + } switch contract.vmType { case EVMType: @@ -76,7 +86,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return nil, errors.New("no compatible interpreter") } - return evm.interpreter.Run(contract, input, readOnly) + return callback, nil } // BlockContext provides the EVM with auxiliary information. Once provided @@ -89,8 +99,8 @@ type BlockContext struct { Transfer TransferFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc - // checkTEVM returns true if the contract has TEVM code - CheckTEVM func(codeHash common.Hash) (bool, error) + // ContractHasTEVM returns true if the contract has TEVM code + ContractHasTEVM func(codeHash common.Hash) (bool, error) // Block information Coinbase common.Address // Provides information for COINBASE @@ -161,9 +171,10 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, state IntraBlockState, chain ChainRules: chainConfig.Rules(blockCtx.BlockNumber), } + evmInterp := NewEVMInterpreter(evm, vmConfig) evm.interpreters = []Interpreter{ - EVMType: NewEVMInterpreter(evm, vmConfig), - TEVMType: NewTEVMInterpreter(evm, vmConfig), + EVMType: evmInterp, + TEVMType: NewTEVMInterpreterByVM(evmInterp.VM), } evm.interpreter = evm.interpreters[EVMType] @@ -249,11 +260,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The depth-check is already done, and precompiles handled above codehash := evm.IntraBlockState.GetCodeHash(addrCopy) - var isTEVM bool - isTEVM, err = evm.Context.CheckTEVM(codehash) + var contractHasTEVM bool + contractHasTEVM, err = evm.Context.ContractHasTEVM(codehash) if err == nil { - contract := NewContract(caller, AccountRef(addrCopy), value, gas, evm.Config.SkipAnalysis, isTEVM) + contract := NewContract(caller, AccountRef(addrCopy), value, gas, evm.Config.SkipAnalysis, contractHasTEVM) contract.SetCallCode(&addrCopy, codehash, code) ret, err = run(evm, contract, input, false) gas = contract.Gas @@ -323,7 +334,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, var isTEVM bool codeHash := evm.IntraBlockState.GetCodeHash(addrCopy) - isTEVM, err = evm.Context.CheckTEVM(codeHash) + isTEVM, err = evm.Context.ContractHasTEVM(codeHash) if err == nil { contract := NewContract(caller, AccountRef(caller.Address()), value, gas, evm.Config.SkipAnalysis, isTEVM) @@ -376,7 +387,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values var isTEVM bool codeHash := evm.IntraBlockState.GetCodeHash(addrCopy) - isTEVM, err = evm.Context.CheckTEVM(codeHash) + isTEVM, err = evm.Context.ContractHasTEVM(codeHash) if err == nil { contract := NewContract(caller, AccountRef(caller.Address()), nil, gas, evm.Config.SkipAnalysis, isTEVM).AsDelegate() @@ -442,7 +453,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // The contract is a scoped environment for this execution context only. var isTEVM bool codeHash := evm.IntraBlockState.GetCodeHash(addrCopy) - isTEVM, err = evm.Context.CheckTEVM(codeHash) + isTEVM, err = evm.Context.ContractHasTEVM(codeHash) if err == nil { contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas, evm.Config.SkipAnalysis, isTEVM) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9e02dd1784d6f56686245706051cc06cc666c1c0 --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,446 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/params" + + "github.com/holiman/uint256" + "pgregory.net/rapid" +) + +func TestInterpreterReadonly(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + env := NewEVM(BlockContext{ + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, + }, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{EnableTEMV: true}) + + isEVMSliceTest := rapid.SliceOfN(rapid.Bool(), 1, -1).Draw(t, "tevm").([]bool) + readOnlySliceTest := rapid.SliceOfN(rapid.Bool(), len(isEVMSliceTest), len(isEVMSliceTest)).Draw(t, "readonly").([]bool) + + isEVMCalled := make([]bool, len(isEVMSliceTest)) + readOnlies := make([]*readOnlyState, len(readOnlySliceTest)) + currentIdx := new(int) + *currentIdx = -1 + + evmInterpreter := &testVM{ + readonlyGetSetter: env.interpreters[EVMType].(*EVMInterpreter), + isTEMV: false, + + recordedReadOnlies: &readOnlies, + recordedIsEVMCalled: &isEVMCalled, + + env: env, + isEVMSliceTest: isEVMSliceTest, + readOnlySliceTest: readOnlySliceTest, + currentIdx: currentIdx, + } + tevmInterpreter := &testVM{ + readonlyGetSetter: env.interpreters[TEVMType].(*TEVMInterpreter), + isTEMV: true, + + recordedReadOnlies: &readOnlies, + recordedIsEVMCalled: &isEVMCalled, + + env: env, + isEVMSliceTest: isEVMSliceTest, + readOnlySliceTest: readOnlySliceTest, + currentIdx: currentIdx, + } + + env.interpreters[EVMType] = evmInterpreter + env.interpreters[TEVMType] = tevmInterpreter + + dummyContract := NewContract( + &dummyContractRef{}, + &dummyContractRef{}, + new(uint256.Int), + 0, + false, + false, + ) + + newTestSequential(env, currentIdx, readOnlySliceTest, isEVMSliceTest).Run(dummyContract, nil, false) + + var gotReadonly bool + var firstReadOnly int + + // properties-invariants + + if len(readOnlies) != len(readOnlySliceTest) { + t.Fatalf("expected static calls the same stack length as generated. got %d, expected %d", len(readOnlies), len(readOnlySliceTest)) + } + + if len(isEVMCalled) != len(isEVMSliceTest) { + t.Fatalf("expected VM calls the same stack length as generated. got %d, expected %d", len(isEVMCalled), len(isEVMSliceTest)) + } + + if *currentIdx != len(readOnlies) { + t.Fatalf("expected VM calls the same amount of calls as generated calls. got %d, expected %d", *currentIdx, len(readOnlies)) + } + + for i, readOnly := range readOnlies { + if isEVMCalled[i] != isEVMSliceTest[i] { + t.Fatalf("wrong VM was called in %d index, got EVM %t, expected EVM %t", + i, isEVMCalled[i], isEVMSliceTest[i]) + } + + if readOnly.outer != readOnlySliceTest[i] { + t.Fatalf("outer readOnly appeared in %d index, got readOnly %t, expected %t", + i, readOnly.outer, readOnlySliceTest[i]) + } + + if i > 0 { + if readOnly.before != readOnlies[i-1].in { + t.Fatalf("before readOnly appeared in %d index, got readOnly %t, expected %t", + i, readOnly.before, readOnlies[i-1].in) + } + } + + if readOnly.in && !gotReadonly { + gotReadonly = true + firstReadOnly = i + } + + if gotReadonly { + if !readOnly.in { + t.Fatalf("readOnly appeared in %d index, got non-readOnly in %d: %v", + firstReadOnly, i, trace(isEVMCalled, readOnlies)) + } + + switch { + case i < firstReadOnly: + if readOnly.after != false { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case <firstReadOnly), got readOnly %t, expected %t", + i, firstReadOnly, readOnly.after, false) + } + case i == firstReadOnly: + if readOnly.after != false { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case ==firstReadOnly), got readOnly %t, expected %t", + i, firstReadOnly, readOnly.after, false) + } + case i > firstReadOnly: + if readOnly.after != true { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case >firstReadOnly), got readOnly %t, expected %t", + i, firstReadOnly, readOnly.after, true) + } + } + } else { + if readOnly.after != false { + t.Fatalf("after readOnly didn't appear. %d index, got readOnly %t, expected %t", + i, readOnly.after, false) + } + } + } + }) +} + +func TestReadonlyBasicCases(t *testing.T) { + cases := []struct { + testName string + readonlySliceTest []bool + + expectedReadonlySlice []readOnlyState + }{ + { + "simple non-readonly", + []bool{false}, + + []readOnlyState{ + { + false, + false, + false, + false, + }, + }, + }, + { + "simple readonly", + []bool{true}, + + []readOnlyState{ + { + true, + true, + true, + false, + }, + }, + }, + + { + "2 calls non-readonly", + []bool{false, false}, + + []readOnlyState{ + { + false, + false, + false, + false, + }, + { + false, + false, + false, + false, + }, + }, + }, + + { + "2 calls true,false", + []bool{true, false}, + + []readOnlyState{ + { + true, + true, + true, + false, + }, + { + true, + true, + true, + true, + }, + }, + }, + { + "2 calls false,true", + []bool{false, true}, + + []readOnlyState{ + { + false, + false, + false, + false, + }, + { + true, + true, + true, + false, + }, + }, + }, + { + "2 calls readonly", + []bool{true, true}, + + []readOnlyState{ + { + true, + true, + true, + true, + }, + { + true, + true, + true, + true, + }, + }, + }, + } + + type evmsParamsTest struct { + emvs []bool + suffix string + } + + evmsTest := make([]evmsParamsTest, len(cases)) + + // fill all possible isEVM combinations + for i, testCase := range cases { + isEVMSliceTest := make([]bool, len(testCase.readonlySliceTest)) + + copy(isEVMSliceTest, testCase.readonlySliceTest) + evmsTest[i].emvs = isEVMSliceTest + + suffix := "-isEVMSliceTest" + for _, evmParam := range testCase.readonlySliceTest { + if evmParam { + suffix += "-true" + } else { + suffix += "-false" + } + } + + evmsTest[i].suffix = suffix + } + + for _, testCase := range cases { + for _, evmsParams := range evmsTest { + + testcase := testCase + evmsTestcase := evmsParams + + if len(testcase.readonlySliceTest) != len(evmsTestcase.emvs) { + continue + } + + t.Run(testcase.testName+evmsTestcase.suffix, func(t *testing.T) { + readonlySliceTest := testcase.readonlySliceTest + + env := NewEVM(BlockContext{ + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, + }, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{EnableTEMV: true}) + + readonliesGot := make([]*readOnlyState, len(testcase.readonlySliceTest)) + isEVMGot := make([]bool, len(evmsTestcase.emvs)) + + currentIdx := new(int) + *currentIdx = -1 + + evmInterpreter := &testVM{ + readonlyGetSetter: env.interpreters[EVMType].(*EVMInterpreter), + isTEMV: false, + + recordedReadOnlies: &readonliesGot, + recordedIsEVMCalled: &isEVMGot, + + env: env, + isEVMSliceTest: evmsTestcase.emvs, + readOnlySliceTest: testcase.readonlySliceTest, + currentIdx: currentIdx, + } + tevmInterpreter := &testVM{ + readonlyGetSetter: env.interpreters[TEVMType].(*TEVMInterpreter), + isTEMV: true, + + recordedReadOnlies: &readonliesGot, + recordedIsEVMCalled: &isEVMGot, + + env: env, + isEVMSliceTest: evmsTestcase.emvs, + readOnlySliceTest: testcase.readonlySliceTest, + currentIdx: currentIdx, + } + + env.interpreters[EVMType] = evmInterpreter + env.interpreters[TEVMType] = tevmInterpreter + + dummyContract := NewContract( + &dummyContractRef{}, + &dummyContractRef{}, + new(uint256.Int), + 0, + false, + false, + ) + + newTestSequential(env, currentIdx, readonlySliceTest, evmsTestcase.emvs).Run(dummyContract, nil, false) + + if len(readonliesGot) != len(readonlySliceTest) { + t.Fatalf("expected static calls the same stack length as generated. got %d, expected %d - %v", len(readonliesGot), len(readonlySliceTest), readonlySliceTest) + } + + if len(isEVMGot) != len(evmsTestcase.emvs) { + t.Fatalf("expected VM calls the same stack length as generated. got %d, expected %d - %v, readonly %v", len(isEVMGot), len(evmsTestcase.emvs), evmsTestcase.emvs, readonlySliceTest) + } + + if *currentIdx != len(readonlySliceTest) { + t.Fatalf("expected VM calls the same amount of calls as generated calls. got %d, expected %d", *currentIdx, len(readonlySliceTest)) + } + + var gotReadonly bool + var firstReadOnly int + + for callIndex, readOnly := range readonliesGot { + if isEVMGot[callIndex] != evmsTestcase.emvs[callIndex] { + t.Fatalf("wrong VM was called in %d index, got EVM %t, expected EVM %t. Test EVMs %v; test readonly %v", + callIndex, isEVMGot[callIndex], evmsTestcase.emvs[callIndex], evmsTestcase.emvs, readonlySliceTest) + } + + if readOnly.outer != readonlySliceTest[callIndex] { + t.Fatalf("outer readOnly appeared in %d index, got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, readOnly.outer, readonlySliceTest[callIndex], evmsTestcase.emvs, readonlySliceTest) + } + + if callIndex > 0 { + if readOnly.before != readonliesGot[callIndex-1].in { + t.Fatalf("before readOnly appeared in %d index, got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, readOnly.before, readonliesGot[callIndex-1].in, evmsTestcase.emvs, readonlySliceTest) + } + } + + if readOnly.in && !gotReadonly { + gotReadonly = true + firstReadOnly = callIndex + } + + if gotReadonly { + if !readOnly.in { + t.Fatalf("readOnly appeared in %d index, got non-readOnly in %d: %v. Test EVMs %v; test readonly %v", + firstReadOnly, callIndex, trace(isEVMGot, readonliesGot), evmsTestcase.emvs, readonlySliceTest) + } + + switch { + case callIndex < firstReadOnly: + if readOnly.after != false { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case <firstReadOnly), got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, firstReadOnly, readOnly.after, false, evmsTestcase.emvs, readonlySliceTest) + } + case callIndex == firstReadOnly: + if readOnly.after != false { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case ==firstReadOnly), got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, firstReadOnly, readOnly.after, false, evmsTestcase.emvs, readonlySliceTest) + } + case callIndex > firstReadOnly: + if readOnly.after != true { + t.Fatalf("after readOnly appeared in %d index(first readonly %d, case >firstReadOnly), got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, firstReadOnly, readOnly.after, true, evmsTestcase.emvs, readonlySliceTest) + } + } + } else { + if readOnly.after != false { + t.Fatalf("after readOnly didn't appear. %d index, got readOnly %t, expected %t. Test EVMs %v; test readonly %v", + callIndex, readOnly.after, false, evmsTestcase.emvs, readonlySliceTest) + } + } + } + }) + } + } +} + +type testSequential struct { + env *EVM + currentIdx *int + readOnlys []bool + isEVMCalled []bool +} + +func newTestSequential(env *EVM, currentIdx *int, readonlies []bool, isEVMCalled []bool) *testSequential { + return &testSequential{env, currentIdx, readonlies, isEVMCalled} +} + +func (st *testSequential) Run(_ *Contract, _ []byte, _ bool) ([]byte, error) { + *st.currentIdx++ + + nextContract := NewContract( + &dummyContractRef{}, + &dummyContractRef{}, + new(uint256.Int), + 0, + false, + !st.isEVMCalled[*st.currentIdx], + ) + + return run(st.env, nextContract, nil, st.readOnlys[*st.currentIdx]) +} + +func trace(isEVMSlice []bool, readOnlySlice []*readOnlyState) string { + res := "trace:\n" + for i := 0; i < len(isEVMSlice); i++ { + res += fmt.Sprintf("%d: EVM %t, readonly %t\n", i, isEVMSlice[i], readOnlySlice[i].in) + } + return res +} diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index dd26a0b832eaca5a83a7b0daeb0a72f2d1ac0dc0..d02fe88c63762f47598d64fc738fe4a877a2965d 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -96,9 +96,9 @@ func TestEIP2200(t *testing.T) { _ = s.CommitBlock(params.AllEthashProtocolChanges.Rules(0), state.NewPlainStateWriter(tx, tx, 0)) vmctx := BlockContext{ - CanTransfer: func(IntraBlockState, common.Address, *uint256.Int) bool { return true }, - Transfer: func(IntraBlockState, common.Address, common.Address, *uint256.Int, bool) {}, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + CanTransfer: func(IntraBlockState, common.Address, *uint256.Int) bool { return true }, + Transfer: func(IntraBlockState, common.Address, common.Address, *uint256.Int, bool) {}, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, } vmenv := NewEVM(vmctx, TxContext{}, s, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 33718098973b3dd24f5b1c3ef55a1e595fbb9d2f..a10f3a41b28d15e46fb75a06112f53867763b47a 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -96,7 +96,7 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() pc = uint64(0) @@ -197,7 +197,7 @@ func TestSAR(t *testing.T) { func TestAddMod(t *testing.T) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() evmInterpreter = NewEVMInterpreter(env, env.Config) @@ -286,7 +286,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() evmInterpreter = NewEVMInterpreter(env, env.Config) @@ -522,7 +522,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() mem = NewMemory() @@ -548,7 +548,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() mem = NewMemory() @@ -571,7 +571,7 @@ func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpSHA3(bench *testing.B) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, nil, params.TestChainConfig, Config{}) stack = stack.New() mem = NewMemory() diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 78b3e03587002498ca6673173ce618b07abf96cd..97a3906166bd4618c66d70b2431ccc528c5d61f8 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -69,11 +69,16 @@ type keccakState interface { // EVMInterpreter represents an EVM interpreter type EVMInterpreter struct { + *VM + jt *JumpTable // EVM instruction table +} + +//structcheck doesn't see embedding +//nolint:structcheck +type VM struct { evm *EVM cfg Config - jt *JumpTable // EVM instruction table - hasher keccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes @@ -115,9 +120,49 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { } return &EVMInterpreter{ - evm: evm, - cfg: cfg, - jt: jt, + VM: &VM{ + evm: evm, + cfg: cfg, + }, + jt: jt, + } +} + +func NewEVMInterpreterByVM(vm *VM) *EVMInterpreter { + var jt *JumpTable + switch { + case vm.evm.ChainRules.IsLondon: + jt = &londonInstructionSet + case vm.evm.ChainRules.IsBerlin: + jt = &berlinInstructionSet + case vm.evm.ChainRules.IsIstanbul: + jt = &istanbulInstructionSet + case vm.evm.ChainRules.IsConstantinople: + jt = &constantinopleInstructionSet + case vm.evm.ChainRules.IsByzantium: + jt = &byzantiumInstructionSet + case vm.evm.ChainRules.IsEIP158: + jt = &spuriousDragonInstructionSet + case vm.evm.ChainRules.IsEIP150: + jt = &tangerineWhistleInstructionSet + case vm.evm.ChainRules.IsHomestead: + jt = &homesteadInstructionSet + default: + jt = &frontierInstructionSet + } + if len(vm.cfg.ExtraEips) > 0 { + for i, eip := range vm.cfg.ExtraEips { + if err := EnableEIP(eip, jt); err != nil { + // Disable it, so caller can check if it's activated or not + vm.cfg.ExtraEips = append(vm.cfg.ExtraEips[:i], vm.cfg.ExtraEips[i+1:]...) + log.Error("EIP activation failed", "eip", eip, "error", err) + } + } + } + + return &EVMInterpreter{ + VM: vm, + jt: jt, } } @@ -134,10 +179,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // Make sure the readOnly is only set if we aren't in readOnly yet. // This makes also sure that the readOnly flag isn't removed for child calls. - if readOnly && !in.readOnly { - in.readOnly = true - defer func() { in.readOnly = false }() - } + callback := in.setReadonly(readOnly) + defer func() { + callback() + }() // Reset the previous call's return data. It's unimportant to preserve the old buffer // as every returning call will return new data anyway. @@ -290,3 +335,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } return nil, nil } + +func (vm *VM) setReadonly(outerReadonly bool) func() { + if outerReadonly && !vm.readOnly { + vm.readOnly = true + return func() { + vm.readOnly = false + } + } + return func() {} +} + +func (vm *VM) getReadonly() bool { + return vm.readOnly +} diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index f47c8a92d26e979f7a4cc8a259022988feb488ea..69cb44bde67c0240d6e5ad264454008c180bdead 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -23,38 +23,14 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/vm/stack" "github.com/ledgerwatch/erigon/params" ) -type dummyContractRef struct { - calledForEach bool -} - -func (dummyContractRef) ReturnGas(*big.Int) {} -func (dummyContractRef) Address() common.Address { return common.Address{} } -func (dummyContractRef) Value() *big.Int { return new(big.Int) } -func (dummyContractRef) SetCode(common.Hash, []byte) {} -func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { - d.calledForEach = true -} -func (d *dummyContractRef) SubBalance(amount *big.Int) {} -func (d *dummyContractRef) AddBalance(amount *big.Int) {} -func (d *dummyContractRef) SetBalance(*big.Int) {} -func (d *dummyContractRef) SetNonce(uint64) {} -func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } - -type dummyStatedb struct { - state.IntraBlockState -} - -func (*dummyStatedb) GetRefund() uint64 { return 1337 } - func TestStoreCapture(t *testing.T) { var ( env = NewEVM(BlockContext{ - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() diff --git a/core/vm/mock_vm.go b/core/vm/mock_vm.go new file mode 100644 index 0000000000000000000000000000000000000000..6228a307ec45e9d0dca65dc9b3ce68d6a82aa17a --- /dev/null +++ b/core/vm/mock_vm.go @@ -0,0 +1,99 @@ +package vm + +import ( + "fmt" + "math/big" + + "github.com/holiman/uint256" + + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/state" +) + +type readonlyGetSetter interface { + setReadonly(outerReadonly bool) func() + getReadonly() bool +} + +type testVM struct { + readonlyGetSetter + isTEMV bool + + recordedReadOnlies *[]*readOnlyState + recordedIsEVMCalled *[]bool + + env *EVM + isEVMSliceTest []bool + readOnlySliceTest []bool + currentIdx *int +} + +func (evm *testVM) Run(_ *Contract, _ []byte, readOnly bool) (ret []byte, err error) { + currentReadOnly := new(readOnlyState) + + currentReadOnly.outer = readOnly + currentReadOnly.before = evm.getReadonly() + + currentIndex := *evm.currentIdx + + callback := evm.setReadonly(readOnly) + defer func() { + callback() + currentReadOnly.after = evm.getReadonly() + }() + + currentReadOnly.in = evm.getReadonly() + + (*evm.recordedReadOnlies)[currentIndex] = currentReadOnly + (*evm.recordedIsEVMCalled)[currentIndex] = !evm.isTEMV + + *evm.currentIdx++ + + if *evm.currentIdx < len(evm.readOnlySliceTest) { + res, err := run(evm.env, NewContract( + &dummyContractRef{}, + &dummyContractRef{}, + new(uint256.Int), + 0, + false, + !evm.isEVMSliceTest[*evm.currentIdx], + ), nil, evm.readOnlySliceTest[*evm.currentIdx]) + return res, err + } + + return +} + +type readOnlyState struct { + outer bool + before bool + in bool + after bool +} + +func (r *readOnlyState) String() string { + return fmt.Sprintf("READONLY Status: outer %t; before %t; in %t; after %t", r.outer, r.before, r.in, r.after) +} + +type dummyContractRef struct { + calledForEach bool +} + +func (dummyContractRef) ReturnGas(*big.Int) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} +func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { + d.calledForEach = true +} +func (d *dummyContractRef) SubBalance(amount *big.Int) {} +func (d *dummyContractRef) AddBalance(amount *big.Int) {} +func (d *dummyContractRef) SetBalance(*big.Int) {} +func (d *dummyContractRef) SetNonce(uint64) {} +func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } + +type dummyStatedb struct { + state.IntraBlockState +} + +func (*dummyStatedb) GetRefund() uint64 { return 1337 } diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index fdd2fb31d2996e2209b50e70f887c40e5b647a0e..bf514f74e0395f9cf49e3afd43e6b46e0d449b59 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -19,6 +19,7 @@ package runtime import ( "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/ethdb" ) func NewEnv(cfg *Config) *vm.EVM { @@ -26,17 +27,18 @@ func NewEnv(cfg *Config) *vm.EVM { Origin: cfg.Origin, GasPrice: cfg.GasPrice, } + blockContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: cfg.GetHashFn, - CheckTEVM: cfg.CheckTEVM, - Coinbase: cfg.Coinbase, - BlockNumber: cfg.BlockNumber.Uint64(), - Time: cfg.Time.Uint64(), - Difficulty: cfg.Difficulty, - GasLimit: cfg.GasLimit, - BaseFee: cfg.BaseFee, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: cfg.GetHashFn, + ContractHasTEVM: ethdb.GetHasTEVM(cfg.kv), + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber.Uint64(), + Time: cfg.Time.Uint64(), + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + BaseFee: cfg.BaseFee, } return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 799e49274507a3bd334912bfdcb087ac13fc2e2c..82757cabde1080e8fe11186bdc940f9f928e57b2 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -22,6 +22,7 @@ import ( "time" "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/memdb" "github.com/ledgerwatch/erigon/ethdb/olddb" @@ -51,8 +52,8 @@ type Config struct { State *state.IntraBlockState r state.StateReader w state.StateWriter + kv kv.Has GetHashFn func(n uint64) common.Hash - CheckTEVM func(common.Hash) (bool, error) } // sets defaults on the config @@ -100,9 +101,6 @@ func setDefaults(cfg *Config) { return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) } } - if cfg.CheckTEVM == nil { - cfg.CheckTEVM = func(common.Hash) (bool, error) { return false, nil } - } } // Execute executes the code using the input as call data during the execution. @@ -121,6 +119,7 @@ func Execute(code, input []byte, cfg *Config, blockNr uint64) ([]byte, *state.In defer db.Close() cfg.r = state.NewDbStateReader(db) cfg.w = state.NewDbStateWriter(db, 0) + cfg.kv = db cfg.State = state.New(cfg.r) } var ( @@ -159,6 +158,7 @@ func Create(input []byte, cfg *Config, blockNr uint64) ([]byte, common.Address, defer db.Close() cfg.r = state.NewDbStateReader(db) cfg.w = state.NewDbStateWriter(db, 0) + cfg.kv = db cfg.State = state.New(cfg.r) } var ( diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 6dd6e247595579e24c2c5d2b2e578efeac054e83..4f977937a0815cb42acc1439425eff2bfe71a666 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -115,7 +115,7 @@ func TestCall(t *testing.T) { byte(vm.RETURN), }) - ret, _, err := Call(address, nil, &Config{State: state}) + ret, _, err := Call(address, nil, &Config{State: state, kv: tx}) if err != nil { t.Fatal("didn't expect error", err) } diff --git a/core/vm/tevm_interpreter.go b/core/vm/tevm_interpreter.go index f5c73191e3c80fa01fd7b53508b363996010fab1..4053ce944b18f385c3c7c8b931dfe61bd30a8e89 100644 --- a/core/vm/tevm_interpreter.go +++ b/core/vm/tevm_interpreter.go @@ -18,3 +18,7 @@ const ( func NewTEVMInterpreter(evm *EVM, cfg Config) *TEVMInterpreter { return &TEVMInterpreter{NewEVMInterpreter(evm, cfg)} } + +func NewTEVMInterpreterByVM(vm *VM) *TEVMInterpreter { + return &TEVMInterpreter{NewEVMInterpreterByVM(vm)} +} diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index 5ed5bbfd8726c48c9db5d61b4db1a72dc6a4aea3..82814cd2ab11b91d51e721d2d085ed531a8e9533 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -97,7 +97,7 @@ func executeBlock( writeChangesets bool, writeReceipts bool, writeCallTraces bool, - checkTEVM func(contractHash common.Hash) (bool, error), + contractHasTEVM func(contractHash common.Hash) (bool, error), initialCycle bool, ) error { blockNum := block.NumberU64() @@ -106,10 +106,10 @@ func executeBlock( // where the magic happens getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - callTracer := NewCallTracer(checkTEVM) + callTracer := NewCallTracer(contractHasTEVM) vmConfig.Debug = true vmConfig.Tracer = callTracer - receipts, err := core.ExecuteBlockEphemerally(cfg.chainConfig, &vmConfig, getHeader, cfg.engine, block, stateReader, stateWriter, epochReader{tx: tx}, chainReader{config: cfg.chainConfig, tx: tx}, checkTEVM) + receipts, err := core.ExecuteBlockEphemerally(cfg.chainConfig, &vmConfig, getHeader, cfg.engine, block, stateReader, stateWriter, epochReader{tx: tx}, chainReader{config: cfg.chainConfig, tx: tx}, contractHasTEVM) if err != nil { return err } @@ -274,17 +274,17 @@ Loop: lastLogTx += uint64(block.Transactions().Len()) - var checkTEVMCode func(contractHash common.Hash) (bool, error) + var contractHasTEVM func(contractHash common.Hash) (bool, error) if cfg.vmConfig.EnableTEMV { - checkTEVMCode = ethdb.GetCheckTEVM(tx) + contractHasTEVM = ethdb.GetHasTEVM(tx) } // Incremental move of next stages depend on fully written ChangeSets, Receipts, CallTraceSet writeChangeSets := nextStagesExpectData || blockNum > cfg.prune.History.PruneTo(to) writeReceipts := nextStagesExpectData || blockNum > cfg.prune.Receipts.PruneTo(to) writeCallTraces := nextStagesExpectData || blockNum > cfg.prune.CallTraces.PruneTo(to) - if err = executeBlock(block, tx, batch, cfg, *cfg.vmConfig, writeChangeSets, writeReceipts, writeCallTraces, checkTEVMCode, initialCycle); err != nil { + if err = executeBlock(block, tx, batch, cfg, *cfg.vmConfig, writeChangeSets, writeReceipts, writeCallTraces, contractHasTEVM, initialCycle); err != nil { log.Error(fmt.Sprintf("[%s] Execution failed", logPrefix), "block", blockNum, "hash", block.Hash().String(), "error", err) u.UnwindTo(blockNum-1, block.Hash()) break Loop diff --git a/eth/stagedsync/stage_mining_exec.go b/eth/stagedsync/stage_mining_exec.go index 5ba6050bd8a2c864141e16ab22bb13b35cbc76e5..173a622189d317f6c34a3e3b75e3dfaa7b1d851a 100644 --- a/eth/stagedsync/stage_mining_exec.go +++ b/eth/stagedsync/stage_mining_exec.go @@ -74,14 +74,14 @@ func SpawnMiningExecStage(s *StageState, tx kv.RwTx, cfg MiningExecCfg, quit <-c } getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - checkTEVM := ethdb.GetCheckTEVM(tx) + contractHasTEVM := ethdb.GetHasTEVM(tx) // Short circuit if there is no available pending transactions. // But if we disable empty precommit already, ignore it. Since // empty block is necessary to keep the liveness of the network. if noempty { if !localTxs.Empty() { - logs, err := addTransactionsToMiningBlock(current, cfg.chainConfig, cfg.vmConfig, getHeader, checkTEVM, cfg.engine, localTxs, cfg.miningState.MiningConfig.Etherbase, ibs, quit) + logs, err := addTransactionsToMiningBlock(current, cfg.chainConfig, cfg.vmConfig, getHeader, contractHasTEVM, cfg.engine, localTxs, cfg.miningState.MiningConfig.Etherbase, ibs, quit) if err != nil { return err } @@ -93,7 +93,7 @@ func SpawnMiningExecStage(s *StageState, tx kv.RwTx, cfg MiningExecCfg, quit <-c //} } if !remoteTxs.Empty() { - logs, err := addTransactionsToMiningBlock(current, cfg.chainConfig, cfg.vmConfig, getHeader, checkTEVM, cfg.engine, remoteTxs, cfg.miningState.MiningConfig.Etherbase, ibs, quit) + logs, err := addTransactionsToMiningBlock(current, cfg.chainConfig, cfg.vmConfig, getHeader, contractHasTEVM, cfg.engine, remoteTxs, cfg.miningState.MiningConfig.Etherbase, ibs, quit) if err != nil { return err } @@ -145,7 +145,7 @@ func SpawnMiningExecStage(s *StageState, tx kv.RwTx, cfg MiningExecCfg, quit <-c return nil } -func addTransactionsToMiningBlock(current *MiningBlock, chainConfig params.ChainConfig, vmConfig *vm.Config, getHeader func(hash common.Hash, number uint64) *types.Header, checkTEVM func(common.Hash) (bool, error), engine consensus.Engine, txs types.TransactionsStream, coinbase common.Address, ibs *state.IntraBlockState, quit <-chan struct{}) (types.Logs, error) { +func addTransactionsToMiningBlock(current *MiningBlock, chainConfig params.ChainConfig, vmConfig *vm.Config, getHeader func(hash common.Hash, number uint64) *types.Header, contractHasTEVM func(common.Hash) (bool, error), engine consensus.Engine, txs types.TransactionsStream, coinbase common.Address, ibs *state.IntraBlockState, quit <-chan struct{}) (types.Logs, error) { header := current.Header tcount := 0 gasPool := new(core.GasPool).AddGas(current.Header.GasLimit) @@ -156,7 +156,7 @@ func addTransactionsToMiningBlock(current *MiningBlock, chainConfig params.Chain var miningCommitTx = func(txn types.Transaction, coinbase common.Address, vmConfig *vm.Config, chainConfig params.ChainConfig, ibs *state.IntraBlockState, current *MiningBlock) ([]*types.Log, error) { snap := ibs.Snapshot() - receipt, _, err := core.ApplyTransaction(&chainConfig, getHeader, engine, &coinbase, gasPool, ibs, noop, header, txn, &header.GasUsed, *vmConfig, checkTEVM) + receipt, _, err := core.ApplyTransaction(&chainConfig, getHeader, engine, &coinbase, gasPool, ibs, noop, header, txn, &header.GasUsed, *vmConfig, contractHasTEVM) if err != nil { ibs.RevertToSnapshot(snap) return nil, err diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 1be589ed252d31c0bed26f4320485c53594c4b03..c7ffca2faa73b620941134b4c0ca0e87ef129602 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -57,8 +57,8 @@ type vmContext struct { func testCtx() *vmContext { return &vmContext{blockCtx: vm.BlockContext{ - BlockNumber: 1, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + BlockNumber: 1, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } @@ -88,8 +88,8 @@ func TestTracer(t *testing.T) { execTracer := func(code string) []byte { t.Helper() ctx := &vmContext{blockCtx: vm.BlockContext{ - BlockNumber: 1, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + BlockNumber: 1, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} tracer, err := New(code, ctx.txCtx) if err != nil { @@ -161,8 +161,8 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } env := vm.NewEVM(vm.BlockContext{ - BlockNumber: 1, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + BlockNumber: 1, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, }, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, uint256.NewInt(0), 0, false, false) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 65dfac255fa651d07466ed781b747c51171f1eda..742beaaf80cc7a8fa154dc3b67c93e670e209464 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -152,14 +152,14 @@ func TestPrestateTracerCreate2(t *testing.T) { GasPrice: big.NewInt(1), } context := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: common.Address{}, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, - BlockNumber: 8000000, - Time: 5, - Difficulty: big.NewInt(0x30000), - GasLimit: uint64(6000000), + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: common.Address{}, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, + BlockNumber: 8000000, + Time: 5, + Difficulty: big.NewInt(0x30000), + GasLimit: uint64(6000000), } alloc := core.GenesisAlloc{} @@ -244,14 +244,14 @@ func TestCallTracer(t *testing.T) { GasPrice: big.NewInt(int64(txn.GetPrice().Uint64())), } context := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: test.Context.Miner, - BlockNumber: uint64(test.Context.Number), - Time: uint64(test.Context.Time), - Difficulty: (*big.Int)(test.Context.Difficulty), - GasLimit: uint64(test.Context.GasLimit), - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: test.Context.Miner, + BlockNumber: uint64(test.Context.Number), + Time: uint64(test.Context.Time), + Difficulty: (*big.Int)(test.Context.Difficulty), + GasLimit: uint64(test.Context.GasLimit), + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, } _, tx := memdb.NewTestTx(t) diff --git a/ethdb/kv_util.go b/ethdb/kv_util.go index 586d294e076e262b1830c1c6b63e2ebe43cb7f74..c64656521ce05ad398e0636df53736c136424798 100644 --- a/ethdb/kv_util.go +++ b/ethdb/kv_util.go @@ -32,16 +32,16 @@ func Walk(c kv.Cursor, startkey []byte, fixedbits int, walker func(k, v []byte) } // todo: return TEVM code and use it -func GetCheckTEVM(db kv.Getter) func(contractHash common.Hash) (bool, error) { - checked := map[common.Hash]struct{}{} +func GetHasTEVM(db kv.Has) func(contractHash common.Hash) (bool, error) { + contractsWithTEVM := map[common.Hash]struct{}{} var ok bool return func(contractHash common.Hash) (bool, error) { if contractHash == (common.Hash{}) { - return true, nil + return false, nil } - if _, ok = checked[contractHash]; ok { + if _, ok = contractsWithTEVM[contractHash]; ok { return true, nil } @@ -51,11 +51,15 @@ func GetCheckTEVM(db kv.Getter) func(contractHash common.Hash) (bool, error) { contractHash.String(), err) } - if !ok { - checked[contractHash] = struct{}{} + if errors.Is(err, ErrKeyNotFound) { + return false, nil + } + + if ok { + contractsWithTEVM[contractHash] = struct{}{} } - return ok, nil + return true, nil } } diff --git a/migrations/receipt_repair.go b/migrations/receipt_repair.go index 41cc0684a5a6ddaa67f8377ade26ff4567abccb5..8cf8055c6ff6e4bc16835ff7e529423feb95ac9c 100644 --- a/migrations/receipt_repair.go +++ b/migrations/receipt_repair.go @@ -17,6 +17,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/ethdb/cbor" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/log/v3" @@ -114,7 +115,8 @@ var ReceiptRepair = Migration{ intraBlockState := state.New(dbstate) getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } - receipts1, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, nil /* checkTEVM */, block, vmConfig) + contractHasTEVM := ethdb.GetHasTEVM(tx) + receipts1, err1 := runBlock(intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, contractHasTEVM, block, vmConfig) if err1 != nil { return err1 } @@ -147,7 +149,7 @@ var ReceiptRepair = Migration{ } func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWriter state.StateWriter, - chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, checkTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { + chainConfig *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, contractHasTEVM func(common.Hash) (bool, error), block *types.Block, vmConfig vm.Config) (types.Receipts, error) { header := block.Header() vmConfig.TraceJumpDest = true engine := ethash.NewFullFaker() @@ -159,7 +161,7 @@ func runBlock(ibs *state.IntraBlockState, txnWriter state.StateWriter, blockWrit } for i, tx := range block.Transactions() { ibs.Prepare(tx.Hash(), block.Hash(), i) - receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, checkTEVM) + receipt, _, err := core.ApplyTransaction(chainConfig, getHeader, engine, nil, gp, ibs, txnWriter, header, tx, usedGas, vmConfig, contractHasTEVM) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%x] failed: %v", i, tx.Hash(), err) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 750002e79d2b95ef1cf7bd2353f00cb09c961790..82cfd4dc2b7ce854bd6dd6c5d82efc89b6421562 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -213,8 +213,8 @@ func (t *StateTest) RunNoVerify(rules params.Rules, tx kv.RwTx, subtest StateSub // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - checkTEVM := func(common.Hash) (bool, error) { return false, nil } - context := core.NewEVMBlockContext(block.Header(), nil, nil, &t.json.Env.Coinbase, checkTEVM) + contractHasTEVM := func(common.Hash) (bool, error) { return false, nil } + context := core.NewEVMBlockContext(block.Header(), nil, nil, &t.json.Env.Coinbase, contractHasTEVM) context.GetHash = vmTestBlockHash if baseFee != nil { context.BaseFee = new(uint256.Int) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index c4de0d2cfcb86d344b981c578de2d3e66628c953..163b1b2f6bc50f0c6063591a4c0767c3fb01527f 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -150,15 +150,15 @@ func (t *VMTest) newEVM(state vm.IntraBlockState, vmconfig vm.Config) *vm.EVM { } transfer := func(db vm.IntraBlockState, sender, recipient common.Address, amount *uint256.Int, bailout bool) {} context := vm.BlockContext{ - CanTransfer: canTransfer, - Transfer: transfer, - GetHash: vmTestBlockHash, - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, - Coinbase: t.json.Env.Coinbase, - BlockNumber: t.json.Env.Number, - Time: t.json.Env.Timestamp, - GasLimit: t.json.Env.GasLimit, - Difficulty: t.json.Env.Difficulty, + CanTransfer: canTransfer, + Transfer: transfer, + GetHash: vmTestBlockHash, + ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil }, + Coinbase: t.json.Env.Coinbase, + BlockNumber: t.json.Env.Number, + Time: t.json.Env.Timestamp, + GasLimit: t.json.Env.GasLimit, + Difficulty: t.json.Env.Difficulty, } vmconfig.NoRecursion = true return vm.NewEVM(context, txContext, state, params.MainnetChainConfig, vmconfig) diff --git a/turbo/transactions/call.go b/turbo/transactions/call.go index f58889073d2b304d369d3660a8af4691ce285d23..21ee15706b74a11b4c27263adc7b46bf8929ddab 100644 --- a/turbo/transactions/call.go +++ b/turbo/transactions/call.go @@ -24,7 +24,7 @@ import ( const callTimeout = 5 * time.Minute -func DoCall(ctx context.Context, args ethapi.CallArgs, tx kv.Tx, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]ethapi.Account, gasCap uint64, chainConfig *params.ChainConfig, filters *filters.Filters) (*core.ExecutionResult, error) { +func DoCall(ctx context.Context, args ethapi.CallArgs, tx kv.Tx, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]ethapi.Account, gasCap uint64, chainConfig *params.ChainConfig, filters *filters.Filters, contractHasTEVM func(hash common.Hash) (bool, error)) (*core.ExecutionResult, error) { // todo: Pending state is only known by the miner /* if blockNrOrHash.BlockNumber != nil && *blockNrOrHash.BlockNumber == rpc.PendingBlockNumber { @@ -112,7 +112,7 @@ func DoCall(ctx context.Context, args ethapi.CallArgs, tx kv.Tx, blockNrOrHash r if err != nil { return nil, err } - blockCtx, txCtx := GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx) + blockCtx, txCtx := GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx, contractHasTEVM) evm := vm.NewEVM(blockCtx, txCtx, state, chainConfig, vm.Config{NoBaseFee: true}) @@ -136,7 +136,7 @@ func DoCall(ctx context.Context, args ethapi.CallArgs, tx kv.Tx, blockNrOrHash r return result, nil } -func GetEvmContext(msg core.Message, header *types.Header, requireCanonical bool, tx kv.Tx) (vm.BlockContext, vm.TxContext) { +func GetEvmContext(msg core.Message, header *types.Header, requireCanonical bool, tx kv.Tx, contractHasTEVM func(address common.Hash) (bool, error)) (vm.BlockContext, vm.TxContext) { var baseFee uint256.Int if header.Eip1559 { overflow := baseFee.SetFromBig(header.BaseFee) @@ -145,16 +145,16 @@ func GetEvmContext(msg core.Message, header *types.Header, requireCanonical bool } } return vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: getHashGetter(requireCanonical, tx), - CheckTEVM: func(common.Hash) (bool, error) { return false, nil }, - Coinbase: header.Coinbase, - BlockNumber: header.Number.Uint64(), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - GasLimit: header.GasLimit, - BaseFee: &baseFee, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: getHashGetter(requireCanonical, tx), + ContractHasTEVM: contractHasTEVM, + Coinbase: header.Coinbase, + BlockNumber: header.Number.Uint64(), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + GasLimit: header.GasLimit, + BaseFee: &baseFee, }, vm.TxContext{ Origin: msg.From(), diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index a005581fcffd1c7c5403fc26fea2a65425125353..2ada4759de89484bc01bf31caa191c482c3e94bd 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -31,7 +31,7 @@ type BlockGetter interface { } // computeTxEnv returns the execution environment of a certain transaction. -func ComputeTxEnv(ctx context.Context, block *types.Block, cfg *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, checkTEVM func(common.Hash) (bool, error), engine consensus.Engine, dbtx kv.Tx, blockHash common.Hash, txIndex uint64) (core.Message, vm.BlockContext, vm.TxContext, *state.IntraBlockState, *state.PlainState, error) { +func ComputeTxEnv(ctx context.Context, block *types.Block, cfg *params.ChainConfig, getHeader func(hash common.Hash, number uint64) *types.Header, contractHasTEVM func(common.Hash) (bool, error), engine consensus.Engine, dbtx kv.Tx, blockHash common.Hash, txIndex uint64) (core.Message, vm.BlockContext, vm.TxContext, *state.IntraBlockState, *state.PlainState, error) { // Create the parent state database reader := state.NewPlainState(dbtx, block.NumberU64()-1) statedb := state.New(reader) @@ -42,7 +42,7 @@ func ComputeTxEnv(ctx context.Context, block *types.Block, cfg *params.ChainConf // Recompute transactions up to the target index. signer := types.MakeSigner(cfg, block.NumberU64()) - BlockContext := core.NewEVMBlockContext(block.Header(), getHeader, engine, nil, checkTEVM) + BlockContext := core.NewEVMBlockContext(block.Header(), getHeader, engine, nil, contractHasTEVM) vmenv := vm.NewEVM(BlockContext, vm.TxContext{}, statedb, cfg, vm.Config{}) for idx, tx := range block.Transactions() { select {