以太坊源码分析之七智能合约

以太坊源码分析之七智能合约

这个以太坊之所以较之于比特币看上去更高大上一些,主要原因是提供了智能合约,以太坊的智能合约确实提供了一个更方便、更开放区块链的接口。

首先说明一下,要想看懂以太坊的智能合约部分,首先要对智能合约有所了解,回头会专门写一个智能合约编写的系列。扯回来。

先看智能合约的入口,不管智能合约还是交易,创建的时候儿都是有Transaction来实现的。不同的是创建的目标是一样的,在前面的交易中提到过,没有目标地址的创建即为智能合约的地址。

一、合约的创建

//如果TO为空,则为智能合约交易,前面提到过

if tx.To() == nil {

//前面这调用SendTx->addTx

signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())

from, err := types.Sender(signer, tx)

if err != nil {

return common.Hash{}, err

}

addr := crypto.CreateAddress(from, tx.Nonce())

log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())

}

其它节点通过网络传播过来的Tx(handler.go:handle()->handleMsg:case msg.Code == TxMsg->AddRemotes):

func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {

         return pool.addTxs(txs, false)

}

 

// addTx enqueues a single transaction into the pool if it is valid.

func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {

         pool.mu.Lock()

         defer pool.mu.Unlock()

 

         // Try to inject the transaction and update any state

         replace, err := pool.add(tx, local)

         if err != nil {

                   return err

         }

         // If we added a new transaction, run promotion checks and return

         if !replace {

                   from, _ := types.Sender(pool.signer, tx) // already validated

                   pool.promoteExecutables([]common.Address{from})

         }

         return nil

}

从前面的分析可以看出其实最终他们都调用了promoteExecutables这个函数来进行预执行,判断是否合格的交易。在addr := crypto.CreateAddress(from, tx.Nonce())可以看到,一个预先被预测出来(看下面分析)的合约地址被创建出来写入日志。

为什么说是预测,因为只有真正上链,这个地址才会真正创建出来,如下:

/core/blockchain.go

// SetReceiptsData computes all the non-consensus fields of the receipts

func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) {

……

     for j := 0; j < len(receipts); j++ {

              // The transaction hash can be retrieved from the transaction itself

              receipts[j].TxHash = transactions[j].Hash()

 

              // The contract address can be derived from the transaction itself

              if transactions[j].To() == nil {

                       // Deriving the signer is expensive, only do if it's actually needed

                       from, _ := types.Sender(signer, transactions[j])

                       receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce())

              }

     }…….

}

看看是一样的代码吧,如果用交易信息来查看,则提交上去时,地址是空的,等真正打包上链,这个地址才会有。明白了原因吧。所以不管是交易还是合约都是交易需要走下面流程:

Addtx->判断是否合约->promoteExecutables

下面来看具体的创建过程:

// TransitionDb will transition the state by applying the current message and

// returning the result including the the used gas. It returns an error if it

// failed. An error indicates a consensus issue.

func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {

         if err = st.preCheck(); err != nil {

                   return

         }

         msg := st.msg

         sender := st.from() // err checked in preCheck

 

         homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)

         contractCreation := msg.To() == nil

 

         // Pay intrinsic gas

         gas, err := IntrinsicGas(st.data, contractCreation, homestead)

         if err != nil {

                   return nil, 0, false, err

         }

         if err = st.useGas(gas); err != nil {

                   return nil, 0, false, err

         }

 

         var (

                   evm = st.evm

                   // vm errors do not effect consensus and are therefor

                   // not assigned to err, except for insufficient balance

                   // error.

                   vmerr error

         )

         if contractCreation {

        //创建合约

                   ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)

         } else {

                   // Increment the nonce for the next transaction

                   st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1)

                   ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)

         }

         if vmerr != nil {

                   log.Debug("VM returned with error", "err", vmerr)

                   // The only possible consensus-error would be if there wasn't

                   // sufficient balance to make the transfer happen. The first

                   // balance transfer may never fail.

                   if vmerr == vm.ErrInsufficientBalance {

                            return nil, 0, false, vmerr

                   }

         }

         st.refundGas()

         st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))

 

         return ret, st.gasUsed(), vmerr != nil, err

}

从AddTx---ApplyTransaction—ApplyMessage到上面的函数终于是一个合约创建好了。需要注意的,这个AddTx和前面的交易的不是一个,别弄混。这个是从DeployContract(base.go)这个函数过来的。它调用transact再SendTransaction(simulated.go)-AddTx。

而在TransitionDb中调用:

//core/vm/evm.go

// Create creates a new contract using code as deployment code.

