本文大量借鉴了大佬的文章:https://blog.csdn.net/Mu_Xiaoye/article/details/104299664
ETH篇
1.ETH概述
1.1 BTC和ETH
BTC和ETH为最主要的两种加密货币,BTC称为区块链1.0,以太坊称为区块链2.0。之前文章中提出了比特币设计中存在某些不足,以太坊便对其进行了改进。例如:出块时间、共识协议、mining puzzle(对内存要求高,反ASIC芯片使用)
未来,以太坊还将会用 权益证明(POS)替代工作量证明(POW)
此外,以太坊增加了对 智能合约(smart contract) 的支持。
1.2 去中心化的合约—智能合约
BTC本身是一个去中心化的货币,在比特币取得成功之后,很多人就开始思考:除了货币可以去中心化,还有什么可以去中心化?以太坊的一个特性就是增加了对去中心化的合约的支持。
如果说比特币系统本身是一个货币应用,以太坊则由于智能合约,升级成为了一个平台,用户可以依据该平台自行开发业务应用。
2.ETH账户
BTC是基于交易的模式,系统中并未显示记录账户有多少钱,只能通过UTXO进行推算。
而以太坊是基于账户的模式,与现实中银行账户相似。
以太坊系统中有两类账户: 外部账户 和 合约账户
- 外部账户: 类似于BTC系统中公私钥对。存在
账户余额balance
和计数器nonce
- 合约账户: 并非通过公私钥对控制。(不能主动发起交易,只能接收到外部账户调用后才能发起交易或调用其他合约账户)其除了
balance
和nonce
之外还有code(代码)
、storage(相关状态-存储)
。创建合约时候会返回一个地址,就可以对其调用。调用过程中,代码不变但状态会发生改变。
3.ETH数据结构
3.1 引入
在以太坊中,有 三棵树 的说法,分别是 状态树、收据树和交易树 。了解了这三棵树,就弄清楚了以太坊的基础数据结构设计。
以太坊采用基于账户的模式,系统中显式记录每个账户的余额。而以太坊这样一个大型分布式系统中,是采用的什么样的数据结构来实现对这些数据的管理的。
在以太坊中,账户地址为160字节,表示为40个16进制数额。状态包含了余额(balance)、交易次数(nonce),合约账户中还包含了code(代码)、存储(stroge)。
- 直观地来看,其本质上为
Key-value键值对
,所以直观想法便用哈希表实现。若不考虑哈希碰撞,查询直接为常数级别的查询效率。
但采用哈希表,难以提供Merkle proof(BTC数据结构篇中有对Merkle proof的介绍,还记得是什么吗?)。
需要记住的是,在BTC和以太坊中,交易保存在区块内部,一个区块可以包含多个交易。通过区块构成区块链,而非交易。
思考如何组织账户的数据结构?
- 我们能否像BTC中,将哈希表的内容组织为Merkle Tree?
但当新区块发布,哈希表内容会改变,再次将其组织为新的Merkle Tree?如果这样,每当产生新区块(ETH中新区块产生时间为10s左右),都要重新组织Merkle Tree,很明显这是不现实的。
需要注意的是,比特币系统中没有账户概念,交易由区块管理,而区块包含上限为4000个交易左右,所以Merkle Tree不是无限增大的。而ETH中,Merkle Tree来组织账户信息,很明显其会越来越庞大。
实际中,发生变化的仅仅为很少一部分数据,我们每次重新构建Merkle Tree代价很大 - 那我们不要哈希表了,直接使用Merkle Tree,每次修改只需要修改其中一部分即可,这个可以吗?
实际中,Merkle Tree并未提供一个高效的查找和更新的方案。此外,将所有账户构建为一个大的Merkle Tree,为了保证所有节点的一致性和查找速度,必须进行排序。 - 那么经过排序,使用Sorted Merkle Tree可以吗?
新增账户,由于其地址随机,插入Merkle Tree时候很大可能在Tree中间,发现其必须进行重构。所以Sorted Merkle Tree插入、删除(实际上可以不删除)的代价太大。
实际中以太坊采取的数据结构:Merkle Patricia Tries(MPT)。
3.2 trie
MPT是基于Trie字典树进行的修改,把指针改为哈希指针

