从比特币脚本引擎到以太坊虚拟机
這個(gè)系列是目標(biāo)受眾是區(qū)塊鏈開(kāi)發(fā)者和有其他開(kāi)發(fā)經(jīng)驗(yàn)的CS專業(yè)學(xué)生
面對(duì)媒體對(duì)區(qū)塊鏈相關(guān)技術(shù)的解讀和吹捧,許多人一時(shí)不知所措。投資人、大公司都在FOMO(fear of missing out)的心理驅(qū)動(dòng)下,爭(zhēng)相宣布all in區(qū)塊鏈。各路大咖坐而論道,談?wù)搮^(qū)塊鏈技術(shù)的社會(huì)、政治、經(jīng)濟(jì)乃至哲學(xué)上的意義。人類對(duì)未知和不懂的東西有種天然的不安全感,作為一名開(kāi)發(fā)人員,我認(rèn)為克服焦慮(以及帶來(lái)的投機(jī)心理)最好的方法是盡可能增加對(duì)底層的原理及實(shí)現(xiàn)的認(rèn)知。
從技術(shù)角度來(lái)看,目前不論是比特幣、以太坊,抑或是尚未正式上線的EOS、IPFS,都帶有很強(qiáng)的實(shí)驗(yàn)性質(zhì),存在各種局限,而這種局限不可避免影響上層應(yīng)用的開(kāi)發(fā)。區(qū)塊鏈應(yīng)用也大多涉及金融、信用等重要領(lǐng)域,所以深入理解底層原理是對(duì)區(qū)塊鏈開(kāi)發(fā)者的一個(gè)基本要求,而不僅僅是跟著教程10分鐘部署一段智能合約,特別是早期各種技術(shù)未成熟的情況下,生搬硬套稍不留心就會(huì)造成極大的損失。
本系列的第一篇文章,主要是以比特幣為代表的加密貨幣架構(gòu)(區(qū)塊鏈1.0),和以以太坊為代表的可編程分布式信用基礎(chǔ)設(shè)施(區(qū)塊鏈2.0)的核心差異之一——是否支持圖靈完備的語(yǔ)言,來(lái)看看區(qū)塊鏈技術(shù)架構(gòu)的演進(jìn)。
比特幣和以太坊的淵源:對(duì)幣圈和鏈圈的人來(lái)說(shuō),Vitalik Buterin(1994年出生)是無(wú)可爭(zhēng)議的大神。很多人可能不知道,V神作為早期比特幣社區(qū)的活躍成員,一開(kāi)始提議bitcoin需要開(kāi)發(fā)通用的腳本語(yǔ)言來(lái)支持豐富功能的應(yīng)用開(kāi)發(fā),但沒(méi)有獲得比特幣開(kāi)發(fā)團(tuán)隊(duì)的支持。于是重起爐灶,2013年發(fā)起以太坊項(xiàng)目,有了今天的繁榮的加密token、收藏品游戲、DAO。接下來(lái),我們就先看看,V神不滿的比特幣腳本系統(tǒng)到底是什么樣的?
Part I:比特幣腳本引擎
交易
交易是在區(qū)塊鏈?zhǔn)澜缋锩嬗泻軓V泛的含義,在加密貨幣應(yīng)用中可以狹義理解為比特幣額度在不同地址間的轉(zhuǎn)移,即轉(zhuǎn)賬。轉(zhuǎn)賬是個(gè)歷史悠久的行為,但轉(zhuǎn)賬技術(shù)一直在革新。
理解比特幣轉(zhuǎn)賬模型尤其重要,因?yàn)楸忍貛拍_本引擎建立在該模型之上。
兩種轉(zhuǎn)賬方式
1、簡(jiǎn)化下的傳統(tǒng)中心式轉(zhuǎn)賬:alice(A賬戶)轉(zhuǎn)賬到bob(B賬戶)x元,銀行需要原子化的操作balance[A]-=x,balance[B]+=x,當(dāng)然隱含條件是alice完成了對(duì)A賬戶的認(rèn)證。
2、一種解釋比特幣交易原理的說(shuō)法:
網(wǎng)絡(luò)中每個(gè)節(jié)點(diǎn)維護(hù)獨(dú)立的數(shù)據(jù)庫(kù),記錄著每個(gè)地址的余額,如果Alice(addressA的擁有者)想向Bob(addressB的擁有者)轉(zhuǎn)賬x元,她會(huì)在網(wǎng)絡(luò)中廣播出去"addressA gives X units to addressB",帶上pubkeyA,用privatekeyA簽名。每個(gè)節(jié)點(diǎn)收到后,校驗(yàn)成功后,在各自數(shù)據(jù)庫(kù)中執(zhí)行原子化操作balance[addressA]-=x,balance[addressB]+=x。(注:實(shí)際地址由pubkey生成,這里為簡(jiǎn)化省略)。
上面1在現(xiàn)實(shí)中占據(jù)主流,有成熟的擴(kuò)展方案,但中心化不可避免帶來(lái)成本、平臺(tái)作惡等問(wèn)題;
2的描述來(lái)自于b-money, an anonymous, distributed electronic cash system(這篇文章非常之重要,深刻影響了中本聰對(duì)比特幣的設(shè)計(jì)),但在當(dāng)時(shí)無(wú)法實(shí)踐,因?yàn)橹囟纫蕾囉谝粋€(gè)同步、不受干擾的網(wǎng)絡(luò)環(huán)境,否則保持一致性難度很大。而且這種分布式數(shù)據(jù)庫(kù)提交問(wèn)題(Byzantine Problem),現(xiàn)有的一致性算法paxos、raft(non-byzantine)包括pbft(byzantine)擴(kuò)展性都無(wú)法支撐比特幣上萬(wàn)的節(jié)點(diǎn)數(shù)。
比特幣交易模型的設(shè)計(jì)
關(guān)于比特幣交易模型最早來(lái)自于中本聰?shù)?br />Bitcoin: A Peer-to-Peer Electronic Cash System。中本聰實(shí)際提出了兩種chain,大家現(xiàn)在一直說(shuō)的區(qū)塊鏈(chain of blocks)是顯式的數(shù)據(jù)組織方式,另一個(gè)隱式的是交易鏈(chain of transactions)才是比特幣價(jià)值流動(dòng)的鏈條。
如圖,最早的交易描述模型:
如果Alice(addressA的擁有者)想向Bob(addressB的擁有者)轉(zhuǎn)賬x元,她同樣需要把這個(gè)交易簽名后在網(wǎng)絡(luò)中廣播出去。不同的是,addressA的余額,并非存儲(chǔ)在各個(gè)節(jié)點(diǎn)的數(shù)據(jù)庫(kù)里,而是在別人給addressA轉(zhuǎn)賬的未花費(fèi)交易輸出中,即UTXO(unspent transaction output)。我們查詢addressA的余額,實(shí)際得到的是所有收款地址是adressA的UTXOs的額度的求和。廣播內(nèi)容類似"addressA(combining UTXO1...UTXO3) gives X units to addressB",帶上pubkeyA,用privatekeyA簽名。
交易在網(wǎng)絡(luò)中被確認(rèn)后,Bob就會(huì)多了一個(gè)可用UTXO。如果他想花費(fèi)這筆錢,需要證明自己擁有addressB對(duì)應(yīng)的privatekeyB,那么Bob也用私鑰簽名。這樣交易就成了一串簽名的鏈條。
顯然這里有三個(gè)問(wèn)題:
1.如果任意的交易的input都需要某個(gè)之前交易的輸出,那么最初比特幣從哪里來(lái)?
所以在比特交易中,有種叫做coinbase的交易,就是我們所周知的挖礦獎(jiǎng)勵(lì)。比特幣的產(chǎn)生就通過(guò)挖礦算法生成,這里的input來(lái)自于系統(tǒng)獎(jiǎng)勵(lì)。實(shí)際上還會(huì)校驗(yàn)coinbase是否是"mature"的,即該塊是否經(jīng)過(guò)足夠的確認(rèn)。在比特幣中如果最終沒(méi)有歸入最長(zhǎng)鏈,那么會(huì)作為orphan塊被棄,獎(jiǎng)勵(lì)也作廢。
2.判斷一個(gè)交易輸出是否是UTXO需要回溯整個(gè)區(qū)塊鏈嗎?
不需要,因?yàn)榻灰装凑誱erkel tree的結(jié)構(gòu)組織,決定了從整個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)中查詢一個(gè)交易會(huì)非常低效。UTXOs專門存儲(chǔ)在leveldb的數(shù)據(jù)庫(kù)chainstate中,并且緩存在內(nèi)存中。每當(dāng)一個(gè)新的block生成,就會(huì)更新UTXOs集;當(dāng)某個(gè)節(jié)點(diǎn)發(fā)生鏈重建現(xiàn)象,會(huì)回滾該過(guò)程。這里需要注意的是,UTXOs集不是待確認(rèn)交易池(TxMemPool),而是所有待確認(rèn)交易的input來(lái)源;UTXOs理論上也可以通過(guò)--reindex從整個(gè)區(qū)塊鏈中重建。
3.Alice的賬戶余額來(lái)自于四個(gè)UTXO,分別是0.05,0.2,0.2,0.3,現(xiàn)在需要轉(zhuǎn)帳0.6給Bob,怎么辦?
理論上Alice可以三次轉(zhuǎn),但實(shí)際上很不明智,既要多付手續(xù)費(fèi),體驗(yàn)也差,所以交易Input可以包括多個(gè)UTXO,如何選擇UTXO組合有專門的分析;多個(gè)Input之和不一定恰好等于轉(zhuǎn)賬金額Output1,還需要一個(gè)找零錢refund(Output2),當(dāng)然還會(huì)有手續(xù)費(fèi)fee(Output3),所以交易會(huì)包括多個(gè)Output。當(dāng)然對(duì)于用戶來(lái)說(shuō),只需要設(shè)定轉(zhuǎn)賬地址、額度、手續(xù)費(fèi),組合UTXO、找零等是透明的。
注:以太坊摒棄了UTXOs模型,采用類似于bmoney的賬戶范式。具體原因等到介紹以太坊虛擬機(jī)設(shè)計(jì)中再分析。
做了這么多鋪墊,終于可以進(jìn)入比特幣的腳本設(shè)計(jì)了。
Script opcodes
比特幣交易由一套腳本引擎(Script)處理。這里引用bitcoin-core源碼interpreter.cpp里的一段注釋:
/*** Script is a stack machine (like Forth) that evaluates a predicate* returning a bool indicating valid or not. There are no loops.*/Script是一種類Forth、基于棧式模型、無(wú)狀態(tài)的、非圖靈完備的語(yǔ)言。
opcodes分為常量、流程控制、棧操作、算術(shù)運(yùn)算、位運(yùn)算、密碼學(xué)運(yùn)算、保留字等若干類,還包括3個(gè)內(nèi)部使用的偽指令。下面舉幾個(gè)在后面的腳本中會(huì)出現(xiàn)的指令,全部的指令可參考官方文檔和源碼。
- OP_0 ... OP_16: 將字面量值壓入棧中。
- OP_DUP: 將棧頂元素復(fù)制一個(gè),壓入棧中。
- OP_ADD: 彈出棧頂元素和次棧頂元素,相加后壓入棧中。
- OP_EQUAL: 彈出棧頂元素和次棧頂元素,比較是否相等,相等則將1壓入棧中,否則壓入0。
- OP_SHA256: 彈出棧頂元素,進(jìn)行sha-256加密運(yùn)算,結(jié)果壓入棧中。
- OP_HASH160: 彈出棧頂元素,先進(jìn)行sha-256加密運(yùn)算,再進(jìn)行ripemd160摘要運(yùn)算,結(jié)果壓入棧中。值得注意的是,這是基于公鑰生成address的過(guò)程的一部分。
- OP_CHECKSIG: 彈出棧頂元素和次棧頂元素,這里分別是sig和pubkey;內(nèi)部有個(gè)VerifySignature函數(shù),驗(yàn)證簽名和公鑰是否匹配。
- OP_CHECKMULSIG:棧內(nèi)壓入m個(gè)簽名,n個(gè)公鑰,逐一校驗(yàn)m個(gè)簽名是否對(duì)應(yīng)n個(gè)公鑰的某個(gè)子集。
Pay-to-PubkeyHash(P2PKH)
上面Alice轉(zhuǎn)載給Bob的例子,就是一個(gè)典型的P2PKH。中本聰在論文中只是給出了交易模型,下面看看更具體的實(shí)現(xiàn)。
如上圖,Alice在轉(zhuǎn)賬給Bob前,Bob需要提供一個(gè)自己的收款地址,但實(shí)際P2PKH中使用的是Public Key Hash。這里簡(jiǎn)單補(bǔ)充下key生成過(guò)程,如下圖,私鑰單向生成公鑰,公鑰通過(guò)OP_HASH160指令生成160位的PKH(公鑰哈希),PKH可以轉(zhuǎn)成更可讀用戶使用的地址,但編碼、校驗(yàn)過(guò)程等是雙向的。所以提供地址等價(jià)于提供PKH。
下圖,Alice轉(zhuǎn)賬給Bob的錢鎖定在TX1 Output中,通過(guò)一個(gè)Pubkey Script。Bob如果嘗試花掉這筆錢,他需要解鎖這個(gè)PubkeyScript,通過(guò)證明自己是TX1 Output中Public Key Hash的私鑰擁有者,提供一個(gè)Signature Script。
下面就是這兩個(gè)腳本。
鎖定腳本: scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG 解鎖腳本: scriptSig: <sig> <pubKey>上面包括在<>之間的為要壓入棧中的數(shù),push指令缺省。實(shí)際執(zhí)行時(shí),會(huì)將scriptSig和scriptPubkey連接起來(lái),按照從左往右順序運(yùn)行腳本。
驗(yàn)證過(guò)程: <sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG棧上的情況如下面兩圖所示。有匯編基礎(chǔ)的同學(xué),對(duì)棧式計(jì)算機(jī)模型的運(yùn)作原理會(huì)很熟悉。
generation 獎(jiǎng)勵(lì)礦工
獎(jiǎng)勵(lì)礦工可以看作一種簡(jiǎn)化的P2PKH,區(qū)別在于交易的輸入來(lái)自coinbase而不是某個(gè)UTXO。
Pay-to-Script-Hash(P2SH)
P2PKH設(shè)計(jì)比較簡(jiǎn)單,接受者Bob直接提供收款地址。實(shí)際的價(jià)值流通過(guò)程中,會(huì)涉及很多條件。為了滿足更復(fù)雜的功能,BIP12中提出加入OP_EVAL指令(在程序語(yǔ)言設(shè)計(jì)中,eval意味著語(yǔ)言具備了元編程能力),并在之后由BIP16提出了更完善的交易標(biāo)準(zhǔn)P2SH。
收款方Bob需要先設(shè)計(jì)一個(gè)RedeemScript——提款腳本,再生成該腳本的Hash,提供給Alice。
Bob如果想花費(fèi)該筆UTXO,則需要提供簽名和RedeemScript,校驗(yàn)成功后執(zhí)行RedeemScript的內(nèi)容,滿足條件后則成功解鎖。
下面的redeemScript結(jié)合具體場(chǎng)景設(shè)計(jì)。后面結(jié)合智能合約的應(yīng)用給出相應(yīng)的例子。
鎖定腳本: Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL 解鎖腳本: Signature script: <sig> [sig] [sig...] <redeemScript>在P2SH交易中,由于Bob提供的是一段腳本的Hash,那么Alice實(shí)際上不知道這筆交易的細(xì)節(jié),交易的具體內(nèi)容需要Bob來(lái)設(shè)計(jì)。這就是所謂的"moving the responsibility for supplying the conditions to redeem a transaction from the sender of the funds to the redeemer. They allow the sender to fund an arbitrary transaction, no matter how complicated, using a 20-byte hash"。這在設(shè)計(jì)上也不是說(shuō)沒(méi)有爭(zhēng)議的,但是在比特幣的技術(shù)框架下,是一種以最小的改動(dòng)支持更多的特性的路徑。
比特幣的智能合約
雖然一提起智能合約,人們更多會(huì)想起來(lái)以太坊。但正如前面提到的,技術(shù)發(fā)展是一脈相承。早在1997年Nick Szabó在開(kāi)創(chuàng)性的論文 Formalizing and Securing Relationships on Public Networks中提出了智能合約的概念。比特幣的腳本系統(tǒng)支持有限的智能合約的開(kāi)發(fā),主要通過(guò)P2SH交易實(shí)現(xiàn)的。
MultiSig 多重簽名
BIP11提出了M-of-N多重簽名交易。一個(gè)交易的解鎖條件是預(yù)定指定的N個(gè)pubkey中的M個(gè)簽名認(rèn)證(M<=N)。P2PKH可以看作1-of-1的簽名。多重簽名在增加安全、托管交易等場(chǎng)景下十分有用。所以比特幣中專門實(shí)現(xiàn)了OP_CHECKMULTISIG的指令。可以通過(guò)下面的腳本設(shè)計(jì)來(lái)實(shí)現(xiàn)。
鎖定腳本: Pubkey script: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG 解鎖腳本: Signature script: OP_0 <A sig> [B sig] [C sig...]如果使用P2SH交易,也可以設(shè)計(jì)成如下腳本。
鎖定腳本: Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL 解鎖腳本: Signature script: OP_0 <A sig> <C sig> <redeemScript> 其中: Redeem script: <OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> OP_CHECKMULTISIGGavin Andresen寫(xiě)了一個(gè)2-of-3的多重簽名交易的使用例子,十分詳細(xì),我就不搬運(yùn)了。
more...
Part II:以太坊虛擬機(jī)
區(qū)塊鏈范式
Gavin Wood在黃皮書(shū)中將區(qū)塊鏈系統(tǒng)抽象為基于交易的狀態(tài)機(jī):
公式(1)中S是系統(tǒng)內(nèi)部的狀態(tài)集合,f是交易狀態(tài)轉(zhuǎn)移函數(shù),T是交易信息,初始狀態(tài)即Gensis狀態(tài);
公式(2)中F是區(qū)塊層面狀態(tài)轉(zhuǎn)移函數(shù),B是區(qū)塊信息;
公式(3)定義B是一系列交易的區(qū)塊,每個(gè)區(qū)塊都包括多個(gè)transaction;
公式(4)G是區(qū)塊定稿函數(shù),在以太坊中包括uncle塊校驗(yàn)、獎(jiǎng)勵(lì)礦工、POW校驗(yàn)等。
這個(gè)數(shù)學(xué)模型不僅是以太坊的基礎(chǔ),也是目前大多數(shù)基于共識(shí)的去中心化交易系統(tǒng)的基礎(chǔ)。
以太坊相對(duì)比特幣的提升,本質(zhì)體現(xiàn)在這個(gè)范式中的f和S。它的核心理念——具備圖靈完備和不受限制的內(nèi)部交易存儲(chǔ)空間的區(qū)塊鏈。分別對(duì)應(yīng):
- 功能強(qiáng)大的函數(shù)f,能夠執(zhí)行任何計(jì)算,比特幣不支持loop;
- 狀態(tài)S記錄任意類型的數(shù)據(jù)(包括代碼),而比特幣的UTXO模型只能計(jì)算出地址的可花費(fèi)額度。
數(shù)據(jù)結(jié)構(gòu)
在數(shù)據(jù)存儲(chǔ)方面,比特幣通過(guò)UTXO模型計(jì)算地址余額,不鼓勵(lì)用戶存入其他數(shù)據(jù);通過(guò)P2SH腳本機(jī)制,理論上可以設(shè)計(jì)各種智能合約,但受限于腳本語(yǔ)言的表達(dá)能力,難以支持復(fù)雜的合約開(kāi)發(fā)。這種設(shè)計(jì)對(duì)于加密貨幣來(lái)說(shuō)是合理的。
以太坊為了支持記錄任意的信息、執(zhí)行任意函數(shù),需要重新設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)。
Merkle Patricia Trie
以太坊中重度使用Merkle Patricia Trie組織、存儲(chǔ)數(shù)據(jù),下面我們會(huì)看到,這個(gè)新的數(shù)據(jù)結(jié)構(gòu)是通過(guò)對(duì)哈希樹(shù)和前綴樹(shù)的組合創(chuàng)新來(lái)達(dá)到目的。
約定:下面使用MPT來(lái)代替Merkle Patricia Trie。
Merkle Tree
又稱hash tree:樹(shù)的每個(gè)葉子結(jié)點(diǎn)是某個(gè)數(shù)據(jù)塊的哈希值,而每個(gè)非葉子結(jié)點(diǎn)是孩子結(jié)點(diǎn)的哈希值。如圖所示,這棵樹(shù)不存儲(chǔ)Data blocks本身。在P2P網(wǎng)絡(luò)環(huán)境中,惡意網(wǎng)絡(luò)節(jié)點(diǎn)如果修改了這顆樹(shù)上的數(shù)據(jù),將無(wú)法通過(guò)校驗(yàn)(Merkle Proof),從而保證了數(shù)據(jù)的完整、有效性。這依賴于單向哈希加密的性質(zhì)。這種性質(zhì)讓它廣泛應(yīng)用在分布式系統(tǒng)的數(shù)據(jù)校驗(yàn)中,比如IPFS、Git等。
中本聰也巧妙利用該性質(zhì),設(shè)計(jì)了比特幣的SPV(簡(jiǎn)化支付驗(yàn)證)功能。如下圖,用戶不需要運(yùn)行完整的結(jié)點(diǎn),只需要下載最長(zhǎng)鏈的區(qū)塊頭數(shù)據(jù),然后獲取待驗(yàn)證交易對(duì)應(yīng)區(qū)塊的merkle樹(shù)做校驗(yàn)。
Patricia Trie
又叫Radix Trie,是前綴樹(shù)的空間優(yōu)化變種:如果樹(shù)上某個(gè)節(jié)點(diǎn)是其父節(jié)點(diǎn)的唯一子結(jié)點(diǎn),則這兩個(gè)結(jié)點(diǎn)可以合并起來(lái)。它在這里的應(yīng)用是對(duì)長(zhǎng)整型數(shù)據(jù)的映射,由某個(gè)20bytes的以太坊地址映射到其賬戶,形如\,Address會(huì)加密編碼成16進(jìn)制的數(shù)字——在Patricia Trie上,表現(xiàn)為非葉結(jié)點(diǎn)連成的路徑。
比如,在Patricia Trie上存儲(chǔ)\<"dog","Snoopy">,"dog"會(huì)被編碼為"64 6f 67",先找到根節(jié)點(diǎn),則查詢路線為root->6->4->6->15->6->7->value,value也就是一個(gè)指向"Snoopy"的hash。這種方式相對(duì)hash表的好處在于不會(huì)出現(xiàn)沖突;但如果不做優(yōu)化,查詢步驟太長(zhǎng)。
改良點(diǎn)
為了提高效率,以太坊對(duì)樹(shù)上結(jié)點(diǎn)數(shù)據(jù)類型進(jìn)行了專門的設(shè)計(jì)。包括以下四類結(jié)點(diǎn)
- null結(jié)點(diǎn) 代表空字符串
- branch結(jié)點(diǎn) 17個(gè)元素的非葉節(jié)點(diǎn),形如\
- leaf結(jié)點(diǎn) 2個(gè)元素的葉結(jié)點(diǎn),形如\,encodedPath是地址加密編碼后的長(zhǎng)整型數(shù)字串的一部分
- extension結(jié)點(diǎn) 2個(gè)元素的非葉結(jié)點(diǎn),形如\,extension的作用是把沒(méi)有分叉的路徑上結(jié)點(diǎn)合并起來(lái),節(jié)省空間資源
如圖,是一個(gè)簡(jiǎn)化的狀態(tài)樹(shù)(狀態(tài)樹(shù)后文很快會(huì)詳細(xì)解釋,這里不妨礙作示意圖),右上角就是\<地址,余額>的映射。prefix項(xiàng)的作用是輔助編碼,可以忽略。4個(gè)賬戶的地址,按照MPT組織起來(lái)。其中所有的extension節(jié)點(diǎn)只是優(yōu)化作用,都可以用多個(gè)branch結(jié)點(diǎn)替代。
使用MPT需要有后端數(shù)據(jù)庫(kù)(以太坊中使用levelDB)維護(hù)每個(gè)結(jié)點(diǎn)間的連接關(guān)系,這個(gè)數(shù)據(jù)庫(kù)叫做狀態(tài)數(shù)據(jù)庫(kù)。使用MPT的好處包括:(1)這個(gè)結(jié)構(gòu)的根節(jié)點(diǎn)是加密的且依賴于所有的內(nèi)部數(shù)據(jù),它的哈希可以用于安全性校驗(yàn),這是merkle樹(shù)的性質(zhì),但和merkle樹(shù)不存儲(chǔ)數(shù)據(jù)塊本身不同的是,MPT樹(shù)結(jié)點(diǎn)存儲(chǔ)了地址數(shù)據(jù),這是Patricia樹(shù)的性質(zhì)(2)允許任何一個(gè)之前狀態(tài)(根部哈希已知的條件下)通過(guò)簡(jiǎn)單地改變根部哈希值而被召回。
狀態(tài)
上面在解釋MPT時(shí)已經(jīng)介紹了狀態(tài)樹(shù)的概念。以太坊中的世界狀態(tài)(World State)的概念,通過(guò)MPT映射存儲(chǔ)去中心化交易系統(tǒng)記錄的任意狀態(tài)。這對(duì)應(yīng)了區(qū)塊鏈范式中的S,是以太坊設(shè)計(jì)的一個(gè)核心概念。
如圖,一個(gè)簡(jiǎn)化的區(qū)塊中有三個(gè)root hash,對(duì)應(yīng)三棵MPT。其中state root就是狀態(tài)樹(shù)的根哈希,它是地址(160bit)到賬戶數(shù)據(jù)(Account,序列化存儲(chǔ)在levelDB)中。每次有效的交易都會(huì)導(dǎo)致?tīng)顟B(tài)變化,比如圖中簡(jiǎn)單示意了Account175的balance從27變?yōu)?5,而所有其他的賬戶多沒(méi)有發(fā)生交易,那么block175224只需要新建Account175相關(guān)分支上的數(shù)據(jù),而其他分支不需要復(fù)制!當(dāng)然以太坊主網(wǎng)上新區(qū)塊包含的交易大概為幾十到幾百不等,那么涉及的修改也會(huì)更多。關(guān)于這種結(jié)構(gòu)性能上的討論參考這篇文章。查詢最新的賬戶狀態(tài)的入口應(yīng)該是最新被確認(rèn)的區(qū)塊的狀態(tài)樹(shù)。
對(duì)以太坊的賬戶模型需要專門做個(gè)介紹。
Account
比特幣使用UTXO模型計(jì)算余額,無(wú)法滿足記錄任意狀態(tài)的需求。以太坊設(shè)計(jì)了Account模型,它會(huì)存儲(chǔ)包括:
[nonce, balance, storageRoot, codeHash]
其中nonce是交易計(jì)數(shù)器,balance是余額信息,storageRoot對(duì)應(yīng)另外一個(gè)MPT,通過(guò)它能夠在數(shù)據(jù)庫(kù)中檢索到合約的變量信息,codeHash是代碼hash值,創(chuàng)建后不可更改。
賬戶分為兩種
- 外部賬戶(externally owned accounts)
外部賬戶由私鑰控制,對(duì)應(yīng)Account模型里,storageRoot、codeHash并不存在,也就是不會(huì)存儲(chǔ)、執(zhí)行代碼。如果只有外部賬戶,那么以太坊只能支持轉(zhuǎn)賬功能。 - 合約賬戶(contract accounts)
合約賬戶可以通過(guò)外部賬戶發(fā)起交易創(chuàng)建,也可以是由另一個(gè)合約賬戶創(chuàng)建。合約賬戶在收到消息調(diào)用時(shí),會(huì)加載代碼,通過(guò)EVM執(zhí)行相應(yīng)的邏輯,修改內(nèi)部存儲(chǔ)的狀態(tài)。
交易
在UTXO模型下,交易本質(zhì)上是(通過(guò)簽名的數(shù)據(jù))對(duì)input的解鎖和對(duì)output的鎖定。在Account模型下,交易分為兩種:
- 創(chuàng)建合約,通過(guò)代碼創(chuàng)建新的合約
- 消息調(diào)用,可以轉(zhuǎn)賬也可以觸發(fā)合約的某個(gè)函數(shù)
兩種類型的交易都包括以下字段:
[nonce,gasPrice,gasLimit,to,value,[v,r,s]]
- nonce: 賬戶發(fā)出交易數(shù)量
- gasPrice,gasLimit: 用于限制交易執(zhí)行時(shí)間,防止程序死循環(huán)
- to:交易的接受者
- value:轉(zhuǎn)賬額度,如果是創(chuàng)建合約,就是捐贈(zèng)給合約的額度
- v,r,s:交易簽名相關(guān)數(shù)據(jù),可以用來(lái)確定交易發(fā)送者
合約創(chuàng)建還需要:
- init:一段不限大小的字節(jié)數(shù)組表示的EVM代碼,僅在合約創(chuàng)建時(shí)運(yùn)行一次;init執(zhí)行后返回body代碼片段,之后的合約調(diào)用都會(huì)運(yùn)行body代碼內(nèi)容。
合約賬戶的地址由sender和nonce共同決定,所以任意兩次成功的合約部署得到的地址都是不同的。從上圖能看出,代碼和狀態(tài)的存儲(chǔ)是分開(kāi)的。實(shí)際上編譯后的字節(jié)碼會(huì)存儲(chǔ)在一個(gè)virtual ROM中,且不可修改。
消息調(diào)用還需要:
- data:一段不限大小的字節(jié)數(shù)組,表示消息調(diào)用時(shí)的輸入
消息調(diào)用會(huì)修改賬戶的狀態(tài),可能是EOA賬戶也可能是合約賬戶。
交易既可以由外部賬戶發(fā)起,也可以由合約發(fā)起。比如第5228886區(qū)塊包含170個(gè)交易和7個(gè)內(nèi)部合約交易。
區(qū)塊
以太坊的區(qū)塊了加入更多的數(shù)據(jù)項(xiàng),相對(duì)比特幣要復(fù)雜很多,但其實(shí)本質(zhì)上區(qū)別不大。比如加入了叔鏈哈希,優(yōu)化激勵(lì)措施,這是為了支持挖礦協(xié)議;區(qū)塊本身還會(huì)有大量的有效性驗(yàn)證、序列化。這些內(nèi)容不在本文主題范圍,不深入討論。
參見(jiàn)上面這張圖的右半部分,一窺以太坊區(qū)塊如何組織數(shù)據(jù),能看到MPT樹(shù)的大量使用;左半部分涉及到EVM,將會(huì)是接下來(lái)的重點(diǎn)。
執(zhí)行模型
EVM準(zhǔn)確來(lái)說(shuō)是一個(gè)準(zhǔn)圖靈機(jī),文法上它能夠執(zhí)行任意操作,但為了防止網(wǎng)絡(luò)濫用、以及避免由于圖靈完整性帶來(lái)的安全問(wèn)題,以太坊中所有操作都進(jìn)行了經(jīng)濟(jì)學(xué)上的限制,也就是gas機(jī)制,有三種情況:
- 一般操作消耗費(fèi)用,比如SLOAD,SSTORE等
- 子消息調(diào)用或者合約創(chuàng)建而消耗燃料,這是執(zhí)行CREATE、CALL、CALLCODE費(fèi)用中的一部分
- 內(nèi)存使用消耗費(fèi)用,與所需要的32bytes的字?jǐn)?shù)量成正比
下圖展示了EVM執(zhí)行的內(nèi)部流程,從EVM code中取指令,所有的操作在Stack上進(jìn)行,Memory作為臨時(shí)的變量存儲(chǔ),storage是賬戶狀態(tài)。執(zhí)行受到gas avail限制。
現(xiàn)在結(jié)合EVM我們?cè)賮?lái)看看之前介紹的交易的執(zhí)行細(xì)節(jié)。正如區(qū)塊鏈范式定義的,T是以太坊狀態(tài)轉(zhuǎn)移函數(shù),也是以太坊最復(fù)雜的部分。所有的交易在執(zhí)行前,都需要先經(jīng)過(guò)內(nèi)部的有效性驗(yàn)證:
- 交易是RLP格式數(shù)據(jù),沒(méi)有多余的后綴字節(jié);
- 交易的簽名是有效的;
- 交易的隨機(jī)數(shù)是有效的;
- 燃料上限不小于實(shí)際交易過(guò)程中用的燃料;
- 發(fā)送者賬戶的余額至少大于費(fèi)用v0,需要提前支付;
下圖是消息調(diào)用的過(guò)程,每個(gè)交易可能會(huì)形成很深的調(diào)用棧,交易內(nèi)部由不同的合約之間的調(diào)用。調(diào)用通過(guò)CALL指令,參數(shù)和返回值通過(guò)memory傳遞。
錯(cuò)誤處理
EVM在合約執(zhí)行時(shí)會(huì)發(fā)生若干種錯(cuò)誤:
- 燃料不足
- 無(wú)效指令
- 缺少棧數(shù)據(jù)
- 指令JUMP JUMPI的目標(biāo)地址無(wú)效
- 新棧大小大于1024
- 棧調(diào)用深度超過(guò)1024
EVM的錯(cuò)誤處理有個(gè)簡(jiǎn)單的原則,叫做revert-state-and-consume-all-gas,即狀態(tài)恢復(fù)到交易執(zhí)行前的checkpoint,但消耗的gas不會(huì)再退還。虛擬機(jī)把錯(cuò)誤全看作是代碼出錯(cuò),不作特定的錯(cuò)誤處理。
EVM分析工具
關(guān)于EVM分析的工具可以參考Ethereum Virtual Machine (EVM) Awesome List
類EVM的圖靈完備虛擬機(jī)(WIP)
完整的EVM規(guī)格是很復(fù)雜的,但具備一定的匯編基礎(chǔ)和簡(jiǎn)化模型的能力,實(shí)現(xiàn)一個(gè)類EVM的虛擬機(jī)是可以嘗試的挑戰(zhàn)。等有空我再把自己的實(shí)現(xiàn)放上來(lái)吧。有興趣的同學(xué)可以自己動(dòng)手試試。
參考
1.A Next-Generation Smart Contract and Decentralized Application Platform
2.ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
3.Design Rationale
4.Stack Exchange: Ethereum block architecture
5.Go Ethereum
6.evm-illustrated
7.Diving Into The Ethereum VM
https://zhuanlan.zhihu.com/p/34456971
總結(jié)
以上是生活随笔為你收集整理的从比特币脚本引擎到以太坊虚拟机的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【译】zkSNARKs in a nut
- 下一篇: RAFT 寻找一种易于理解的一致性算法(