Ethereum source parsing-virtual machines & Smart Contracts

Source: Internet
Author: User
   本文将从代码层级深入分析以太坊的虚拟机的设计原理和运行机制,以及智能合约运行的相关机制。   1.虚拟机堆栈和内存数据结构   虚拟机的底层数据机构是一个堆栈,包括一个stack和一个memory。

1) Let's take a look at the data structure of the stack:
Stack is a object for basic stack operations. Items popped to the stack is
Expected to be changed and modified. Stack does not take care of adding newly
initialised objects.
Type Stack struct {
data []big. Int//big.int is a struct,a 32-byte slice
}
Func newstack () Stack {
return &stack{data:make ([]
big. Int, 0, 1024)}//Specify depth 1024
}
and Push/pop/dup (copy stack top element)/peek (view stack top element)/back/swap (swap stack top and specified element)/require (guarantees that the number of top elements in the stack is greater than or equal to N)

2) Intpool
The big int pool, which can be reused, is 256 in size.
Type Intpool struct {
Pool *stack
}
As well as the Get/put function, remove or set the default value,

3) Intpoolpool
Intpool Management pool, the default capacity is 25
Type Intpoolpool struct {
Pools []*intpool
Lock sync. Mutex
}
Get/put, remove or add intpool, use the sync lock to control.

4) Memory
A simple RAM model that contains recent gas cost records, why?
Type Memory struct {
Store []byte
Lastgascost UInt64
}

Func newmemory () *memory {
return &memory{}
}
First allocated space using Resize
//Resize resizes the Memory to size
Func (M *memory) Resize (size UInt64) {
If UInt64 (M.len ()) < size {
M.store = append (M.store, make ([]byte, Size-ui Nt64 (M.len ())) ...)
}
}
Use set again to set the value
//Set sets offset + size to value
Func (M *memory) set (offset, size uint64, value []byte) {
//length of store may never is less than offset + size.
//The store should is resized PRIOR to setting the memory
If size > UInt64 (len (m.store)) {
Panic ("INVALID Me Mory:store empty ")
}
//It's possible the offset is greater than 0 and size equals 0. This is because
//the Calcmemsize (COMMON.GO) could potentially return 0 when size is zero (no-op)
If size > 0 {
Copy (M.store[offset:offset+size], value)
}
}
and functions including Get/getpro/len/data/print, There may be an issue where the slice access is out of bounds in the Getpro function.

5) Some tool class functions, such as determining whether a stack can perform a DUP or swap operation:
Func makedupstackfunc/makeswapstackfunc (n int) stackvaludationfunc

2. Virtual machine instructions, jump tables and interpreters
Operation identifies the functions and variables required for an action instruction, jumptable is a [256]operation data structure.
Type operation struct {
Execute is the Operation function
Execute Executionfunc//execute function
Gascost is the gas function and returns the gas required for execution
Gascost Gasfunc//Consumption function
Validatestack validates the stack (size) for the operation
Validatestack Stackvalidationfunc//Verify the size of the stack
Memorysize returns the memory size required for the operation
Memorysize Memorysizefunc//Memory size

halts   bool // indicates whether the operation shoult 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 opertions sets the return data content 确定操作是否设置了返回数据内容

}
Then set up three instruction sets separately:
Newhomesteadinstructionset
Newbyzantiuminstructionset
Newconstantinopleinstructionset
The latter is generated on the basis of the former.

The

Instruction.go lists a number of specific instructions, such as:
Func opPc (PC *uint64, interpreter *evminterpreter, contract *contract, memory *memory, Stack stack) ([]byte, error) {
Stack.push (Interpreter.intPool.get (). SetUint64 (
pc))
return nil, nil
}
Func opmsize (PC *uint64, interpreter *evminterpreter, contract * Contract, Memory *memory, Stack *stack) ([]byte, error) {
Stack.push (Interpreter.intPool.get ().) SetInt64 (Int64 (memory). Len ())))
return nil, nil
}

The

Gas_table.go returns a function of gas that is consumed by various instructions, basically only the errgasuintoverflow integer overflow error.
For example
Func memorygascost (Mem *memory, Newmemsize UInt64) (UInt64, error) {
If newmemsize = = 0 {
return 0, nil}
//The maximum that would fit in a UInt64 was max_word_count-1
//anything above that would result in an overflow .
//Additionally, a newmemsize which results in a
//newmemsizewords larger than 0x7ffffffff would cause the square O Peration
//to overflow.
The constant 0xffffffffe0 is the highest number so can be used without
//overflowing the gas calculation
if NE Wmemsize > 0xffffffffe0 {
return 0, Errgasuintoverflow
}
Newmemsizewords: = Towordsize (newmemsize)
Newmemsize = newmemsizewords * +
If newmemsize > UInt64 (mem. Len ()) {
Square: = newmemsizewords * Newmemsizewords
Lincoef: = newmemsizewords * params. Memorygas
Quadcoef: = Square/params. Quadcoeffdiv
Newtotalfee: = Lincoef + quadcoef

    fee := newTotalFee - mem.lastGasCost    mem.lastGasCost = newTotalFee    return fee, nil}return 0, nil

}
This function calculates the cost of memory expansion by 2, only for extended memory. NMS2 + nms*3-Last cost of memory.
There are many gas-defined functions for various directives.