如图是一颗Trie,它有如下特点
- trie中每个节点的分支数目取决于Key值中每个元素的取值范围(图例中最多26个英文字母分叉+一个结束标志位)。
- trie查找效率取决于key的长度。实际应用中(以太坊地址长度为160byte)。
- 理论上哈希会出现碰撞,而trie上面不会发生碰撞。
- 给定输入,无论如何顺序插入,构造的trie都是一样的。
- 更新操作局部性较好
那么trie有缺点
吗?当然有:
trie的存储浪费。很多节点只存储一个key,但其“儿子”只有一个,过于浪费。因此,为了解决这一问题,我们引入 Patricia tree/trie
Patricia trie(Patricia tree) 就是进行了路径压缩的trie,上图压缩后如下图:

需要注意的是 ,如果新插入单词,原本压缩的路径可能需要扩展开来
。那么,需要考虑什么情况下路径压缩效果较好?树中插入的键值分布较为稀疏的情况下,可见路径压缩效果较好。
在以太坊系统中,160位的地址存在2^160 种,该数实际上已经非常大了,和账户数目相比,可以认为地址这一键值非常稀疏。
3.3 状态树
以太坊中针对MPT(Merkle Patricia tree)
进行了修改,我们称其为MPT(Modified Patricia tree)

如图:右上角表示四个账户,账户的Key-Value Pair用MPT来构建,MPT的叶子节点存储value,路径就是Key
其中Block Header会保存MPT的根哈希值
每次发布新区块,状态树中部分节点状态会改变。但改变并非在原地修改,而是 新建一些分支,保留原本状态
。如下图中,仅仅有新发生改变的节点才需要修改,其他 未修改节点直接指向前一个区块中的对应节点。

所以:系统中全节点并非维护一棵MPT,而是 每次发布新区块都要新建MPT 。只不过 大部分节点共享。
3.4 以太坊中的数据结构
Block Header中的数据结构
区块结构
区块在网上真正发布时的信息
3.5 交易树和收据树
每次发布一个区块时,区块中的交易会形成一颗MPT,即交易树。
每个交易执行完之后形成一个收据,区块中的交易会形成一颗MPT,记录交易相关信息,即收据树。
ETH中的三棵树都是一样的结构,MPT
MPT的好处是支持查找操作,通过键值沿着树进行查找即可。对于状态树
,查找键值为 账户地址 ;对于交易树和收据树
,查找键值为 交易在发布的区块中的序号 。
交易树和收据树的用途:
- 向轻节点提供Merkle Proof。
- 更加复杂的查找操作(例如:查找过去十天的交易;过去十天的众筹事件等)
3.6 以太坊中的Bloom Filter
Bloom Filter
Bloom filter特点:有可能出现误报,但不会出现漏报。
Bloom filter变种:采用一组哈希函数进行向量映射,有效避免哈希碰撞
如果集合中删除元素该怎么操作?
无法操作。也就是说,简单的Bloom filter不支持删除操作。如果想要支持删除操作,需要将记录数不能为0和1,需要修改为一个计数器(需要考虑计数器是否会溢出)。
以太坊中的Bloom Filter
每个交易完成后会产生一个收据,收据包含一个Bloom filter记录交易类型、地址等信息。
在区块block header中也包含一个Bloom filter,其为该区块中所有交易的Bloom filter的一个并集。
作用: 通过Bloom filter这样一个结构,快速大量过滤掉大量无关区块,从而提高了查找效率
。
4.GHOST协议
BTC系统中出块时间为10min,而以太坊中出块时间被降低到15s左右,虽然有效提高了系统反应时间和吞吐率,却也导致系统临时性分叉变成常态,且分叉数目更多
。这对于共识协议来说,就存在很大挑战。
对此,以太坊设计了新的公式协议——GHOST协议
(该协议并非原创,而是对原本就有的Ghost协议进行了改进)。
4.1 初版GHOST协议

如图,出现分叉后,随时间推移,中间的区块成为最长合法链,上面和下面的都被废弃
但为了补偿这些区块所属矿工所作的工作,给这些区块一些“补偿”,并称其为**“Uncle Block”(叔父区块)**

