Handler
This is the logic part of the Evm. It contains the Specification ID, list of functions that do the logic and list of registers that can change behavior of the Handler when it is build.
Functions can be grouped in five categories and are marked in that way in the code:
- Validation functions:
ValidationHandler
- Pre-execution functions:
PreExecutionHandler
- Execution functions:
ExecutionHandler
- Post-execution functions:
PostExecutionHandler
- Instruction table:
InstructionTable
Handle Registers
This is a simple function that is used to modify handler functions.
The amazing thing about them is that they can be done over generic external type.
For example, this allows to have a register over trait that allows to add hooks to any type that implements the trait.
That trait can be a GetInspector
trait, so any implementation is able to register inspector-related functions.
GetInspector
is implemented on every Inspector
and it is used inside the EvmBuilder
to change behavior of the default mainnet Handler.
Handle registers are set in EvmBuilder
.
The order of the registers is important as they are called in the order they are registered.
It matters if register overrides the previous handle or just wraps it, overriding handle can disrupt the logic of previous registered handles.
Registers are very powerful as they allow modification of any part of the Evm and with additional of the External
context it becomes a powerful combo.
A simple example is to register new pre-compiles for the Evm.
ValidationHandler
Consists of functions that are used to validate transaction and block data.
They are called before the execution of the transaction, to check whether the (Environment
) data is valid.
They are called in the following order:
validate_env
: Verifies if all data is set inEnvironment
and if valid, for example ifgas_limit
is smaller than blockgas_limit
.validate_initial_tx_gas
: Calculates initial gas needed for the transaction to be executed and checks if it is less than the transaction gas_limit. Note that this does not touch theDatabase
or state.validate_tx_against_state
: Loads the caller account and checks their information. Among them the nonce, if there is enough balance to pay for max gas spent and balance transferred.
PreExecutionHandler
Consists of functions that are called before execution. They are called in the following order:
load
: Loads access list and beneficiary fromDatabase
. Cold load is done here.load_precompiles
: Retrieves the precompiles for the given spec ID. More info: precompile.deduct_caller
: Deducts values from the caller to calculate the maximum amount of gas that can be spent on the transaction. This loads the caller account from theDatabase
.
ExecutionHandler
Consists of functions that handle the execution of the transaction and the stack of the call frames.
-
call
: Called on every frame. It creates a new call frame or returns the frame result (the frame result is only returned when callingprecompile
). IfFrameReturn
is returned, then the next function that is called isinsert_call_outcome
. -
call_return
: Called after call frame returns from execution. It is used to calculate the gas that is returned from the frame and create theFrameResult
that is used to apply the outcome to parent frame ininsert_call_outcome
. -
insert_call_outcome
: Inserts the call outcome to the parent frame. It is called on every frame that is created except the first one. For the first frame we uselast_frame_return
. -
create
: Creates new create call frame, create new account and execute bytecode that outputs the code of the new account. -
create_return
: This handler is called after every frame is executed (Expect first). It will calculate the gas that is returned from the frame and apply output to the parent frame. -
insert_create_outcome
: Inserts the outcome of a call into the virtual machine's state. -
last_frame_return
: This handler is called after last frame is returned. It is used to calculate the gas that is returned from the first frame and incorporate transaction gas limit (the first frame has limitgas_limit - initial_gas
).
InstructionTable
This is a list of 256 function pointers that are used to execute instructions.
They have two types, first is simple function that is faster and second is BoxedInstruction
that has a small performance penalty but allows to capture the data.
Look at the Interpreter documentation for more information.
PostExecutionHandler
Is a list of functions that are called after the execution. They are called in the following order:
-
reimburse_caller
: Reimburse the caller with gas that was not spent during the execution of the transaction. Or balance of gas that needs to be refunded. -
reward_beneficiary
: Reward the beneficiary with the fee that was paid for the transaction. -
output
: Returns the state changes and the result of the execution. -
end
: Always called after transaction. End handler will not be called if validation fails. -
clear
: Clears journal state and error and it is always called for the cleanup.