func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err 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

         }

         // Ensure there's no existing contract already at the designated address

    //设置随机值

         nonce := evm.StateDB.GetNonce(caller.Address())

         evm.StateDB.SetNonce(caller.Address(), nonce+1)

 

    //创建合约地址,和上面是不是一样,不同的情况分别创建这里是部署时

         contractAddr = crypto.CreateAddress(caller.Address(), nonce)

         contractHash := evm.StateDB.GetCodeHash(contractAddr)

         if evm.StateDB.GetNonce(contractAddr) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {

                   return nil, common.Address{}, 0, ErrContractAddressCollision

         }

         // Create a new account on the state

         snapshot := evm.StateDB.Snapshot()

         evm.StateDB.CreateAccount(contractAddr)

         if evm.ChainConfig().IsEIP158(evm.BlockNumber) {

                   evm.StateDB.SetNonce(contractAddr, 1)

         }

    //见下面的传输数据

         evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)

 

         // initialise a new contract and set the code that is to be used by the

         // E The contract is a scoped evmironment for this execution context

         // only.

    //创建合约

         contract := NewContract(caller, AccountRef(contractAddr), value, gas)

         contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)

 

         if evm.vmConfig.NoRecursion && evm.depth > 0 {

                   return nil, contractAddr, gas, nil

         }

 

         if evm.vmConfig.Debug && evm.depth == 0 {

                   evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)

         }

         start := time.Now()

 

    //预先执行合约,见下面

         ret, err = run(evm, contract, nil)

 

         // check whether the max code size has been exceeded

         maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize

         // if the contract creation ran successfully and no errors were returned

         // calculate the gas required to store the code. If the code could not

         // be stored due to not enough gas set an error and let it be handled

         // by the error checking condition below.

         if err == nil && !maxCodeSizeExceeded {

                   createDataGas := uint64(len(ret)) * params.CreateDataGas

                   if contract.UseGas(createDataGas) {

            //更新接口代码

                            evm.StateDB.SetCode(contractAddr, ret)

                   } else {

                            err = ErrCodeStoreOutOfGas

                   }

         }

 

         // When an error was returned by the EVM or when setting the creation code

         // above we revert to the snapshot and consume any gas remaining. Additionally

         // when we're in homestead this also counts for code storage gas errors.

         if maxCodeSizeExceeded || (err != nil && (evm.ChainConfig().IsHomestead(evm.BlockNumber) || 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, contractAddr, contract.Gas, err

}

它会调用core/evm.go

// NewEVMContext creates a new context for use in the EVM.

func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {

         // 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{

                   CanTransfer: CanTransfer,

                   Transfer:    Transfer,

                   GetHash:     GetHashFn(header, chain),

                   Origin:      msg.From(),

                   Coinbase:    beneficiary,

                  BlockNumber: new(big.Int).Set(header.Number),

                   Time:        new(big.Int).Set(header.Time),

                   Difficulty:  new(big.Int).Set(header.Difficulty),

                   GasLimit:    header.GasLimit,

                   GasPrice:    new(big.Int).Set(msg.GasPrice()),

         }

}

// GetHashFn returns a GetHashFunc which retrieves header hashes by number

func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {

         return func(n uint64) common.Hash {

                   for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {

                            if header.Number.Uint64() == n {

                                     return header.Hash()

                            }

                   }

 

                   return common.Hash{}

         }

}

 

// CanTransfer checks wether there are enough funds in the address' account to make a transfer.

// This does not take the necessary gas in to account to make the transfer valid.

func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {

         return db.GetBalance(addr).Cmp(amount) >= 0

}

 

// Transfer subtracts amount from sender and adds amount to recipient using the given Db

func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {

         db.SubBalance(sender, amount)

         db.AddBalance(recipient, amount)

}

再往下调用就是执行部分了,后面再分析,这里先埋个坑。

最后更新接口代码:

func (self *StateDB) SetCode(addr common.Address, code []byte) {

         stateObject := self.GetOrNewStateObject(addr)

         if stateObject != nil {

                   stateObject.SetCode(crypto.Keccak256Hash(code), code)

         }

}

func (self *stateObject) SetCode(codeHash common.Hash, code []byte) {

         prevcode := self.Code(self.db.db)

         self.db.journal = append(self.db.journal, codeChange{

                   account:  &self.address,

                   prevhash: self.CodeHash(),

                   prevcode: prevcode,

         })

         self.setCode(codeHash, code)

}

func (self *stateObject) setCode(codeHash common.Hash, code []byte) {

         self.code = code

         self.data.CodeHash = codeHash[:]

         self.dirtyCode = true

         if self.onDirty != nil {

                   self.onDirty(self.Address())

                   self.onDirty = nil

         }

}