规定:
- 叔父区块可以得到出块奖励的
7/8
- 为了激励区块包含叔父区块,规定每包含
一个叔父区块
就可以额外得到1/32
的出块奖励 - 但一个区块 只能包含最多两个叔父区块
缺陷:
- 因为叔父区块最多只能包含两个,出现3个怎么办
- 矿工自私,故意不包含叔父区块,导致叔父区块7/8出块奖励没了,而自己仅仅损失1/32。
4.2 新版GHOST协议
对于上面出现3个的情况,新版GHOST协议作了补充:
虽然一个区块最多只能包含2个区块,但是可以隔很多代包含
(意思是可以包含爷爷辈或更以前的),
所以对于3个的情况,包含不了就**让自己后面的区块来包含
**
需要注意的是:每隔一代减少 1/8 的奖励 ,如图所示

这样隔代就会减少奖励在一定意义上也鼓励了出现分叉之后马上进行合并
4.3 以太坊中的奖励
- BTC:静态奖励(出块奖励) + 动态奖励(交易费,占据比例很小)
- ETH:静态奖励(出块奖励 + 动态奖励(汽油费,占据比例很小,叔父区块没有) + 包含叔父区块的奖励)
问题1:以太坊中包含了叔父区块,要不要包含叔父区块中的交易?
不应该 ,叔父区块和同辈的主链上 区块有可能包含有冲突的交易
。而且我们前文也提到,叔父区块是没有动态奖励的。因此,一个节点在收到一个叔父区块的时候,只检查区块合法性而不检查其中交易的合法性。
问题2:对于分叉后的堂哥区块怎么办?例如下图所示,A->F该链并非一个最长合法链,所以B->F这些区块怎么办?该给挖矿补偿吗?
如果规定将下面整条链作为一个整体,给予出块奖励,这一定程度上鼓励了分叉攻击(降低了分叉攻击的成本,因为即使攻击失败也有奖励获得)。 因此,ETH系统中规定,只认可A区块为叔父区块
,给予其补偿, 而其后的区块全部作废。
5.ETH挖矿算法
以太坊的理念与莱特币相同,所以先介绍一下莱特币的算法
以太坊和莱特币的算法希望做到 ASIC Resistance(抗拒ASIC专用矿机)
由于ASIC芯片相对普通计算机来说,算力强但访问内存性能差距不大,因此常用的方法为Memory Hard Mining Puzzle
,即 增加对内存访问的需求。
5.1 LiteCoin挖矿算法基本思想
- 设置一个很大的数组,按照顺序填充伪随机数。
Seed为种子节点,通过Seed进行一些运算获得第一个数,之后**每个数字都是通过前一个位置的值取哈希得到的。
**
可以看到,这样的数组中取值存在前后依赖关系
- 在需要求解Puzzle的时候,按照伪随机顺序,从数组中读取一些数,每次读取位置与前一个数相关。
例如:第一次,从A位置读取其中数据,根据A中数据计算获得下一次读取位置B;第二次,从B位置读取其中数据,根据B中数据计算获得下一次读取位置C;
分析:
如果数组足够大,对于挖矿矿工来说,必须保存该数组以便查询,否则每次不仅计算位置,还要根据Seed计算整个数组数据,才能查询到对应位置的数据。这对于矿工来说,计算复杂度大幅度上升。
核心思想:不能仅仅进行运算,增加其对内存的访问,从而实现对ASIC芯片不友好。
缺陷:该方法对Puzzle验证并不是很友好。 想要验证该Puzzle,也需要存储该数组,因此对于轻节点来说,并不友好(系统中绝大多数节点为轻节点)。
5.2 以太坊挖矿算法基本思想
莱特币的设计方案中,对轻节点的Puzzle验证不是很友好,因为要保存这个很大的数组
以太坊中,运用一个巧妙的设计解决了这一问题
以太坊中,设计了两个数据集,一大一小。小的为16MB的cache,大的数据集为1G的dataset(DAG)。其关系为, 1G的数据集是通过16MB数据集生成而来的。
思考为何要设计一大一小两个数据集?
为了便于进行验证,轻节点保存16MB的Cache进行验证即可,而矿工为了挖矿更快,减少重复计算则需要存储1GB大小的大数据集。
这样一来,轻节点只需要保存16MB的Cache进行验证,矿工保存大数据集为了挖矿更快
16MB的小Cache数据生成方式与莱特币中生成方式较为类似,通过Seed进行一些运算获得第一个数,之后每个数字都是通过前一个位置的值取哈希获得的。
不同点:
- 莱特币:直接从数组中按照伪随机顺序读取一些数据进行运算
- 以太坊:会接着生成一个大数组,大数组就是大的数据集(这两个数组大小并不固定,因为考虑到计算机内存不断增大,因此该两个数组需要定期增大)
大的DAG生成方式:
大的数组中
每个元素都是从小数组中按照伪随机顺序读取一些元素
,方法同莱特币中相同。如第一次读取A位置数据,对当前哈希值更新迭代算出下一次读取位置B,再进行哈希值更新迭代计算出C位置元素。如此来回迭代读取256次,最终算出一个数作为DAG中第一个元素,如此类推,DAG中每个元素生成方式都依次类推。
5.3 以太坊的挖矿过程
设置一个Nonce值,然后对block header计算一个初始哈希,根据其映射到某个初始位置A,读取A位置的数及其相邻的后一个位置A’上的数,根据该两个数进行运算,算得下一个位置B,读取B和B’位置上的数,依次类推,迭代读取64次,共读取128个数。
最后,计算出一个哈希值与挖矿难度目标阈值比较,若不符合就重新更换Nonce,重复以上操作直到最终计算哈希值符合难度要求或当前区块已经被挖出。
目前以太坊挖矿以GPU为主,可见其设计较为成功,这与以太坊设计的挖矿算法(Ethash)所需要的大内存具有很大关系。
1G的大数组与128k相比,差距8000多倍,即使是16MB与128K相比,也大了一百多倍,可见对内存需求的差距很大(况且两个数组大小是会不断增长的)。
当然,以太坊实现ASIC Resistance除了挖矿算法设计之外,还存在另外一个原因,即其预期 从工作量证明(POW)转向权益证明(POS) ,以太坊目前仍然是POW挖矿共识机制。在设计之初,以太坊开发者就设想要从POW转向POS,并为了防止有矿工不愿意转埋下了一颗“难度炸弹”。
什么是权益证明POS?
POS: Proof of State;意思是 按照所占权益投票进行共识达成
,类似于股份制有限共识按照股份多少投票, 权益证明不需要挖矿
6.ETH挖矿难度调整
6.1 难度调整算法
6.2 难度炸弹
为什么要设置难度炸弹?
根据以上以太坊难度调整算法可以看到,该算法可以很好地动态调整挖矿难度,从而保障系统整体出块时间维持在15s左右。但之前在挖矿算法的文章中有介绍到,以太坊在设计之初就计划要逐步从POW(工作量证明)转向POS(权益证明),而权益证明不需要挖矿。
从旁观者角度来看,挖矿消耗了大量电力、资金等,如果转入放弃挖矿,必然是一件好事。但从矿工的角度,花费了很大精力投入成本购买设备,突然被告知“不挖矿了”,这必然是一件很难接受的事情。而以太坊本身为一个分布式系统,其转入POS必须经过系统中大多数矿工认可才行,如果届时矿工联合起来转入POS,那么这一设计初衷就成了一江流水。
因此,以太坊在设计之初便添加了难度炸弹,迫使矿工转入POS。
数学上,指数函数是一个很可怕的东西。我们谈论一个算法,无论其时间复杂度还是空间复杂度,只要达到了指数级别,这个算法必然难以应用于大规模计算上。指数函数在前期增长相对缓慢,但在后期呈现“指数爆炸”,而这往往是我们无法通过升级硬件所能解决的。
可以看到,在以太坊早期时,区块号较小,难度炸弹计算所得值较小,难度调整级别基本上通过难度调整中的自适应难度调整部分决定,而随着越来越多区块被挖出,难度炸弹的威力开始显露出来,这也就使得挖矿变得越来越难,从而迫使矿工愿意转入POS。
6.3 难度炸弹调整
上面提到,以太坊设想是通过埋设难度炸弹迫使矿工届时愿意转入权益证明,但现实中有一句话:“理想很丰满,现实很骨感”。在实际应用中,权益证明的方式仍然并不成熟,目前以太坊共识机制仍然是POW,依然需要矿工参与挖矿维护以太坊系统的稳定。也就是说,转入POS的时间节点被一再推迟,虽然挖矿变得越来越难,系统出块时间开始逐渐变长,但矿工仍然需要继续挖矿。
在上面难度炸弹的公式中,有人应该注意到了第二项中的fake block number,该数仅仅为对当前区块编号减去了三百万,也就是相当于将区块编号回退了三百万个。那么,在前三百万个区块的时候,这个fake block number就是负数吗?
答案是否定的。实际上,在以太坊最初的设计中,并没有第二个公式。也就是说,最初就是简单地直接用区块编号除以100000。而在转入权益证明时间节点一再推迟后,以太坊系统采取了将区块编号回退三百万个区块的方法来降低挖矿难度,当然,为了保持公平,也将出块奖励从5个以太币减少到了3个以太币,这也是fake block number这一项出现的原因。
下图显示了难度调整对难度炸弹难度影响的结果:
7.ETH权益证明POS
7.1 POW过于耗能
比特币和以太坊目前采用的都是POW(工作量证明)机制,但这种方式一直为人所诟病,正在于其浪费电力资源的特点。
如果将以太坊、比特币系统相加,作为一个国家,其所消耗能耗在世界排名如下图:
显而易见,“挖矿”过程消耗了大量的电力资源,这些能耗是必须的吗?
矿工挖矿是为了取得出块奖励,获取收益。而系统给予出块奖励的目的是激励矿工参与区块链系统维护,进行记账,而 挖矿本质上是看矿工投入资金来决定的(投入资金买设备->设备决定算力->算力比例决定收益)。
那么,为什么不直接拼“钱”呢?现状是用钱购买矿机维护系统稳定, 为什么不大家都将钱投入到系统开发和维护中,而根据投入钱的多少来进行收益分配呢?这就是权益证明的基本思想。
7.2 权益证明
一般来说,采用权益证明的货币,会先预留一些货币给开发者,而开发者也会出售一些货币换取开发所需要的资金,在系统进入稳定状态后,每个人都安装持有货币的数量进行投票。
优点:
- 省去了挖矿的过程,也避免了因此产生的能耗和对环境影响,减少了温室气体的排放。
- 维护区块链安全的资源形成闭环,而POW中维护其安全的资源需要通过现实中流通的货币购买矿机等设备进去区块链的,这也就导致只要有人想要攻击,只需要外部聚集足够资金就可以攻击成功(小型币种很容易被攻击,也就是在摇篮里就扼杀掉)。可见,POS机制可以有效防御这种情况。
有些币种根据持有币的权益进行挖矿难度调整(实际并不能这么简单设置,因为会导致“旱的旱死,涝的涝死”,需要添加一定限制),也就是结合POW和POS。可见,POS与POW并不互斥。
当然,权益证明这么好,为什么实际中并未得到大规模应用呢?
原因是其中仍然存在很多挑战,例如“双边下注”:
如下图所示,区块链系统产生了分叉,存在两个区块A和B竞争主链时,
采用权益证明的方法就是所有持币者对这两个区块投入币进行投票
,从而决定哪一个区块成为最长合法链上的区块。假如有一个人,在A和B同时进行了下注。最终A区块胜出,那么他能够获得A区块相应收益,而在B区块进行投票放入的“筹码”也会被退还,这也就导致其每次都能获得收益。
由于一个人可以拥有多个账户,所以我们 无法强迫一个人一次只能投向一个区块。而越有钱的人,通过“双边下注”得到的收益也就越多。
7.3 以太坊中的权益证明
以太坊中,准备采用的权益证明协议为Casper the Friendly Finality Gadget(FFG)
,该协议在过渡阶段是要和POW结合使用的。
Casper协议引入一个概念:Validator(验证者),一个用户想要成为Validator,需要上交一笔“保证金”,这笔保证金会被系统锁定。Validator的职责是推动系统达成共识,投票决定哪一条链成为最长合法链,投票权重取决于保证金数目。
实际中,采用两次投票的方式
:预投票和Commit 投票,规定每次投票结果都要获得2/3以上的验证者同意。在实际中,针对其进行了一些修改,两次投票在实际中只需要一次即可。(由于我觉得书面难免会有遗漏,这里就不详细展开了,推荐去肖老师视频中观看,大概从25:00起)。视频观看传送门:Click Here。
矿工挖矿会获得出块奖励,而验证者也会得到相应奖励。当然,为了防止验证者的不良行为,规定其被发现时要受到处罚。例如某个验证者“行政不作为”,不参与投票导致系统迟迟无法达成共识,这时扣掉部门保证金;如果某个验证者“乱作为”,给两边都进行投票,被发现后没收全部保证金。没收的保证金被销毁,从而减少系统中货币总量。验证者存在“任期”,在任期结束后,进入“等待期”,在此期间等待其他节点检举揭发是否存在不良行为,若通过等待期,则可以取回保证金并获得一定投票奖励。
Q:这样一定能保证不被篡改吗?
在该协议下,矿工无论算力多么强,最终投票权都不在其手中。必须在系统中,存在大量“验证者”进行了两边投票,也就是说,至少1/3(该协议规定超过2/3才有效)的验证者两侧都投票,才会导致系统被篡改。而这一旦被发现,这1/3验证者的保证金将会被没收。
以太坊系统设想,随着世界推移,挖矿奖励逐渐减少而权益证明奖励逐渐增多,从而实现POW到POS的过渡,最终实现完全放弃挖矿。
然而权益证明仍然存在缺陷,但工作量证明已经得到了事实检验,该机制较为成熟。
目前, EOS加密货币 ,即“柚子”,2018年上线,就是采用权益证明的共识机制,其采用的是DPOS:Delegated Proof of Stake。该协议核心思想是通过投票选21个超级节点,再由超级节点产生区块。但目前,权益证明仍然处于探索阶段。
8.ETH智能合约
8.1 简介
智能合约:运行在区块链系统上的一段代码,代码逻辑定义了合约内容。
智能合约的账户保存了合约当前的运行状态:
- balance:当前余额
- nonce:交易次数
- code:合约代码
- storage:存储,数据结构为一棵MPT
如图:为一段智能合约代码Solidity
8.2 合约的调用
外部账户调用合约账户
合约账户调用合约账号
合约账户之间也可以进行调用。其调用方式如下:
直接调用
错误处理:直接调用中, 一方产生异常会导致另一方也进行回滚操作。
address调用
错误处理:address.call()的方法,如果调用过程中被调用合约产生异常, 会导致call()返回false ,但 发起调用的函数不会抛出异常,而是继续执行。
代理调用
和call()调用基本一致,区别在于其并不会切入被调用合约的上下文中。
Payable修饰符
如下,成员函数中的第一个函数,有一个payable修饰。原因是以太坊中规定,如果一个函数可以接收外部转账,则必须标记为payable
。该例中背景为拍卖,bid()为出价,因此需要payable进行标记;withdraw()为其他未拍卖到的人将锁定在智能合约中的钱取出的函数,其不涉及转账,因此不需要payable进行标记。

