Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

core, all: split vm.Context into BlockContext and TxContext #21672

Merged
merged 3 commits into from
Nov 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,11 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// Execute the call.
msg := callMsg{call}

evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil)
txContext := core.NewEVMTxContext(msg)
evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{})
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{})
gasPool := new(core.GasPool).AddGas(math.MaxUint64)

return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb()
Expand Down
10 changes: 4 additions & 6 deletions cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
txIndex = 0
)
gaspool.AddGas(pre.Env.GasLimit)
vmContext := vm.Context{
vmContext := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Coinbase: pre.Env.Coinbase,
Expand All @@ -119,7 +119,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
Difficulty: pre.Env.Difficulty,
GasLimit: pre.Env.GasLimit,
GetHash: getHash,
// GasPrice and Origin needs to be set per transaction
}
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
// done in StateProcessor.Process(block, ...), right before transactions are applied.
Expand All @@ -143,10 +142,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
vmConfig.Tracer = tracer
vmConfig.Debug = (tracer != nil)
statedb.Prepare(tx.Hash(), blockHash, txIndex)
vmContext.GasPrice = msg.GasPrice()
vmContext.Origin = msg.From()
txContext := core.NewEVMTxContext(msg)

evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
if chainConfig.IsYoloV2(vmContext.BlockNumber) {
statedb.AddAddressToAccessList(msg.From())
if dst := msg.To(); dst != nil {
Expand Down Expand Up @@ -185,7 +183,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
receipt.GasUsed = msgResult.UsedGas
// if the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce())
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
Expand Down
10 changes: 6 additions & 4 deletions cmd/geth/retesteth.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,12 +671,13 @@ func (api *RetestethAPI) AccountRange(ctx context.Context,
}
// Recompute transactions up to the target index.
signer := types.MakeSigner(api.blockchain.Config(), block.Number())
context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil)
for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer)
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
txContext := core.NewEVMTxContext(msg)
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
Expand Down Expand Up @@ -781,12 +782,13 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
}
// Recompute transactions up to the target index.
signer := types.MakeSigner(api.blockchain.Config(), block.Number())
context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil)
for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer)
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
txContext := core.NewEVMTxContext(msg)
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
Expand Down
16 changes: 11 additions & 5 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,32 @@ type ChainContext interface {
GetHeader(common.Hash, uint64) *types.Header
}

// NewEVMContext creates a new context for use in the EVM.
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) 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 {
beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation
} else {
beneficiary = *author
}
return vm.Context{
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Origin: msg.From(),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).SetUint64(header.Time),
Difficulty: new(big.Int).Set(header.Difficulty),
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),
}
}

// NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg Message) vm.TxContext {
MariusVanDerWijden marked this conversation as resolved.
Show resolved Hide resolved
return vm.TxContext{
Origin: msg.From(),
GasPrice: new(big.Int).Set(msg.GasPrice()),
}
}

Expand Down
5 changes: 3 additions & 2 deletions core/state_prefetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co
return err
}
// Create the EVM and execute the transaction
context := NewEVMContext(msg, header, bc, author)
vm := vm.NewEVM(context, statedb, config, cfg)
context := NewEVMBlockContext(header, bc, author)
txContext := NewEVMTxContext(msg)
vm := vm.NewEVM(context, txContext, statedb, config, cfg)

_, err = ApplyMessage(vm, msg, gaspool)
return err
Expand Down
48 changes: 30 additions & 18 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb)
}
blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number))
if err != nil {
return nil, nil, 0, err
}
statedb.Prepare(tx.Hash(), block.Hash(), i)
receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv)
if err != nil {
return nil, nil, 0, err
}
Expand All @@ -81,34 +87,25 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil
}

// ApplyTransaction attempts to apply a transaction to the given state database
// 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, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, err
}
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment
context := NewEVMContext(msg, header, bc, author)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg)