到现在一个智能合约就创建成功了。

 

二、合约的调用:

EVM的调用接口在 vm/inferface.go

// CallContext provides a basic interface for the EVM calling conventions. The EVM EVM

// depends on this context being implemented for doing subcalls and initialising new EVM contracts.

type CallContext interface {

         // Call another contract

         Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)

         // Take another's contract code and execute within our own context

         CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)

         // Same as CallCode except sender and value is propagated from parent to child scope

         DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error)

         // Create a new contract

         Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error)

}

在vm/evm.go中实现

这里只分析一个:

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {

         if evm.vmConfig.NoRecursion && evm.depth > 0 {

                   return nil, gas, nil

         }

 

         // Fail if we're trying to execute above the call depth limit

         if evm.depth > int(params.CallCreateDepth) {

                   return nil, gas, ErrDepth

         }

         // Fail if we're trying to transfer more than the available balance

         if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {

                   return nil, gas, ErrInsufficientBalance

         }

 

         var (

                   to       = AccountRef(addr)

                   snapshot = evm.StateDB.Snapshot()

         )

         if !evm.StateDB.Exist(addr) {

                   precompiles := PrecompiledContractsHomestead

                   if evm.ChainConfig().IsByzantium(evm.BlockNumber) {

                            precompiles = PrecompiledContractsByzantium

                   }

                   if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {

                            return nil, gas, nil

                   }

                   evm.StateDB.CreateAccount(addr)

         }

         evm.Transfer(evm.StateDB, caller.Address(), to.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.

         contract := NewContract(caller, to, value, gas)

         contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

 

         start := time.Now()

 

         // Capture the tracer start/end events in debug mode

         if evm.vmConfig.Debug && evm.depth == 0 {

                   evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)

 

                   defer func() { // Lazy evaluation of the parameters

                            evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)

                   }()

         }

         ret, err = run(evm, contract, input)

 

         // When an error was returned by the EVM or when setting the creation code

         // above we revert to the snapshot and consume any gas remaining. Additionally

         // when we're in homestead this also counts for code storage gas errors.

         if err != nil {

                   evm.StateDB.RevertToSnapshot(snapshot)

                   if err != errExecutionReverted {

                            contract.UseGas(contract.Gas)

                   }

         }

         return ret, contract.Gas, err

}

Call,CallCode, DelegateCall, StaticCall四者有什么区别呢?先说简单的,静态调用表示不能修改状态,类似于const函数,委托调用是委托别人调用。麻烦在前两个Call和CallCode,它们的区别在于存储不同,啥意思呢?举一个例子:

A调用Call()  B,使用B的存储,如果A调用CallCode() B,则使用A的存储。详细的见下面的相关解释。

https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall

// Execute executes the code using the input as call data during the execution.

// It returns the EVM's return value, the new state and an error if it failed.

//

// Executes sets up a in memory, temporarily, environment for the execution of

// the given code. It enabled the JIT by default and make sure that it's restored

// to it's original state afterwards.

func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {

         if cfg == nil {

                   cfg = new(Config)

         }

         setDefaults(cfg)

 

         if cfg.State == nil {

                   db, _ := ethdb.NewMemDatabase()

                   cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))

         }

         var (

                   address = common.StringToAddress("contract")

                   vmenv   = NewEnv(cfg)

                   sender  = vm.AccountRef(cfg.Origin)

         )

         cfg.State.CreateAccount(address)

         // set the receiver's (the executing contract) code for execution.

         cfg.State.SetCode(address, code)

         // Call the code with the given configuration.

         ret, _, err := vmenv.Call(

                   sender,

                   common.StringToAddress("contract"),

                   input,

                   cfg.GasLimit,

                   cfg.Value,

         )

 

         return ret, cfg.State, err

}

 

// Create executes the code using the EVM create method

func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {

         if cfg == nil {

                   cfg = new(Config)

         }

         setDefaults(cfg)

 

         if cfg.State == nil {

                   db, _ := ethdb.NewMemDatabase()

                   cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))

         }

         var (

                   vmenv  = NewEnv(cfg)

                   sender = vm.AccountRef(cfg.Origin)

         )

 

         // Call the code with the given configuration.

         code, address, leftOverGas, err := vmenv.Create(

                   sender,

                   input,

                   cfg.GasLimit,

                   cfg.Value,

         )

         return code, address, leftOverGas, err

}

 

// Call executes the code given by the contract's address. It will return the

// EVM's return value or an error if it failed.

//

// Call, unlike Execute, requires a config and also requires the State field to

// be set.

