以太坊运行原理笔记:
(参考以太坊黄皮书)
那么既然是区块链,必然是从创世区块开始(Genesis),创世区块有一个最初的原始状态,通过交易,状态逐步改变至最终的状态,也就是理解为基于交易的状态机。(黄皮书中定义)。交易是状态改变的桥梁。
挖矿是为争夺记账权,且获得奖励。那么挖矿作为状态转换函数来实现。
以太坊挖矿者在区块链状态中获取随机数据,计算一些从区块链最后的N个区块中随机选择的交易,然后返回结果散列。
先来看看以太坊的挖矿流程:
缓存和数据集DAG每增加30000个区块更新一次,故矿工的精力应该是在读取数据集上而不是去改变。
这种方式的优点,以太坊中的智能合约会有任何种类的计算,这样ASIC就是一个提供计算的的专门的集成电路。就类似一个全球计算机的CPU。然后挖矿中要求保存全节点完整的区块链。
以太坊在计划过度到POS机制,那么以太坊的POS机制先用V神的一段话来说明:POS协议,每个人都是“矿工”,因此,除非他们选择通过放弃以太币来违反规则,否则每个人都必须承担确认和验证交易的责任,从本质上来说,这才是去中心化的管理模式,能够提高利益相关者在网络中的参与度。
以太币:那么以太币的来源包括以下几部分:
比特币的总量是2100万个,每年以太币发行1800万。之前笔者文章中有提到过,这个数字货币因为密钥的丢失,所以每年的发行和意外的丢失会达到一个动态的平衡。并不是你看官方数据有多少就真正有多少在流通,这个应该能理解。
从矿工的角度来看待以太币,那么就分为三块:
以太币的单位:
基本单位为wei,下表具体是各个比例:
单位 | 维度 | 个数 (wei) |
---|---|---|
wei | 1 wei | 1000 |
Kwei | 1e3 wei | 1000000 |
Mwei | 1e6 wei | 1000000000 |
Gwei | 1e9 wei | 1000000000000 |
microether | 1e12 wei | 10000000000000000 |
milliether | 1e15 wei | 10000000000000000000 |
ether | 1e18 wei | 10000000000000000000000 |
状态
世界状态:世界状态(state)是在地址(160位的标识符)和账户状态(序列化为RLP的数据结构)的映射。在实施过程中会映射维护在一个修改的Merkle Patricia树上。后端有一个后端数据库去维护映射,称为状态数据库。
账户状态包括:
交易消息
交易
以太坊的交易最直观解释:从外部账户发送到区块链上的另一个账户的消息和签名的数据包。
包含如下内容:
以太坊上的交易都是需要支付费用,和比特币以比特币来支付一定的交易费用不同,以太坊上固定了这个环节,那么这个间接理解是以太坊的一种安全防范错误,防止了大量的无意义的交易,保证一定的安全性,特别是智能合约的创建、执行、调用都需要消耗费用,那么也保证了整个系统的稳定性,防止了一些链上无意义的恶意行为。
交易手续费
以太坊的核心是EVM,以太坊虚拟机,那么在EVM中执行的字节码都是要支付费用。也就是经常看到的Gas、Gas limit、Gas Price这几个概念。
操作 | Gas消耗 | 具体内容 |
---|---|---|
step | 1 | 执行周期的默认费用。 |
stop | 0 | 终止操作是免费的。 |
suicide | 0 | 智能合约账户的内部数据存储空间,当合约账户调用suicide()方法时,该值将被置为null。 |
sha3 | 20 | 加解密 |
sload | 20 | 在固定的存储器中去获取 |
sstore | 100 | 输入到固定的存储器中 |
balance | 20 | 账户余额 |
create | 100 | 创建合约 |
call | 20 | 初始化一个只读调用 |
memory | 1 | 扩充内存额外支付的费用 |
txdata | 5 | 交易过程中数据或者编码的每一个字节的消耗 |
transaction | 500 | 交易费用 |
contract creation | 53000 | homestead中目前从21000调整到53000 |
所以有些公司或者个人觉得区块链技术去中介化,不需要中心服务器,这种开发模式是比较便宜的,但是事实上区块链的开发不比之前的那些传统软件开发来的便宜。
具体交易
以太坊上交易可以是简单的以太币的转移,同时也可以是智能合约的代码消息。列个表格看下交易的具体内容:
代码 | 内容 |
---|---|
from | 交易发起者的地址、不能为空,源头都没有不合理。 |
to | 交易接收者的地址(这个可以为空,空的时候就表示是一个合约的创建) |
value | 转移的以太币数量 |
data | 数据字段。这个字段存在的时候表示的是,交易是一个创建或者是一个调用智能合约的交易 |
Gas Limit | 字面理解就是Gas的限制,限制是必要的,没有限制就没有约束。这个Gas Limit是有两个意思的。首先针对单个交易,那么这个表示交易的发起者他愿意支付最多是多少Gas,这个交易发起者在发起交易的时候需要设置好。还有一个是针对区块的Gas Limit,一个单独的区块也有Gas的限制。 |
Gas Price | 一个Gas Price就是单价,那么你的交易费用=Gas*Gas Price,然后以以太币来ether来支出。以太坊默认的Gas Price是1wei。 |
nonce | 用于区别用户发出交易的标识。 |
hash | 交易ID,是由上述的信息生成的一个hash值 |
r、s、v | 交易签名的三部分,交易发起者的私钥对hash签名生成。 |
交易分三种类型
转账:简单明了的以太坊上的以太币的转移,就和比特币类似,A向B转移一定数量的以太币。这种交易包含:交易发起者、接收者、value的数量,其余类似Gas Limit、hash、nonce都会默认生成。
智能合约创建:创建智能合约就是把智能合约部署到区块链上,那么这个时候to是一个空的字段。data字段则是初始化合约的代码。
消息
合约之间可以发生“消息”,消息是虚拟对象,不会序列化,存在于以太坊的执行环境中,消息一般包括如下:
消息类似交易,但是交易是由一个合约产生,在以太坊环境中,但执行某些代码的合约的时候,在call函数时候,会产生并执行一个消息。消息触发接收方账户运行代码,理解为合约之间的互相作用。
以太坊区块组成:
区块:相关信息片段(区块头)+区块内交易+其他的区块头(一些上一级区块和上上级区块,称为ommers)。
先说下ommers:当前区块的上一级区块(父区块)和当前区块上一级区块(父区块)的上上级区块(爷区块)是相同的。(有点拗口)
以太坊的出块时间大概在15-17秒,那么和比特币对比起来肯定是快了很多,这样的话整个交易处理会更快。在同一时间点,这个时候会出现同时出现两个竞争区块的情况也就会多一些,那么最终只有一个会被链接下去,其他的就称为孤区块。当然如果是完全抛弃了,那么也就没有意义了。ommers奖励矿工纳入这些区块,这些区块有效就也能获得一定的奖励。有效的判定为:ommers必须在上一级(父区块)的第6个子区块之内或更小范围内。在第6个子区块之后,之前的孤区块将不会再被引用。
奖励如下:
叔区块顾名思义是区块的父区块的兄弟区块。那么区块链只有一条主链,故叔区块不在主链上,导致叔区块的原因,由于是网络的延迟没有同步,那么一个叔区块如果引用在有效的主链上,挖出叔区块的矿工获得4.375个以太币(区块奖励的7/8)。上文说到叔区块的引用获得奖励,那么这个奖励对挖到叔区块的矿工也是有一个间隔层数的关系。具体如下:
间隔的层数 | 获取的比例 | 以太币 |
---|---|---|
1 | 7/8 | 4.375 |
2 | 6/8 | 3.75 |
3 | 5/8 | 3.125 |
4 | 4/8 | 2.5 |
5 | 3/8 | 1.875 |
6 | 2/8 | 1.25 |
回过头看以太坊区块头组成:
parentHash:父区块头的Hash值,Keccak 256位hash值
ommersHash:当前区块ommers列表的Hash值,Keccak 256位hash值
beneficiary:挖到该区块的矿工的地址,用于接收所有交易费用,160位的接收地址
stateRoot:所有交易执行完且区块定稿后的状态树根节点的Keccak 256位hash值(三棵树之一)
transactionsRoot:当前区块所有交易的树的根节点的Keccak 256位hash值(三棵树之一)
receiptsRoot:当前区块所列的所有交易收据的树的根节点的Keccak 256位hash值(三棵树之一)
logsBloom:当前区块中所有交易收据数据中可索引信息组成的Bloom过滤器
difficulty:当前区块难度水平的纯量值,通过前一个区块的难度水平和时间戳来计算。
number:当前区块的计数,创世纪块的区块序号为0,后续区块序号都增加1
gasLimit:区块的gas上限
gasUsed: 当前区块中交易所用的总gas量
timestamp:当前区块初始化时的unix的时间戳
extraData:当前区块相关的任意字节数据,32字节以内
mixHash:256位Hash值,当与nonce组合时,证明此区块已经执行了足够的计算
nonce:64位Hash值,当与mixHash组合时,证明此区块已经执行了足够的计算
上文中提到的三棵树,简单说明:
Merkle Patricia Tree(简称MPT树,实际上是一种trie前缀树)是以太坊中的一种加密认证的数据结构,可以用来存储所有的(key,value)对。以太坊区块的头部包括一个区块头,一个交易的列表和一个uncle区块的列表。在区块头部包括了交易的hash树根,用来校验交易的列表。在p2p网络上传输的交易是一个简单的列表,它们被组装成一个叫做trie树的特殊数据结构,来计算根hash。值得注意的是,除了校验区块外,这个数据结构并不是必须的,一旦区块被验证正确,那么它在技术上是可以忽略的。但是,这意味着交易列表在本地以trie树的形式存储,发送给客户端的时候序列化成列表。客户端接收到交易列表后重新构建交易列表trie树来验证根hash。RLP(Recursive length prefix encoding,递归长度前缀编码),用来对trie树种所有的条目进行编码.参考:http://www.cnblogs.com/fengzhiwu/p/5565559.html
针对merkle树和Trie树的特点,以太坊对其改进。
目标一:保证树的加密安全,那么每个节点的散列值引用,在levelDB中查询。在非叶节点,数据的表现形式:key代码RLP编码的SHA3散列值,value是节点RLP编码。在获取一个节点的内容,只要根据节点的散列值去访问数据库然后获得RLP编码,解码即可。
目标二:引入多节点来提高效率。Merkle Patricia Tree节点分为下面几种,
1.空节点,就理解为一个空串。
2.叶节点,键值对应为列表,key为16进制编码,value是RLP编码。
3.扩展节点,键值对应列表,value是其他节点散列值,通过这个散列值去链接到其他的节点。
4.分支节点,长度为17的列表,key还是编码为16进制编码,加上value,前16个元素对应key的16个十六进制字符,如果一个键值在这个分支终止了,那么最后的一个元素表示为一个值。分支节点既是搜索的终止也可是路径的中间点。
MPT树中是16进制的hex-prefix,HP编码,对key编码。所以每个节点可能有16个孩子,引入一种特殊的终止标识符来标识key所对应的值是真实的值,还是其他节点的hash值。
一旦终止标识打开,那么key是叶节点,对应真实的value。
终止标识关闭,那么值就是在数据块中查询对应节点的hash。
不论key是奇偶长度,HP对其编码后,一个单独的hex字符或者4bit二进制数字,称为一个nibble。
一个nibble加上key前面,对终止奇偶符编码,最低位标奇偶性,第二低位编码终止符状态,一旦key是偶数,那么加上另外一个nibble,值0来保证整体的偶性。
交易收据:
在交易执行过程中,把一些特殊的信息编码为交易收据,并把收据信息保存在一个以索引为键的树种,树的根节点保存在区块头中。
那么交易收据组成:
处理:
整体有效性:
区块需要同时满足要求才能确认是有效的。
难度算法:
区块的难度是被用来在验证区块时加强一致性。创世纪区块的难度是131,072,有一个特殊的公式用来计算之后的每个块的难度。如果某个区块比前一个区块验证的更快,以太坊协议就会增加区块的难度。(算法详见黄皮书-中文版第5页)
区块难度影响的是nonce,通过POW算法来计算一个hash值,关系为:n<或等于2的256次/Hd(Hd为难度)
找到符合难度阈值的nonce唯一方法就是使用POW算法来列举所有的可能性。找到解决方案预期时间与难度成正比—难度越高,找到nonce就越困难,因此验证一个区块也就越难,这又相应地增加了验证新块所需的时间。所以,通过调整区块难度,协议可以调整验证区块所需的时间。
如果验证时间变的越来越慢,协议就会降低难度。这样的话,验证时间自我调节以保持恒定的速率—平均每15s-17s一个块。
gas
执行交易
交易执行前要有一个基本的验证
那么在交易过程中有一个子状态,是交易执行过程中累积的特定的信息,元组表示。这个元组内包括:
交易的gas limit 一定要等于或者大于交易使用的intrinsic gas,intrinsic gas包括:
执行交易预订费用
随交易发送的数据的gas费用
交易是合约创建交易,还需要额外的gas
发送账户余额必须有足够的Ether来支付”前期”gas费用。前期gas费用的计算比较简单:首先,交易的gas limit乘以交易的gas价格得到最大的gas费用。然后,这个最大gas费用被加到从发送方传送给接收方的总值。
开始执行交易。在交易执行的整个过程中,以太坊保持跟踪“子状态”。子状态是记录在交易中生成的信息的一种方式,当交易完成时会立即需要这些信息。
交易所需的各种计算开始被处理。
当交易所需的步骤全部处理完成,并假设没有无效状态,通过确定退还给发送者的未使用的gas量,最终的状态也被确定。除了未使用的gas,发送者还会得到上面所说的“退款余额”中退还的一些津贴。
一旦发送者得到退款之后:
gas的Ether就会矿工
交易使用的gas会被添加到区块的gas计数中
所有在自毁集中的账户都会被删除
最终形成新的状态以及交易创建的一系列日志。
简单理解:
首先交易发起者A发起一笔转账交易,那么发送的格式如下:
代码 | 具体内容 |
---|---|
from | 交易发起者的地址 |
to | 交易接收者的地址 |
value | 转移的以太币数量 |
Gas | Gas的量 |
Gas Price | Gas的单价 |
data | 发送给接收者的消息 |
nonce | 交易编号 |
节点验证
以太坊网络中会有节点收到A发送出来的消息,那么会去检查这个消息格式时候有效,然后计算Gas Limit。这个时候回去验证A的以太坊余额,如果余额不足,那么就返回错误,不予处理。一旦A发送的消息通过了节点的验证,那么节点就会把这个交易放到交易存储池中。并广播到区块链网络。
矿工验证
那么写入区块链必须要矿工打包,矿工在接收到A发出的交易,会和其他交易一块打包,普通转账交易打包即可,那么合约调用的交易则需要在矿工本地的EVM上去执行调用的合约代码,代码执行过程中检查Gas的消耗。一旦Gas消耗完了,那么就回滚,如果Gas足够那么返回多余的Gas。并广播到区块链网络。
其余节点
重复节点验证步骤,然后合约也会在本地EVM上执行验证。通过验证后同步区块链。
合约创建
之前的文章说到过外部账户和合约账户,那么合约的创建,理解为以创建合约账户为交易。
那么首先是来初始化一个合约账户。
合约账户中有相关下列参数:发送者、原始交易人、可用的gas、gas单价、初始费用,任意长度的字节数组、EVM的初始化代码、合约常见当前的栈深度、状态更改许可。
新的合约账户地址是一个hash值的最右侧的160位,通过发送者的地址和账户的nonce(nonce使用时候减去1,认为在这个调用之前对发送者的nonce加1,在一个可靠交易执行或者虚拟机操作开始会对这个发送者的nonce加1)进行RLP编码后,再经过Keccak HASH计算来获得。
通过状态转换,账户初始nonce设置为1,balance交易传出的值,storage为空,codehash为空的Keccak 256位的hash值,减去发送者相应转账值,状态变化。通过evm代码来执行。
代码的执行过程中会有gas的消耗,必须要有足够的gas,那么gas不足,出现out-of-gas异常,gas变为0,合约创建是当做交易来处理,那么不影响合约创建的固定费用的支付,但是gas不足后附带的金额不会转移到取消的的合约地址,剩余的gas返还给最原始的交易发起人,状态也改变。
消息调用
消息调用中有相关下列参数:发送者、交易发起人、接收者、执行代码的账户、可用的gas、转账金额、gas单价、函数调用输入的数据、消息调用合约创建当前栈深度、状态更改许可。
消息调用中有一个额外的元素-由字节数组形式表示的输出数据,在执行交易过程中输出数据是被忽略了,那么在消息调研中vm代码执行产生,在这种情况下使用这些信息。
就像合约创建一样,如果消息通信执行退出是因为gas不足或交易无效(例如栈溢出,无效跳转目的地或无效指令),那么已使用的gas是不会被退回给原始触发者的。相反,所有剩余的未使用gas也会被消耗掉,并且状态会被立刻重置为余额转移之前的那个点。没有任何方法停止或恢复交易的执行而不让系统消耗你提供的所有gas,直到最新的以太坊更新。例如,假设你编写了一个合约,当调用者没有授权来执行这些交易的时候抛出一个错误。在以太坊的前一个版本中,剩余的gas也会被消耗掉,并且没有任何gas退回给发送者。但是拜占庭更新包括了一个新的“恢复”代码,允许合约停止执行并且恢复状态改变而不消耗剩余的gas,此代码还拥有返回交易失败原因的能力。如果一个交易是由于恢复而退出,那么未使用的gas就会被返回给发送者。
在消息调用的通用执行框架中,会有8个所谓的预编译合约、地址1-8分别是椭圆曲线公钥恢复函数、SHA2 256 位哈希方案、RIPEMD 160 位哈希方案、标识函数、任
意精度的模幂运算、椭圆曲线加法、椭圆曲线纯量乘法和椭
圆曲线配对检查。
执行模型
执行模型使用一系列的字节代码指令和一个环境数据的元组去改变系统状态,使用以太坊虚拟机EVM运行,通过gas来限制。
EVM是图灵完备虚拟机器。EVM存在而典型图灵完备机器不存在的唯一限制就是EVM本质上是被gas束缚。因此,可以完成的计算总量本质上是被提供的gas总量限制的。EVM具有基于堆栈的架构。堆栈机器 就是使用后进先出来保存临时值的计算机。
EVM的存储方式有三种:
有一定计算机基础的应该理解。栈是常见的线性数据结构,先进后出的模式。EVM基于栈的虚拟机,那么所有的运算都在栈上,一个栈之前提到是256bit。账户存储和内存将EVM的账户存储和内存类比成我们通常看到的计算机的硬盘和内存。
EVM同样有属于它自己的语言:“EVM字节码”,当一个程序员比如你或我写一个在以太坊上运行的智能合约时,我们通常都是用高级语言例如Solidity来编写代码。然后我们可以将它编译成EVM可以理解的EVM字节码。(摘自以太坊工作原理)
那么问题来了,代码在EVM中执行,以太坊网络中每个节点都有一个EVM,的确是每个节点的EVM都会去工作,必须通过在EVM中执行代码来验证区块的结果状态。这样通常理解就是大量的计算资源的浪费,那么从安全性的方面来考虑,在一个去中介化的区块链系统中,这个是保证整体的安全性的必要操作。当然每个节点在执行过程中会出现不同的情况,但是在区块链系统中必须要达到完全的一致,这就使得EVM和智能合约存在一定的局限性。出现不确定的结果无法达成共识。这种场景下必须再次同步区块达成一致性。
EVM有专门的指令集,包括了一般常见的算术运算、位运算、逻辑运算、条件判断等,针对区块链架构还要专门的合约访问区块号,区块时间戳等指令。所有的指令以256bit位单位来传递。智能合约的编译就是讲高级语言写的代码编译为指令集字节码。