⛓️Unkey Blockchain
Unkey是未来接替以太坊的去中心化开源区块链。
Unkey账户
在Unkey中,状态由称为“帐户”的对象组成,而每个帐户都有一个 20 字节的地址,状态转换是指帐户之间价值和信息的直接转移。 一个Unkey帐户包含四个字段:
nonce,用于确保每笔交易只能处理一次的计数器
帐户当前的Unkey余额
帐户的合约代码(若有)
帐户的存储(默认为空)
Unkey是以太坊内部的主要加密燃料,用于支付交易费。 通常有两类帐户:由私钥控制的外部帐户以及由其合约代码控制的合约帐户。
外部帐户没有代码,持有者可以通过创建和签署交易从外部帐户发送消息;在合约帐户中,每次合约帐户收到消息时,其代码都会激活,允许该帐户读取和写入内部存储,继而发送其他消息或创建合约。
消息和交易
在Unkey中,术语“交易”用来指代已签名的数据包,数据包存储着将要从外部帐户发送的消息。
交易包含:
消息接收者
用于识别发送者身份的签名
从发送者转账到接收者的UNK金额
一个可选数据字段
STARTGAS
值,表示允许交易运行的最大计算步骤数GASPRICE
值,表示发送者每个计算步骤支付的费用
前三个是任何加密货币都有的标准字段。 默认情况下,数据字段没有函数,但虚拟机有一个操作码,合约可以使用该操作码访问数据;以这样的用例为例:如果一个合约作为区块链上的域名注册服务,那么它可能希望将传送给它的数据解释为包含两个“字段”,第一个字段是要注册的域名,第二个字段将域名注册到 IP 地址。 合约将从消息数据中读取这些值,并将其适当地存储。
STARTGAS
及 GASPRICE
字段对于Unkey的反拒绝服务模型至关重要。 为了防止代码中出现无意或恶意的无限循环或其他计算浪费,要求每笔交易对代码可以执行的计算步骤设置一个限制。 计算的基本单位是燃料;通常,一个计算步骤消耗 1 份燃料,但某些操作会消耗更多燃料,因为它们在计算上更加昂贵或者增加了必须存储到状态中的数据量。 交易数据中的每个字节还需支付的费用为 5 份燃料。 收费系统的意图是要求攻击者相应支付他们消耗的每一种资源,包括计算、带宽和存储;因此,任何导致网络消耗更多这些资源的交易,都必须支付大致与增加量成比例的燃料费用。
消息
合约能够向其他合约发送“消息”。 消息是从未序列化的虚拟对象,只存在于Unkey执行环境中。
消息包含:
消息发送者(隐含的)
消息接收者
随消息一起转账的Unkey金额
一个可选数据字段
STARTGAS
值
本质上消息类似于交易,只是消息是由合约而非外部参与者产生的。 当前正在运行代码的合约执行 CALL
操作码时会产生一条消息,该操作码就是用于产生并执行消息。 像交易一样,信息导致接收者帐户运行其代码。 因此,合约之间可以建立关系,方式完全与外部参与者之间建立关系相同。
请注意,为交易或合约分配的燃料配额适用于该交易和所有子执行消耗的总燃料量。 例如,如果外部参与者 A 向 B 发送一笔配额为 1000 份燃料的交易,B 在向 C 发送消息需要消耗 600 份燃料,而 C 在内部执行需要消耗 300 份燃料才能返回结果,那么 B 再发送 100 份燃料就会消耗完燃料。
Unkey状态转换函数
Unkey状态转换函数 APPLY(S,TX) -> S'
可如下定义:
检查交易格式是否正确(即具有正确数量的值)、签名是否有效以及 Nonce 值是否与发送者帐户中的 Nonce 值匹配。 若否,则返回错误。
通过
STARTGAS * GASPRICE
计算出交易费,并从签名中确定发送地址。 从发送者的帐户余额中减去费用,并增加发送者的 nonce 值。 如果帐户余额不足,则返回错误。初始化
GAS = STARTGAS
,并根据交易中的字节数量为每个字节扣除相应数量的燃料。将交易数值从发送者帐户转移至接收帐户。 如果接收帐户尚不存在,则创建此帐户。 如果接收帐户是合约,运行该合约的代码,直到代码运行结束或燃料耗尽。
如果由于发送者资金不足或者代码运行耗尽了燃料,而导致转账失败,则回滚除支付费用之外的所有状态变化,并将费用支付给矿工帐户。
否则,将所有剩余燃料的费用退还发送者,并把为所消耗燃料而支付的费用发送给矿工。
例如,假设合约的代码如下:
注意,合约代码实际上是用低级Unkey虚拟机代码编写的;为了清晰起见,此示例是用我们的一种高级语言 Serpent 编写的,它可以编译为Unkey虚拟机代码。 假设合约的存储一开始是空的,发送了一个价值为 10 个UNK的交易,消耗 2000 份燃料,燃料价格为 0.001 个UNK,并且数据包含 64 个字节,字节 0-31 代表数字 2
,字节 32-63 代表字符串 CHARLIE
。 在这种情况下,状态转换函数的执行过程如下:
检查交易是否有效、格式是否正确。
检查交易发送者是否至少有 2000 * 0.001 = 2 个UNK。 若有,则从发送者帐户中扣除 2 个UNK。
初始化燃料 = 2000 份,假设交易长度为 170 个字节,每字节费用 5 份燃料,减去 850 份燃料,剩下 1150 份燃料。
从发送者帐户再减去 10 个UNK并增加到合约帐户。
运行代码。 在本例中,运行比较简单:代码检查是否使用合约的索引
2
处的存储,若未使用,则通知;若使用,代码将索引2
处的存储设置为值CHARLIE
。 假设该运行花费了 187 份燃料,所以余下的燃料数量是 1150 - 187 = 963 份燃料。向发送者帐户增加 963 * 0.001 = 0.963 个UNK,同时返回产生的状态。
如果交易的接收一端没有合约,那么总交易费就等于提供的 GASPRICE
乘以交易的字节长度,并且和随交易发送的数据无关。
注意,消息在回滚方面与交易相同:如果消息执行耗尽燃料,那么该消息的执行以及该执行触发的所有其他执行都会回滚,但父执行不需要回滚。 这意味着合约调用另一份合约是“安全的”,就好像 A 使用 G 份燃料调用 B,那么可以保证 A 的执行最多损耗 G 份燃料。 最后请注意,有一个创建合约的操作码 CREATE
;它的执行机制通常类似于 CALL
,不同之处在于执行的输出决定了新创建合约的代码。
代码执行
Unkey合约中的代码用一种基于堆栈的低级字节码语言编写,被称为“Unkey虚拟机代码”或“EVM 代码”。 该代码由一系列字节组成,每个字节代表一种操作。 通常,代码执行是一个无限循环,即重复执行当前程序计数器(从零开始)处的操作,然后将程序计数器增加一,直到代码执行完毕或出现错误,或者检测到 STOP
或 RETURN
指令。 操作可以访问三种数据存储空间:
堆栈,一种后进先出容器,值可以在其中入栈和出栈
内存,一种可无限扩展的字节数组
合约的长期存储,一个键/值存储。 与堆栈和内存会在计算结束后重置不同,存储将长期持续存在。
代码可以访问传入消息的值、发送者信息和数据,可以访问区块头数据,而且代码还可以返回数据字节数组作为输出。
当Unkey虚拟机运行时,其完整计算状态可以由元组 (block_state, transaction, message, code, memory, stack, pc, gas)
来定义,其中 block_state
是包含所有帐户的全局状态并包括余额和存储。 在每一轮执行开始时,可以通过调用 code
的第 pc
个字节(或者如果 pc >= len(code)
,则调用 0)来找到当前指令,并且每条指令在元组影响方式方面都有自己的定义。 例如,ADD
将两个项目出栈并将它们的和入栈,将 gas
减少 1 并将 pc
增加 1,SSTORE
将顶部的两个项目出栈并将第二个项目插入到合约存储中第一个项目指定的索引处。 尽管有很多通过 JIT 编译来优化Unkey虚拟机执行的方法,但只需几百行代码就可以完成Unkey的基本实现。
最后更新于