func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) {

         setDefaults(cfg)

 

         vmenv := NewEnv(cfg)

 

         sender := cfg.State.GetOrNewStateObject(cfg.Origin)

         // Call the code with the given configuration.

         ret, leftOverGas, err := vmenv.Call(

                   sender,

                   address,

                   input,

                   cfg.GasLimit,

                   cfg.Value,

         )

 

         return ret, leftOverGas, err

}

 

三、合约的执行

合约的执行是需要一个执行机,也就是Stack,它来操作内存并控制Code的动作流程

// stack is an object for basic stack operations. Items popped to the stack are

// expected to be changed and modified. stack does not take care of adding newly

// initialised objects.

type Stack struct {

         data []*big.Int

}

 

func newstack() *Stack {

         return &Stack{data: make([]*big.Int, 0, 1024)}

}

 

func (st *Stack) Data() []*big.Int {

         return st.data

}

 

func (st *Stack) push(d *big.Int) {

         // NOTE push limit (1024) is checked in baseCheck

         //stackItem := new(big.Int).Set(d)

         //st.data = append(st.data, stackItem)

         st.data = append(st.data, d)

}

func (st *Stack) pushN(ds ...*big.Int) {

         st.data = append(st.data, ds...)

}

 

func (st *Stack) pop() (ret *big.Int) {

         ret = st.data[len(st.data)-1]

         st.data = st.data[:len(st.data)-1]

         return

}

 

func (st *Stack) len() int {

         return len(st.data)

}

 

func (st *Stack) swap(n int) {

         st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]

}

 

func (st *Stack) dup(pool *intPool, n int) {

         st.push(pool.get().Set(st.data[st.len()-n]))

}

 

func (st *Stack) peek() *big.Int {

         return st.data[st.len()-1]

}

 

// Back returns the n'th item in stack

func (st *Stack) Back(n int) *big.Int {

         return st.data[st.len()-n-1]

}

 

func (st *Stack) require(n int) error {

         if st.len() < n {

                  return fmt.Errorf("stack underflow (%d <=> %d)", len(st.data), n)

         }

         return nil

}

 

func (st *Stack) Print() {

         fmt.Println("### stack ###")

         if len(st.data) > 0 {

                   for i, val := range st.data {

                            fmt.Printf("%-3d  %v\n", i, val)

                   }

         } else {

                   fmt.Println("-- empty --")

         }

         fmt.Println("#############")

}

 

它调用的内存控制:

// Memory implements a simple memory model for the ethereum virtual machine.

type Memory struct {

         store       []byte

         lastGasCost uint64

}

 

func NewMemory() *Memory {

         return &Memory{}

}

 

// Set sets offset + size to value

func (m *Memory) Set(offset, size uint64, value []byte) {

         // length of store may never be less than offset + size.

         // The store should be resized PRIOR to setting the memory

         if size > uint64(len(m.store)) {

                   panic("INVALID memory: 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)

         }

}

 

// 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-uint64(m.Len()))...)

         }

}

 

// Get returns offset + size as a new slice

func (self *Memory) Get(offset, size int64) (cpy []byte) {

         if size == 0 {

                   return nil

         }

 

         if len(self.store) > int(offset) {

                   cpy = make([]byte, size)

                   copy(cpy, self.store[offset:offset+size])

 

                   return

         }

 

         return

}

 

// GetPtr returns the offset + size

func (self *Memory) GetPtr(offset, size int64) []byte {

         if size == 0 {

                   return nil

         }

 

         if len(self.store) > int(offset) {

                   return self.store[offset : offset+size]

         }

 

         return nil

}

 

// Len returns the length of the backing slice

func (m *Memory) Len() int {

         return len(m.store)

}

 

// Data returns the backing slice

func (m *Memory) Data() []byte {

         return m.store

}

 

func (m *Memory) Print() {

         fmt.Printf("### mem %d bytes ###\n", len(m.store))

         if len(m.store) > 0 {

                   addr := 0

                   for i := 0; i+32 <= len(m.store); i += 32 {

                            fmt.Printf("%03d: % x\n", addr, m.store[i:i+32])

                            addr++

                   }

         } else {

                   fmt.Println("-- empty --")

         }

         fmt.Println("####################")

}

内存的操作(在指令instructions.go中)

