基于Qtum-x86智能合約的創(chuàng)建過程解析
Qtum-x86智能合約創(chuàng)建過程
Qtum-x86虛擬機(jī)與以太坊EVM最大的區(qū)別之一就是智能合約實現(xiàn)過程。一般來說,智能合約開發(fā)人員會使用Remix,甚至用solc來進(jìn)行開發(fā)工作,以便將合約編譯成字節(jié)碼。在EVM合約中,發(fā)送到區(qū)塊鏈的字節(jié)碼就是從“0”處開始執(zhí)行。當(dāng)?shù)V工(或質(zhì)押人)構(gòu)建區(qū)塊并將合約交易完成上鏈時,字節(jié)開始執(zhí)行。底層的執(zhí)行過程基本概括如下:
1. 交易字節(jié)碼被發(fā)送到區(qū)塊鏈節(jié)點
2. 礦工/質(zhì)押人開始構(gòu)建一個新區(qū)塊,并開始完成包含交易的字節(jié)碼
3. 字節(jié)碼從“0”處開始執(zhí)行
4. 執(zhí)行為智能合約構(gòu)造函數(shù)生成的solidity字節(jié)碼
5.“持久的(persistent)”字節(jié)碼被復(fù)制到內(nèi)存中(即所需的全部數(shù)據(jù)和字節(jié)碼,通常不包括構(gòu)造函數(shù))
6.構(gòu)造函數(shù)修改后的“常量”在內(nèi)存中更改,以匹配要部署的最終版本
7.合約執(zhí)行結(jié)束,同時指定了應(yīng)該持久化到區(qū)塊鏈的內(nèi)存范圍
8. 區(qū)塊鏈將數(shù)據(jù)保存到以太坊的Global State Trie,通過“code”字段將要持久化的字節(jié)碼與地址關(guān)聯(lián)起來
再次調(diào)用合約時,會執(zhí)行以下過程:
1. 當(dāng)調(diào)用合約時,執(zhí)行從已持久化的合約字節(jié)碼的“0”處開始(不包括構(gòu)造函數(shù))
2. Solidity自動生成的合約代碼解析ABI數(shù)據(jù),指定應(yīng)對哪些函數(shù)和參數(shù)值執(zhí)行操作
3. 執(zhí)行函數(shù)
4. 函數(shù)返回的數(shù)據(jù)被復(fù)制到內(nèi)存中,并在合約退出執(zhí)行時指定地址(開始地址和結(jié)束地址)
正是因為Solidity編寫可能存在相關(guān)漏洞,Qtum-x86就要求大多數(shù)智能合約開發(fā)人員需要更加細(xì)心。近期,許多智能合約編寫者都知道智能合約返回的數(shù)據(jù)大小是有固定限制的,EVM可通過一些專門的操作碼允許返回長度可變的數(shù)據(jù),當(dāng)然Solidity會在需要時使用這些操作碼。
x86合約的處理過程有著明顯的不同,在許多方面更為復(fù)雜,但同時也更加靈活。其中部分原因在于它繼承了所使用的現(xiàn)有編程語言(例如,C或Rust)的差異,而沒有使用抽象了所有細(xì)節(jié)的專門構(gòu)建的語言。首先,編寫合約的過程盡管更透明,但也更復(fù)雜:
1. 創(chuàng)建一個“SimpleABI”文件,該文件指定了可以從外部調(diào)用合約的接口
2. SimpleABI程序與生成“分派”代碼的ABI文件,以及其他必需的內(nèi)容一起運行,這樣智能合約開發(fā)人員就可以不用關(guān)心解析和創(chuàng)建ABI數(shù)據(jù)的底層細(xì)節(jié)
3.然后,開發(fā)人員為指定的所有接口函數(shù)(當(dāng)然還有其他所需的非接口函數(shù))編寫代碼-;如果沒有實現(xiàn)接口函數(shù),則會導(dǎo)致鏈接器錯誤
4.然后代碼被編譯成單獨的對象文件,這些對象文件會鏈成一個內(nèi)聚的ELF文件(Linux和其他一些unix操作系統(tǒng)的標(biāo)準(zhǔn)二進(jìn)制格式)
5. 然后,ELF文件被載入一個自定義的Qtum程序,該程序?qū)⑻崛〕龈信d趣的數(shù)據(jù)并將其編譯成一個Qtum-x86字節(jié)碼格式的文件
在大多數(shù)情況下,使用Makefiles之類的工具進(jìn)行設(shè)置之后,這種過程可以自動執(zhí)行,Qtum當(dāng)然也會提供可以更改和修改的模板項目,以避免復(fù)雜的設(shè)置過程。
Qtum-x86字節(jié)碼格式不像Solidity那樣“扁平”。它有一個頭部區(qū)域,指定有關(guān)字節(jié)碼的一些信息,以及3個獨立的部分。頭部包含的數(shù)據(jù)有:
· 選項部分大小
· 代碼段大小
· 數(shù)據(jù)段大小
·“保留”(即尚未使用)
包含的三個部分是:
1. 選項 – 諸如選擇禁用或選擇啟用某些Qtum-x86功能的標(biāo)志(例如禁用字節(jié)碼升級)或更豐富的數(shù)據(jù)(如依賴關(guān)系圖和元數(shù)據(jù))之類的東西
2. 代碼 – 合約的實際可執(zhí)行數(shù)據(jù)。該部分?jǐn)?shù)據(jù)存儲為只讀和可執(zhí)行文件
3. 數(shù)據(jù) – 在合約執(zhí)行期間使用和/或修改的明文數(shù)據(jù)。該部分?jǐn)?shù)據(jù)以讀寫方式存儲,且不可執(zhí)行
與EVM不同,x86所有內(nèi)存都被認(rèn)為既是數(shù)據(jù),也是代碼。存在一些保護(hù)機(jī)制,但它要求代碼和數(shù)據(jù)能清晰地區(qū)分開來,并存儲在單獨的內(nèi)存區(qū)域中。這種保護(hù)機(jī)制是為了防止一些潛在的安全問題。盡管本身不會有安全問題,但如果代碼設(shè)計不當(dāng),就可能因為覆蓋了保護(hù)區(qū)代碼或執(zhí)行了調(diào)用方可控制的數(shù)據(jù),將小錯誤“放大”為更大的錯誤。因此,這就需要對Qtum-x86的字節(jié)碼格式進(jìn)行結(jié)構(gòu)化處理。
當(dāng)然,ELF能夠指定所有這些內(nèi)容,此外還能提供其他更多的功能,那么為什么不使用ELF作為字節(jié)碼格式呢?最大的原因是為了簡化共識模型。無論使用什么樣的字節(jié)碼格式,每個節(jié)點都必須完全一致地解析和理解這些字節(jié)碼是“共識關(guān)鍵”所在。ELF明顯更為復(fù)雜,而過去那些簡單的解析器又有幾個安全漏洞。因此,一種只保留我們所需內(nèi)容的大大精簡過的格式才是最佳的選擇。這也允許編譯器的其他可能的中間輸出格式,例如PE格式(用于Windows.exe文件的格式)。
既然知道了最終的字節(jié)碼,就可以將其廣播到區(qū)塊鏈上。Qtum-x86中執(zhí)行合約和持久化合約的方法是類似的,但二者也有足夠的差異,由此可能會影響智能合約的開發(fā)人員:
1. 包含交易的字節(jié)碼被發(fā)送到區(qū)塊鏈節(jié)點
2. 礦工/質(zhì)押人開始構(gòu)建一個新的區(qū)塊,并開始完成包含交易的字節(jié)碼
3. 解析字節(jié)碼格式,并將每個部分被映射到VM的內(nèi)存中
4. 字節(jié)碼從“代碼”內(nèi)存的第0個位置開始執(zhí)行
5. 執(zhí)行由編譯器/鏈接器插入的“crt0”代碼,以初始化堆棧并準(zhǔn)備執(zhí)行
6. 系統(tǒng)調(diào)用目的是使合約意識到正在被構(gòu)建(因此不應(yīng)期望出現(xiàn)合約調(diào)用)
7. 執(zhí)行智能合約構(gòu)造函數(shù)代碼
8. 代碼退出并結(jié)束執(zhí)行
9. 區(qū)塊鏈持久化存儲了整個合約字節(jié)碼格式文件。字節(jié)碼作為地址的“bytecode”數(shù)據(jù)字段保存到DeltaDB中。一個表示構(gòu)造、執(zhí)行和字節(jié)碼的“delta”會被插入到區(qū)塊頭中的Merkle哈希樹中
再次調(diào)用合約時,將執(zhí)行以下過程:
1. 調(diào)用合約時,執(zhí)行從“代碼”部分的“0”開始,與創(chuàng)建合約時的執(zhí)行方式相同。
2. 執(zhí)行由編譯器/鏈接器插入的“crt0”代碼,以初始化堆棧并準(zhǔn)備執(zhí)行
3. 系統(tǒng)調(diào)用目的是使合約意識到正在被構(gòu)建(因此不應(yīng)期望出現(xiàn)合約調(diào)用)
4. 執(zhí)行SimpleABI生成的代碼并解析發(fā)送給合約的ABI數(shù)據(jù)。
5. SimpleABI代碼從“共享的合約通信堆?!敝蝎@取ABI數(shù)據(jù),并將其復(fù)制到參數(shù)堆棧中,并執(zhí)行用戶實現(xiàn)的接口函數(shù)。
6. 執(zhí)行用戶實現(xiàn)的功能和邏輯
7. 將邏輯的返回數(shù)據(jù)放入?yún)?shù)堆棧中并退出函數(shù)
8. SimpleABI生成的代碼獲取返回數(shù)據(jù)并將其推送到合約通信堆棧上
9. 退出合約