1.智能合約的源起
區(qū)塊鏈技術(shù)自比特幣誕生之日起便受到了廣泛的關(guān)注。最初,區(qū)塊鏈僅僅作為記錄用戶交易的底層賬本,不支持用戶訂制其它功能。
比特幣為了實(shí)現(xiàn)交易,即用戶間轉(zhuǎn)賬的功能,設(shè)計(jì)了一套基于棧的簡(jiǎn)單腳本語(yǔ)言。這套語(yǔ)言不支持循環(huán),不具備圖靈完備性,僅限于比特幣客戶端內(nèi)部使用,且只圍繞交易這一項(xiàng)功能,一般被稱之為“Bitcoin Script”。
如果大家去查看比特幣區(qū)塊鏈中的每一筆交易記錄,那么會(huì)發(fā)現(xiàn)交易內(nèi)容其實(shí)是一串字節(jié)碼,這串字節(jié)碼就是Bitcoin Script。比特幣對(duì)Bitcoin Script書寫的交易代碼的格式進(jìn)行了限制,這種做法保證了交易的合法性與資金的安全性,但犧牲了整個(gè)系統(tǒng)的可編程性與靈活性。
一般情況下,一套語(yǔ)言能實(shí)現(xiàn)成千上萬(wàn)種功能,如果設(shè)計(jì)一套語(yǔ)言只是實(shí)現(xiàn)了一個(gè)功能,未免有些可惜。或許Vitalik Buterin正是發(fā)現(xiàn)了區(qū)塊鏈中腳本語(yǔ)言的可能性,于是他在以太坊中把語(yǔ)言請(qǐng)到了舞臺(tái)中央,供用戶創(chuàng)建與調(diào)用,這也成為了以太坊最具魅力的特性——智能合約。
與比特幣的Bitcoin Script對(duì)應(yīng),以坊中腳本語(yǔ)言名字叫EVM語(yǔ)言(Ethereum Virtual Machine Code)。EVM語(yǔ)言也是基于棧的語(yǔ)言,但它是圖靈完備的語(yǔ)言,且以太坊設(shè)計(jì)了專門的虛擬機(jī)EVM來(lái)為其提供運(yùn)行環(huán)境,這和Bitcoin Script有明顯的區(qū)別。
2.智能合約的使用以交易為接口
為了明確系統(tǒng)的功能,以太坊擴(kuò)充了交易的概念。在比特幣中,交易一般指用戶之間的轉(zhuǎn)賬操作,在以太坊中,交易除了轉(zhuǎn)賬,還包括創(chuàng)建或者調(diào)用智能合約。因此可以說(shuō)EVM語(yǔ)言也是為了交易而存在,但它服務(wù)的交易的內(nèi)容廣泛得多。
我們先補(bǔ)充一些必要的概念。以太坊中賬戶分為外部賬戶與合約賬戶,外部賬戶就是用戶使用的賬戶,其中包括了用戶的私鑰和錢包等重要信息;合約賬戶用來(lái)存放一個(gè)智能合約,通常是由外部賬戶創(chuàng)建的。
用戶發(fā)送到以太坊區(qū)塊鏈上的每一筆交易中都包含幾個(gè)關(guān)鍵字段:“from”表示交易發(fā)起者,“to”表示交易接收者,“value”表示交易金額,“data”表示附帶的信息。
上文提及的三種操作的交易格式如下:
1) 普通轉(zhuǎn)賬操作:“from A, to B, value C”表示從外部賬戶A向外部賬戶B轉(zhuǎn)賬,轉(zhuǎn)賬金額為C;
2) 智能合約創(chuàng)建操作:“from A, to (空), value C, data D”表示外部賬戶A創(chuàng)建一個(gè)智能合約,向該合約賬戶里轉(zhuǎn)賬C, 合約的代碼為D;
3) 智能合約調(diào)用操作:“from A, to E, data F”表示外部賬戶A調(diào)用合約賬戶E的智能合約,本次調(diào)用傳入的參數(shù)為F。
3一筆交易的處理流程
下面我們來(lái)分析一筆交易在以太坊區(qū)塊鏈中是如何被處理與執(zhí)行的。
這部分在以太坊的源碼中十分清晰,因此我們跟隨源碼里的函數(shù)調(diào)用流程來(lái)進(jìn)行說(shuō)明。以太坊Go版本源碼地址:https://github.com/ethereum/go-ethereum。
首先先定位,我們可以從core/blockchain.go中找到執(zhí)行的core/state_processor.go中Process()方法,在Process()方法中可以找到如下一行代碼:
receipt, _,err:= ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
根據(jù)這個(gè)函數(shù)名字我們知道已經(jīng)找到了執(zhí)行交易的入口。
3.1 創(chuàng)建EVM虛擬機(jī)
瀏覽在core/state_processor.go中的ApplyTransaction()方法,可發(fā)現(xiàn)如下三行關(guān)鍵代碼:
context := NewEVMContext(msg, header, bc, author)
vmenv := vm.NewEVM(context, statedb, config, cfg)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
第一行是創(chuàng)建新的EVM的執(zhí)行上下文環(huán)境,第二行是創(chuàng)建新的EVM,第三行是用新創(chuàng)建的EVM來(lái)處理交易消息。由此可知,每一筆交易在執(zhí)行之前以太坊都會(huì)創(chuàng)建一個(gè)EVM虛擬機(jī)來(lái)負(fù)責(zé)該交易的執(zhí)行。
繼續(xù)瀏覽core/state_transition.go中的ApplyMessage()方法,發(fā)現(xiàn)該方法只有一行代碼:
return NewStateTransition(evm, msg, gp).TransitionDb()
繼續(xù)看core/state_transition.go中的TransitionDb()方法,發(fā)現(xiàn)方法中有一個(gè)重要的分支:
if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
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)
}
這段代碼的意思是先判斷是不是創(chuàng)建合約的操作,如果是,則調(diào)用Create()方法,如果不是,則調(diào)用Call()方法。由此可知,交易的三種操作中,創(chuàng)建合約使用的方法是Create(),而調(diào)用合約與轉(zhuǎn)賬使用的方法是Call()。
接下來(lái)我們分別看一下這兩個(gè)方法。
3.2 智能合約的創(chuàng)建
先看core/vm/evm.go中的Create()方法。該方法只有兩行代碼:
contractAddr=crypto.CreateAddress(caller.Address(),evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
第一行是根據(jù)外部賬戶的地址和nonce值計(jì)算將要?jiǎng)?chuàng)建的合約賬戶的地址,這個(gè)nonce參數(shù)用來(lái)記錄該外部賬戶已創(chuàng)建的合約的數(shù)目。
第二行是將創(chuàng)建的合約賬戶地址和其它參數(shù)一起傳給同文件中的create()方法。
繼續(xù)看create()方法可看到如下三行代碼:
nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1)
contractHash := evm.StateDB.GetCodeHash(contractAddr)
第一行是獲取想創(chuàng)建合約的外部賬戶的nonce值。
第二行是將該nonce的值加一后寫回去,第三行是計(jì)算合約地址的哈希值確保不會(huì)發(fā)生地址沖突。
在計(jì)算出合約地址后,可看到下面一行代碼:
evm.StateDB.CreateAccount(contractAddr)
這行代碼是根據(jù)合約地址創(chuàng)建出了合約賬戶。合約賬戶創(chuàng)建完后,可看到下面一行代碼:
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
創(chuàng)建者從外部賬戶向合約賬戶轉(zhuǎn)賬,金額為value。至此,合約賬戶創(chuàng)建工作完成了。
接下來(lái)需要?jiǎng)?chuàng)建合約對(duì)象并把合約代碼跑起來(lái):
contract:=NewContract(caller, AccountRef(contractAddr), value, gas) contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)
第一行是創(chuàng)建合約對(duì)象,第二行是將用戶定義的智能合約代碼綁定到該合約對(duì)象上。
合約對(duì)象創(chuàng)建完后,用下面一行代碼運(yùn)行該合約:
ret, err = run(evm, contract, nil)
可能有人會(huì)疑惑:創(chuàng)建完合約為什么要運(yùn)行一遍?
這主要有兩方面的原因:其一,系統(tǒng)需保證合約代碼是能正確運(yùn)行的,這樣在以后的調(diào)用中才不會(huì)出錯(cuò);其二,系統(tǒng)需要通過(guò)運(yùn)行才能計(jì)算出消耗的gas數(shù)量,進(jìn)而完成對(duì)外部賬戶的創(chuàng)建合約操作的扣費(fèi)。其實(shí)在create()方法中還有檢查棧深度、創(chuàng)建快照、出錯(cuò)后回滾、gas計(jì)算等代碼,因它們不涉及到本文的主要內(nèi)容,故略過(guò)。
3.3 轉(zhuǎn)賬與智能合約的調(diào)用
然后我們看core/vm/evm.go中的Call()方法,該方法負(fù)責(zé)合約調(diào)用和轉(zhuǎn)賬兩種交易操作。
Call()方法中不需要?jiǎng)?chuàng)建新的地址,只需要:
to = AccountRef(addr)
該行代碼獲取交易接收方的地址。之后可看到下面一行代碼:
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
交易發(fā)起者向交易接收方轉(zhuǎn)賬value金額。接下來(lái)的代碼和Create()相像:
contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr,evm.StateDB.GetCodeHash(addr),evm.StateDB.GetCode(addr))
第一行創(chuàng)建合約對(duì)象,第二行將接收者地址上的智能合約代碼綁定到合約對(duì)象上。如果是轉(zhuǎn)賬操作,接收者地址上沒(méi)有代碼,因此綁定的代碼是空,之后的運(yùn)行會(huì)很快結(jié)束。綁定完成后,使用run()方法運(yùn)行該合約完成調(diào)用:
ret, err = run(evm, contract, input)
在Call()方法中同樣還有檢查棧深度、創(chuàng)建快照、出錯(cuò)會(huì)滾、gas計(jì)算等代碼,留給感興趣的讀者自行閱讀。
Create()與Call()中最后執(zhí)行都調(diào)用了core/vm/evm.go中的run()方法,而run()方法中可發(fā)現(xiàn):
return interpreter.Run(contract, input, readOnly)
接下來(lái)定位到core/vm/interpreter.go中的Run()方法。該方法是EVM中解釋器的運(yùn)行流程,核心邏輯為循環(huán)取出合約的代碼,查表解析出具體的操作碼,再查表計(jì)算出需要消耗的gas數(shù)目,然后調(diào)用操作碼相應(yīng)的處理函數(shù)執(zhí)行。
核心代碼如下:
for atomic.LoadInt32(&in.evm.abort) == 0 {
…
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
…
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
…
res, err := operation.execute(&pc, in, contract, mem, stack)
…
}
至此,一筆交易的運(yùn)行就結(jié)束了。
4. 結(jié)語(yǔ)
綜上所述,我們能了解到以太坊設(shè)計(jì)的三種交易背后能給予用戶極大的自由度,也充分發(fā)揮了EVM語(yǔ)言及其虛擬機(jī)的功能。
用戶通過(guò)Solidity等語(yǔ)言編寫智能合約,然后編譯成EVM語(yǔ)言,再打包成交易的格式發(fā)送到區(qū)塊鏈上運(yùn)行。以太坊得益于這種模式帶來(lái)的可編程的特性,引領(lǐng)區(qū)塊鏈技術(shù)進(jìn)入了2.0時(shí)代。
然而,現(xiàn)在智能合約由于EVM的棧深度與gas消耗的限制,多數(shù)都是簡(jiǎn)單且袖珍的程序。即使現(xiàn)在這樣的程序已經(jīng)足夠滿足需求,但未來(lái)必將面臨更多更加復(fù)雜化的交易場(chǎng)景。
如何去應(yīng)對(duì)這些場(chǎng)景,需要廣大開(kāi)發(fā)者們繼續(xù)努力。
評(píng)論
查看更多