編者的話:本提案爲Qtum聯合創始人以及核心開發工程師Jordan Earls發佈在Github的Qtum x86虛擬機最新進展中(點擊閱讀原文或複製連接至瀏覽器打開:https://x86.qtum.org/),他提出了一種關於存儲的全新技術提案——Qtum-x86虛擬機中全部存儲區均可實現租賃機制,高效的節省了虛擬機內存。就目前而言,在EVM基礎設施不受影響下,全新租賃機制是一種「被動」的方式去實現,不須要特殊的合約邏輯來處理相關的租賃過程,也不須要支付所消耗存儲區的租金。該技術設計將有效地限制節點須要存儲的數據總量,同時還能限制輕量級SPV節點對智能合約進行有效治理與交互所需的總空間。git
爲何會提出EVM租賃機制?github
區塊鏈基礎設施技術背後的邏輯都面臨着一個問題:「區塊鏈虛擬機數據愈來愈多怎麼辦?」,所以存儲空間的消耗長期以來一直是區塊鏈領域內一個受人關注的話題。任何區塊鏈,尤爲是具備智能合約功能和附加狀態的區塊鏈,據估計存儲空間的需求會在短短10年內超出大多數計算機和服務器的存儲能力。數據庫
這種磁盤空間需求的膨脹將致使全節點會集中到那些能負擔得起價格高昂的具備很是大存儲的價格的人手中。簡而言之,將來就會出現因爲存儲設備的門檻致使資源的集中,也就與去中心化背道而馳。瀏覽器
舉例而言,數年來隨着區塊鏈市場的蓬勃發展,以太坊交易數量愈來愈多,單個區塊體積的最大值限制使得區塊空餘空間顯得愈來愈小。如圖,相比比特幣而言以太坊的區塊大小更加呈現增量上升的模式,甚至在2017年後以太坊的區塊大小由不足0.1TB上升至接近0.4TB。
安全
*比特幣與以太坊區塊體積大小(來源自網絡)
服務器
所以,愈來愈多的開發者和相關技術都在進行底層基礎設施的探索,想要商業應用於區塊鏈技術真正實現技術的融合,就須要不斷去提出新的思想和新的技術探索,所以本文提出共享存儲的設想,幫助區塊鏈的基礎設施早一步更好的搭建商業設施的橋樑。
網絡
QIP-17:Qtum-x86內的存儲區租賃區塊鏈
對此在Qtum x86設計上所作的變動能夠劃分爲如下3個部分:ui
DeltaDB 從新傳播和租賃行爲設計
「休眠」狀態的智能合約行爲
「喚醒」休眠狀態的方法
首先,用於不一樣狀態的術語解釋
活躍:該狀態要求支付必定的租金,從而在區塊鏈上保持其活躍性並易於訪問
休眠:未能在適當的時間內支付租金時所處的狀態,而且在沒有經過交易進行從新傳播的狀況下不能經過智能合約直接訪問
喚醒:這是將休眠狀態恢復到活躍狀態的動做,以即可以再次經過智能合約直接訪問它
DeltaDB 從新傳播和租賃行爲
合約的每個狀態,包括它本身的字節碼,都有一個經過區塊高度表示的租金計時器。一旦該計時器值爲0,就會從活躍狀態切換到休眠狀態,而且節點能夠安全地從其內部數據庫中刪除與該狀態相關的大部分數據。當訪問或修改狀態時,會隱性地進行租金支付,這會被計入至該操做的gas開銷中。當經過訪問數據進行租金支付時,該狀態會將其計時器重置爲RENT_TERM。沒法經過預付款的方式將一個狀態的計時器設置爲大於RENT_TERM的值。
使用DeltaDB當前的共識模型時,讀取一個狀態(一般)不會向DeltaDB證實樹添加新的delta(狀態更改/通知)。使用本文提出的存儲區租金提案,每一個狀態訪問都會經過向DeltaDB證實樹提交一個delta從而引發狀態的「從新傳播」。雖然這對合約甚至大多數區塊鏈開發人員而言都沒有影響,但仍是會帶來許多反作用:
SPV(輕錢包)節點能夠證實狀態最近一次租金支付的時間
相反,它容許能夠從SPV或全節點的內部數據庫中刪除狀態和大多數證實開銷的證實
SPV節點能夠更快地得到狀態數據的抗審查證實,經過更頻繁地傳播合約中最經常使用的數據,須要掃描的區塊也更少
除了喚醒狀態所需的開銷以外,這不會消耗額外的區塊空間,由於DeltaDB證實樹會以單個32字節長的哈希值的形式保存在區塊頭中,而不會帶來其餘的開銷
因爲可以證實再也不須要比RENT_TERM更舊的數據, 這能夠大大下降Qtum-x86區塊鏈理論上的最大磁盤空間消耗,尤爲是在進行修剪操做時。經過修剪,通過500個區塊後,大多數喚醒狀態下的交易就再也不須要存儲了
固然,這將限制節點存儲的數據僅限於持續共識所需的數據。區塊鏈上的證實老是可用的,例如出版證實等用例。然而,這些證實一般只會被添加一次,而且以後也是偶爾纔會被訪問,所以對於共識而言是非必要的。
對於每一個休眠狀態,節點須要記錄如下數據:
狀態最後一次傳播所處的區塊高度(即最後一次支付租金的時間)
索引數據的密鑰哈希
智能合約
大多數關於存儲租賃設計的提案都要求智能合約具備明確且易錯的租金管理和意識。在這種設計中,一切都是隱性的,在特定合約設計以外不須要進行租金檢測操做。經過這個提案,必定程度上會對不可避免的異常行爲產生影響,包括合約試圖訪問休眠狀態時拋出的異常。
與以太坊的異常模型會消耗全部的gas不一樣,該機制只會消耗合約產生異常時的那部分gas,以及必定的「異常稅」
全部修改後的狀態都會被恢復,這點與以太坊的異常模型相似,而且這些被恢復的狀態不會在DeltaDB中傳播
在發生異常以前訪問的全部活躍狀態都會有租金支付,所以這些狀態會在DeltaDB中傳播
若是活躍狀態被修改了而且實際執行過程當中從未讀取過該狀態,則狀態不會有租金支付,所以也不會在DeltaDB中傳播。若是執行沒有以異常結束,則將傳播修改後的新狀態
若是執行附加了喚醒狀態,則此狀態會被標記爲「已訪問」,所以即便在執行中出現異常,該狀態仍會在DeltaDB中傳播並恢復。請注意,恢復狀態下存在「喚醒稅」,必須在合約執行開始前支付。若是發送到帳戶的用於支付喚醒稅的gas數太少,則將不會進行任何恢復操做,除了返回代表執行失敗的收據以外,不會執行其餘的操做而且全部gas都會被消耗掉
若是執行附加了喚醒狀態,但該狀態已經處於被喚醒的狀態,那麼這個已經處於喚醒狀態的狀態將被忽略,而且也不會消耗任何gas。這使得那些爲確保合約成功執行而謹慎地加入即將到期的喚醒狀態的人沒必要支付成本。附加的休眠狀態將被喚醒並需支付喚醒稅
在上述這些被附加的狀態已經處於喚醒狀態的狀況下,該狀態會被認爲是已訪問的,所以會在DeltaDB中傳播而且INDEX_TAX + PROP_TAX將按狀態鍵收費
這種隱性租金支付和異常設計方案意味着大多數合約徹底不須要擔憂租賃機制的正常運做。可是,對於那些須要對租賃機制有一些自我意識的合約,則須要添加一些額外的系統接口:
uint32_t remainingRent(uint8_t * key,size_t keylen); -- 針對區塊而言,將返回特定狀態鍵所需支付的剩餘租金。若是狀態處於休眠狀態或還沒有寫入,則返回0
uint32_t remainingExternalRent(UniversalAddressABI * target,uint8_t * key,size_t keylen); -- 與remainingRent方法的行爲相同,但做用於外部合約
uint32_t remainingExternalBytecodeRent(UniversalAddressABI * target); -- 與remainingExternalRent方法的行爲相同,但該方法會檢查外部合約的字節碼而不是狀態鍵。請注意,不須要內部版本,由於經過執行合約的行爲,剩餘的租金將始終是RENT_TERM
uint32_t Block>
GAS模型
當前Qtum-x86虛擬機中用於存儲的gas模型設計尚未徹底實現,否則要是實現了的話,本提議將徹底地改變它。因此,如今最好是暫時放下手中的設計工做。
定義:
PROP_TAX -- 對任何添加到DeltaDB樹的傳播收取的稅費
READ_TAX(size) -- 從節點數據庫讀取狀態所收取的稅費。這個開銷不是固定不變的,多是由最小成本加上必定長度後的每字節成本構成。這會對存儲開銷產生影響,例如將數據複製到VM內存中
EXEC_TAX -- 爲了執行合約而初始化一個新的VM實例所收取的稅費
SHORT_READ_TAX(size) -- 當前執行中讀取先前從數據庫讀取的狀態所收取的稅費。其餘方面與READ_TAX相似
INDEX_TAX(key_size) -- 對數據庫中數據創建索引收取的稅費。這設計的相對便宜,而且包括了在須要時對密鑰進行哈希的成本
WRITE_TAX(size) -- 將狀態寫入數據庫而收取的稅費
EXTERNAL_TAX -- 訪問外部帳戶狀態的一小筆額外稅費
LIBEXEC_COST -- 執行任何可信庫合約的固定成本。請注意,爲防止濫用,可信庫合約的大小存在嚴格限制
STORE_REFUND(size) -- 假設狀態減小爲0,則因修改狀態而給予的退款
DIRTY_STORE_REFUND(old_size) -- 與STORE_REFUND相似,但若是狀態減小爲0而且它是在當前執行中建立的(即,它從未寫入數據庫),則返回一筆更大金額的退款
PROP_REFUND -- 若是狀態在執行開始時未創建,而在執行期間創建,且在執行完成以前減小到0,則給予退款,這意味着不須要進行傳播。這隻適用於以null狀態做爲開始狀態和結束狀態的修改。即,若是狀態切換過程爲「abc」 - > 0 - >「abc」,則仍將收取PROP_TAX的費用,但若是狀態切換過程爲0 - >「abc」 - >「xyz」 - > 0,則將會退款
CLEANING_REFUND(size) -- 這是額外的退款,用來激勵對存儲進行清理。僅在狀態重置爲0時有效,而在狀態調整時不會給予退款
WAKE_TAX(size) -- 將狀態恢復爲「活躍」狀態的額外開銷
SLEEPING_REFUND -- 打破休眠狀態的固定退款
實際操做
在閱讀時請注意:
初始化合約執行:PROP_TAX + READ_TAX(size)+ EXEC_TAX
首次執行外部合約:PROP_TAX + READ_TAX(size)+ EXEC_TAX + EXTERNAL_TAX
第二次執行外部合約:SHORT_READ_TAX(size)+ EXEC_TAX + EXTERNAL_TAX
遞歸地執行合約:SHORT_READ_TAX(size)+ EXEC_TAX
合約自我銷燬:PROP_TAX + STORE_REFUND(size)+ CLEANING_REFUND(size)
內部首次大小檢查:PROP_TAX + INDEX_TAX(key_size) - 這可用於強制支付(便宜的)租金而不用將狀態實際讀入內存;當前這只是讀取一個0字節長的狀態。(狀態讀取一般返回數據的實際大小)
外部首次大小檢查:PROP_TAX + INDEX_TAX(key_size)+ EXTERNAL_TAX
內部第二次大小檢查:INDEX_TAX(key_size) -- 這裏的第二次表示發生在先前的大小檢查或狀態讀取以後
外部第二次大小檢查:INDEX_TAX(key_size)+ EXTERNAL_TAX
內部首次讀取:PROP_TAX + READ_TAX(size)+ INDEX_TAX(key_size)
內部第二次讀取:SHORT_READ_TAX(size)+ INDEX_TAX(key_size) -- 讀取在同一執行過程當中寫入的狀態
首次寫入新狀態:PROP_TAX + WRITE_TAX(size)+ INDEX_TAX(key_size)
首次寫入新狀態,設置爲0:INDEX_TAX(key_size) - 這是一個空操做,因此正常狀況下不該該執行
第二次寫入新狀態:WRITE_TAX(size)+ STORE_REFUND(size)+ INDEX_TAX(key_size)
第二次寫入新狀態,設置爲0:DIRTY_STORE_REFUND(old_size)+ INDEX_TAX(key_size)+ PROP_REFUND
首次寫入現有狀態:PROP_TAX + WRITE_TAX(size)+ STORE_REFUND(old_size)+ INDEX_TAX(key_size)
首次寫入現有狀態,設置爲0:PROP_TAX + STORE_REFUND(old_size)+ INDEX_TAX(key_size)+ CLEANING_REFUND(size)
第二次寫入現有狀態:WRITE_TAX(size)+ STORE_REFUND(old_size)+ INDEX_TAX(key_size)
第二次寫入現有狀態,設置爲0:PROP_TAX + STORE_REFUND(old_size)+ INDEX_TAX(key_size)+ CLEANING_REFUND(size) - 與首次寫入相同
第二次寫入先前在第一次寫入時設置爲0的現有狀態:WRITE_TAX(大小)+ INDEX_TAX(key_size) - 基本上與正常的第二次寫入相同
首次寫入休眠狀態:PROP_TAX + WRITE_TAX(size)+ INDEX_TAX(key_size) - 請注意,在這種狀況下,減小數據沒法給與退款,但WAKE_TAX預期會高於退款金額。另請注意,第二次寫入與寫入正常的現有(髒)狀態相同
首次寫入休眠狀態,設置爲0:PROP_TAX + INDEX_TAX(key_size)+ SLEEPING_REFUND - 理論上這應該與平均鍵大小(小於32字節)四捨五入後的值相抵消
注意:在第一次寫入以後,休眠狀態在設置爲0、調整大小等方面會被視爲與其餘狀態的行爲相同
外部首次讀取:PROP_TAX + READ_TAX(size)+ INDEX_TAX(key_size)+ EXTERNAL_TAX
外部第二次讀取:SHORT_READ_TAX(size)+ INDEX_TAX(key_size)+ EXTERNAL_TAX
獨立代碼執行:EXEC_TAX - 「獨立」執行只是一段UTXO中的代碼,執行一次而不對狀態進行存儲,所以除了執行以外沒有任何其餘成本
休眠狀態的預執行恢復:WAKE_TAX(size)+ INDEX_TAX(size)+ PROP_TAX - 請注意,這是在執行原始合約以前發生的
注意:恢復休眠狀態後,全部gas成本與正常的活躍狀態的存儲相同
可信庫執行:PROP_TAX + LIBEXEC_COST - 這顯然不會帶來每字節長度的開銷。除了實際執行代碼所需的gas成本以外,該執行的成本是固定的。這是爲了使可信庫執行更具可預測性
注意:表明合約的全部受信任庫讀/寫與正常的合約執行相同,不會帶來EXTERNAL_TAX
雖然這個操做列表看起來很是大,但實際上它是很是公式化的,而且在代碼的實現過程當中不會太難。它是很是有規則的,應該只須要處理不多的邊界狀況。上面定義的每一個常量或方法應該是不言自明的,而且應該考慮到節點和更大網絡所需的全部成本。
這種方法的一些風險在於退款必須是保守的,以免出現下面這種投機取巧的狀況:例如,先將數據寫入狀態,而後將狀態修改成較小的大小,而不是在開始簡單地就寫入較小的狀態。退款行爲與以太坊不一樣,執行操做後的任何剩餘的gas都會被髮送回收款人,其中數量不超過發送給合約的總gas數。若是容許發送回多於合約中發送的gas數,那麼能夠人爲地利用高的gas價格輸出Qtum,從而以比初始支付時更高的gas價格進行退款。
AAL帳戶抽象層修改
爲了適當地修剪合約交易中的無關數據,全部的合約執行和交易建立都將經由AAL支出並進行壓縮。這也會極大地簡化未來其餘的QIPs,例如基於UTXO模型的「一次性擁有」狀態的提案。目前,合約執行僅在執行中的資金實際用於智能合約時才由AAL支出。此外,合約建立交易僅在合約自毀時花費。這容許SPV節點利用一些額外的功能來跟蹤合約行爲,但這會以在 UTXO集中保留重複且不太相關的數據爲代價。DeltaDB中的SPV目標訪問和跟蹤方法將有效地取代此功能。
新的節點分類
目前,Qtum生態系統中有三種主要類型的節點:
存檔節點 :該類節點包含整個區塊鏈的數據。UTXO集被修剪至不包含重複數據,但全部已花費的交易數據會保存在磁盤上,數據存取較慢。該類節點可用於任何用例,包括委託,常規錢包,歷史數據分析,開發等。
修剪的全節點: 該類節點相似於一個全節點,會下載並驗證整個區塊鏈,但會刪除那些可證實不被使用的數據。特別地,這包括已花費的交易數據以及舊的區塊數據。除歷史數據分析外,該節點可以處理全節點的全部用例。
SPV節點 :這種類型的節點經過按需下載與當前錢包「相關」的數據以及整個區塊鏈的區塊頭來進行驗證和證實。該類節點是很是輕量級的,一般用於移動設備和「快速同步」的錢包。節點是去中心化的,但會受審查的影響,由於沒法證實它所鏈接的全節點是否具備應該存在的數據。一般認爲這種最終的安全性是穩定的,但容易受到女巫攻擊。這種類型的節點一般僅可用於錢包和一些有限類型的智能合約的開發。值得注意的是,它不能用於委託。
基於本提案提出的新功能和可證實的行爲,提出了一種新的節點分類方案:快速開發節點。該類節點使用了SPV節點的通用安全範例,而且初始時須要下載如下數據:
使用最佳區塊狀態樹根節點的完整EVM數據(這是沒法避免的)
區塊鏈的全部區塊頭(與SPV相同)
DeltaDB證實以及那些最近RENT_PERIOD區塊的數據(根據區塊頭的DeltaDBRoot進行驗證)。修改後的數據能夠在處理時進行修剪
相關的UTXOs和當前受控錢包的證實(與SPV相同)
按需下載的數據包括:
爲新區塊委託UTXO
用於證實UTXO存在性的UTXO證實(即區塊哈希和merkle路徑),而後接受區塊將其做爲委託花費
須要已花費的用於委託UTXO的UTXOs的證實(與SPV節點相同)
須要已花費且爲相關地址建立的UTXOs的證實(與SPV節點相同)
須要與合約交互並跟蹤RENT_PERIOD內的DeltaDB狀態變化的交易(不只僅是UTXO)。交易數據在執行後被修剪,只留下DeltaDB跟蹤數據
正在進行的區塊頭下載
對於委託和安全性這類關鍵目的而言,這種方案並非安全的,由於它的核心安全性仍然是由SPV保證的。然而,對於智能合約開發而言這已經徹底足夠了,同時也可做爲SPV節點的一個功能更強大的版本。歷史合約執行能夠忽略,但進行中的新合約執行能夠徹底地被執行和跟蹤。最初的同步過程只會比SPV慢一點,主要是由於須要下載完整的EVM和修剪的DeltaDB數據。帶寬成本也只比SPV節點略高,主要用於完整地下載智能合約執行所涉及到的全部交易。
原理
這種特定的租賃實現方式不一樣於現有的已提出的大多數方案。這其中一個最大的擔心是,智能合約自己已經至關複雜了,租賃方案所帶來的額外的複雜性會大大增長智能合約代碼中的問題和漏洞利用的可能性。該提案以一種不一樣的方式利用DeltaDB的特性從而使租賃系統對生態系統有益,而在大多數狀況下不須要任何額外的智能合約邏輯。
此外,該提案的一個重點是將節點達成共識的所需和其餘內容分離開來。將那些不多訪問且永遠不會更新的數據上鍊是徹底能夠接受的,但這些數據對節點而言應該是無關的。固然,仍然能夠證實數據在某個區塊高度時在區塊鏈上的存在性,可是,預計它不會被網絡上的大多數節點直接存儲和訪問。這種證實能夠在不消耗任何會帶來gas開銷的區塊鏈資源的狀況下完成。此類歷史數據能夠轉移到歸檔節點上。對於僅須要訪問某個智能合約的休眠數據的應用程序,可使用部分歸檔節點。這基本上是一個標準的修剪節點,但它會存儲相關智能合約的完整歷史數據。
策略
這將在Qtum-x86的初始版本中實現。在發佈後更改該存儲模型是很是困難的,所以,在已經實現的狀況下推出Qtum-x86是有很大好處的。
待實現
須要計算不一樣的RENT_TERM值的理論上的數據上限
須要計算對於一個合約執行的完整區塊而言,DeltaDB merkle樹的大小,以及一個典型的區塊
這並不能徹底消除對存儲全部數據的「歸檔節點」的需求。爲了無信任地同步一個全節點,仍然必須且/或須要從區塊數據重建全部的「休眠」數據,以便證實區塊鏈的當前狀態是有效的
爲了實現更好的區塊鏈技術與互信商業之間的橋樑,Qtum量子鏈不斷在努力,持續的技術更新迭代和提出新的可實現的技術思考。以上,爲Qtum x86內的存儲區租部分技術提案,Qtum x86 也將會持續更新,關注每週週報或者點擊持續更新的x86虛擬機開發任務列表:https://github.com/qtumproject/x86-stories/issues
歡迎你們積極在公衆號文章評論區互動,
說出你對Qtum x86虛擬機技術想法和建議!
技術想法和建議一經採納將
送出一臺Qtum樹莓派!