txContext := NewEVMTxContext(msg)
// Add addresses to access list if applicable
if config.IsYoloV2(header.Number) {
statedb.AddAddressToAccessList(msg.From())
if dst := msg.To(); dst != nil {
statedb.AddAddressToAccessList(*dst)
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range vmenv.ActivePrecompiles() {
for _, addr := range evm.ActivePrecompiles() {
statedb.AddAddressToAccessList(addr)
}
}

// Update the evm with the new transaction context.
evm.Reset(txContext, statedb)
// Apply the transaction to the current state (included in the env)
result, err := ApplyMessage(vmenv, msg, gp)
result, err := ApplyMessage(evm, msg, gp)
if err != nil {
return nil, err
}
Expand All @@ -128,7 +125,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
receipt.GasUsed = result.UsedGas
// if the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
Expand All @@ -139,3 +136,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo

return receipt, err
}

// ApplyTransaction attempts to apply a transaction to the given state database
// 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, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, err
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
}
8 changes: 4 additions & 4 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR contains a lot of these changes -- might be better to leave it as is, to make the change smaller?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: ignore that. You made the Context explicit now, so the change is needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a follow-up PR can do some changes that cleans up some names, e.g. changing evm.Context into evm.BlockCtx. But let's try to keep this PR small

istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
contractCreation := msg.To() == nil

// Check clauses 4-5, subtract intrinsic gas if everything is correct
Expand All @@ -245,7 +245,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.gas -= gas

// Check clause 6
if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) {
if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) {
return nil, ErrInsufficientFundsForTransfer
}
var (
Expand All @@ -260,7 +260,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
st.refundGas()
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))

return &ExecutionResult{
UsedGas: st.gasUsed(),
Expand Down
41 changes: 27 additions & 14 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
return nil, errors.New("no compatible interpreter")
}

// Context provides the EVM with auxiliary information. Once provided
// BlockContext provides the EVM with auxiliary information. Once provided
// it shouldn't be modified.
type Context struct {
type BlockContext struct {
// CanTransfer returns whether the account contains
// sufficient ether to transfer the value
CanTransfer CanTransferFunc
Expand All @@ -102,10 +102,6 @@ type Context struct {
// GetHash returns the hash corresponding to n
GetHash GetHashFunc

// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE

// Block information
Coinbase common.Address // Provides information for COINBASE
GasLimit uint64 // Provides information for GASLIMIT
Expand All @@ -114,6 +110,14 @@ type Context struct {
Difficulty *big.Int // Provides information for DIFFICULTY
}

// TxContext provides the EVM with information about a transaction.
// All fields can change between transactions.
type TxContext struct {
MariusVanDerWijden marked this conversation as resolved.
Show resolved Hide resolved
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
}

// EVM is the Ethereum Virtual Machine base object and provides
// the necessary tools to run a contract on the given state with
// the provided context. It should be noted that any error
Expand All @@ -125,7 +129,8 @@ type Context struct {
// The EVM should never be reused and is not thread safe.
type EVM struct {
// Context provides auxiliary blockchain related information
Context
Context BlockContext
TxContext
// StateDB gives access to the underlying state
StateDB StateDB
// Depth is the current call stack
Expand Down Expand Up @@ -153,17 +158,18 @@ type EVM struct {

// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
evm := &EVM{
Context: ctx,
Context: blockCtx,
TxContext: txCtx,
StateDB: statedb,
vmConfig: vmConfig,
chainConfig: chainConfig,
chainRules: chainConfig.Rules(ctx.BlockNumber),
chainRules: chainConfig.Rules(blockCtx.BlockNumber),
interpreters: make([]Interpreter, 0, 1),
}

if chainConfig.IsEWASM(ctx.BlockNumber) {
if chainConfig.IsEWASM(blockCtx.BlockNumber) {
// to be implemented by EVM-C and Wagon PRs.
// if vmConfig.EWASMInterpreter != "" {
// extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
Expand All @@ -187,6 +193,13 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
return evm
}

// Reset resets the EVM with a new transaction context.Reset
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
evm.TxContext = txCtx
evm.StateDB = statedb
}

// Cancel cancels any running EVM operation. This may be called concurrently and
// it's safe to be called multiple times.
func (evm *EVM) Cancel() {
Expand Down Expand Up @@ -233,7 +246,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}
evm.StateDB.CreateAccount(addr)
}
evm.Transfer(evm.StateDB, caller.Address(), addr, value)
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)

// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
Expand Down Expand Up @@ -426,7 +439,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, ErrDepth
}
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
nonce := evm.StateDB.GetNonce(caller.Address())
Expand All @@ -447,7 +460,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1)
}
evm.Transfer(evm.StateDB, caller.Address(), address, value)
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)

// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
Expand Down
4 changes: 2 additions & 2 deletions core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ func TestEIP2200(t *testing.T) {
statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original}))
statedb.Finalise(true) // Push the state into the "original" slot

vmctx := Context{
vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
}
vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})

_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
if err != tt.failure {
Expand Down
Loading