func opMload(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {

         offset := stack.pop()

         val := new(big.Int).SetBytes(memory.Get(offset.Int64(), 32))

         stack.push(val)

 

         evm.interpreter.intPool.put(offset)

         return nil, nil

}

 

func opMstore(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {

         // pop value of the stack

         mStart, val := stack.pop(), stack.pop()

         memory.Set(mStart.Uint64(), 32, math.PaddedBigBytes(val, 32))

 

         evm.interpreter.intPool.put(mStart, val)

         return nil, nil

}

func opMstore8(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {

         off, val := stack.pop().Int64(), stack.pop().Int64()

         memory.store[off] = byte(val & 0xff)

 

         return nil, nil

}

操作码部分太多,不列出来了。然后就是执行了,要填坑了:

再看看run:vm/evm.go

// 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) ([]byte, error) {

         if contract.CodeAddr != nil {

        //调用原生合约(预编译好,不用解释器解释)

                   precompiles := PrecompiledContractsHomestead

                   if evm.ChainConfig().IsByzantium(evm.BlockNumber) {

                            precompiles = PrecompiledContractsByzantium

                   }

                   if p := precompiles[*contract.CodeAddr]; p != nil {

                            return RunPrecompiledContract(p, input, contract)

                   }

         }

    //否则调用解释器进行解释执行

         return evm.interpreter.Run(contract, input)

}

它会调用:

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.

func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {

         gas := p.RequiredGas(input)

         if contract.UseGas(gas) {

                   return p.Run(input)

         }

         return nil, ErrOutOfGas

}

//下面为原生合约的Map中四个Run中的一个

func (c *ecrecover) Run(input []byte) ([]byte, error) {

         const ecRecoverInputLength = 128

 

         input = common.RightPadBytes(input, ecRecoverInputLength)

         // "input" is (hash, v, r, s), each 32 bytes

         // but for ecrecover we want (r, s, v)

 

         r := new(big.Int).SetBytes(input[64:96])

         s := new(big.Int).SetBytes(input[96:128])

         v := input[63] - 27

 

         // tighter sig s values input homestead only apply to tx sigs

         if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) {

                   return nil, nil

         }

         // v needs to be at the end for libsecp256k1

         pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v))

         // make sure the public key is a valid one

         if err != nil {

                   return nil, nil

         }

 

         // the first byte of pubkey is bitcoin heritage

         return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil

}

如果不是预编译好的合约(可以认为类似于标准库),调用解释器(vm/interpreter.go):

func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {

         // Increment the call depth which is restricted to 1024

         in.evm.depth++

         defer func() { in.evm.depth-- }()

 

         // Reset the previous call's return data. It's unimportant to preserve the old buffer

         // as every returning call will return new data anyway.

         in.returnData = nil

 

         // Don'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

                   stack = 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 deferred Tracer

                   gasCopy uint64 // for Tracer to log gas remaining before execution

                   logged  bool   // deferred Tracer should ignore already logged steps

         )

         contract.Input = input

 

         if in.cfg.Debug {

                  defer func() {

                            if err != nil {

                                     if !logged {

                                               in.cfg.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)

                                     }

                            }

                   }()

         }

         // The Interpreter main run loop (contextual). This loop runs until either an

         // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during

         // the execution of one of the operations or until the done flag is set by the

         // parent context.

         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 operation from the jump table and validate the stack to ensure there are

                   // enough stack items available to perform the operation.

                   op = contract.GetOp(pc)

                   operation := in.cfg.JumpTable[op]

                   if !operation.valid {

                            return nil, fmt.Errorf("invalid opcode 0x%x", int(op))

                   }

                   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 memory to fit

                   // the operation

                   if operation.memorySize != nil {

                            memSize, overflow := bigUint64(operation.memorySize(stack))

                            if overflow {

                                     return nil, errGasUintOverflow

                            }

                            // memory is expanded in words of 32 bytes. Gas

                            // is also calculated in words.

                            if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {

                                     return nil, errGasUintOverflow

                            }

                   }

 

                   if !in.cfg.DisableGasMetering {

                            // consume the gas and return an error if not enough gas is available.

                            // cost is explicitly set so that the capture state defer method cas get the proper cost

                            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.evm, contract, mem, stack)

                   // 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 returning data)

                   // set the last return to the result of the operation.

                   if operation.returns {

                            in.returnData = res

                   }

 

                   switch {

                   case err != nil:

                            return nil, err

                   case operation.reverts:

                            return res, errExecutionReverted

                   case operation.halts:

                            return res, nil

                   case !operation.jumps:

                            pc++

                   }

         }

         return nil, nil

}

这样智能合约从创建到调用,最后执行,从字节码部署,到Code的组装,内存分配,然后栈执行,解释器动作,一套流程就把智能合约基本理清楚了。

在下一篇的虚拟机里,举一个例子看一下整个调用走的流程,这样会更清晰一些。

如果对区块链和c++感兴趣,欢迎关注:

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/81427651
今日推荐