该RSKIP提议合约应支付存储租金,以降低存储垃圾的风险,使存储支付更加公平。同时,讨论了由于额外的复杂性和开销,存储租金的局限性,在某些情况下,这些复杂性和开销超过了收益。提出了一种多级缓存机制,根据未付租金的高低,在不同速度的存储介质(DRAM、SSD、HDD)之间移动合约数据,以平衡成本和性能。
RSKIP | 52 |
---|---|
标题 | 面向缓存的存储租金 |
创建于 | 12-DIC-2017 |
作者 | SDL |
目的 | Sca |
层级 | 核心 |
复杂度 | 2 |
状态 | 草案* |
本RSKIP提议合约应该支付存储租金,以减少存储垃圾的风险,并使存储支付更加公平。同时,本RSKIP讨论了存储租金的局限性,因为额外的复杂性和开销在某些情况下超过了收益。
RSK平台的一个问题是,内存可以以低成本获取,并且永远不会被释放,迫使所有剩余节点永久存储信息。在现实世界的商业中,几乎没有用户通过一次性非经常性付款获得对需要持续维护的财产的永久权利的例子,因此这意味着第三方需要定期维护成本。维护成本很低但不可忽略,因为持久性数据必须存储在SSD中,以便访问成本与实际成本相匹配。区块链状态存储就是这种情况,成本要乘以网络中状态副本的数量。在某些情况下,空间是免费提供的(例如,Google云端硬盘空间),但这是因为空间是由google用户使用的其他服务补贴的。此外,无法保证Google将永远提供免费空间。有人可能会说,完整节点是利他的,因此他们愿意承担网络所需的任何存储成本。虽然这在过去可能对Bitcoin节点部分成立,但这种利他行为可能会减少。Bitcoin节点的数量一直在下降,而Bitcoin用户的数量却大大增加,这意味着新用户不愿意比老用户运行更多完整节点。预计区块修剪和分片技术使使用者能够提交某些部分的存储,而不是整个区块链。然而,新区块的验证,而非历史存储,才是定义完整节点的关键。为了验证一个区块,一个节点需要完整的状态,或者接收所有使用状态数据的使用证明。分片因子必须与对等方连接的诚实主机的数量成反比,因此如果状态大小增长,而其他因素保持不变,则本地存储也必须增长。因此,原则上,用户应该为消耗持久性存储支付存储租金(例如,bitcoins/月)。然而,尚不清楚谁应该支付这笔租金。许多合约都是大众合约的例子:由大众提供资金和使用的程序,因此它们可能会消耗大量内存,但没有单个用户能够承担租金的负担。无论是在金钱方面,还是在事实上,没有单个用户可能有动机来执行该任务,无论成本如何。
一个设计良好的大众合约应该有一种收入生成方法来支付存储租金。例如,每个大众合约的操作都应该伴随用bitcoins支付给一个特殊的租金子账户,大众合约在那里收集所有面向租金的收入。然而,由于大多数大众合约都是不可变的,因此这种收入收集方法必须在第一天就定义好,并且在该阶段还不清楚收入模型是否可以维持内存租金。RSKIP21深入介绍了存储租金的问题。主要问题是效率:大多数租金支付都是小额支付,因此支付成本(开销)过高。另一个问题是可扩展性:收取租金的系统可能会引入新的低效率(例如重复状态写入),从而干扰可扩展性计划。
已经设计了三种方法:
合约使用DEPOSIT操作码每期支付一次租金。
每次调用合约时,都会自动推迟休眠截止日期。调用会消耗gas来支付租金。租金gas的数量与未使用的增量时间(当前时间减去上次访问合约的时间)和合约的持久内存大小(代码和存储)成正比。如果调用没有提供足够的gas,则调用失败。如果调用合约并且增量时间大于一年,则合约将休眠。
与之前相同,但没有休眠。节点将具有多级缓存来存储合约数据(帐户状态、代码和存储),以便当未付租金高于较慢存储的访问成本的两倍时,将数据移动,并且当访问数据时,将其带入最快的缓存。从一个缓存移动到另一个缓存的确切阈值将在另一个RSKIP中指定,并且不受共识规则的约束,因为这对网络是透明的。多级缓存可以由DRAM、SSD驱动器和HDD驱动器组成。
本RSKIP指定了第三种方法,因为它干扰较少,并且可以轻松兼容预构建的Ethereum应用程序。
如果调用没有更改目标合约的存储或帐户状态(包括余额),则只有在租金高于10000 gas时才支付租金。如果被调用合约的状态发生更改,则只有当租金高于1000时才支付租金。这可以防止代价高昂的小额交易。 如果交易被正确安排,本RSKIP不会干扰添加并行交易执行的计划。
租金通过扩展交易来支付,以添加一个新字段“maximumRentGas”。交易消耗的总gas将等于正常消耗的gas加上租金消耗的gas。如果消耗的租金gas变得高于maximumRentGas,则交易将中止并显示OOG。与正常gas一样,全部的maximumRentGas金额将从起始地址中扣除,然后剩余的部分将被报销。
每个帐户/合约都有一个新的字段lastRentPaidTime。设d为执行调用的区块的时间戳。这两个字段都以秒为单位给出。
以下代码说明了当交易开始执行和完成时,如何全额预付租金:
// 在开始时调用
void prepayAllRentGas(transactionMaximumRentGas) {
sourceAccount.subtract(transactionMaximumRentGas*gasPrice);
availableRentGas = transactionMaximumRentGas
}
// 在执行期间调用
void consumeRentGas(gas) {
rentGasConsumed +=gas;
availableRentGas -=gas;
}
// 在执行期间调用
void reimburseRentGas(gas) {
rentGasConsumed -=gas;
availableRentGas +=gas;
}
// 在完成时调用
void finalizeRentGas() {
sourceAccount.add(availableRentGas*gasPrice);
minerAccount.add(rentGasConsumed *gasPrice);
}
以下伪代码说明了如何计算和支付租金。
if (d>lastRentPaidTime)
rentGas = (storageSize+codeSize+256)*(d-lastRentPaidTime)*2000/2^32
else
rentGas = 0
consumeRentGas(rentGas)
dest_contract.call()
if (dest_contract modified its state) {
if (rentGas<1000)
reimburseRentGas(rentGas)
else
dest_contract.lastRentPaidTime = now
} else {
if (rentGas<10000)
reimburseRentGas(rentGas)
else
dest_contract.lastRentPaidTime = now
}
一年有31536000秒。2^32 =4294967296。一年中的秒数/2^32= 0.00734。一个字节每年支付14.68 gas单位。一个简单的帐户(没有代码)每年支付3750 gas单位。一个简单的帐户每年不能消耗超过四次的租金。
这个rentGas是从maximumRentGas中消耗的。如果变为零,则会引发gas不足异常。成功后,d-lastRentPaidTime的值将被更新。
每个字节的成本是每年每字节15.625 gas。
存储大小(storageSize)计算为128*N,其中N是存储trie中的条目数。
如果在同一交易或同一区块中有多个调用,则只有第一个调用会支付,因为其余调用将具有(d==lastRentPaidTime)
创建合约时,lastRentPaidTime设置为未来6个月。这意味着一些租金是预付的。
操作码EXTCODECOPY、EXTCODESIZE和BALANCE操作码也必须支付租金,因为它们访问其他合约。
区块gas限制不适用于租金:以gas支付的租金金额可能高于gas限制。因此,租金是矿工的额外无上限收入来源。
交易格式已修改。目前,交易包含以下字段:
如果交易有10个或更多字段,则索引10(从1开始)处的字段将对应于字段maximumRentGas。字段gasLimit上的相同大小限制将应用于maximumRentGas。此外,maximumRentGas将从发送者的余额中全额扣除,然后未花费的金额将被报销。如果交易未指定maximumRentGas,则maximumRentGas假定消耗所有剩余的发送者余额:帐户余额减去转移的价值,除以指定的GasPrice。 如果由于达到maximumRentGas而无法支付租金,则交易将被还原,并且到目前为止所有消耗的gas将被扣除(不报销),就像已执行REVERT操作码一样。
如果交易被手动还原(REVERT),则在交易收据中记录新的状态(-1)。 如果交易由于标准的OOG而还原,则仍使用旧的空向量状态。 如果交易由于租金OOG而还原,则在交易收据中记录新的状态(-2)。
如果VM指令同时生成标准OOG和租金OOG异常,则报告标准OOG。
如果合约的未付租金高于某个阈值,则可以使合约休眠。
版权
版权及相关权利通过CC0放弃。
- 原文链接: github.com/rsksmart/RSKI...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!