Interpreter.go Interpreter
Config is the configuration options for the interpreter
Type Config struct {
Debug enabled Debugging interpreter options
Debug BOOL
Tracer is the OP code logger
Tracer Tracer
Norecursion disabled interpreter call, Callcode,
Delegate call and create.
norecursion BOOL
Enable recording of Sha3/keccak preimages
enablepreimagerecording BOOL
Jumptable contains the EVM instruction table. This
May is left uninitialised and would be set to the default
Table.
jumptable [256]operation
}

Interpreter is used to run Ethereum based contracts and would utilise the
Passed environment to query external sources for state information.
The interpreter would run the byte code VM based on the passed
Configuration.
Type Interpreter Interface {
Run loops and evaluates the contract ' s code with the given input data and returns
The return byte-slice and an error if one occurred.
Run (contract *contract, input []byte) ([]byte, error)
Canrun tells if the contract, passed as an argument, can be
Run by the current interpreter. This was meant so and the
Caller can do something like:
//
//golang // for _, interpreter := range interpreters { // if interpreter.CanRun(contract.code) { // interpreter.Run(contract.code, input) // } // } //
Canrun ([]byte) bool
IsReadOnly reports if the interpreter is in read only mode.
IsReadOnly () bool
Setreadonly sets (or unsets) read only mode in the interpreter.
Setreadonly (BOOL)
}

Evminterpreter represents an EVM interpreter
Type Evminterpreter struct {
EVM *EVM
CFG Config
Gastable params. Gastable//Identifies gas prices for many operations
Intpool *intpool
readOnly bool//Whether to throw on stateful modifications
Returndata []byte//Last Call ' return data for subsequent reuse return value of final function
}

//Newinterpreter Returns a new instance of the interpreter.
Func newevminterpreter (evm *evm, cfg Config) *interpreter {
//We Use the STOP instruction whether to see
//The Jump table is initialised. If It is not
//We'll set the default Jump table.
Tests whether the jumptable has been initialized with a stop instruction and, if not initialized, sets the default value of
if!cfg. Jumptable[stop].valid {
Switch {
case EVM. Chainconfig (). Isconstantinople (EVM. Blocknumber):
CFG. jumptable = Constantinopleinstructionset
Case EVM. Chainconfig (). Isbyzantium (EVM. Blocknumber):
CFG. jumptable = Byzantiuminstructionset
Case EVM. Chainconfig (). Ishomestead (EVM. Blocknumber):
CFG. jumptable = Homesteadinstructionset
Default:
cfg. jumptable = Frontierinstructionset
}
}
return &interpreter{
Evm:evm,
Cfg:cfg,
GASTABLE:EVM. Chainconfig (). Gastable (EVM. Blocknumber),
Intpool:newintpool (),
}
}

Func (in *evminterpreter) enforcerestrictions (OP OpCode, operation operation, stack *stack) error {
If In.evm.chainRules.IsByzantium {
If in.readonly {
If The interpreter is operating in readonly mode, make sure no
State-modifying operation is performed. The 3rd Stack item
For a call operation is the value. Transferring value from one
Account to the others means the state is modified and should also
Return with an error.
If Operation.writes | | (OP = = call && stack.) Back (2). Bitlen () > 0) {
Return errwriteprotection
}
}
}
return Nil
}

