go-ethereum 源码笔记(基础知识)

本篇将梳理以太坊的基本概念,说明一些值得注意的地方,这里不会讲解比特币的原理,代码,但会介绍以太坊与比特币的差异,所以最好看过比特币的论文,对比特币的基本原理、实现有所了解。这一篇将以太坊的白皮书作为重要参考,可以看做是以太坊白皮书的概述。以太坊的白皮书是一个非常好的学习资料,它在介绍以太坊前分析了比特币存在的问题,因此我们可以通过这份白皮书了解整个加密货币的生态。

以太坊

设计理念

  • 简单性
  • 普遍性
  • 模块化
  • 敏捷性
  • 不歧视,非审查

架构

见上一篇 go-ethereum 源码笔记(概览)

特性

智能合约

智能合约是以太坊与比特币最大的不同。智能合约是不可篡改的合同,它运行在由以太坊组成的分布式计算机上,由一段支持图灵完备的程序实现。

初学智能合约的最佳资料:cryptozombies

智能合约与一般的程序有些不同:

  • Address 类型可以定位用户,定位合约的代码
  • 语言内嵌框架支持支付,所以提供了一些如 payable 的关键字,在语言层面支持支付
  • 使用区块链作为存储
  • 运行环境是去中心化的网络,比较强调合约或函数执行的调用方式。
  • 一旦出现异常,所有的执行都会回撤,合约的执行具有原子性

有四种专用语言可以写智能合约,Solidity,Serpent,Mutan,LLL。

编程语言

Solidity

受 Javascript 启发。

是以太坊的首选语言,语法接近 Javascript,它是一种面向对象的语言,易于被掌握和使用,由于语法与 Javascript 相近,并且学习门栏相对较低,目前是使用人数最多的智能合约开发语言。编译器用 C++ 实现。

项目主页是:ethereum/solidity

Serpent

受 Python 启发。兼顾底层语言效率和良好编程风格的同时尽可能追求简洁,加入了一些针对合约编程的特性。编译器用 C++ 实现。

项目主页是:ethereum/serpent

Mutan

受 Golang 启发。Golang 实现,2015年就被废弃了。

项目主页是:obscuren/mutan

LLL(Lisp Like Language)

受 Lisp 启发。

特征 (来自知识星球 区块链学习小组 的讨论)

  • 自治 一旦启动,不受干预
  • 自足 程序自主控制其计算涉及的资源,有权限调配参与者的资金,财产
  • 去中心化 不依赖某个单独的服务器,由分布式网络的节点共同支持运行

DApp 的优势

  • DApp 大多为开源项目,公开透明
  • 去中心化
  • 具有激励机制
  • 具有共识协议

应用

  • 信用卡的定期扣款(按月订阅的自动扣款)
  • 飞机延误险(各类保险)
  • ICO

EVM 高级语言

以太坊实现了一个叫做 Ethereum Virtual Machine 的运行时环境,类似于 JVM,它的主要工作是执行智能合约的字节码。

账户系统

以太坊中有两类账户,外部账户和合约账户,两类账户对于 EVM 来说没有区别,每个账户都有一个与之关联的账户状态和一个20字节的地址,都可以存储以太币。

外部账户:由私钥控制,没有代码与之关联,地址由公钥决定。私钥可用于对交易签名从而主动向其他账户发起交易(transaction)进行消息传递。

合约账户:由合约代码控制,有代码与之关联,其地址由合约创建者的地址和该地址发出过的交易数量 nonce 共同决定。不能主动向其他账户发起交易,但可以『响应』其他账户进行消息调用(message call)。

外部账户之间的消息传递是价值转移的过程,外部账户到合约账户的交易或合约账户到合约账户的消息会激发合约账户代码的执行,允许它执行如转移代币,写入内部存储,执行运算,创建合约等各种操作。

不论账户类型,账户状态都包含以下四个字段:

  • nonce:由账户发出的交易数及创建的合约数量决定。
  • Balance:余额,账户拥有以太币数量,单位为 Wei,1Ether=10^18Wei。
  • storageRoot:存储根节点,账户内容的 Merkle Patricia 树根节点的哈希编码。
  • codeHash:代码哈希,与账户关联的 EVM 代码的哈希值,外部账户的 codeHash 为一个空字符串的哈希,创建后不可更改。状态数据库中包含所有代码片段哈希, 以便后续使用。

Gas 的设计

