三稚新、交易執(zhí)行
交易執(zhí)行是以太坊中最為重要的部分勘伺。
在執(zhí)行交易之前首先需要對(duì)交易進(jìn)行初步校驗(yàn):
- 交易是RLP格式的,無多余字符
- 交易的簽名是有效的
- 交易的nonce是有效的(與發(fā)送者賬戶的nonce值一致)
- gasLimit的值不小于固有g(shù)as
- 賬戶余額至少夠支付預(yù)付費(fèi)用
當(dāng)交易滿足上述條件后褂删,交易才會(huì)被執(zhí)行飞醉。
// preCheck校驗(yàn)的后三條。
//交易校驗(yàn)的前兩條是在其他地方執(zhí)行的屯阀。對(duì)于礦工來說交易簽名在加txpool的時(shí)候會(huì)檢查缅帘,在commitTransactions的時(shí)候也會(huì)檢查。
func (st *StateTransition) preCheck() error {
// Make sure this transaction's nonce is correct.
// 檢查nonce值
if st.msg.CheckNonce() {
nonce := st.state.GetNonce(st.msg.From())
if nonce < st.msg.Nonce() {
return ErrNonceTooHigh
} else if nonce > st.msg.Nonce() {
return ErrNonceTooLow
}
}
return st.buyGas()
}
func (st *StateTransition) buyGas() error {
//gasLimit*gasPrice即為預(yù)付的費(fèi)用v0
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err
}
st.gas += st.msg.Gas()
//initialGas
st.initialGas = st.msg.Gas()
st.state.SubBalance(st.msg.From(), mgval)
return nil
}
交易的形式化表示
公式51难衰,是對(duì)交易的形式化定義钦无。交易的執(zhí)行,相當(dāng)于當(dāng)前狀態(tài)和交易,通過交易轉(zhuǎn)變函數(shù),到達(dá)新的狀態(tài)盖袭。
3.1 子狀態(tài)
在交易執(zhí)行的整個(gè)過程中失暂,以太坊保持跟蹤“子狀態(tài)”。子狀態(tài)是紀(jì)錄交易中生成信息的一種方式鳄虱,當(dāng)交易完成時(shí)會(huì)立即需要這些信息弟塞。
交易的子狀態(tài)包含:
- 自毀集合(self-destruct set),用表示拙已,指在交易完成之后需要被銷毀的賬戶集合决记。
- 日志序列 (log series),用表示倍踪,指虛擬機(jī)代碼執(zhí)行的歸檔的和可檢索的檢查點(diǎn)系宫。
- 賬戶集合(touched accounts),用表示惭适,其中空的賬戶在交易結(jié)束時(shí)將被刪除。
- 退款余額(refund balance)楼镐,用癞志,指在交易完成之后需要退還給發(fā)送賬戶的總額。
子狀態(tài)的形式化表示
公式52框产,是交易子狀態(tài)的形式化表示.
公式53凄杯,定義了空的子狀態(tài).
3.2 執(zhí)行
- 對(duì)交易進(jìn)行初步檢查错洁,從發(fā)送者賬戶中扣除預(yù)付的交易費(fèi)。預(yù)付交易費(fèi)值如公式57所示戒突,為gasLimit*gasPrice + value屯碴。(代碼中是gasLimit*gasPrice,Value是在Call的過程中判斷和扣除的)
- 計(jì)算固有g(shù)as消耗,如公式54-56所示膊存。并消耗掉該花費(fèi)导而。
- 如果是創(chuàng)建合約,則走合約創(chuàng)建流程隔崎。消耗相應(yīng)花費(fèi)今艺。
- 如果是合約執(zhí)行,則走合約執(zhí)行流程爵卒。消耗相應(yīng)花費(fèi)虚缎。
- 計(jì)算退款余額,將余額退還到發(fā)送者賬戶钓株。
- 將交易的交易費(fèi)加到礦工賬戶实牡。
- 返回當(dāng)前狀態(tài),以及交易的花費(fèi)轴合。
/*
The State Transitioning Model
A state transition is a change made when a transaction is applied to the current world state
The state transitioning model does all all the necessary work to work out a valid new state root.
1) Nonce handling
2) Pre pay gas
3) Create a new state object if the recipient is \0*32
4) Value transfer
== If contract creation ==
4a) Attempt to run transaction data
4b) If valid, use result as code for the new state object
== end ==
5) Run Script section
6) Derive new state root
*/
//gp 中一開始有g(shù)asLimit數(shù)量的gas
type StateTransition struct {
gp *GasPool
msg Message
gas uint64
gasPrice *big.Int
initialGas uint64
value *big.Int
data []byte
state vm.StateDB
evm *vm.EVM
}
// 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) {
//交易檢查创坞,檢查正確的話,st.gas為gasPrice*gasLimit值桩,即預(yù)付的交易費(fèi)摆霉。
if err = st.preCheck(); err != nil {
return
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
contractCreation := msg.To() == nil
// Pay intrinsic gas
// 固有g(shù)as,也就是g0
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
)
//創(chuàng)建合約
if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// Increment the nonce for the next transaction
//執(zhí)行合約
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), 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
}
}
//計(jì)算退款奔坟,并返回到發(fā)送者賬戶
st.refundGas()
//付交易費(fèi)給礦工
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
}
3.2.1 每一步的形式化表示
計(jì)算固有g(shù)as消耗的形式化表示
公式54-56為固有g(shù)as消耗的計(jì)算方式携栋。
- 統(tǒng)計(jì),,即交易的init和data字段咳秉,0和非0分開計(jì)算婉支。為0的字節(jié)數(shù)TxDataZeroGas,非0字節(jié)數(shù)TxDataNonZeroGas澜建,并求和向挖。
- 如果是創(chuàng)建合約,則加上創(chuàng)建合約的固定消耗炕舵。
- 如果是執(zhí)行合約何之,加上合約執(zhí)行的固定消耗。
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
// 計(jì)算g0
func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
//公式55和56
if contractCreation && homestead {
gas = params.TxGasContractCreation
} else {
gas = params.TxGas
}
//公式54咽筋,根據(jù)non-zero和zero data進(jìn)行計(jì)算
// Bump the required gas by the amount of transactional data
if len(data) > 0 {
// Zero and non-zero bytes are priced differently
var nz uint64
for _, byt := range data {
if byt != 0 {
nz++
}
}
// Make sure we don't exceed uint64 for all data combinations
if (math.MaxUint64-gas)/params.TxDataNonZeroGas < nz {
return 0, vm.ErrOutOfGas
}
gas += nz * params.TxDataNonZeroGas
z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, vm.ErrOutOfGas
}
gas += z * params.TxDataZeroGas
}
return gas, nil
}
計(jì)算預(yù)付交易費(fèi)的形式化表示
公式57表示預(yù)付費(fèi)用的計(jì)算溶推。表示預(yù)付費(fèi)用為gasLimit*gasPrice+value。實(shí)際代碼中如上文buyGas.
交易初步校驗(yàn)的形式化表示
公式58為交易的初步驗(yàn)證的形式化表示蒜危。第一和第二行表示發(fā)送者賬戶不為空虱痕,且存在。第三行表示交易的nonce值為發(fā)送者的當(dāng)前nonce值辐赞。第四行表示固定消耗小于等于gasLimit(否則連固定消耗都不夠)部翘。第五行表示當(dāng)前賬戶余額必須足夠支付預(yù)付費(fèi)用。第六行表示當(dāng)前區(qū)塊的gasLimit-已經(jīng)消耗掉的gas值大于等于交易的gasLimit响委。
交易初始狀態(tài)(虛擬機(jī)執(zhí)行之前)的形式化表示
公式59-61表示交易執(zhí)行時(shí)的初始狀態(tài)晃酒,該處也是一個(gè)檢查點(diǎn)表牢。用于后續(xù)操作中出錯(cuò)時(shí)的回滾。
交易開始執(zhí)行時(shí)贝次,會(huì)首先將發(fā)送者賬戶的balance減去預(yù)付gas(gasLimit*gasPrice)崔兴,并將該賬戶的nonce加一。其他與原狀態(tài)相同蛔翅。
虛擬機(jī)執(zhí)行的形式化表示
公式62表示
- 如果交易的to為空敲茄,則是合約創(chuàng)建交易,狀態(tài)轉(zhuǎn)變函數(shù)為山析。
- 否則為合約執(zhí)行堰燎,狀態(tài)轉(zhuǎn)變函數(shù)為。
- 兩個(gè)轉(zhuǎn)變的共同參數(shù)有:初始狀態(tài)笋轨,發(fā)送者賬戶地址S(T),可用gas值g秆剪,gasPrice,交易value,原始調(diào)用者,0,和.其中可用gas值g為gasLimit-爵政,如公式63所示仅讽。原始調(diào)用者,根據(jù)交易是創(chuàng)建合約還是執(zhí)行合約會(huì)有所不同,不由交易控制钾挟,由虛擬機(jī)來控制洁灵。
- 接收賬戶
經(jīng)過虛擬機(jī)執(zhí)行后狀態(tài)從轉(zhuǎn)變?yōu)?img class="math-inline" src="https://math.jianshu.com/math?formula=%7B%5Csigma%7D_%7BP%7D" alt="{\sigma}_{P}" mathimg="1">,剩余的gas為,交易子狀態(tài)為A掺出,交易的狀態(tài)碼為z徽千。
計(jì)算退款的形式化表示
公式64表示交易執(zhí)行之后的子狀態(tài)的退款額A'為原先子狀態(tài)退款額與此次需要銷毀的自毀集合返回的退款的和。
公式65時(shí)交易執(zhí)行后的退款額的計(jì)算汤锨,退款額為交易執(zhí)行后剩余gas值g'加上双抽,子狀態(tài)退款額A'和用掉的gas(-g')的一半中較小的那個(gè)。
func (st *StateTransition) refundGas() {
// Apply refund counter, capped to half of the used gas.
// 用掉gas的一半闲礼,與子狀態(tài)退款額比較
refund := st.gasUsed() / 2
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
//剩余的總gas值
st.gas += refund
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
st.state.AddBalance(st.msg.From(), remaining)
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gas)
}
交易之后狀態(tài)的形式化表示
公式66-69定義了交易之后的預(yù)結(jié)束狀態(tài)(之所以說是預(yù)結(jié)束狀態(tài)是這個(gè)時(shí)候一些需要銷毀的狀態(tài)尚未銷毀),其和虛擬機(jī)執(zhí)行后的狀態(tài)的區(qū)別為:
- 將退款額返回給交易發(fā)送者賬戶聂抢。公式67钧嘶。
- 支付交易費(fèi)給礦工。公式68琳疏。其中m為礦工的地址有决。公式69。
公式70-72定義了空盼,交易的結(jié)束狀態(tài)书幕。結(jié)束狀態(tài)與預(yù)結(jié)束狀態(tài)的區(qū)別為,將自毀集合中的賬戶置為空揽趾,公式71台汇,將可控的賬戶集合中的死掉的賬戶,置為空篱瞎。公式72.
公式73-75苟呐,定義了交易的其他幾個(gè)相關(guān)字段。其中公式73俐筋,定義了交易的花費(fèi)為牵素,gasLimit-退款。公式74定義了交易的日志序列即為交易子狀態(tài)中的日志序列澄者。公式75定義了交易的最終狀態(tài)即為虛擬機(jī)執(zhí)行后的狀態(tài)碼笆呆。
四、合約創(chuàng)建
第三部分于合約創(chuàng)建和合約執(zhí)行的具體過程未詳細(xì)介紹粱挡。該部分對(duì)合約創(chuàng)建的過程進(jìn)行詳細(xì)的解釋赠幕。
以太坊中有兩類賬戶,一類為外部擁有賬戶抱怔,即通常意義上的用戶賬戶劣坊。一類為合約賬戶。當(dāng)一個(gè)交易是合約創(chuàng)建屈留,是指該交易的目的是創(chuàng)建一個(gè)新的合約賬戶局冰。合約賬戶創(chuàng)建的過程如下:
- 根據(jù)規(guī)則生成合約賬戶地址。公式77.
- 設(shè)置合約賬戶nonce值為1灌危,其balance設(shè)為捐獻(xiàn)值康二,storageRoot設(shè)為空,codeHash為空的Hash勇蝙。公式78-79.
- 當(dāng)前賬戶余額中減去捐獻(xiàn)值沫勿。公式80-81.
- 運(yùn)行合約,進(jìn)行合約的初始化工作。如果運(yùn)行過程中g(shù)as不足产雹,則所有狀態(tài)回滾诫惭,消耗掉所有g(shù)as。也就是被創(chuàng)建的合約賬戶也會(huì)被回滾掉蔓挖,捐獻(xiàn)值回滾到原賬戶夕土。
- 如果合約初始化運(yùn)行成功,則計(jì)算存儲(chǔ)code的花費(fèi)瘟判,若成功怨绣,則設(shè)置賬戶的code。
- 如果不足以支付存儲(chǔ)費(fèi)用拷获,回滾狀態(tài)篮撑。(這個(gè)地方根據(jù)配置不同菠镇,homestead 和 byzantium會(huì)有所不同)
// 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)
//若之前該合約賬戶對(duì)應(yīng)的地址不空吼虎,則返回地址沖突的錯(cuò)誤
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()
//創(chuàng)建合約賬戶
evm.StateDB.CreateAccount(contractAddr)
if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
evm.StateDB.SetNonce(contractAddr, 1)
}
//將捐獻(xiàn)值轉(zhuǎn)移給合約賬戶
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, 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, 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()
//運(yùn)行合約汹忠,進(jìn)行合約的初始化社付,錯(cuò)誤交由最后處理
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.
//如果合約創(chuàng)建成功奸鸯,無錯(cuò)誤返回祠丝,則計(jì)算合約存儲(chǔ)代碼的花費(fèi)嘶伟。成功的話九昧,設(shè)置合約賬戶的code铸鹰。如果存儲(chǔ)時(shí)gas不足皂岔,err交由下面處理躁垛。
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.
// 如果不是ErrCodeStoreOutOfGas的話剖毯,revert當(dāng)前狀態(tài),消耗gas教馆。說明ErrCodeStoreOutOfGas賬戶會(huì)創(chuàng)建成功逊谋。
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
}
4.1 形式化表示
合約創(chuàng)建流程的形式化表示
公式76表示合約創(chuàng)建的形式化表示。
合約創(chuàng)建需要的參數(shù)有:系統(tǒng)狀態(tài),發(fā)送者(s)土铺,原始調(diào)用者(o)胶滋,可用gas值(g)板鬓,gas價(jià)格(p),捐獻(xiàn)值(v)究恤,虛擬機(jī)的初始化代碼其實(shí)際為一段任意長度的字節(jié)數(shù)組(i),當(dāng)前虛擬機(jī)調(diào)用的棧深度(e)俭令,以及權(quán)限控制列表(w)。
虛擬機(jī)執(zhí)行合約創(chuàng)建的結(jié)果為新的中間過程狀態(tài)集合部宿,剩余的gas值g',交易子狀態(tài)A唤蔗,交易狀態(tài)碼z,合約的body code .
新建合約賬戶地址的形式化表示
公式77給出了合約賬戶的地址的計(jì)算方法窟赏,可以看出是跟發(fā)送者賬戶地址以及發(fā)送者的nonce值有關(guān)。
// CreateAddress creates an ethereum address given the bytes and the nonce
func CreateAddress(b common.Address, nonce uint64) common.Address {
data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
return common.BytesToAddress(Keccak256(data)[12:])
}
合約賬戶初始化的形式化表示
生成了合約賬戶的地址后,需要對(duì)賬戶進(jìn)行相應(yīng)的初始化。公式78-82給出了創(chuàng)建合約之后的相關(guān)狀態(tài)揽咕。
公式79表示新建的合約賬戶,其nonce值為1蛹头,balance值為捐獻(xiàn)值+原有值(如果原合約賬戶不為空,公式82),storage為空斑胜,codeHash為空的hash掺炭。
公式80-81表示如果調(diào)用賬戶若為空,則仍為空者冤。若不為空,則相應(yīng)的balance值減去捐獻(xiàn)值v。
虛擬機(jī)執(zhí)行的形式化表示
合約賬戶地址生成且初始化后吗跋,需要虛擬機(jī)執(zhí)行合約的相應(yīng)初始化代碼。公式83給出了虛擬機(jī)執(zhí)行的過程。
公式83表示入问,虛擬機(jī)在基礎(chǔ)上執(zhí)行,可用gas值為g棱烂,運(yùn)行環(huán)境參數(shù)為I哩治,s為合約創(chuàng)建的調(diào)用者地址,a為新生成的合約賬戶地址。 虛擬機(jī)執(zhí)行之后生成新的臨時(shí)狀態(tài),剩余的gas值,子狀態(tài)A台谢,以及狀態(tài)碼z。
其中I中的項(xiàng)如公式84-92所示怀读。
- 為新生成的合約賬戶地址苍糠,即a;
- 為原始調(diào)用者,即o姚炕;
- 為gas價(jià)格,即p掸刊;
- 為虛擬機(jī)調(diào)用的input data,因?yàn)槭呛霞s創(chuàng)建尼斧,所以data段為空;
- 為發(fā)送者烛恤,或者說調(diào)用者,即s;
- 為捐獻(xiàn)值杀餐,即v;
- 為初始化代碼段琼讽,即i;
- 為當(dāng)前調(diào)用棧深度脉让,即e;
- 為權(quán)限管理列表滚澜,即w借浊。
虛擬機(jī)執(zhí)行過程中如果出現(xiàn)了gas值不足的情況,則會(huì)回滾所有的狀態(tài)曙蒸。狀態(tài)回到調(diào)用合約創(chuàng)建開始時(shí)的狀態(tài),也就是調(diào)用過程中消耗掉了所有的gas值,但是合約賬戶被混滾掉审孽,變成沒有被創(chuàng)建的狀態(tài)。如果有捐獻(xiàn)值搓萧,捐獻(xiàn)值回滾至原賬戶揍移。
如果虛擬機(jī)執(zhí)行初始化合約的操作成功踏施,則需要存儲(chǔ)相應(yīng)的合約代碼到新創(chuàng)建的合約賬戶诉探。公式93時(shí)計(jì)算存儲(chǔ)合約代碼所需的花費(fèi)竖席,其值與合約的代碼長度有關(guān)束析。
虛擬機(jī)執(zhí)行之后根據(jù)執(zhí)行的情況
- gas值虽填,如果沒有出錯(cuò)牲览。當(dāng)前的臨時(shí)gas值為兔港。即在虛擬機(jī)執(zhí)行完成后飒赃,再消耗掉存儲(chǔ)code的gas。如果出錯(cuò)蔫慧,則剩余gas值為0.公式94.
- 狀態(tài)集合盟蚣,如果出錯(cuò)阐枣,則回滾到虛擬機(jī)執(zhí)行前的狀態(tài)。若成功,則狀態(tài)轉(zhuǎn)變?yōu)榕R時(shí)狀態(tài)集彬祖,然后進(jìn)行一些后續(xù)處理,如果合約賬戶是個(gè)死賬戶突倍,則將該賬戶置為空淡喜,否則對(duì)合約賬戶的code進(jìn)行設(shè)置澎嚣。
- 狀態(tài)碼模狭,如果臨時(shí)狀態(tài)集為空(虛擬機(jī)運(yùn)行初始化的時(shí)候就出錯(cuò)了)驱富,或者gas不足以支付存儲(chǔ)code的費(fèi)用线脚,則狀態(tài)碼為0.沒有出錯(cuò)則為1.
- 出錯(cuò)的情況有,1. 生成的臨時(shí)狀態(tài)集為空且code為空 2. gas不足以支付存儲(chǔ)code的費(fèi)用 3. code長度>24576。
4.2 特別提示
當(dāng)初始化代碼交由虛擬機(jī)執(zhí)行的時(shí)候伶选,新創(chuàng)建的合約賬戶地址是存在的史飞,只不過是沒有body code。因此任何調(diào)用該合約的代碼都會(huì)因?yàn)闆]有可執(zhí)行的代碼而返回錯(cuò)誤仰税。如果初始化代碼中是以自毀操作為結(jié)束的构资,那么合約賬戶在交易完成之前就會(huì)被刪除掉,目前該問題還有爭(zhēng)議陨簇。而對(duì)于正常的STOP代碼葵姥,或者是初始化執(zhí)行返回的代碼為空,虛擬機(jī)執(zhí)行完成后瞻惋,判定到該合約賬戶的code也還是空,那么這個(gè)合約賬戶就會(huì)變成一個(gè)僵尸賬戶悄晃,而其余額也會(huì)被永久的凍結(jié)在里面。
五枫疆、合約調(diào)用
合約調(diào)用的流程如下:
- 如果to賬戶地址不存在愿险,則新建.
- 從sender中轉(zhuǎn)賬value值到to賬戶.
- 從合約賬戶中獲取合約代碼,進(jìn)行設(shè)置恍箭,供虛擬機(jī)執(zhí)行.
- 虛擬機(jī)執(zhí)行合約代碼。
- 如果合約執(zhí)行出錯(cuò)呛凶,則回滾到合約執(zhí)行之前的狀態(tài)拣播。
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
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()
)
//to賬戶不存在讹剔,則新建
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 {
// Calling a non existing account, don't do antything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
}
//轉(zhuǎn)賬
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.
//設(shè)置要執(zhí)行的代碼
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)
}()
}
//執(zhí)行代碼
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
}
5.1 合約調(diào)用的形式化表示
公式98為合約調(diào)用的形式化表示蹬音。
合約調(diào)用需要的參數(shù)有:系統(tǒng)狀態(tài),發(fā)送者(s)衫画,原始調(diào)用者(o)辛孵,收款人(r),合約賬戶地址(c)斜做,可用gas值(g),gas價(jià)格(p)搜贤,轉(zhuǎn)賬額(v)锌介,捐獻(xiàn)值(),虛擬機(jī)的執(zhí)行代碼的input data其實(shí)際為一段任意長度的字節(jié)數(shù)組(d),當(dāng)前虛擬機(jī)調(diào)用的棧深度(e),以及權(quán)限控制列表(w)。
虛擬機(jī)執(zhí)行合約調(diào)用的結(jié)果為新的中間過程狀態(tài)集合世分,剩余的gas值g',交易子狀態(tài)A,交易狀態(tài)碼z臀玄,合約的調(diào)用結(jié)果.
合約調(diào)用前的狀態(tài)
公式99-105為虛擬機(jī)執(zhí)行合約代碼之前的一些臨時(shí)狀態(tài)募胃。
在虛擬機(jī)執(zhí)行合約代碼之前旗唁,首先進(jìn)行轉(zhuǎn)賬。(除非發(fā)送者和接收者相同)公式99.
由于調(diào)用者有可能是未定義的痹束。所以更嚴(yán)謹(jǐn)?shù)亩x如公式100-105.
- 調(diào)用者如果之前為空且value為0检疫,則調(diào)用者依然為空。否則祷嘶,調(diào)用者的balance減去轉(zhuǎn)賬值屎媳。公式100-102.
- 如果接收者賬戶不存在,且轉(zhuǎn)賬值不為0论巍,則新建接收者烛谊,將其nonce值0,余額為轉(zhuǎn)賬額v嘉汰,storageRoot為空的TRIE丹禀,codeHash為空的hash。
- 如果接收者賬戶不存在鞋怀,且轉(zhuǎn)賬值為0.則不處理双泪。
- 如果接收者賬戶存在,則其余額為原先的余額加上轉(zhuǎn)賬值密似。
虛擬機(jī)執(zhí)行合約調(diào)用的形式化表示
虛擬機(jī)執(zhí)行的參數(shù)為,公式109-118:
- 為接收者賬戶残腌,即r村斟,(在合約創(chuàng)建的時(shí)候,該處為新創(chuàng)建的合約賬戶);
- 為原始調(diào)用者抛猫,即o;
- 為gasPric蟆盹,即p;
- 為輸入數(shù)據(jù)(input data),即d;
- 為調(diào)用者賬戶邑滨,即s;
- 為捐獻(xiàn)值(注意這里并不是轉(zhuǎn)賬值)日缨,即;
- 當(dāng)前調(diào)用棧深度,即e;
- 權(quán)限管理掖看,即w;
- 可控的賬戶(touched accounts)為調(diào)用者賬戶和接收者賬戶;
虛擬機(jī)執(zhí)行的合約為公式119-120匣距,其中公式119的前8種為預(yù)編譯好的合約,主要完成一些基本的加密和運(yùn)算等操作哎壳,最后一種即為調(diào)用用戶的合約毅待。
合約執(zhí)行的結(jié)果如公式106-118所示。
- 如果合約執(zhí)行失敗归榕,則狀態(tài)回滾到之前的狀態(tài)尸红。如果執(zhí)行成功,則狀態(tài)轉(zhuǎn)變?yōu)閳?zhí)行后狀態(tài)。公式106.
- 如果合約執(zhí)行失敗外里,則消耗掉所有的gas怎爵,gas剩余值為0.成功,則消耗掉執(zhí)行過程中的gas盅蝗。公式107.
- 如果合約執(zhí)行失敗鳖链,則返回0,成功返回1.