1. The StateProcessor processor traverses every transaction in the block and executes the transaction through the ApplyTransaction function
The Process function returns receipts and logs as well as the gas spent on the exchange. If the transaction fails due to insufficient gas, then an error will be returned.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) header = block.Header() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) ) // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { statedb.Prepare(tx.Hash(), block.Hash(), i) receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg) if err != nil { return nil, nil, 0, err } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) return receipts, allLogs, *usedGas, nil }
Apply Transaction creates the EVM execution environment, applies the transaction in the state database, receipts the returned receipt, and creates a bloom filter
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, uint64, error) { msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, 0, err } // 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) // Apply the transaction to the current state (included in the env) _, gas, failed, err := ApplyMessage(vmenv, msg, gp) if err != nil { return nil, 0, err } // Update the state with pending changes var root []byte if config.IsByzantium(header.Number) { statedb.Finalise(true) } else { root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() } *usedGas += gas // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // based on the eip phase, we're passing whether the root touch-delete accounts. receipt := types.NewReceipt(root, failed, *usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = gas // 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()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.BlockHash = statedb.BlockHash() receipt.BlockNumber = header.Number receipt.TransactionIndex = uint(statedb.TxIndex()) return receipt, gas, err }
ApplyMessage creates the New StateTransition and calls the Transition Db () of the StateTransition.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { return NewStateTransition(evm, msg, gp).TransitionDb() }
In TransitionDb, it will read whether the transaction is a contract creation transaction, call evm.Create if it is, and call evm.Call if it is not.
EVM contract creation process
The parameters required by the create function of EVM
- ContractRef ContractRef is a reference to the contract's backing object
- codeAndHash
type codeAndHash struct {
code []byte
hash common.Hash
}
- gas
- The value part wishes to transfer the value to the contract address when creating the contract
- Addresses contract address remember to see how the contract address is generated
The main processes are as follows.
First, the contract address is calculated based on the caller's address and the nonce value of that address.
func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) return common.BytesToAddress(Keccak256(data)[12:]) }
Then call the create function to create the contract
- Check whether the depth of contract execution has exceeded the depth of CallCreateDepth (1024) and returns empty if it exceeds.
- Check whether the sender's balance is sufficient for transfer, if not, return the error;
- The nonce value of the sender is added to 1.
- It does not exist before checking the current contract address.
- Snapshot current stateDB, if an error occurs, it can be returned according to the snapshot;
- According to the contract address, create an account in the state (that is, the contract address), and set the non CE value of the address to be 1;
- Transfer to that address (of course, some of the transactions creating the contract have a value of 0);
- Create a new contract to perform the current operation (the contract only works in the current context);
- Binding code for contract address is explained below.
- Execute in EVM and return results
- Check whether the code size exceeds the system settings (24576, byte units);
- If there is no error return, then calculate the gas needed to store code, and subtract the gas spent;
- If there are any errors, the snapshot will be used to return to the previous state of the world (of course, gas is still spent, the initial error is errExecution Reverted);
How does gas store code compute:
createDataGas := uint64(Contract Code byte Length) * params.CreateDataGas
CreateDataGas=200
The code is as follows
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) { // Depth check execution. Fail if we're trying to execute above the // limit. if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } //Add 1 to the nonce value of the caller nonce := evm.StateDB.GetNonce(caller.Address()) evm.StateDB.SetNonce(caller.Address(), nonce+1) // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a snapshot, and restore status based on that snapshot when subsequent transactions fail snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } evm.Transfer(evm.StateDB, caller.Address(), address, value) //Create a temporary contract to execute content that only works in the current context contract := NewContract(caller, AccountRef(address), value, gas) contract.SetCodeOptionalHash(&address, codeAndHash) if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, address, gas, nil } if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value) } start := time.Now() //run is where the contract creation is actually performed in evm ret, err := run(evm, contract, nil, false) // check whether the max code size has been exceeded maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize //Calculate the cost of storing contracts, 200 gas per byte if err == nil && !maxCodeSizeExceeded { createDataGas := uint64(len(ret)) * params.CreateDataGas if contract.UseGas(createDataGas) { evm.StateDB.SetCode(address, ret) } else { err = ErrCodeStoreOutOfGas } } //Any errors will be restored to the previous state according to snapshot, but gas costs will still be spent (except errExecution Reverted) if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) { evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted { contract.UseGas(contract.Gas) } } // Assign err if contract code size exceeds the max while the err is still empty. if maxCodeSizeExceeded && err == nil { err = errMaxCodeSizeExceeded } if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) } return ret, address, contract.Gas, err }
Execute contract calls
core/vm/interpreter.go fun(int *EVMInterpreter) Run(... ) 133 lines
Get the opcode, and then get the execution instructions from JumpTable according to the opcode. If the operation is illegal, the error will be returned.
op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] if !operation.valid { return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) }
The specific operation of GetOp is as follows
// GetOp returns the n'th element in the contract's byte array func (c *Contract) GetOp(n uint64) OpCode { return OpCode(c.GetByte(n)) } // GetByte returns the n'th byte in the contract's byte array func (c *Contract) GetByte(n uint64) byte { if n < uint64(len(c.Code)) { return c.Code[n] } return 0 }
Operations are defined as follows:
type operation struct { execute executionFunc //The function that the operation executes concretely constantGas uint64 //constantGas cost for this operation dynamicGas gasFunc //This operation calculates dynamic dynamicGas function // minStack tells how many stack items are required minStack int // maxStack specifies the max length the stack can have for this operation // to not overflow the stack. maxStack int // memorySize returns the memory size required for the operation memorySize memorySizeFunc halts bool // indicates whether the operation should halt further execution jumps bool // indicates whether the program counter should not increment writes bool // determines whether this a state modifying operation valid bool // indication whether the retrieved operation is valid and known reverts bool // determines whether the operation reverts state (implicitly halts) returns bool // determines whether the operations sets the return data content }
Compute new memory and extend memory to make it suitable for this operation
var memorySize uint64 if operation.memorySize != nil { memSize, overflow := operation.memorySize(stack) if overflow { return nil, errGasUintOverflow } //Memory expansion to 32 byte as a word, how memory overflow will return errors (gas calculation will also be extended later, that is, to calculate the multiple of word if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { return nil, errGasUintOverflow } }
Each operation is executed through execute
res, err = operation.execute(&pc, in, contract, mem, stack)
The execute of each function shows the implementation of core/vm/instructions.go
The design of stack core/vm/stack.go is mainly the operation of push,pop,pek,dup,swap and other general stacks.
Memory's design core/vm/memory.go