以太坊是一个能够运行智能合约的去中心化平台,它提供了一个以太坊虚拟机,简称 EVM,开发者可以在上面开发各种应用,而且它是图灵完备的,这意味着我们写的智能合约是可以运行死循环的。要知道,『不存在这样一个程序,它能够检测任何程序在给定输入上是否会结束』,这称为图灵停机问题。以太坊用一个很精彩的设计来解决这个问题,这就是 Gas。Gas 的设计基于这样一个想法:执行程序应该是消耗资源的,每一步操作,ADD 也好,DIV 也好,都应该消耗不同程度的资源,资源提前消耗完了,就强行终止程序。总的来说,Gas 是以太坊中对所有活动进行消耗资源计量的单位,包括但不限于:转账,合约创建,合约指令执行,扩展内存。Gas 是一个浮动的量,每一笔交易可以自行指定 Gas 价格(以以太币计算),价格越高,矿工将你的交易打包进区块的优先级就越高。最终,Gas 的消耗等于消耗的 Gas 数量乘以 Gas 价格,这笔钱将奖励给矿工。交易完成后,剩余的 Gas 以购买时的价格退回到交易发送者账户,若交易过程中发生 Gas 不足异常(out-of-gas, OOG),交易会被当做无效交易,已消耗 Gas 不会退回,仍作为矿工贡献计算资源的奖励。

更多细节请查阅:wiki/Design-Rationale#gas-and-fees

世界状态

世界状态是地址(160位标示符)和账户状态(序列化为 RLP 的数据结构)间的映射,区块链不直接存储世界状态,而是在区块头中存储相关 Merkle Patricia 树根节点的哈希值。

日志

日志是 EVM 提供的一项功能。开发者可以在合约代码里记录各种事件产生的日志,这些日志可以帮助开发者调试代码,或者作为在区块链上发生交易的证据。

各版本特性

  • Frontier(边境)2015年7月发布,以太坊的第一个版本,只有命令行界面,主要使用者是开发者
  • Homestead(家园)2016年3月发布,第一个生产环境版本,加快了交易速度,增加图形界面,让普通用户也可以使用以太坊的功能
  • Metropolis(大都会)2017年10月发布,这个阶段分两个版本,先是拜占庭,2017年10月发布,然后是君士坦丁堡,预计2018年发布,增加浏览器,应用商店的特性,使用更方便,可以安装插件实现更多功能。更轻量、更快速、更安全。
  • Serenity(宁静)时间待定,使用 PoS,使用 Casper 共识算法。

以太坊和比特币的差异

理念不同

比特币想要实现的是电子现金系统,而以太坊想要实现的是图灵完备的智能合约平台。

智能合约 VS Script

比特币协议本身也是可以实现智能协议的。在比特币中,有一个交易脚本语言,它是一种基于栈的执行语言,包含基本算数计算、基本逻辑(if 等)、报错及返回结果和一些加密指令,但不支持循环。

根据比特币协议的实现,在花费 UTXO 前,必须满足脚本的要求,即满足解锁 UTXO脚本,用私钥匹配解锁脚本(Signature script),以保证交易只能花费自己的比特币,即交易的输入,交易的输入指向的是锁定脚本(PubKey script),它确保签名能匹配输出地址。显然我们可以应用更复杂的脚本实现智能合约,没有循环也可以用重复的代码实现,显然这样的方法太糟糕了。

比特币的脚本语言存在的几处限制在 Vitalik 最初发布的白皮书里也有描述:

  • 缺乏图灵完整性,不支持循环语句
  • 价值盲区,无法对 UTXO 进行精细化控制
  • 不能保存状态,UTXO 只有用完和没用两种状态,没法实现多阶段期权合约
  • 区块链盲区,UTXO 中没有区块链数据(随机数,时间戳,前一个区块哈希),不能挖掘随机性的潜在价值

更具体的描述可以查看这个地址

我们知道区块链的本质是一个分布式的公共数据库,它最早用来包括数字交易记录,这也是它到目前为止最为广泛的应用,区块链的特别之处在于,它不需要任何中央权威机构来维护,它是一个不需要第三方的 p2p 的框架。以太坊的智能合约让它与比特币又有了本质的不同,把区块链看做分布式数据库,那么以太坊就是一个能够能够在数据库上运行分布式应用的超级计算机,是智能合约让以太坊和比特币有了本质的不同,它是代码和数据(状态)的集合。

比特币的脚本有诸多限制,能够编写的程序有限,而以太坊的智能合约是图灵完备的,它非常适合于对信任、安全和持久性要求较高的应用场景,如:数字货币、数字资产、投票,保险、金融应用、预测市场,产权所有权管理、物联网等等。目前来说,除了数字货币之外,真正落地的应用还不多,因为区块链还存在诸多问题,比如亟需解决的交易性能问题,因此隔离见证,闪电网络,侧链等技术飞速发展,各种公链也针对某些问题提出自己的方案,这些既是挑战,也是机遇。