fallback()函数

该函数主要是防止A向B转账,但没有在data域中说明要调用哪个函数
或说明的要调用函数不存在
,此时调用fallback()函数。
只有合约账户才有代码,因此这些只和合约账户有关。如果没有fallback(),在发生之前的情况后,就会直接抛出异常。
另:转账金额和汽油费是不同的。汽油费是为了让矿工打包该交易,而转账金额是单纯为了转账,其可以为0,但汽油费必须给。
8.3 智能合约创建与运行
实际上并不是想转账,而是想要创建智能合约。EVM设计思想类似于JAVA中的JVM,便于跨平台增强可移植性。EVM中寻址空间256位,而目前个人机主流位32位和64位,与之存在较大差距。

8.4 汽油费Gas
solidity是图灵完备的语言,当一个全节点收到一个对智能合约调用时, 发生了死循环怎么办
事实上,无法预知其是否会导致死循环,该问题是一个停机问题,而停机问题不可解。
因此,以太坊引入汽油费机制将该问题扔给了发起交易的账户。
以太坊规定,执行合约中指令需要收取汽油费
,并且由发起交易的人进行支付
。

代码成员变量解释:
- AccountNonce:交易序号
- Price:单位汽油价格
- GasLimit:愿意支付的最大汽油量
- Recipient:收款人地址
- Amount:转账金额
- Payload:data域
当一个全节点收到一个对智能合约的调用,先按照最大汽油费收取,从其账户一次性扣除,再根据实际执行情况,多退少补(汽油费不够会引发回滚,而非简单的补齐)。
以太坊中存在gaslimit,通过收取汽油费保障系统中不会存在对资源消耗特别大的调用。
为什么要引入汽油费?
在比特币系统中,交易是比较简单的,仅仅是转账操作,也就是说可以通过交易的字节数衡量出交易所需要消耗的资源多少。
但以太坊中引入了智能合约,而
智能合约逻辑很复杂
,其字节数与消耗资源数并无关联。存在某些交易,从字节数来看很小,但其实际消耗资源很大
(例如调用其他合约等),因此要根据交易的具体操作收费,所有引入了汽油费这一概念。Block Header也存在GasLimit,其并非将所有交易的消耗汽油费相加,而是该区块中所有交易能够消耗的资源的上限。
汽油费是怎么扣除的?
首先,之前在以太坊数据结构中介绍了以太坊中“三棵树”——状态树、交易树、收据树
。这三棵树都位于全节点
中,是全节点在本地
维护的数据结构,记录了每个账户的状态等数据,所以该节点收到调用时,是在本地
对该账户的余额减掉即可。所以多个全节点每人扣一次,仅仅是每个全节点各自在本地扣一次。
也就是说,智能合约在执行过程中,修改的都是本地
的数据结构,只有在该智能合约被发布到区块链上,所有节点才需要同步状态,各自在本地执行该智能合约。
8.5 以太坊错误处理

