干货|Qtum-x86虚拟机实现存储区租赁,高效节省虚拟机内存


    者的话:本提案为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

    1. DeltaDB 从新传播和租赁行为设计

    2. “休眠”状态的智能合约行为

    3. “唤醒”休眠状态的方法


    首先,用于不一样状态的术语解释

    • 活跃:该状态要求支付必定的租金,从而在区块链上保持其活跃性并易于访问

    • 休眠:未能在适当的时间内支付租金时所处的状态,而且在没有经过交易进行从新传播的状况下不能经过智能合约直接访问

    • 唤醒:这是将休眠状态恢复到活跃状态的动做,以即可以再次经过智能合约直接访问它

    DeltaDB 从新传播和租赁行为


    合约的每个状态,包括它本身的字节码,都有一个经过区块高度表示的租金计时器。一旦该计时器值为0,就会从活跃状态切换到休眠状态,而且节点能够安全地从其内部数据库中删除与该状态相关的大部分数据。当访问或修改状态时,会隐性地进行租金支付,这会被计入至该操做的gas开销中。当经过访问数据进行租金支付时,该状态会将其计时器重置为RENT_TERM。没法经过预付款的方式将一个状态的计时器设置为大于RENT_TERM的值。

    使用DeltaDB当前的共识模型时,读取一个状态(一般)不会向DeltaDB证实树添加新的delta(状态更改/通知)。使用本文提出的存储区租金提案,每一个状态访问都会经过向DeltaDB证实树提交一个delta从而引发状态的“从新传播”。虽然这对合约甚至大多数区块链开发人员而言都没有影响,但仍是会带来许多反作用:

    1. SPV(轻钱包)节点能够证实状态最近一次租金支付的时间

    2. 相反,它容许能够从SPV或全节点的内部数据库中删除状态和大多数证实开销的证实

    3. SPV节点能够更快地得到状态数据的抗审查证实,经过更频繁地传播合约中最经常使用的数据,须要扫描的区块也更少

    4. 除了唤醒状态所需的开销以外,这不会消耗额外的区块空间,由于DeltaDB证实树会以单个32字节长的哈希值的形式保存在区块头中,而不会带来其余的开销

    5. 因为可以证实再也不须要比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生态系统中有三种主要类型的节点:

    1. 存档节点 :该类节点包含整个区块链的数据。UTXO集被修剪至不包含重复数据,但全部已花费的交易数据会保存在磁盘上,数据存取较慢。该类节点可用于任何用例,包括委托,常规钱包,历史数据分析,开发等。


    2. 修剪的全节点: 该类节点相似于一个全节点,会下载并验证整个区块链,但会删除那些可证实不被使用的数据。特别地,这包括已花费的交易数据以及旧的区块数据。除历史数据分析外,该节点可以处理全节点的全部用例。


    3. SPV节点 :这种类型的节点经过按需下载与当前钱包“相关”的数据以及整个区块链的区块头来进行验证和证实。该类节点是很是轻量级的,一般用于移动设备和“快速同步”的钱包。节点是去中心化的,但会受审查的影响,由于没法证实它所链接的全节点是否具备应该存在的数据。一般认为这种最终的安全性是稳定的,但容易受到女巫攻击。这种类型的节点一般仅可用于钱包和一些有限类型的智能合约的开发。值得注意的是,它不能用于委托。


    基于本提案提出的新功能和可证实的行为,提出了一种新的节点分类方案:快速开发节点。该类节点使用了SPV节点的通用安全范例,而且初始时须要下载如下数据:

    • 使用最佳区块状态树根节点的完整EVM数据(这是没法避免的)

    • 区块链的全部区块头(与SPV相同)

    • DeltaDB证实以及那些最近RENT_PERIOD区块的数据(根据区块头的DeltaDBRoot进行验证)。修改后的数据能够在处理时进行修剪

    • 相关的UTXOs和当前受控钱包的证实(与SPV相同)

    按需下载的数据包括:

    1. 为新区块委托UTXO

    2. 用于证实UTXO存在性的UTXO证实(即区块哈希和merkle路径),而后接受区块将其做为委托花费

    3. 须要已花费的用于委托UTXO的UTXOs的证实(与SPV节点相同)

    4. 须要已花费且为相关地址建立的UTXOs的证实(与SPV节点相同)

    5. 须要与合约交互并跟踪RENT_PERIOD内的DeltaDB状态变化的交易(不只仅是UTXO)。交易数据在执行后被修剪,只留下DeltaDB跟踪数据

    6. 正在进行的区块头下载

    对于委托和安全性这类关键目的而言,这种方案并非安全的,由于它的核心安全性仍然是由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树莓派!

    相关文章
    相关标签/搜索