Accounts VS UTXO

比特币用 UTXO 方法计算某账户的余额,UTXO 即 Unspent Transaction Outputs。这个概念稍稍有点复杂,这里只做简单介绍。比特币整个系统的状态由未花费交易输出组成。每个 UTXO 都有拥有者和自身的价值属性,一笔交易会消费若干个 UTXO,同时也会生成若干个新的 UTXO,UTXO 有下面几点约束:

  1. 每个被引用的输入必须有效,且未被使用过
  2. 交易的签名必须与每笔输入的所有者签名匹配
  3. 输入的总值必须等于或大于输出的总值

transaction of bitcoin, https://bitcoin.org/img/dev/en-transaction-propagation.svg

个人认为理解 UTXO 的最佳方式是读这个仓库的源代码,这个作者写了7篇文章,实现了一个比特币的原型,Bitcoin Core 的代码是 C++ 写的,且代码复杂,年代久远,这个仓库使用 Golang,简单易懂,UTXO 的实现在 Transactions 1。感兴趣的朋友可以进一步阅读。

UTXO 的好处是(来自以太坊的维基):

  1. 匿名性更好,一个用户接收到一笔转账,这些转账的输入可以有多个私钥形成,但其实这些输入可以是同一个人的,这样能保证一定程度的匿名性。
  2. UTXO 是无状态的,更具扩展性。

以太坊没有采用 UTXO 的方式进行记账,而是采用了传统金融的记账方式–使用账户,每笔交易只有一个输入,一个输出,一个签名。使用单独的账户系统的好处是(来自以太坊维基):

  1. 节省大量存储空间。每笔交易只有一个输入和一个输出。
  2. 可替换性。可操控性可能更好一些,使用账户模型可以更轻松地实现黑名单这样的模式。
  3. 编码上更简单。获取账户余额时,只需要一个查询,而比特币需要整合指定地址所拥有的所有 UTXO 的总值。
  4. 可以更轻松地实现轻客户端。

Vitalik 在一篇博客中还谈到 UTXO 可能引发拒绝服务漏洞。而且基于 UTXO 的模型与有状态的智能合约不太契合。以太坊最终选择使用账户模型。

以太坊使用状态(state)的概念来存储一系列账户,每个账户有自己的余额以及特定数据(代码或内部存储),如果交易发起方有足够余额支付交易费用,则交易有效,发起方账户扣除相应金额,接收账户增加余额。账户还用于智能合约的创建和执行,可以通过转账来触发接收账户对应的代码的执行,该账户的内部存储可能会发生变化,同时也可以创建额外信息发给其他账户,触发新的交易。从这一点可以看到,dapp 需要跟用户状态进行复杂的交互,通过 UTXO 实现会比较难满足需求。

区块链设计的不同

比特币

比特币的区块包括区块头和区块体两部分,区块头封装了前一个区块的哈希值、时间戳,随机数,默克尔树根值和当前区块的哈希值,区块体中包括交易计数和交易详情。区块结构如下图(摘自 Bitcoin 白皮书):

bitcoin_block

以太坊

以太坊在比特币区块链基础上做了一些调整,区块主要由区块头、交易列表和叔区块头三部分组成。区块头包括父区块的哈希值、叔区块的哈希值、状态树根哈希值、交易树根哈希值、收据树根哈希值、时间戳、随机数,比较特别的是采用了改进的默克尔树,压缩前缀树的结构 MPT,这部分在 go-ethereum 源码笔记(trie 模块-MPT 的实现) 会进一步探讨。区块结构如下图(摘自 Ethereum 博客):

ethereum_block

PoW 机制的不同

PoW,Proof of Work 的缩写,即工作量证明,又称挖矿,目前比特币,以太坊都基于 PoW 算法实现共识机制,即根据挖矿的贡献决定货币的分配。

比特币

比特币的 PoW 的过程,需要不断调整 Nonce 值,对区块头做双重 SHA256 哈希运算,使得结果满足给定数量前导0的哈希值的过程。其中前导0的个数,取决于挖矿难度,前导 0 的个数越多,挖矿难度越大。

以太坊

