logo资料库

以太坊黄皮书中文版.pdf

第1页 / 共29页
第2页 / 共29页
第3页 / 共29页
第4页 / 共29页
第5页 / 共29页
第6页 / 共29页
第7页 / 共29页
第8页 / 共29页
资料共29页,剩余部分请下载后查看
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 译者: 崔广斌, GUANGBINCUI@GMAIL.COM; 高天露, TIANLU.JORDEN.GAO@GMAIL.COM 原文作者: DR. GAVIN WOOD, GAVIN@ETHCORE.IO Abstract. 区块链对交易数据加密以保证安全, 已经通过一系列项目展示了它的实用性, 尤其是比特币。每一个这个的项目都可以看 作是一个基于去中心化的单实例且拥有计算资源的应用。我们称这种模式为可以共享状态的单例状态机。 以太坊以更广义的方式实现了这种模式。它提供了大量的资源, 每一个资源都拥有独立的状态和操作码, 并且可以通过消息传递方 式和其它资源交互。我们讨论了它的设计、实现难题、它提供的机会以及以后可能有的一些问题。 1. 简介 随着互联网连接了世界上绝大多数地方,全球信息共享 的成本越来越低。比特币网络通过共识机制、自愿遵守的社 会合约, 实现一个去中心化的价值转移系统且可以在全球范 围内自由使用, 这样的技术改革展示了它的巨大力量。这样 的系统可以说是加密安全、基于交易的状态机的一种具体 应用。后续类似这样的系统, 如域名币(Namecoin), 从最 原先的货币应用发展到了其它应用, 虽然它只是其中很简单 的一种应用。 以太坊是一个尝试达到通用性的技术项目, 可以构建任 何基于交易的状态机。而且以太坊致力于为开发者提供一 个紧凑的、整合的端到端系统, 这个系统提供了一种可信的 消息传递计算框架让开发者以一种前所未有的范式来构建 软件。 1.1. 驱动因素. 这个项目有很多目标,其中最重要的目标是 为了促成不信任对方的个体之间的交易。这些不信任可能 是因为地理位置分离、接口对接难度, 或者是不兼容、不称 职、不情愿、支出、不确定、不方便, 或现有法律系统的腐 败。于是我们想用一个丰富且清晰的语言去实现一个状态 变化的系统,期望协议可以自动被执行, 我们可以为此提供 一种实现方式。 这个提议系统中的交易, 有一些在现实世界中并不常见 的属性。审判廉洁,在现实世界往往很难找到,但对公正的 算法解释器是天然的; 透明,或者说通过交易日志和规则或 代码指令能够清晰的看到状态变化或者判决, 但是因为人类 语言的模糊性、信息的缺乏以及老的偏见难以撼动, 导致基 于人的系统中从来没有完美实现透明。 总的来说,我们希望能提供一个系统,能够保证用户无 论是和其他个体、系统还是组织交互, 都能对可能的结果及 产生结果的过程完全信任。 1.2. 前人工作. Buterin [2013a] 在 2013 年 9 月下旬第一 次提出了这种系统的核心机制。虽然现在发展出了多种方 案, 但最关键的部分, 具备图灵完备语言且不受限制的内部 交易存贮容量的区块链, 仍未变化。 Dwork and Naor [1992] 提出了一种使用计算支出的密 码学证明方式 (proof-of-work, 工作量证明) 在互联网上传 递信号值。信号值用作阻挡垃圾邮件,而不是任何一种货 币, 但展示了一个基本的数据通道可以承载强大经济信号的 可能性, 允许接受者无需依赖信任而做出物理断言。Back [2002] 后来设计了一个类似的系统。 Vishnumurthy et al. [2003] 最早使用工作量证明作为强 大的经济信号保证货币安全。在这个案例中,代币用作检查 点对点(peer-to-peer,p2p)文件交易,同时保证“消费者” 能支付给为他们提供服务的“供应商”。这种通过工作量证 明的安全模型逐步扩展, 包括使用电子签名和账本技术, 以 保证历史记录不被篡改, 怀有恶意的用户不能进行欺诈支付 或不公平的抱怨。五年后(2008 年),中本聪 Nakamoto [2008] 介绍了另一种更广泛的工作量证明安全价值代币。这 个项目的成果就是比特币, 比特币成为了第一个被全球广泛 认可的去中心化交易账本。 由于比特币的成功,竞争币 (alt-coins) 开始兴起, 通过 修改比特币的协议创建了大量的其它数字货币。比较知名的 有莱特币(Litecoin)和素数币(Primecoin), 参见 Sprankel [2013] 。一些项目使用比特币的核心机制并重新改造以应用 在其它领域, 例如域名币(Namecoin)致力于提供一个去中 心化的名字解析系统, 参见 Aron [2012] 。 其它在比特币网络之上构建的项目, 也是依赖巨大的系 统价值和巨大的算力来保证共识机制。万事达币(Master- coin)项目是在比特币协议之上, 通过一系列基于核心协议 的的辅助插件, 构建一个包含许多高级功能的富协议, 参见 Willett [2013]。彩色币(Coloured Coins, 参见 Rosenfeld [2012], 采用了类似的但更简化的协议, 以实现比特币基础 货币的可替代性, 并允许通过色度钱包(chroma-wallet)来 创建和跟踪代币。 其它一些工作通过放弃中心化来进行。瑞波币(Ripple) , 参见 Boutellier 和 Heinzen [2014], 试图去创建一个货币 兑换的联邦系统(“federated”system)和一个新的金融清 算系统。这个系统展示了放弃去中心化特性可以获得性能 上的提升。 Szabo [1997] 和 Miller [1997] 进行了智能合约(smart contract)的早期工作。大约在上世纪 90 年代,人们逐渐 认识到协议算法的执行可以成为人类合作的重要力量。虽 然当时没有这样的系统, 但可以预见未来的法律将会受到这 种系统的影响。基于此, 以太坊或许可以成为这种密码学-法 律系统的通用实现。 2. 区块链 以太坊在整体上可以看作一个基于交易的状态机:起始 于一个创世块(Genesis)状态,然后随着交易的执行状态 逐步改变一直到最终状态, 这个最终状态是以太坊世界的权 威版本。状态中包含的信息有: 账户余额、名誉度、信誉度、 现实世界的附属数据等; 简而言之,能包含电脑可以描绘 的任何信息。因此,交易是连接两个状态的有效桥梁;“有 效”非常重要—因为无效的状态改变远超过有效的状态改 变。例如:无效的状态改变可能是减少账号余额,但是没有 在其它账户上加上同等的额度。一个有效的状态转换是通 过交易进行的, 表达式如下: (1) 1 t+1 (t; T )
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 2 是以太坊状态转换函数。在以太坊中, 和 比已有 的任何比较系统都强; 可以执行任意计算, 而 可以存贮 交易中的任意状态。 区 块 中 记 录 着 交 易 信 息; 区 块 之 间 通 过 密 码 学 哈 希 (hash) 链接起来。区块链就像一个分类账, 将一系列交 易记录在一起, 并且连接上一个区块及最终状态 (并没有直 接保存最终状态本身—否则整个区块链就太大了)。系统激 励节点去挖矿, 挖矿获得激励后, 会执行状态转移函数, 增 加挖矿者的账户余额。 挖矿是和其它潜在区块竞争一系列交易 (一个区块) 的记 账权。它是通过密码安全证明的方式来实现。这个机制称为 工作量证明, 会在 11.5 详细讨论。 公式如下: (2) (3) (4) t+1 (t; B) B (:::; (T0; T1; :::)) (; B) Ω(B; ((; T0); T1):::) 其中 Ω 是区块定稿状态转换函数(这个函数奖励一个特 定的账户);B 表示包含一系列交易的区块; 是区块级的 状态转换函数。 上述是区块链的基本内容,这个模型不仅是以太坊的基 础,还是迄今为止所有基于共识的去中心化交易系统的基 础。 2.1. 面值. 为了激励网络中的计算,需要定义一种转账方 法。以太坊设计了一种内置货币以太币 (Ether),ETH 是 大家所熟知的符号,有时用 Ð 表示。以太币最小的面额是 Wei(伟),所有货币值都以 Wei 的整数倍记录。一个以太币 等于 1018 Wei 。不同的面值如下表: 倍数 面值 100 Wei(伟) Szabo(萨博) 1012 1015 Finney(芬尼) 1018 Ether(以太) 在整个工作中,任何涉及到价值、以太币相关的、货币、 余额或者支付,都以 Wei 作为单位来计算。 2.2. 历史? 因为这是一个去中心化的系统, 所有人都有机会 在之前的某一个区块创建新的区块并连接在其后, 这会行成 一个树状的区块。为了能在这个树状结构上从根节点(创世 块)到叶子节点(包含最新交易的区块)能形成一个一致的 区块链, 必须有一个共识方案。如果有人认为从根节点到叶 子节点的路径不是最佳的区块链, 那这时候就会发生分叉。 这就意味着在一个给定的时间点, 系统中会有多个状态 共存:一些节点相信一个区块是包含权威的交易,其它节点 则相信另外一些区块包含权的交易,其中就包含彻底不同 或者不兼容的交易。这一点必须要避免, 因为它会破坏整个 系统信用。 我们使用了一个简易 GHOST 协议版本来达成共识, 参 见 Sompolinsky and Zohar [2013] 。我们会在小结 10 详细 说明。 有 时 会 从 一 个 特 定 的 区 块 链 高 度 启 用 新 的 协 议。 本 文描述了协议的一个版本, 如果要跟踪历史区块链路径, 可 能 需 要 查 看 这 份 文 档 的 历 史 版 本。(译 者 注: 可 以 到 https://github.com/ethereum/yellowpaper 查看英文版历 史版本) 3. 约定 我们用了大量的印刷约定表示公式中的符号,其中一些 需要特别说明: 有两个高度结构化的顶层状态值, 使用粗体小写希腊 字母: 表示世界状态 (world-state); 表示机器状态 (machine-state)。 作用在高度结构化数据上的函数,使用大写的希腊字母, 例如: , 是以太坊中的状态转换函数。 对于大部分函数来说,通常用一个大写的字母表示,例 如:C, 表示费用函数。可能会使用下角标表示一些特别的 变量,例如:CSSTORE,表示执行 SSTORE 操作的费用函数。 对于一些可能是外部定义的函数,可能会使用打印机文字 字体,例如:KEC512 哈希函数, 使用 KEC 表示。KEC512 表 示 Keccack-512 哈希函数。 元组通常使用一个大写字母,例如:T ,表示一个以太坊 交易。使用下标可能会表示一个独立的变量, 例如:Tn ,表 示交易中随机数。角标形式用于表示它们的类型;例如:大 写的下角标表示元组包含的下角标变量。 标量和固定大小的字节序列(或数组)都使用小写字母 来表示,例如:n 在本文中表示交易随机数。小写的希腊字 母一般表示一些特别的含义,例如: 表示在栈上一个给定 操作需要的条目数量。 任意长度的序列通常用加粗的小写字母表示,例如 o 表 示消息调用中输出的数据字节序列。有时候,会对特别重要 的值使用粗体。 我们认为标量都是正整数且属于集合 P。所有的字节序 列属于集合 B,附录 B 给出了正式的定义。用下角标表示 这样的序列集合限制在一定长度以内, 长度为 32 的字节序 列使用 B32 表示, 所有比 2256 小的正整数使用 P256 表示。 详细定义见 4.3。 使用方括号表示序列中的一个元素或子序列, 例如:s[0] 表示栈中的第一个条目。对于子序列来说,使用省略号表示 一定的范围,且含首尾限制,例如:m[0::31] 表示计算机 内存中的前 32 个条目。 在全局状态的情况下,￿ 是一个含多个账号的序列,本身 的数组,方形括号被用作去表示一个单独的账号。 以全局状态 为例, 它表示一系列的账户, 它们自身的 元组, 方括号用于表示一个独立的账户。 当去考虑现有的变量时,我们遵循在给定的范围内去定 义的原则,使用占位符 □ 表示未修改的输入变量, 使用 □′ 表示修改的和可用的变量, □, □ &c 表示中间变量。在 特殊情况下,为了提高可读性和清晰性,我可能会使用字 母-数字下角标表示中间值。 表 当使用已有的函数时,给定一个函数 f, 那么函数 f 示一个相似的、替换序列的函数映射。详细定义见 4.3。 整个过程中,我们定义了大量的函数。一个常见的函数 是 ℓ,表示给定序列的最后一个条目: (5) ℓ(x) x[∥x∥ 1] 4. 块、状态和交易 介绍了以太坊的基本概念后, 我们将详细地讨论交易、区 块和状态的含义。 4.1. 世界状态. 世界状态是在地址(160 位的标志符)和账 户状态(序列化为 RLP 的数据结构,详见附录 B)的映射。 虽然世界状态没有直接储存在区块链上,但会假定在实施 过程中会将这个映射维护在一个修改过的 Merkle Patricia 树 (简称 trie, 字典树, 详见附录 D)。字典树需要一个简单 的后端数据库去维护字节数组到字节数组的映射;我们称 这个后端数据库为状态数据库。它有一系列的好处: 第一, 这个结构的根节点是加密的且依赖于所有的内部数据,它 的哈希可以作为整个系统状态的一个安全标志; 第二,作为 一个不变的数据结构,因此它允许任何一个之前状态(根部 哈希已知的条件下)通过简单地改变根部哈希值而被召回。
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 3 因为我们在区块链中储存了所以这样的根部哈希值,所以 我们能恢复到指定的历史状态。 账户状态包含以下四个字段: nonce, 随机数: 这个值等于账户发出的交易数及这 个账户创建的合约数量之和。[a]n 表示状态 中 的地址 a 的 nonce 值。 balance, 余额: [a]b, 表示这个账户拥有多少 Wei。 storageRoot, 存储根节点: 保存账户内容的 Merkle Patricia 树根节点的 256 位哈希编码到字典树中, 作为从 256 位整数键值哈希的 Keccak 256 位哈希 到 256 位整数的 RLP-编码映射。这个哈希定义为 [a]s。 codeHash, 代码哈希: 这个账户的 EVM(Ethereum Virtual Machine, 以太坊虚拟机) 代码哈希值—代 码执行时, 这个地址会接收一个消息调用; 它和其 它字段不同, 创建后不可更改。状态数据库中包含 所有像这样的代码片段哈希, 以便后续使用。这个 哈希定义为 [a]c,b 表示代码, KEC(b) = [a]c. 因为我通常希望所指的并不是字典树树的根哈希, 而是 所保存的键值对集合,我做了一个更方便的定义: I, 定义为基于基础函数 字典树中的键值对集合函数, L ) [a]s ( TRIE L I ([a]s) ( ) ( LI (k; v) KEC(k); RLP(v) ) (6) LI 的元素转换: (7) 其中: (8) k 2 B32 ^ v 2 P ( ) 需要说明的是,[a]s 不应算作这个账户的” 物理” 成员, 它不参与序列化。 如果 codeHash 字段是一个空字符串的 Keccak-256 哈 , 则表示对应的节点表示一个简单 () 希, 例如 [a]c = KEC 账户, 有时简称非合约账户。 因此我们可能定义一个世界状态函数 LS: LS() fp(a) : [a] ̸= ∅g (9) where (10) p(a) ( ( KEC(a); RLP ([a]n; [a]b; [a]s; [a]c) )) 函数 LS 和字典树函数是为了提供一个世界状态的简短 身份(哈希)。我们假定: (11) 8a : [a] = ∅ _ (a 2 B20 ^ v([a])) v 是账户合法性验证函数: v(x) xn 2 P256^xb 2 P256^xs 2 B32^xc 2 B32 (12) 4.2. 交易. 交易(符号,T )是个单一的加密指令,通过以 太坊中系统之外的操作者创建。我们假设外部的操作者是 人, 软件工具用于创建和传播1。这里的交易类型有两种:一 种是消息调用, 另一种通过代码创建新的账户(称为“合约 创建”)。两种类型的交易都有的共同字段如下: nonce, 随机数: Tn, 账户发出的交易数量。 gasPrice, 燃料价格: Tp, 为执行交易所需要的计算 资源付的 gas 价格, 以 Wei 为单位。 gasLimit, 燃料上限: Tg, 用于执行交易的最大 gas 数量。这个值须在交易前设置, 且设定后不能再修 改。 to, 接收者地址: 消息调用接收者的 160 位地址。对 与合约创建交易, 无需接收者地址, 使用 ∅ 表示, ∅ 是 B0 的唯一成员。 value, 转账额度: Tv, 转到接收者账户的额度, 以 Wei 为单位。对于合约创建, 表示捐赠到合约地址的额 度。 v, r, s: Tw, Tr and Ts, 和交易签名相关的变量, 用于 确定交易的发送者。详见附录 F。 此外, 合约创建还包含以下字段: init, 初始化: Ti, 一个不限制大小的字节数组, 表示 账户初始化程序的 EVM 代码。 init 是 EVM 代码片段; 执行 init 后会返回另外一个代 码片段, 每次合约接受消息调用 (通过交易或内部调用) 后 都会执行这个代码片段。仅当合约账户创建时会执行一次 init。 相比之下, 一个消息调用的交易包括: data, 数据: Td, 一个不限制大小的字节数组, 表示消 息调用的输入数据。 附录 F 详细描述了映射发送者交易的函数 S, 通过 SECP-256k1 的 ECDSA 曲线, 使用交易 (除了最后的 3 个签名字段) 作为数据来签名。目前我们先简单使用 S(T ) 表示发送者的指定交易 T 。 { (13) LT (T ) if Tt = ∅ (Tn; Tp; Tg; Tt; Tv; Ti; Tw; Tr; Ts) (Tn; Tp; Tg; Tt; Tv; Td; Tw; Tr; Ts) otherwise 在这里, 我们假设所有变量都是 RLP 编码的整数, 除了 2 个任意长度的字节数组 Ti 和 Td。 (14) 其中 (15) Tn 2 P256 ^ Tv 2 P256 ^ Tp 2 P256 ^ ^ Tr 2 P256 ^ Tg 2 P256 ^ Tw 2 P5 Ts 2 P256 ^ Td 2 B ^ Ti 2 B Pn = fP : P 2 P ^ P < 2ng 地址哈希 Tt 稍微有些不同: 它是一个 20 字节的地址哈 希值, 但创建合约时它是 RLP 空字节系列, 使用 B0 表示: { (16) Tt 2 B20 B0 if Tt ̸= ∅ otherwise 4.3. 区块. 在以太坊中, 区块是相关信息的集合。与区块头 H 想对应的交易信息为 T, 其它区块头数据的集合 U 表示 它的父级区块中有和当前区块的爷爷辈区块是相同的。(这 样的区块称为 ommers2, 译者注: 不妨称之为叔链)。区块头 包含的的信息如下: parentHash, 父块哈希: Hp, 父 区 块 头 的 Keccak 256 位哈希。 ommersHash, 叔链哈希: Ho, 当前区块的叔链列表 Keccak 256 位哈希。 beneficiary, 受益者地址: Hc, 成功挖到这个区块的 160 位地址, 这个区块中的所有交易费用都会转到 这个地址。 stateRoot, 状态字典树根节点哈希: 状 态 字 典 树 根 节点的 Keccak 256 位哈希, 交易打包到当前区 块且区块定稿后可以生成这个值。 1显著地,这样的“工具”可以从基于人类行为的初始化中移除—或者人类可能变成有原因的中立—可能有一点他们被视为自治的代理人。例如:合约可能 会给人类好处, 让人发送交易从而触发合约的执行。 2ommer 的意思和自然界中的父母的兄弟姐妹最相近, 详见 http://nonbinary.org/wiki/Gender_neutral_language#Family_Terms
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 4 transactionsRoot, 交易字典树根节点哈希: 交 易 字典树根节点的 Keccak 256 位哈希, 在交易字典 树含有区块中的所有交易列表。 receiptsRoot, 接受者字典树根节点哈希: 接受者字 典树根节点的 Keccak 256 位哈希, 在接受者字典 树含有区块中的所有交易信息中的接受者。 logsBloom, 日志 Bloom: Hb, 日记 Bloom 过滤器 由可索引信息(日志地址和日志主题)组成,这个 信息包含在每个日志入口, 来自交易列表中的每个 交易的接受者。 difficulty, 难度: Hd, 表示当前区块的难度水平, 这 个值根据前一个区块的难度水平和时间戳计算得 到。 number, 区块编号: Hi, 等于当前区块的直系前辈区 块数量。创始区块的区块编号为 0。 gasLimit, 燃料限制: Hl, 目前每个区块的燃料消耗 gasUsed, 燃料使用量: Hg, 当前区块的所有交易使 timestamp, 时间戳: Hs, 当前区块初始化时的 Unix 上限。 用燃料之和。 时间戳。 extraData, 附加数据: Hx, 32 字节以内的字节数组。 mixHash, 混合哈希: Hm, 与一个与随机数 (nonce) 相关的 256 位哈希计算, 用于证明针对当前区块已 经完成了足够的计算。 nonce, 随机数: Hn, 一个 64 位哈希, 和计算混合哈 希相关, 用于证明针对当前区块已经完成了足够的 计算。 此外, 当前区块还记录着这个区块的交易列表, 以及 2 个 B (BH ; BT; BU) 叔链 (ommer) 的区块头列表。我们以 B 表示一个区块: (17) 4.3.1. 交易收据. 为了让交易信息编码能有利于零知识证 明、索引、搜索, 我们将每个包含一定信息的交易收据进行 编码。以 BR[i] 表示第 i 个交易, 保存在一个索引字典树 中,He 是这个字典树的根节点。 交易收据是一个包含四个条目的元组:交易后的状态 R;当前区块中交易累计燃料使用量 Ru, 交易发生后会立 即更新这个值; 交易执行过程中创建的日志集合 Rl;和日 志 Bloom 过滤器 Rb : (18) R (R; Ru; Rb; Rl) 函数 LR 是一个将交易收据转换为 RLP 编码的预处理 函数: (19) LR(R) (TRIE(LS(R)); Ru; Rb; Rl) 交易后状态 R 会编码到一个字典树中, 字典树的根节 点组成了第一个条目。 我们假定累计的燃料使用量 Ru 是一个正整数, 日志 Bloom Rb 是 2048 位 (256 字节) 的哈希: Ru 2 P ^ Rb 2 B256 (20) Rl 是一系列的日志入口, 例如 (O0; O1; :::)。一个日志入 口 O 是一个日志记录器的地址 Oa 的元组, Ot 是一系列 32 字节的日志主题, Od 是一些字节数据: (21) O (Oa; (Ot0; Ot1; :::); Od) (22) Oa 2 B20 ^ 8t2Ot : t 2 B32 ^ Od 2 B 我们定义 Bloom 过滤器函数 M 将一个日志入口转换为 一个 256 字节哈希: (23) M (O) ∨ ( ) M3:2048(t) t2fOag[Ot 其中 M3:2048 是一个特别的 Bloom 过滤器, 针对任意一 个字节序列, 它舍弃这个 2048 位字节序列的前三位。它通 过对一个字节序列的 Keccak-256 哈希的每一个前三对字节 取其的低 11 位来实现: (24) (25) (26) (27) M3:2048(x : x 2 B) y : y 2 B256 where: except: 8i2f0;2;4g m(x; i) KEC(x)[i; i + 1] mod 2048 Bm(x;i)(y) = 1 y = (0; 0; :::; 0) : 其中 B 是位引用函数, Bj(x) 等于字节数组 x 中的索引 j(从 0 开始索引) 的位。 4.3.2. 整体有效性. 如果一个区块同时满足以下几个条件, 我们才能认为这个区块是有效的:当从起始状态 (父块的 最终状态) 按顺序执行完生成新的状态 Hr 后, 在内部要保 持一致, 包括叔链、交易区块哈希、给定的交易 BT(详细 描述见 11): Hb ∨ (28) Hr TRIE(LS((; B))) ^ Ho KEC(RLP(L ^ ( ) H (BU))) Ht TRIE(f8i < ∥BT∥; i 2 P : p(i; LT (BT[i]))g) ^ He TRIE(f8i < ∥BR∥; i 2 P : p(i; LR(BR[i]))g) ^ p(k; v) ( 这个区块中的交易索引,v 为交易收据: (29) 其中 p(k; v) 是 RLP 的简单对转换, 在这个例子中, k 为 RLP(k); RLP(v) r2BR ) rb 此外: (30) TRIE(LS()) = P (BH )H r TRIE(LS()) 是包含以 RLP 编码的状态 键值对的 Merkle Patricia 树根节点哈希, P (BH ) 是父节点。 这些值根据交易计算产生,特别是交易收据 BR, 这个通 过交易状态累积函数 定义,在 11.4 会详细说明。 4.3.3. 序列化. 函数 LB 和 LH 分别是区块和区块头的预 备函数。类似交易收据预备函数 LR,当转换为 RLP 格式 时, 假设对应的类型、顺序及结构如下: LH (H) ( Hp; Ho; Hc; Hr; Ht; He; Hb; Hd; ) Hi; Hl; Hg; Hs; Hx; Hm; Hn ) LH (BH ); L T (BT); L H (BU) H 是元素序列转换函数, 因此: 其中 L T 和 L (31) (32) LB(B) ( ) ( (33) f 元素类型定义如下: (x0; x1; :::) ( f (x0); f (x1); ::: 对于任何函数 f ) ^ ^ Hc 2 B20 ^ Ho 2 B32 (34) Hp 2 B32 ^ He 2 B32 ^ ^ Ht 2 B32 Hr 2 B32 ^ ^ Hi 2 P Hb 2 B256 ^ Hd 2 P ^ Hs 2 P256 ^ Hl 2 P ^ Hg 2 P ^ Hm 2 B32 ^ Hn 2 B8 Hx 2 B 其中 (35) Bn = fB : B 2 B ^ ∥B∥ = ng 我们现在有了一个严密正式的区块结构结构说明。RLP 函数(见附录 B)提供了一个标准方法来把这个结构转换为 一个字节序列。
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 5 4.3.4. 区块头验证. 我们定义 P (BH ) 为 B 的父区块: (36) P (H) B ′ : KEC(RLP(B ′ H )) = Hp 当前区块编号等于它的父块编号加 1: (37) Hi P (H)H i + 1 { 区块难度定义为 D(H): (38) D(H) ( ) D0; P (H)H d + x &2 + ϵ D0 max if Hi = 0 otherwise 其中: (39) (40) (41) &2 max (42) ⌋ D0 131072 ⌊ x ⌊ P (H)H d 2048 Hs P (H)H s ⌋ 10 ⌊Hi100000⌋2 ( 1 ⌊ ϵ 2 ⌋ ) ;99 区块的燃料限制 Hl 需要满足下面条件: ⌊ ⌊ P (H)H l 1024 P (H)H l 1024 ⌋ ⌋ ^ ^ (43) (44) (45) Hl < P (H)H l + Hl > P (H)H l Hl ⩾ 125000 Hs 是区块 H 的时间戳, 需满足下面条件: (46) Hs > P (H)H s 这个机制保证了区块间时间平衡;如果最近的两个区块 时间间隔短, 则会导致难度系数增加, 因此需要额外的计算 量,大概率会延长下个区块的出块时间。相反,如果最近的 2 个区块时间间隔时间过长,难度系数和下一个区块的出块 预期时间也会减少。 随机数 Hn 需满足下面关系: (47) n ⩽ 2256 Hd ^ m = Hm with (n; m) = PoW(Hn; Hn; d). 其中 Hn 是新区块的区块头,但不包含随机数和混合哈 希值,d 是当前的数据集合 DAG(有向无环图),需要去计 算混合哈希, PoW 是工作量证明函数(见 11.5):第一个元 素用于计算混合哈希值,以证明使用了一个正确的 DAG, 第 2 个元素是伪随机数, 依赖于 H 及 d 。给定一个范围在 [0; 264) 的均匀分布,则求解时间和难度 Hd 成比例。 这就是区块链安全基础,这也是一个恶意节点不能用其 新创建的区块中重写历史数据的重要原因。因为这个随机 数必须满足这些条件,且因为条件依赖于这个区块的内容 和相关交易, 创建新的合法的区块是困难且耗时的, 需要超 过所有诚实矿工的算力总和。 因此, 我们定义这个区块头的验证函数 V (H) 为: ⌋ ⌋ ^ ^ (48) (49) (50) (51) V (H) n ⩽ 2256 Hd ^ m = Hm ^ Hd = D(H) ^ Hg Hl ^ ⌊ ⌊ Hl < P (H)H l + (52) Hl > P (H)H l Hl ⩾ 125000 ^ (53) (54) Hs > P (H)H s Hi = P (H)H i + 1 ^ (55) ∥Hx∥ 32 (56) 其中 (n; m) = PoW(Hn; Hn; d) 此外, extraData 最多 32 字节。 ^ P (H)H l 1024 P (H)H l 1024 5. 燃料和支付 为了避免网络滥用及回避由于图灵完整性而带来的一些 不可避免的问题,在以太坊中所有的程序执行都需要费用。 各种操作费用以 gas (详见附录 G ) 为单位计算。任意的程 序片段(包括合约创建、信息调回、利用及访问账户存储、 在虚拟机上执行操作等)都可以根据规则计算出消耗的燃 料数量。 每一个交易都有燃料上限:gasLimit (燃料上限)。这些 燃料从发送者的账户中扣除。具体从账户上扣除的额度和 gasPrice(燃料价格) 有关 (译者注: 扣除额度 = gasLimit * gasPrice), 在执行交易前会指定燃料价格。如果这个账 户不能支付起燃料费用,交易会被当作无效交易。之所以命 名为燃料上限,是因为剩余的燃料在交易完成之后会被退 回(以购买时的同样价格)到发送者账户。燃料不会被用在 交易执行之外。因此对于可信任账户,应该设置一个相对较 高的燃料上限。 通常来说,以太币(Ether)用作购买燃料, 未退回的部 分转到了区块受益人的地址,通常这个账户的地址是由矿 工设定。交易者可以任意设定燃料价格,然而矿工也可以任 意地忽略某个交易。在一个交易中,高价格的燃料将消费 这个发送者更多的以太币, 并转给矿工更多的以太币,因此 这个交易会被更多的矿工选择。通常来说,矿工会选择去 通知这是他们执行交易最低燃料价格, 交易者们一般也会些 选择一个高过燃料价格下限的价格。因此, 会有一个(加权 的)最低燃料可接受价格分布,交易者们需要权衡降低燃料 价格和交易快速被矿工打包。 6. 交易执行 交易执行是以太坊协议中最复杂的部分:它定义了状态 转换函数 。所有交易在执行时, 都要先通过内部的有效性 测试, 这些包含: (1) 交易是 RLP 格式数据,没有多余的后缀字节; (2) 交易的签名是有效的; (3) 交易的随机数是有效的(等于发送者账户的当前随 (4) 燃料上限不小于实际交易过程中用的燃料 g0; (5) 发送者账户的余额至少大于费用 v0, 需要提前支 机数); 付。 T 表示交易, 表示状态, 状态转移函数 如下: (57) ′ = (; T ) ′ 是交易后的状态。我们定义 g 为交易执行所消耗的 燃料量, l 为交易过程中产生的日志记录, 这些都会在后文 正式定义。
以太坊:一种安全去中心化的通用交易账本 EIP-150 版本 (a96c905 - 2017-09-18) 6 6.1. 子状态. 从交易执行过程来看,伴随交易会产生一些特 定的信息, 我们称为交易子状态,用 A 来表示: (58) A (As; Al; Ar) 元组内容包含自毁集合 As: 一个在交易完成后被删除的 账户集合。Al 是一系列的日志:在代码执行过程中是可归 档, 可索引的检查点, 允许以太坊世界的外部旁观者 (例如 去中心化应用的前端) 跟踪合约调用。Ar 表示返回的余额, 当使用 SSTORE 指令将非 0 的合约存贮空间置为 0 时, 返 回余额会增加。虽然不是立即返回余额,但可以部分抵消整 个执行费用。 我们定义空的子状态 A0, 它没有自毁、没有日志、返回 余额为 0: (59) A0 (∅; (); 0) { 6.2. 执行. 执行过程中需要的燃料需要在交易进行之前支 付, 定义 g0 : g0 (60) Gtxdatazero Gtxdatanonzero if i = 0 otherwise ∑ { i2Ti;Td (61) (62) + Gtxcreate 0 + Gtransaction if Tt = ∅ otherwise Ti 合约初始化 EVM 代码, Td 是交易附带的关联数据, 取决于交易是合约创建还是消息调用。如果这个交易是合 约创建, 那么会增加 Gtxcreate , 其它情况则不增加。G 的详 细定义见附录 G。 预支付的费用 v0 计算如下: v0 TgTp + Tv (63) 需满足下面关系: (64) S(T ) [S(T )] ̸= ∅ ^ ̸= ∅ ^ Tn = [S(T )]n ^ g0 ⩽ Tg ^ v0 ⩽ [S(T )]b ^ Tg ⩽ BH l ℓ(BR)u 注意最后的条件: 交易燃料限制 Tg <= 这个区块的燃料 限制 BH l - 这个区块之前的所有交易已使用的燃料 ℓ(BR)u 。 有效交易的执行起始于一个对状态不能撤回的改变:发 送者账户 S(T ) 的随机数会加 1, 账户余额扣减部分预付费 用 TgTp。计算过程中有效的燃料 g = Tg g0。无论是合约 创建还是消息调用后的的最终状态(可能等于当前的状态), 这个改变是确定的且从来没有无效的: 从这个观点看不会存 在无效的交易。 我们定义检查点状态 0: (65) (66) (67) 0 except: 0[S(T )]b [S(T )]b TgTp 0[S(T )]n [S(T )]n + 1 从 0 转变为 P 和交易类型相关。不管它是创建合约 还是消息调用, 我们定义元组包括执行后的临时状态 P , 剩余的燃料 g (68) ′ 和子状态 A : ′ ; A) (P ; g (0; S(T ); To; g; Tp; Tv; Ti; 0) 3(0; S(T ); To; if Tt = ∅ Tt; Tt; g; Tp; Tv; Tv; Td; 0) otherwise 8>>><>>>: 其中 g 是燃料上限扣除已有的交易消耗的燃料: (69) g Tg g0 To 是原始执行者,与和合约创建或消息调用 (直接由交 易触发) 不同, 可以通过 EVM 代码内部调用来执行。 注意,我们使用 3 表示取函数值的前三个元素; 最终结 果是消息调用的输出(一个字节数组), 最终结果并没有在 交易的上下文使用。 在消息调回或者创建合约被处理后,再确定退回的燃料 ′, 再加上一些补偿, 得 后最终状态就确定了。剩余的燃料 g 到最终需要退回发送者的燃料 g (70) g ′ g + minf 最终要退回发送者的燃料 g 等于当前剩余的燃料 g 加一个补偿, 这个补偿是 Ar 和总使用燃料量 Tg g 半中的小者。 ′, 再 ′ 的一 燃料对应的以太币给了矿工, 矿工地址是当前区块 B 的 受益者地址。我们定义预备最终状态 , 临时状态 P : ⌊ : Tg g ′ ⌋ 2 ; Arg (71) (72) (73) (74) P except [S(T )]b P [S(T )]b + g Tp [m]b P [m]b + (Tg g m BH c )Tp 删除所有出现在自毁集合中的账户后, 达到了最终状态 ′: (75) (76) 8i 2 As : ′ [i] ∅ ′ except 最后, 我们定义这次交易中总共使用的燃料 g, 这次交 易中创建的日志 l: (77) (78) g(; T ) Tg g (; T ) Al 这些有助于后续讨论的交易收据。 l ′ 7. 合约创建 当创建一个账户时会有很多参数: 发送者(s)、原始执行 者(o)、可用的燃料(g)、燃料价格(p)、转账额度(v)、 任意长度的字节数组 i、EVM 初始化代码、消息调用/合约 创建的当前栈深度 (e) 。 ′ ′ ′ ; g ′ ; g 我们定义创建函数为函数 ,相关的变量有状态 , 包 ; A) (详见第 6 小 含新状态、剩余燃料及交易子状态 ( 结): (79) ; A) (; s; o; g; p; v; i; e) ( 这个新账户的地址被定义为包含发送者和随机数 RLP 编码的 Keccak 哈希的最边 160 位。因此我们定义新帐户 a 的地址: (80) (s; [s]n 1) a B96::255 ))) ( KEC RLP ( ( 其中 KEC 是 Keccak 256 位哈希函数,RLP 是 RLP 编码 函数,Ba::b(X) 表示取二进制数据 X 位数为范围 [a; b] 的 值,[x] 是地址 x 的状态,∅ 表示地址不存在时的状态。注 意, 我们使用的是一个比发送者随机数要小的值;我们断言 会在在这个合约创建是前会增加发送者账户的随机数,因 此在刚开始在交易或 VM 操作中使用的随机数就是发送者 的随机数。
以太坊:一种安全去中心化的通用交易账本 账户的随机数开始被定义为 0,余额为传递的转账值,存 储空间为空, 哈希代码为 Keccak 256 位的空字符串哈希值; 发送者的余额会减去转账值。这个变化的状态变成 : ( )) () except: (81) (82) (83) [a] ( { ; TRIE(∅); KEC ′ 0; v + v [s]b [s]b v ′ 是账户在交易之前就有的余额: 其中 v (84) v ′ 0 [a]b if [a] = ∅ otherwise 最后,这个账户是通过执行初始化账户的 EVM 代码 i (执行模型详见小结 9 )来初始化的。代码执行可以影响一 些事件:可以改变当前账户的存储,能创建更多的账户, 执 行更多的消息调用。代码执行函数 可以得到一个元组, 包 , 子状态 A, 以及账户 括结果状态 o 的代码。赋值到最后的状态的数组中,有效的剩余燃料, 当前产生的子状态 A 和账号代码本身 o。 , 可用的剩余燃料 g (85) ; A; o) ( ; g ( ; g; I) 其中 I 包含执行环境的相关参数, 这些参数在小结 9 中有 详细定义: (86) (87) (88) (89) (90) (91) (92) (93) Ia a Io o Ip p Id () Is s Iv v Ib i Ie e 7 if F EIP-150 版本 (a96c905 - 2017-09-18) 燃料和子状态 ( ′ (95) g ′ ; A) 的关系如下: if F c otherwise ′ ; g 0 g { 8><>: F ( ′ where (96) (97) ′ except: [a]c = KEC(o) otherwise ) < c _ joj > 24576 = ∅ _ g 上述 ′ 的公式, 表明了执行代码初始化得到的最终字节 序列 o, 就是新创建账户的代码体。 当合约成功创建时, 如果有转账, 对应的转账也会转到合 约中; 如果没有转账, 则仅是合约创建。 7.1. 细微之处. 有一种情况需要注意, 当初始化代码正在执 行时,新创建的地址出现了, 但还没有内部的代码时。在这 个时间内, 任意消息调用不会引起代码执行。如果这个初始 化执行结束于一个 SELFDESTRUCT 指令,这个账号会在 交易执行完前将被删除, 这个行为是无意义的。对于一个正 常的 STOP 指令代码,或者返回的代码是空的, 这时候会出 现一个僵尸账户,而且账户中剩余的余额将被永远被锁定 在这个僵尸账户中。 8. 消息调用 当执行消息调用时需要多个参数:发送者(s)、交易发 起人(o)、接受者(r)、执行代码的账户(c, 通常就是接受 者)、可用的燃料(g)、转账额度(v)、燃料价格(p)、函 数调用的一个任意长度字节的数组 d 的输入数据以及消息 调用/合约创建的当前栈 (e) 深度。 除了转变到新的状态和交易子状态外,消息调用还有一 个额外的元素 — 用字节数组 o 表示的输出数据。执行交易 时输出数据是被忽略的, 但在消息调用时, 输出的数据可以 由虚拟机代码执行时进行初始化, 在这种情况下也使用了这 些信息。 (98) ; A; o) (; s; o; r; c; g; p; v; ~v; d; e) ( ; g ′ ′ 注意我们的是, 当执行 DELEGATECALL 指令时, 我们 需要区别转账额度 v 和执行上下文中的 ~v 。 我们定义在原始状态基础上进行了发送者向接收者转账 如果没有输入数据, 使用 Id 表示空的元组。IH 没有什 后的状态为第一个转变状态 1: 么特别的, 它由区块链决定。 代码执行会消耗燃料,且燃料不能低于 0,因此程序执 行可能会在自然终止之前退出。在这个(以及其它几个)异 常情况, 我们称发生了燃料不足(out-of-gas, OOG)异常: 演进的状态变成空集合 ∅, 整个创建操作对状态没有影响, 状态就像尝试创建合约之前一样。 如果这个初始化代码成功地执行完,那么对应的合约创 建也会被支付。代码保存费用 c 和创建的合约代码大小成 正比: (94) c Gcodedeposit joj 如果没有足够的燃料支付这些, 例如 g < c, 就会抛出 燃料不足异常。 发生这样的异常后, 剩余燃料将变为 0。如果合约创建是 用于接受一个交易, 它不会影响合约创建内部消耗的燃料, 无论如何都必须支付。然而,当燃料不足时, 并不会给这个 合约地址转账。 如果没有出现这样的异常, 那么剩余的燃料会退会给最 原始的发送者, 改变后的新状态也会永久保存。最终状态、 1[r]b [r]b + v ^ 1[s]b [s]b v (99) unless s = r. 我们假设如果 1[r] 还未定义, 则会创建一个没有代码 或没有状态且余额和随机数都为 0 的账户。我们改进上一 个公式: (100) ′ 1 1 1[s]b and except: 1[s]b v ′ 1 except: ′ 1[r] (v; 0; KEC(()); TRIE(∅)) ′ 1[r]b [r]b + v ′ (101) (102) (103) { if [r] = ∅ otherwise 账户关联的代码(整个代码碎片, Keccak 哈希为 [c]c) 依靠执行模式(见小结 9)执行。合约创建时,如果执行因 为一个异常(例如:耗尽了燃料、堆栈溢出、无效的跳转目 的地或者无效的指令)而被终止,燃料不会返回给调用者, 且状态也会立即恢复到转账之前的状态 (例如 )。
(107) (108) (109) (110) (111) (112) (113) (114) Ia r Io o Ip p Id d Is s Iv ~v Ie e (104) (105) ′ ′ g (106) ( ; A; o) ; g 以太坊:一种安全去中心化的通用交易账本 { { 8>>>>>><>>>>>>: 0 g = ∅ if otherwise if otherwise = ∅ ECREC(1; g; I) SHA256(1; g; I) RIP160(1; g; I) ID(1; g; I) (1; g; I) if r = 1 if r = 2 if r = 3 if r = 4 otherwise Let KEC(Ib) = [c]c 我们假设客户端会先保存数据对 (KEC(Ib); Ib) , 以便更 方便地根据 Ib 做出决定。 如我们所见,消息调用执行框架 中有 4 个特例: 这四 个是‘预编译’合约,作为最初架构中的一部分后续可能会 变成本地扩展。这四个合约地址分别为 1、2、3 和 4,分 别是用来执行椭圆曲线公钥恢复函数、SHA2 256 位哈希方 案、RIPEMD 160 位哈希方案和身份函数。 这 4 个合约的详细定义见附录 E。 9. 执行模型 执行模型具体说明怎么使用一系列字节代码指令和一个 小的环境数据元组去改变这个系统状态。这些是通过以太 坊虚拟机 (Ethereum Virtual Machine - EVM), 这个虚拟 状态机来实现的。它是一个准图灵机, 说“准”是因为计算 会被燃料所限制。 9.1. 基础. EVM 基于栈结构, 机器的字大小(以及栈中数 据的大小)是 256 位。主要是便于执行 Keccak-256 位哈希 及椭圆曲线计算。内存模型基于字寻址的字节数据。栈的最 大深度为 1024。EVM 也有一个独立的存储模型;类似内 存但更像一个字节数组, 一个基于字寻址的字数组。不像易 变的内存,存储是非易变的且是作为系统状态的一部分被 维护。所有内存和存储中的数据会初始化为 0。 EVM 不是标准的诺依曼结构。它通过一个特别的指令 把程序代码保存在一个虚拟的可以交互的 ROM 中, 而不是 保存在一般性可访问的内存或存储中。 EVM 在某些情况下会有异常发生,包括堆栈溢出和非 法指令。就像燃料缺乏异常那样, 不会改变状态。有异常时, EVM 立即停止并告知执行代理 (交易的处理者,或执行环 境), 代理会单独处理异常。 9.2. 费用概述. 在三个不同的情况下使用费用(命名为燃 料),在这三种情况下,费用都是执行任何操作的必备条件。 第一种情况也是最普通的情况就是计算操作费用(详见附 录 G)。第二种情况,可能因为一个子消息调用或者合约创 建而消耗燃料, 这是执行 CREATE, CALL and CALLCODE 费用中的一部分。第三种情况,可能因为内存使用的增多而 为燃料付费。 对于一个账户的执行,内存总费用和需要的 32 字节最 小倍数的内存量成正比, 所有的内存是按在一定范围中被包 含的所有记忆索引(无论是读还是写)下要求的 32 位字节 EIP-150 版本 (a96c905 - 2017-09-18) 8 的最小倍数成比例的。这个为了 just-in-time (JIT) 的基础 而去支付的;访问一个大于索引量 32 字节的地址, 就会需 要额外的内存使用费。地址很容易超过 32 字节的限制, 但 EVM 不同。综上所述,必须能够去管理这些可能发生的事 件。 存储费用有一个微妙的行为——为最小化存储费用(直 接与一个在所有结点上的大型状态数据通信),清除存储的 执行费用指令不仅仅免除, 而且会返回一些费用;因为初始 化的存储费用往往比实际使用的多, 在清除存储空间后会有 返回费用, 这个返回的费用是预先支付的费用中的一部分。 详细的 EVM 燃料消耗定义见附录 H。 9.3. 执行环境. 除了系统状态 和计算过程中剩余的燃料 g 外, 还有一些在计算过程中需要提供的信息, 这些信息组 成了元组 I: 一个交易,这个就是交易数据。 Ia, 拥有正在执行代码的账户地址。 Io, 发起这次交易的发送者地址。 Ip, 发起这次交易中的燃料价格。 Id, 执行过程中的输入字节数组;如果这次执行是 Is, 触发代码执行的账户地址;如果这次执行是一 Iv, 以 Wei 为单位的值, 作为计算中的一部分传递 给这个账户; 如果这次执行是一个交易, 这个值为 转账额度。 个交易,则为交易发送者地址。 Ib, 机器代码字节数组, 会用来执行。 IH, 当前区块的区块头。 Ie, 当前消息调用或合约创建的深度 (例如: 当前 ′, 剩余的 ′, 产生的子状态 A 和结果输出 o, 根据当前的上下 CALLs 或 CREATEs 被执行的次数)。 执行模型定义了函数 , 用来计算结果状态 燃料 g 文, 我们定义: (115) ′ ′ ; A; o) (; g; I) ; g ( 应该还记得 A, 这个产生的子状态定义为自毁集合 s、日 志系列 l 和回推金额 r 的元组: (116) A (s; l; r) 9.4. 执行概述. 我们现在必须去定义函数 。在大多实际执 行过程中, 将整个系统状态 和机器状态 的迭代过程建 模。我们定义一个递归函数 X, 它使用这个迭代函数 O(定 义状态机中单循环的结果), 定义函数 Z 用来表示当前状态 是一个异常终止状态, 定义函数 H 表示当前状态是正常终 止时得到的结果数据。 空序列使用 () 表示, 它不等于空的集合 ∅;这对理解 H 的输出结果非常重要, 当 H 的输出结果是 ∅ 时需要继续执 行, 但当 H 的输出结果是时一个空的序列时执行应该终止。 (117) (118) (119) (120) (121) (122) (123) (124) ( ( (; g; I) ( ; A; :::; o) X ′ (; g 0 ) ′ ′ g; A; o) ; (; ; A0; I) g pc m (0; 0; :::) 0 i s () ( ) ( ∅; ; A0; I; () O(; ; A; I) o O(; ; A; I) X ) 8><>: ) if Z(; ; I) if o ̸= ∅ otherwise X (; ; A; I)
分享到:
收藏