以太坊中交易具有原子性
,要么全执行,要么全不执行,不会只执行一部分(包含智能合约)。
注意: 回滚的时候,之前 消耗的汽油费是不会退回的
。这样有效防止了恶意节点对全节点进行恶意调用。

嵌套调用是否发生回滚,取决于调用方式,调用方式错误处理见上面的小节
一个合约向一个合约账户直接转账,因为fallback函数的存在,仍有可能会引发嵌套调用。
8.6 挖矿与智能合约执行
是先挖出区块再执行智能合约,还是先执行合约再挖矿?
先执行智能合约再挖矿,因为执行智能合约要支付汽油费
,需要修改三棵树的值
,ETH的Block Header中有三棵树的根哈希值,所以执行智能合约就会修改Block Header。
这就意味着,只有当智能合约执行完的时候Block Header才能确定,此时才能不断尝试nonce进行挖矿

8.7 一些问题
问题1:发布到区块链上交易无论失败与否都需要支付汽油费吗?
为了防止恶意节点故意发布大量非法交易影响系统运行
,对于其发布的交易即使无法成功执行也需要收取汽油费。但如果交易不被发布到区块链上,是无法收取汽油费的。
问题2:智能合约支持多线程吗?
不支持,根本就没有支持多线程的语句。
因为以太坊本质为一个交易驱动的状态机,面对同一组输入,必须转移到一个确定的状态。但对于多线程来说,同一组输入的输入顺序不同,最终的结果可能不一致。
此外,其他可能导致执行结果不确定的操作也不支持,例如:产生随机数。因此,以太坊中的随机数是伪随机数。
也正是因为其不支持多线程,所以无法通过系统调用获得系统信息,因为每个全节点环境并非完全一样。因此只能通过固定的结构获取。下图分别为为其可以获得的区块链信息和调用信息。
8.8 以太坊地址类型
一个成员变量,五个函数

注意:
第一行的address.balance是一个uint256变量,代表address这个账户的余额
;
而其他的例如address.transfer(12345),并非address向外转账12345Wei,因为这样没有收款人的address。 实际上该函数指的是当前合约向该address转入了12345Wei
在以太坊中,转账有以下三种方法。
- transfer在转账失败后会导致连锁性回滚,抛出异常;
- 而send转账失败会返回false,不会导致连锁性回滚。
- call的方式本意是用于发动函数调用,但是也可以进行转账。
前两者在调用时,只发送2300wei的汽油费,这点汽油费很少,只能写一个log,而call的方式则是将自己还剩下的所有汽油费全部发送过去(合约调用合约时常用call,没用完的汽油费会退回)。
例如A合约调用B合约,而A不知道B要调用哪些合约,为了防止汽油费不足导致交易失败,A将自己所有汽油费发给B来减少失败可能性。