以太坊的 PoW 算法可以表示为如下公式:$RAND(h, n) <= M/d$
其中 $RAND()$ 表示一个概念函数,代表一系列的复杂运算,h 和 n 为输入,即区块 Header 的哈希、以及 Header 中的 Nonce。M 表示一个极大的数,此处使用 $2^{256}-1$。d 为区块难度,即Header 中的 Difficulty。在 h 和 n 确定的情况下,d 越大,挖矿难度越大。需不断变更 Nonce,使$RAND(h, n)$ 满足 $RAND(h, n) <= M / d$ 完成 PoW。

需要注意的是以太坊目前的 PoW 只是临时的,未来将会是 PoS 的形式,到时候不会再需要耗费大量电力进行挖矿。

挖矿难度更新

比特币

比特币每创建2016个块后将计算新的难度,此后的2016个块使用新的难度。计算步骤如下:

  1. 找到前2016个块的第一个块,计算生成这2016个块花费的时间。
    即最后一个块的时间与第一个块的时间差。时间差不小于3.5天,不大于56天。
  2. 计算前2016个块的难度总和,即单个块的难度x总时间。
  3. 计算新的难度,即2016个块的难度总和/14天的秒数,得到每秒的难度值。
  4. 要求新的难度,难度不低于参数定义的最小难度。

以太坊

以太坊每次挖矿都需计算当前区块难度。按版本不同有三种计算难度的规则,分别为:calcDifficultyByzantium(Byzantium 版本)、calcDifficultyHomestead(Homestead 版本)、calcDifficultyFrontier(Frontier 版本)。以 calcDifficultyHomestead 为例。

1
2
3
本区块难度 = 父区块难度 + 难度调整 + 难度炸弹
难度调整 = 父区块难度 // 2048 * MAX(1 - (block_timestamp - parent_timestamp) // 10, -99)
难度炸弹 = INT(2**((block_number // 100000) - 2))

以太坊的区块难度以单个区块为单位进行调整,所以可以比较快地适应算力变化,难度炸弹也是一个很有意思的设计,难度炸弹是指数级增长的,到某个阶段矿工会因为无利可图自动退场,这时 PoS 也应该成熟了,这对于矿工来说是一个预防针。

奖励机制

比特币

在比特币的设计中,最初每挖出一个区块会奖励50个 BTC,每挖出21万个,奖励就会减半,第1-210000个区块,每块奖励50btc,第210001-420000个区块,每块奖励25btc……以此类推。

因此 BTC 的总量为:210000×50(1+0.5+0.25+0.125+……)=2100万

以太坊

以太坊提出了一个叔块的概念。叔块是指没能成为主链的,但在后面的区块放入了 uncles 字段中的区块。

相比于比特币,以太坊对叔块也有奖励,为什么这么做呢,我们知道以太坊的出块时间是15秒左右,相比于比特币,以太坊更容易出现临时分叉和孤块,因为出块时间比较短,区块在整个网络中也比较难传播,对于网速比较慢的矿工就不占优势了,因此挖矿的时候,对于叔块也是有奖励的。

我们知道以太坊是一个运行智能合约的去中心化的平台,它有一个以太坊虚拟机(Ethereum Virtual Machine,常用缩写 EVM)的概念,EVM 是就像一个超级计算机,它是图灵完备的,写程序的时候可能会出现死循环,而智能合约应该避免这种情况,这引出一个停机问题,简单来说就是不存在能够检测程序进入了死循环的方法,以太坊提出了一种设计解决这个问题,EVM 规定了每条指令都会消耗一定的 Gas,指令越复杂,消耗的 Gas 越多,程序运行前是有一个消耗 Gas 的上限的,运行过程中 Gas 消耗完了,无论程序有没有执行完都会被强行终止。智能合约运行时花费的 Gas 最终会奖励给矿工。

综上,以太坊的挖矿奖励包括两部分:

普通区块奖励
  • 固定奖励5ETH
  • 花费的 Gas
  • 如果区块中包括叔块,每包含一个可以得到5ETH的1/32
叔块奖励

叔块奖励 = ( 叔块高度 + 8 - 包含叔块的区块的高度 ) * 普通区块奖励 / 8

其他一些细节上的差异

  • 出块时间,比特币的出块时间是平均10分钟,以太坊的平均出块时间是15秒。以太坊相对于比特币有更大的系统吞吐量和更小的交易确认间隔,尽管从长远来看,这远远不够。
  • 区块奖励,比特币诞生于2009年1月,刚开始时区块奖励是50 BTC,每四年减半一次,2012年11月到2016年7月是25 BTC,目前是12.5 BTC,到2020年的2月将变成6.25 BTC;以太坊的挖矿奖励5个以太币,大都会版本后改成3个以太币。
  • 以太币最多可以显示小数点后18位,比特币最多是小数点后8位。

References