Another important function is run, which executes the code of the contract with the given argument loop and returns a byte fragment of return, and returns an error if an error occurs. Any errors returned by the interpreter are considered to consume all gas except errexecutionreverted.
Func (in *evminterpreter) Run (contract *contract, input []byte) (ret []byte, err Error) {
if In.intpool = = Nil {
In.intpool = Poolofintpools.get ()
Defer func () {
Poolofintpools.put (In.intpool)
In.intpool = Nil
}()
}

Increment the call depth which was restricted to 1024in.evm.depth++defer func () {in.evm.depth--} ()//Reset the Previou s call ' s return data. It ' s unimportant to preserve the old buffer//as every returning call would return new data Anyway.in.returnData = nil//do N ' t bother with the execution if there ' s no code.if len (contract. Code) = = 0 {return nil, Nil}var (OP OpCode//current OpCode mem = newmemory ()//bound memory s    Tack = Newstack ()//local stack//For optimisation reason we "re using UInt64 as the program counter. It ' s theoretically possible to go above 2^64. The YP defines the PC//To be uint256.    Practically much less so feasible. PC = UInt64 (0)//Program counter cost UInt64//copies used by Tracer pccopy UInt64//needed for the Deferre  D Tracer gascopy UInt64//For Tracer to log gas remaining before execution logged bool//deferred Tracer should Ignore already logged steps) contract. Input = input//Reclaim tHe stack as an int pool when the execution Stopsdefer func () {in.intPool.put (Stack.data ...)} ()//check if the Debug state is if In.cfg.Debug {defer func () {if err! = Nil {if!logged {in.c Fg.                Tracer.capturestate (IN.EVM, Pccopy, op, gascopy, Cost, mem, stack, contract, in.evm.depth, err)} else {            In.cfg.Tracer.CaptureFault (IN.EVM, Pccopy, op, gascopy, Cost, mem, stack, contract, in.evm.depth, err) }}} ()} for Atomic.        LoadInt32 (&in.evm.abort) = = 0 {if In.cfg.Debug {//Capture pre-execution values for tracing. Logged, pccopy, gascopy = False, PC, contract. Gas}//Get the next command to execute OP = contract. Getop (PC) Operation: = In.cfg.jumptable[op] If!operation.valid {return nil, fmt. Errorf ("Invalid opcode 0x%x", int (OP))}//Check if there is enough stack space if err: = Operation.validatestack (stack); Err! = Nil {return nil, err}//If the operation is valid, enforce and write restrictions If err: = In.enforcerestrictions (OP, operation, Stack); Err! = Nil {return nil, err} var memorysize uint64//Calculate the new memory size and expand the Memor Y to fit//the operation if operation.memorysize! = nil {memsize, overflow: = BigUint64 (operation.memorysiz  E (stack)) if overflow {return nil, errgasuintoverflow}//memory is expanded in words of Bytes.        Gas//was also calculated in words. If memorysize, overflow = Math. Safemul (Towordsize (memsize), 32);      Overflow {return nil, errgasuintoverflow}}//Calculates the cost of the gas and uses it if it is not enough. Cost, err = Operation.gascost (in.gastable, IN.EVM, contract, Stack, mem, memorysize) if err! = Nil | | !contract. Usegas (cost) {return nil, Erroutofgas} if memorysize > 0 {mem. Resize (memorysize)} if In.cfg.Debug {in.cfg.Tracer.CaptureState (IN.EVM, PC, OP, gascopy, Cost, mem, Stack, Contract, In.evm.depth,ERR) logged = true}//execute the Operation res, err: = Operation.execute (&pc, in, contract, Mem, St ACK)//Verifypool is a build flag.    Pool verification makes sure the integrity//of the integer pool by comparing values to a default value. If Verifypool {verifyintegerpool (In.intpool)}//If the operation clears the return data (e.g. it has retur    Ning data)//Set the last return to the result of the operation.        If Operation.returns {//If there is a return value set the return value, only the last one is valid. In.returndata = res} switch {case err! = Nil:return Nil, err case Operation.reverts:return R  ES, errexecutionreverted case Operation.halts:return Res, nil case!operation.jumps:pc++}}return Nil, Nil

}

Virtual machines
Contract.go
Type Contractref interface{address () common. Address} This is a reference to the support object behind a contract.
The ACCOUNTREF implements the above interface.
Type contract struct {
Calleraddress is the user account that initializes the contract, and is set as the caller of the contract if the contract invocation is initialized
Calleraddress Common. Address
Caller Contractref
Self contractref
Jumpdests Destinations//jumpdest instruction analysis.
code []byte//Codes
Codehash Common. Hash//Code hash
Codeaddr *common. Address//Code addresses
Input []byte//Enter parameter
Gas UInt64//contract remaining gas
Value *big. INT//The remainder of the contract ETH
Args []byte//Parameter
delegatecall BOOL
}

constructor function
Func newcontract (caller Contractref, Object contractref, Value *big. Int, gas UInt64)Contract {
c: = &contract{calleraddress:caller. Address (), Caller:caller, Self:object, Args:nil}
If caller is a contract, Jumpodests is set to caller jumpdests.
If parent, OK: = caller. (
contract); OK {
Reuse Jumpdest analysis from the parent context if available.
C.jumpdests = parent.jumpdests
} else {
C.jumpdests = make (destinations)
}
Gas should is a pointer so it can safely be reduced through the run
This pointer would be is off the state transition
C.gas = Gas
Ensures a value is set
C.value = value
Return C
}
In order to chain call, when the caller is a contract, set the calleraddress value of this contract to caller corresponding values
Func (cContract) Asdelegate () Contract {
C.delegatecall = True
Note:caller must, at the all times is a contract. It should never happen
That caller was something other than a contract.
Parent: = C.caller. (
contract)
C.calleraddress = parent. Calleraddress
C.value = Parent.value
Return C
}
Getop is used to get the next hop command,
It is assumed that the contract code has been disassembled into an instruction collection and then obtained in input or args.
Func (c *contract) getop (n UInt64) OpCode

The next two

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.