这个系列的目标受众是区块链开发者和 CS 专业同学
面对媒体对区块链相关技术的解读和吹捧, 许多人一时不知所措投资人大公司都在 FOMO(fear of missing out)的心理驱动下, 争相宣布 all in 区块链各路大咖坐而论道, 谈论区块链技术的社会政治经济乃至哲学上的意义人类对未知和不懂的东西有种天然的不安全感, 作为一名开发人员, 我认为克服焦虑 (以及带来的投机心理) 最好的方法是尽可能增加对底层的原理及实现的认知
从技术角度来看, 目前不论是比特币以太坊, 抑或是尚未正式上线的 EOSIPFS, 都带有很强的实验性质, 存在各种局限, 而这种局限不可避免影响上层应用的开发区块链应用也大多涉及金融信用等重要领域, 所以深入理解底层原理是对区块链开发者的一个基本要求, 而不仅仅是跟着教程 10 分钟部署一段智能合约, 特别是早期各种技术未成熟的情况下, 生搬硬套稍不留心就会造成极大的损失
本系列的第一篇文章, 主要是以比特币为代表的加密货币架构 (区块链 1.0), 和以以太坊为代表的可编程分布式信用基础设施(区块链 2.0) 的核心差异之一是否支持图灵完备的语言, 来看看区块链技术架构的演进
比特币和以太坊的渊源: 对币圈和链圈的人来说, Vitalik Buterin(1994 年出生)是无可争议的大神很多人可能不知道, V 神作为早期比特币社区的活跃成员, 一开始提议 bitcoin 需要开发通用的脚本语言来支持丰富功能的应用开发, 但没有获得比特币开发团队的支持于是重起炉灶, 2013 年发起以太坊项目, 有了今天的繁荣的加密 token 收藏品游戏 DAO 接下来, 我们就先看看, V 神不满的比特币脚本系统到底是什么样的?
Part I: 比特币脚本引擎
交易
交易是在区块链世界里面有很广泛的含义, 在加密货币应用中可以狭义理解为比特币额度在不同地址间的转移, 即转账转账是个历史悠久的行为, 但转账技术一直在革新 理解比特币转账模型尤其重要, 因为比特币脚本引擎建立在该模型之上
两种转账方式
1 简化下的传统中心式转账: alice(A 账户)转账到 bob(B 账户)x 元, 银行需要原子化的操作
balance[A]-=x,balance[B]+=x
, 当然隐含条件是 alice 完成了对 A 账户的认证
2 一种解释比特币交易原理的说法: 网络中每个节点维护独立的数据库, 记录着每个地址的余额, 如果 Alice(addressA 的拥有者)想向 Bob(addressB 的拥有者)转账 x 元, 她会在网络中广播出去 "addressA gives X units to addressB", 带上 pubkeyA, 用 privatekeyA 签名每个节点收到后, 校验成功后, 在各自数据库中执行原子化操作
- balance[addressA]-=x,balance[addressB]+=x
- (注: 实际地址由 pubkey 生成, 这里为简化省略)
上面 1 在现实中占据主流, 有成熟的扩展方案, 但中心化不可避免带来成本平台作恶等问题; 2 的描述来自于 b-money, an anonymous, distributed electronic cash system(这篇文章非常之重要, 深刻影响了中本聪对比特币的设计), 但在当时无法实践, 因为重度依赖于一个同步不受干扰的网络环境, 否则保持一致性难度很大而且这种分布式数据库提交问题 (Byzantine Problem), 现有的一致性算法 paxosraft(non-byzantine) 包括 pbft(byzantine)扩展性都无法支撑比特币上万的节点数
比特币交易模型的设计
关于比特币交易模型最早来自于中本聪的 Bitcoin: A Peer-to-Peer Electronic Cash System 中本聪实际提出了两种 chain, 大家现在一直说的区块链 (chain of blocks) 是显式的数据组织方式, 另一个隐式的是交易链 (chain of transactions) 才是比特币价值流动的链条
如图, 最早的交易描述模型:
如果 Alice(addressA 的拥有者)想向 Bob(addressB 的拥有者)转账 x 元, 她同样需要把这个交易签名后在网络中广播出去不同的是, addressA 的余额, 并非存储在各个节点的数据库里, 而是在别人给 addressA 转账的未花费交易输出中, 即 UTXO(unspent transaction output)我们查询 addressA 的余额, 实际得到的是所有收款地址是 adressA 的 UTXOs 的额度的求和广播内容类似 "addressA(combining UTXO1...UTXO3) gives X units to addressB", 带上 pubkeyA, 用 privatekeyA 签名 交易在网络中被确认后, Bob 就会多了一个可用 UTXO 如果他想花费这笔钱, 需要证明自己拥有 addressB 对应的 privatekeyB, 那么 Bob 也用私钥签名这样交易就成了一串签名的链条
显然这里有三个问题:
1. 如果任意的交易的 input 都需要某个之前交易的输出, 那么最初比特币从哪里来?
所以在比特交易中, 有种叫做 coinbase 的交易, 就是我们所周知的挖矿奖励比特币的产生就通过挖矿算法生成, 这里的 input 来自于系统奖励实际上还会校验 coinbase 是否是 "mature" 的, 即该块是否经过足够的确认在比特币中如果最终没有归入最长链, 那么会作为 orphan 块被弃, 奖励也作废
2. 判断一个交易输出是否是 UTXO 需要回溯整个区块链吗?
不需要, 因为交易按照 merkel tree 的结构组织, 决定了从整个区块链数据库中查询一个交易会非常低效 UTXOs 专门存储在 leveldb 的数据库 chainstate 中, 并且缓存在内存中每当一个新的 block 生成, 就会更新 UTXOs 集; 当某个节点发生链重建现象, 会回滚该过程这里需要注意的是, UTXOs 集不是待确认交易池(TxMemPool), 而是所有待确认交易的 input 来源; UTXOs 理论上也可以通过 --reindex 从整个区块链中重建
3.Alice 的账户余额来自于四个 UTXO, 分别是 0.05,0.2,0.2,0.3, 现在需要转帐 0.6 给 Bob, 怎么办?
理论上 Alice 可以三次转, 但实际上很不明智, 既要多付手续费, 体验也差, 所以交易 Input 可以包括多个 UTXO, 如何选择 UTXO 组合有专门的分析; 多个 Input 之和不一定恰好等于转账金额 Output1, 还需要一个找零钱 refund(Output2), 当然还会有手续费 fee(Output3), 所以交易会包括多个 Output 当然对于用户来说, 只需要设定转账地址额度手续费, 组合 UTXO 找零等是透明的
注: 以太坊摒弃了 UTXOs 模型, 采用类似于 bmoney 的账户范式具体原因等到介绍以太坊虚拟机设计中再分析
做了这么多铺垫, 终于可以进入比特币的脚本设计了
Script opcodes
比特币交易由一套脚本引擎 (Script) 处理这里引用 bitcoin-core 源码 interpreter.cpp 里的一段注释:
/**
* Script is a stack machine (like Forth) that evaluates a predicate
* returning a bool indicating valid or not. There are no loops.
*/
Script 是一种类 Forth 基于栈式模型无状态的非图灵完备的语言 opcodes 分为常量流程控制栈操作算术运算位运算密码学运算保留字等若干类, 还包括 3 个内部使用的伪指令下面举几个在后面的脚本中会出现的指令, 全部的指令可参考官方文档和源码
OP_0 ... OP_16: 将字面量值压入栈中
OP_DUP: 将栈顶元素复制一个, 压入栈中
OP_ADD: 弹出栈顶元素和次栈顶元素, 相加后压入栈中
OP_EQUAL: 弹出栈顶元素和次栈顶元素, 比较是否相等, 相等则将 1 压入栈中, 否则压入 0
OP_SHA256: 弹出栈顶元素, 进行 sha-256 加密运算, 结果压入栈中
OP_HASH160: 弹出栈顶元素, 先进行 sha-256 加密运算, 再进行 ripemd160 摘要运算, 结果压入栈中值得注意的是, 这是基于公钥生成 address 的过程的一部分
OP_CHECKSIG: 弹出栈顶元素和次栈顶元素, 这里分别是 sig 和 pubkey; 内部有个 VerifySignature 函数, 验证签名和公钥是否匹配
OP_CHECKMULSIG: 栈内压入 m 个签名, n 个公钥, 逐一校验 m 个签名是否对应 n 个公钥的某个子集
Pay-to-PubkeyHash(P2PKH)
上面 Alice 转载给 Bob 的例子, 就是一个典型的 P2PKH 中本聪在论文中只是给出了交易模型, 下面看看更具体的实现
如上图, Alice 在转账给 Bob 前, Bob 需要提供一个自己的收款地址, 但实际 P2PKH 中使用的是 Public Key Hash 这里简单补充下 key 生成过程, 如下图, 私钥单向生成公钥, 公钥通过 OP_HASH160 指令生成 160 位的 PKH(公钥哈希),PKH 可以转成更可读用户使用的地址, 但编码校验过程等是双向的所以提供地址等价于提供 PKH
下图, Alice 转账给 Bob 的钱锁定在 TX1 Output 中, 通过一个 Pubkey ScriptBob 如果尝试花掉这笔钱, 他需要解锁这个 PubkeyScript, 通过证明自己是 TX1 Output 中 Public Key Hash 的私钥拥有者, 提供一个 Signature Script
下面就是这两个脚本
锁定脚本:
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
解锁脚本:
scriptSig: <sig> <pubKey>
上面包括在 <> 之间的为要压入栈中的数, push 指令缺省实际执行时, 会将 scriptSig 和 scriptPubkey 连接起来, 按照从左往右顺序运行脚本
验证过程:
<sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
栈上的情况如下面两图所示有汇编基础的同学, 对栈式计算机模型的运作原理会很熟悉
generation 奖励矿工
奖励矿工可以看作一种简化的 P2PKH, 区别在于交易的输入来自 coinbase 而不是某个 UTXO
Pay-to-Script-Hash(P2SH)
P2PKH 设计比较简单, 接受者 Bob 直接提供收款地址实际的价值流通过程中, 会涉及很多条件为了满足更复杂的功能, BIP12 中提出加入 OP_EVAL 指令(在程序语言设计中, eval 意味着语言具备了元编程能力), 并在之后由 BIP16 提出了更完善的交易标准 P2SH
收款方 Bob 需要先设计一个 RedeemScript 提款脚本, 再生成该脚本的 Hash, 提供给 Alice
Bob 如果想花费该笔 UTXO, 则需要提供签名和 RedeemScript, 校验成功后执行 RedeemScript 的内容, 满足条件后则成功解锁
下面的 redeemScript 结合具体场景设计后面结合智能合约的应用给出相应的例子
锁定脚本:
Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
解锁脚本:
Signature script: <sig> [sig] [sig...] <redeemScript>
在 P2SH 交易中, 由于 Bob 提供的是一段脚本的 Hash, 那么 Alice 实际上不知道这笔交易的细节, 交易的具体内容需要 Bob 来设计这就是所谓的 *"moving the responsibility for supplying the conditions to redeem a transaction from the sender of the funds to the redeemer. They allow the sender to fund an arbitrary transaction, no matter how complicated, using a 20-byte hash"* 这在设计上也不是说没有争议的, 但是在比特币的技术框架下, 是一种以最小的改动支持更多的特性的路径
比特币的智能合约
虽然一提起智能合约, 人们更多会想起来以太坊但正如前面提到的, 技术发展是一脉相承早在 1997 年 Nick Szabó 在开创性的论文 Formalizing and Securing Relationships on Public Networks 中提出了智能合约的概念比特币的脚本系统支持有限的智能合约的开发, 主要通过 P2SH 交易实现的
MultiSig 多重签名
BIP11 提出了 M-of-N 多重签名交易一个交易的解锁条件是预定指定的 N 个 pubkey 中的 M 个签名认证(M<=N)P2PKH 可以看作 1-of-1 的签名多重签名在增加安全托管交易等场景下十分有用所以比特币中专门实现了 OP_CHECKMULTISIG 的指令可以通过下面的脚本设计来实现
锁定脚本:
Pubkey script: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
解锁脚本:
Signature script: OP_0 <A sig> [B sig] [C sig...]
如果使用 P2SH 交易, 也可以设计成如下脚本
锁定脚本:
Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
解锁脚本:
Signature script: OP_0 <A sig> <C sig> <redeemScript>
其中:
Redeem script: <OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> OP_CHECKMULTISIG
Gavin Andresen 写了一个 2-of-3 的多重签名交易的使用例子, 十分详细, 我就不搬运了
Part II: 以太坊虚拟机
区块链范式
Gavin Wood 在黄皮书中将区块链系统抽象为基于交易的状态机:
公式 (1) 中 S 是系统内部的状态集合, f 是交易状态转移函数, T 是交易信息, 初始状态即 Gensis 状态;
公式 (2) 中 F 是区块层面状态转移函数, B 是区块信息;
公式 (3) 定义 B 是一系列交易的区块, 每个区块都包括多个 transaction;
公式(4)G 是区块定稿函数, 在以太坊中包括 uncle 块校验奖励矿工 POW 校验等
这个数学模型不仅是以太坊的基础, 也是目前大多数基于共识的去中心化交易系统的基础
以太坊相对比特币的提升, 本质体现在这个范式中的 f 和 S 它的核心理念具备图灵完备和不受限制的内部交易存储空间的区块链分别对应:
功能强大的函数 f, 能够执行任何计算, 比特币不支持 loop;
状态 S 记录任意类型的数据(包括代码), 而比特币的 UTXO 模型只能计算出地址的可花费额度
数据结构
在数据存储方面, 比特币通过 UTXO 模型计算地址余额, 不鼓励用户存入其他数据; 通过 P2SH 脚本机制, 理论上可以设计各种智能合约, 但受限于脚本语言的表达能力, 难以支持复杂的合约开发这种设计对于加密货币来说是合理的
以太坊为了支持记录任意的信息执行任意函数, 需要重新设计数据结构
Merkle Patricia Trie
以太坊中重度使用 Merkle Patricia Trie 组织存储数据, 下面我们会看到, 这个新的数据结构是通过对哈希树和前缀树的组合创新来达到目的
约定: 下面使用 MPT 来代替 Merkle Patricia Trie
Merkle Tree
又称 hash tree: 树的每个叶子结点是某个数据块的哈希值, 而每个非叶子结点是孩子结点的哈希值如图所示, 这棵树不存储 Data blocks 本身在 P2P 网络环境中, 恶意网络节点如果修改了这颗树上的数据, 将无法通过校验(Merkle Proof), 从而保证了数据的完整有效性这依赖于单向哈希加密的性质这种性质让它广泛应用在分布式系统的数据校验中, 比如 IPFSGit 等
中本聪也巧妙利用该性质, 设计了比特币的 SPV(简化支付验证)功能如下图, 用户不需要运行完整的结点, 只需要下载最长链的区块头数据, 然后获取待验证交易对应区块的 merkle 树做校验
Patricia Trie
又叫 Radix Trie, 是前缀树的空间优化变种: 如果树上某个节点是其父节点的唯一子结点, 则这两个结点可以合并起来它在这里的应用是对长整型数据的映射, 由某个 20bytes 的以太坊地址映射到其账户, 形如 < Address,Account>,Address 会加密编码成 16 进制的数字在 Patricia Trie 上, 表现为非叶结点连成的路径
比如, 在 Patricia Trie 上存储<"dog","Snoopy">,"dog" 会被编码为 "64 6f 67", 先找到根节点, 则查询路线为 root->6->4->6->15->6->7->value,value 也就是一个指向 "Snoopy" 的 hash 这种方式相对 hash 表的好处在于不会出现冲突; 但如果不做优化, 查询步骤太长
改良点
为了提高效率, 以太坊对树上结点数据类型进行了专门的设计包括以下四类结点
null 结点 代表空字符串
branch 结点 17 个元素的非叶节点, 形如 < i0,i1...i15,v>
leaf 结点 2 个元素的叶结点, 形如 < encodedPath,value>,encodedPath 是地址加密编码后的长整型数字串的一部分
extension 结点 2 个元素的非叶结点, 形如 < encodedPath,k>,extension 的作用是把没有分叉的路径上结点合并起来, 节省空间资源
如图, 是一个简化的状态树 (状态树后文很快会详细解释, 这里不妨碍作示意图), 右上角就是<地址, 余额> 的映射 prefix 项的作用是辅助编码, 可以忽略 4 个账户的地址, 按照 MPT 组织起来其中所有的 extension 节点只是优化作用, 都可以用多个 branch 结点替代
使用 MPT 需要有后端数据库 (以太坊中使用 levelDB) 维护每个结点间的连接关系, 这个数据库叫做状态数据库使用 MPT 的好处包括:(1)这个结构的根节点是加密的且依赖于所有的内部数据, 它的哈希可以用于安全性校验, 这是 merkle 树的性质, 但和 merkle 树不存储数据块本身不同的是, MPT 树结点存储了地址数据, 这是 Patricia 树的性质 (2) 允许任何一个之前状态 (根部哈希已知的条件下) 通过简单地改变根部哈希值而被召回
状态
上面在解释 MPT 时已经介绍了状态树的概念以太坊中的世界状态 (World State) 的概念, 通过 MPT 映射存储去中心化交易系统记录的任意状态这对应了区块链范式中的 S, 是以太坊设计的一个核心概念
如图, 一个简化的区块中有三个 root hash, 对应三棵 MPT 其中 state root 就是状态树的根哈希, 它是地址 (160bit) 到账户数据 (Account, 序列化存储在 levelDB) 中每次有效的交易都会导致状态变化, 比如图中简单示意了 Account175 的 balance 从 27 变为 45, 而所有其他的账户多没有发生交易, 那么 block175224 只需要新建 Account175 相关分支上的数据, 而其他分支不需要复制! 当然以太坊主网上新区块包含的交易大概为几十到几百不等, 那么涉及的修改也会更多关于这种结构性能上的讨论参考这篇文章查询最新的账户状态的入口应该是最新被确认的区块的状态树
对以太坊的账户模型需要专门做个介绍
Account
比特币使用 UTXO 模型计算余额, 无法满足记录任意状态的需求以太坊设计了 Account 模型, 它会存储包括:
[nonce, balance, storageRoot, codeHash]
其中 nonce 是交易计数器, balance 是余额信息, storageRoot 对应另外一个 MPT, 通过它能够在数据库中检索到合约的变量信息, codeHash 是代码 hash 值, 创建后不可更改
账户分为两种
外部账户(externally owned accounts)
外部账户由私钥控制, 对应 Account 模型里, storageRootcodeHash 并不存在, 也就是不会存储执行代码如果只有外部账户, 那么以太坊只能支持转账功能
合约账户(contract accounts)
合约账户可以通过外部账户发起交易创建, 也可以是由另一个合约账户创建合约账户在收到消息调用时, 会加载代码, 通过 EVM 执行相应的逻辑, 修改内部存储的状态
交易
在 UTXO 模型下, 交易本质上是 (通过签名的数据) 对 input 的解锁和对 output 的锁定在 Account 模型下, 交易分为两种:
创建合约, 通过代码创建新的合约
消息调用, 可以转账也可以触发合约的某个函数
两种类型的交易都包括以下字段:
[nonce,gasPrice,gasLimit,to,value,[v,r,s]]
nonce: 账户发出交易数量
gasPrice,gasLimit: 用于限制交易执行时间, 防止程序死循环
to: 交易的接受者
value: 转账额度, 如果是创建合约, 就是捐赠给合约的额度
v,r,s: 交易签名相关数据, 可以用来确定交易发送者
合约创建还需要:
init: 一段不限大小的字节数组表示的 EVM 代码, 仅在合约创建时运行一次; init 执行后返回 body 代码片段, 之后的合约调用都会运行 body 代码内容
合约账户的地址由 sender 和 nonce 共同决定, 所以任意两次成功的合约部署得到的地址都是不同的从上图能看出, 代码和状态的存储是分开的实际上编译后的字节码会存储在一个 virtual ROM 中, 且不可修改
消息调用还需要:
data: 一段不限大小的字节数组, 表示消息调用时的输入
消息调用会修改账户的状态, 可能是 EOA 账户也可能是合约账户
交易既可以由外部账户发起, 也可以由合约发起比如第 5228886 区块包含 170 个交易和 7 个内部合约交易
区块
以太坊的区块了加入更多的数据项, 相对比特币要复杂很多, 但其实本质上区别不大比如加入了叔链哈希, 优化激励措施, 这是为了支持挖矿协议; 区块本身还会有大量的有效性验证序列化这些内容不在本文主题范围, 不深入讨论
参见上面这张图的右半部分, 一窥以太坊区块如何组织数据, 能看到 MPT 树的大量使用; 左半部分涉及到 EVM, 将会是接下来的重点
EVM 设计与执行
以太坊虚拟机 (Ethereum Virtual Machine) 是执行以太坊的状态转移函数的运行环境 有个简单的问题, 以太坊是否可以不专门开发一款底层 VM, 而是复用 JavaLispLua 等呢? 理论上是完全可以的, Corda 项目就完全基于 JVM 平台开发但是更多的区块链项目会选择专门开发底层设施, 包括比特币的脚本引擎以太坊官方给出的解释:
以太坊的 VM 规格更简单, 而别的通用 VM 有很多不必要的复杂性
容许定制开发, 比如 32bytes 的字
避免其他 VM 带来的外部依赖问题, 可能导致安装困难
采用其他 VM, 需要做完全的安全性审查, 权衡下不一定能省多少事
内存模型
EVM 也是基于栈式计算机模型, 但除了 stack 外还涉及 memory 和 storage:
stack 栈上元素大小为 32bytes, 这和一般的 4bytes,8bytes 不同, 主要是针对以太坊运算对象多为 20bytes 的地址和 32bytes 的密码学变量; 栈的大小不超过 1024; 栈的调用深度不超过 1024, 主要防止出现内存溢出
memory 虽然运算都在栈上进行, 但临时变量可以存在 memory 里, memory 大小不做限制
storage 状态变量都放在 storage 里, 不像 stack 和 memory 上的量随着 EVM 实例销毁消失, storage 里面的数据修改后都会持久化
如图, 是一个 EVM 架构的示意图, 这种设计对以太坊应用开发有着深远的影响, 包括设计模式和安全考量 一个经典的问题是合约的升级:
合约部署后编译成字节码存储在 virtual rom 中, 代码是不可修改的, 这对很多 DAPP 来说是严重的制约一种思路是, 将代码分布在不同的合约中, 合约间调用通过存储在 storage 中的地址来进行, 这样实现了实际上的合约升级操作
执行模型
EVM 准确来说是一个准图灵机, 文法上它能够执行任意操作, 但为了防止网络滥用以及避免由于图灵完整性带来的安全问题, 以太坊中所有操作都进行了经济学上的限制, 也就是 gas 机制, 有三种情况:
一般操作消耗费用, 比如 SLOAD,SSTORE 等
子消息调用或者合约创建而消耗燃料, 这是执行 CREATECALLCALLCODE 费用中的一部分
内存使用消耗费用, 与所需要的 32bytes 的字数量成正比
下图展示了 EVM 执行的内部流程, 从 EVM code 中取指令, 所有的操作在 Stack 上进行, Memory 作为临时的变量存储, storage 是账户状态执行受到 gas avail 限制
现在结合 EVM 我们再来看看之前介绍的交易的执行细节正如区块链范式定义的, T 是以太坊状态转移函数, 也是以太坊最复杂的部分所有的交易在执行前, 都需要先经过内部的有效性验证:
交易是 RLP 格式数据, 没有多余的后缀字节;
交易的签名是有效的;
交易的随机数是有效的;
燃料上限不小于实际交易过程中用的燃料;
发送者账户的余额至少大于费用 v0, 需要提前支付;
下图是消息调用的过程, 每个交易可能会形成很深的调用栈, 交易内部由不同的合约之间的调用调用通过 CALL 指令, 参数和返回值通过 memory 传递
错误处理
EVM 在合约执行时会发生若干种错误:
燃料不足
无效指令
缺少栈数据
指令 JUMP JUMPI 的目标地址无效
新栈大小大于 1024
栈调用深度超过 1024
EVM 的错误处理有个简单的原则, 叫做 revert-state-and-consume-all-gas, 即状态恢复到交易执行前的 checkpoint, 但消耗的 gas 不会再退还虚拟机把错误全看作是代码出错, 不作特定的错误处理
EVM 分析工具
关于 EVM 分析的工具可以参考 Ethereum Virtual Machine (EVM) Awesome List
类 EVM 的图灵完备虚拟机(WIP)
完整的 EVM 规格是很复杂的, 但具备一定的汇编基础和简化模型的能力, 实现一个类 EVM 的虚拟机是可以尝试的挑战等有空我再把自己的实现放上来吧有兴趣的同学可以自己动手试试
参考
- A Next-Generation Smart Contract and Decentralized Application Platform
- ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
- Design Rationale
- Stack Exchange: Ethereum block architecture
- Go Ethereum
- evm-illustrated
- Diving Into The Ethereum VM
来源: https://juejin.im/post/5aa60e65518825557005a7f6