区块链技术与应用 – ETH基本原理

38673,868次阅读
2 条评论

共计 24308 个字符,预计需要花费 61 分钟才能阅读完成。

以太坊

区块链技术与应用 - ETH基本原理
以太坊开发文档: https://ethereum.org/zh/developers/docs/

一、Etherenm – 概述

BTC和ETH是最主要的两种加密货币,BTC称为区块链1.0,以太坊称为区块链2.0。

关于BTC和ETH
BTC的发明人为中本聪,ETH为Vitalik Buterin受到BTC启发发明出来的“下一代加密货币与去中心化应用平台”。BTC中货币最小单位为“聪”,ETH中货币最小单位为“Wei”。

区块链2.0针对BTC的一些问题与缺点做出了改进

  1. 出块时间为10几秒;BTC是10分钟
  2. 新增加了智能合约Smart contracts
  3. mining puzzle,以太坊这种模式限制了asic芯片的使用
  4. 从BTC的工作量证明(PoW)改为ETH的权益证明(PoS),类似股份制投票的方法
    BitCoin: decentralized currency 比特币:去中心化货币
    Ethereum: decentralized contract 以太坊:去中心化合约
    

    货币是政府以自己信用的方式发行的中心化货币,BTC是密码学以及共识机制发行的货币,以技术手段把政府的部分职能取代了。
    合约是以法律为基础,以司法手段通过政府来维护的。 以太坊(Ethereum)是一个去中心化、开源并且具备智能合约功能的公共区块链平台,以太币(ETH)是以太坊的原生加密货币。

现实生活中,我们经常提到的“契约”,“合同”。合约的有效性也是需要政府进行维护的,如果产生纠纷需要针对合法性合同进行判决,ETH的设计目的就是,通过技术手段来实现取代现代政府对于合约的职能。

Q:去中心化合约有什么好处?
A:若合同的签署方并非一个国家,没有同一的司法部门。如果可以编写无法修改的合约,所有人只能按照相关参与方执行,无法违约。

特点

相较于大多数其他加密货币或区块链技术,以太坊的特点包括以下几点:

  1. 智能合约:存储在区块链上的程序,由各节点执行,需要执行程序的人支付手续费给节点的矿工或权益人
  2. 分布式应用程序:以太坊上的分布式应用程序不会停机,也不能被关掉。
  3. 代币(tokens):智能合约可以创造代币供分布式应用程序使用。分布式应用程序的代币化让用户、投资者以及管理者的利益一致。代币也可以用来进行首次代币发行。
  4. 权益证明:相较于工作量证明更有效率,可节省大量在挖矿时浪费的电脑资源,并避免特殊应用集成电路造成网络中心化。2022年9月15日与主链合并
  5. 燃料(gas):由交易手续费的概念扩展,在执行各种运算时需计算燃料消耗量,并缴交燃料费,包括发送以太币或者其他代币也被视为一种运算动作。
  6. 原丹克分片(Proto-Danksharding):在部分节点上暂存资料,以提升效率(尚未实现)。
  7. 叔块:此功能在转为权益证明后已停用。原本功能是使用有向无环图的相关技术,将因速度较慢而未及时被收入母链的较短区块链并入,用以提升交易量。

以太坊最重要的技术贡献就是智能合约。智能合约是存储在区块链上的程序,可以协助和验证合约的谈判和执行。智能合约的潜在应用很多。

彭博社商业周刊称:它是“所有人共享但无法篡改的软件”

二、Ethereum – 账户

比特币账户模式

BTC必须每次花光自己某一个账户上的金额,余额转移自己另一个账户。
区块链技术与应用 - ETH基本原理

以太坊账户模式

ETH账户知道账户的交易记录,以及账户的金额;ETH可以天然的防止“双花攻击”,并对”重放攻击”做出了保护,添加了计数器nonce。

  1. 外部账户:externally owned account 普通账户,类似于BTC系统中公私钥对。与BTC账户生成方式一样
  2. 合约账户:smart contract account 并非通过公私钥对控制(不能主动发起交易,只能接收到外部账户调用后才能发起交易或调用其他合约账户).特点:身份固定

比特币中支持每次交易更换账户,但以太坊是为了支持智能合约,而合约签订双方是需要明确且较少变化的。尤其是对于合约账户来说,需要保持稳定状态。
账账户地址为160个字节,表示为40个16进制数

三、Ethereum – 数据结构基础

以太坊的区块是由区块头、交易列表和叔区块三部分组成。 其中区块头包含块区号、块哈希、父块哈希等信息, State Root、Transaction Root、Receipt Root分别代表了状态树、交易树和交易树的哈希。 除了创世块外,每个块都有父块,用Parent Hash连成一条区块链
区块链技术与应用 - ETH基本原理

1、Merkle 树(默克尔树)

默克尔树(又叫哈希树)是一种典型的二叉树结构,由一个根节点、一组中间节点和一组叶节点组成。就是存储hash值的一棵树,是一个把任意长度的数据通过哈希函数映射成固定长度数据

其主要特点为:

  1. 最下面的叶节点包含存储数据或其哈希值;
  2. 非叶子节点(包括中间节点和根节点)都是它的两个孩子节点内容的哈希值。
区块链技术与应用 - ETH基本原理

2、Trie 树

Trie树,又称前缀树或字典树。利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串比较,查询效率比哈希树高。典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),经常被搜索引擎系统用于文本词频统计。如图

基本性质:

  1. 根节点不包含字符,除根节点外的每一个子节点都包含一个字符
  2. 从根节点到某一节点。路径上经过的字符连接起来,就是该节点对应的字符串
  3. 每个节点的所有子节点包含的字符都不相同
区块链技术与应用 - ETH基本原理

3、Patricia树

Patricia树,或称Patricia trie,压缩前缀树,是一种更节省空间的Trie。对于基数树的每个节点,如果该节点是唯一的儿子的话,就和父节点合并
区块链技术与应用 - ETH基本原理

四、Ethereum – 状态树MPT

状态树 MPT(Modified Patricia Tree)

注意:叔块:此功能在转为权益证明后已停用

以太坊区块数据有三棵树,分别为状态树,交易树和收据树。整个以太坊系统中只有一棵状态树,记录整个以太坊系统的所有账户状态。每个区块保存着一棵交易树,记录该区块的交易情况,一棵收据树用来记录该区块的交易收据。
状态树,每个节点基本上包含了一个键值映射,其中的键是地址,而值包括账户的声明、余额、随机数nounce、代码以及每一个账户的存储

下图为以太坊中使用的MPT结构示意图。右上角表示四个账户和其状态(只显示账户余额),需要注意这里的指针都是哈希指针。

区块链技术与应用 - ETH基本原理

每次发布新区块,状态树中部分节点状态会改变。但改变并非在原地修改,而是修建一些分支,保留原本状态。如下图中,仅仅有新发生改变的节点才需要修改,其他未修改节点直接指向前一个区块中的对应节点

区块链技术与应用 - ETH基本原理

所以,系统中全节点并非维护一棵MPT,而是每次发布新区块都要新建MPT。只不过大部分节点共享。

为什么要保存原本状态?为何不直接修改?
为了便于回滚。如下1中产生分叉,而后上面节点胜出,变为2中状态。那么,下面节点中状态的修改便需要进行回滚。因此,需要维护这些历史记录。

区块链技术与应用 - ETH基本原理

状态树中有四种节点,分别是空节点、叶子节点、扩展节点和分支节点。

  • 空节点,简单的表示空,在代码中是一个空串。
  • 叶子节点(leaf),表示为[key,value]的一个键值对,其中key是key的一种特殊十六进制编码,value是value的RLP编码。
  • 扩展节点(extension),也是[key,value]的一个键值对,但是这里的value是其他节点的hash值,这个hash可以被用来查询数据库中的节点。也就是说通过hash链接到其他节点。
  • 分支节点(branch),因为MPT树中的key被编码成一种特殊的16进制的表示,再加上最后的value,所以分支节点是一个长度为17的list,前16个元素对应着key中的16个可能的十六进制字符,如果有一个[key,value]对在这个分支节点终止,最后一个元素代表一个值,即分支节点既可以搜索路径的终止也可以是路径的中间节点。

假如有四个账户,账户1地址0x811344,余额1ETH;账户2地址0x879337,余额2ETH;账户3地址0x8fd365,余额3ETH;账户4地址0x879397,余额4ETH,存储如下:

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

从图中可以看出,状态树的存储涉及3种编码方式: KeyBytes编码,Hex编码,Compact编码

在完成Compact编码后,会通过折叠操作把子结点替换成子结点的hash值,然后以键值对的形式将所有结点存储到LevelDBA数据库中。下面详细介绍上面3中编码方式。

  • KeyBytes编码:即原始关键字,比如图中的0x811344、0x879337等。每个字节中包含2个nibble(半字节,4 bits),每个nibble的数值范围时0x0~0xF。
  • Hex编码: 由于我们需要以nibble为单位进行编码并插入MPT,因此需要把一个字节拆分成两个,转换为Hex编码。
  • Compact编码: 当我们需要把内存中MPT存储到数据库中时,还需要再把两个字节合并为一个字节进行存储, 以太坊设计了一种Compact编码方式,具体规则如下:

    扩展结点,关键字长度为偶数,前面加00前缀

    扩展结点,关键字长度为奇数,前面加1前缀(前缀和第1个字节合并为一个字节)

    叶子结点,关键字长度为偶数,前面加20前缀(因为是Big Endian)

    叶子结点,关键字长度为奇数,前面加3前缀(前缀和第1个字节合并为一个字节)

StateDB的存储
StateDB中存储了很多stateObject,而每一个stateObject则代表了一个以太坊账户,包含了账户的地址、余额、nonce、合约代码hash等状态信息。所有账户的当前状态在以太坊中被称为“世界状态”,在每次挖出或者接收到新区块时需要更新世界状态。
为了能够快速检索和更新账户状态,StateDB采用了两级缓存机制,参见下图:

区块链技术与应用 - ETH基本原理

第一级缓存以map的形式存储stateObject
第二级缓存以MPT的形式存储
第三级就是LevelDB上的持久化存储
当上一级缓存中没有所需的数据时,会从下一级缓存或者数据库中进行加载。

代码看以太坊中的数据结构

1.block header中的数据结构

区块链技术与应用 - ETH基本原理

2.区块结构

区块链技术与应用 - ETH基本原理

3.区块在网络中真正发布的信息

区块链技术与应用 - ETH基本原理

五、Ethereum – 交易树和收据树(Merkel树)

区块链技术与应用 - ETH基本原理

从图中可以看出,MPT是以交易在区块中的索引的RLP编码作为key,存储交易数据的RLP编码。事实上交易在LeveDB中并不是单独存储的,而是存储在区块的Body中。在往LeveDB中存储不同类型的键值对时,会在关键字中添加不同的前缀予以区分。
因此,以b + block index + block hash作为关键字就可以唯一确定某个区块的Body所在的位置。另外,为了能够快速查询某笔交易的数据,在数据库中还存储了每笔交易的索引信息,称为TxLookupEntry。TxLookupEntry中包含了block index和block hash用于定位区块Body,同时还包含了该笔交易在区块Body中的索引位置。

交易回执的存储和交易类似,区别是交易回执是单独存储到LevelDB中的,以r为前缀。另外,由于交易回执和交易是一一对应的,因此也可以通过TxLookupEntry快速定位交易回执所在的位置,加速交易回执的查找。

交易树和收据树只将当前区块中的交易组织起来,而状态树将所有账户的状态都包含进去,无论这些账户是否与当前区块中的交易有关系。
多个区块状态树共享节点,而交易树和收据树依照区块独立。

交易树和收据树的用途:
1.向轻节点提供Merkle Proof
2.更加复杂的查找操作(例如:查找过去十天的交易;过去十天的众筹事件等)

Bloom filter(布隆过滤器)

区块链技术与应用 - ETH基本原理

支持较为高效查找某个元素是否在某个集合中。
Bloom filter特点:有可能出现误报,但不会出现漏报。
Bloom filter变种:采用一组哈希函数进行向量映射,有效避免哈希碰撞。

交易树和收据树的创建过程

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

六、Ethereum – 账户存储树

以太坊中有两种账户类型:外部所有账户(Externally Owned Accounts 简称 EOA)以及合约账户。我们用来互相收发以太币、部署智能合约的账户就是 EOA 账户,而部署智能合约时自动生成的账户则是合约账户。每一个智能合约都有其独一无二的以太坊账户。

smart contract account
    codeHash        对于合约账户,就是此账户存储 EVM 代码的哈希值。对于 EOA 账户,此处留空
    balance         从此地址发送出去的交易数量(如果当前为 EOA 账户)或者此账号产生的合约创建操作
    nonce           从此地址发送出去的交易数量(如果当前为 EOA 账户)或者此账号产生的合约创建操作
    storageRoot     账户存储树的根节点哈希值

账户存储树是保存与账户相关联数据的结构。该项只有合约账户才有,而在 EOA 中, storageRoot 留空、 codeHash 则是一串空字符串的哈希值。所有智能合约的数据都以 32 字节映射的形式保存在账户存储树中。此处不再赘述账户状态树如何维持合约数据。账户状态中的 storageRoot 区域负责维持账户存储树根节点哈希值。

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

存储树,账户状态,世界状态的构成关系

根据以太坊黄皮书,账户若是一个智能合约账户,则必定包含了 存储树 (storageRoot)和 代码存储 (codeHash)。

为保证数据完整性,这些数据 也被组织成一棵MPT树的形式。该MPT树的根节点哈希值称为存储树。存储树是账户状态的一个域,该值随着合约的存储区的增加、删除、改动而不断变更。代码存储是只读的,它是合约账户的所执行的代码,它在合约第一次创建完毕后就不可以再变更。

区块链技术与应用 - ETH基本原理

总结一下,以太坊有四种前缀树:
(1)状态树包括了从地址到账户状态之间的映射。状态树的根节点哈希值由区块保存(在 stateRoot 字段),它标示了区块创建时的当前状态。整个网络中只有一个状态树。
状态标识了以太坊这台分布式计算机的硬盘。它是从地址到账户状态的映射。
(2)交易树包含了一个区块中的所有交易信息。由区块头(在 transactionsRoot 区域)保存交易树的根节点哈希值。每个区块都有一棵交易树。
交易标示了系统中的状态转移。它可以是资金的转移、消息调用或是合约的部署。
(3)交易收据树包含了一个区块中所有交易的收据信息。同样由区块头(在 receiptsRoot 区域)保存交易收据树的根节点哈希值;每个区块都有对应的交易收据树。
(4)账户存储树保存了与某一智能合约相关的数据信息。由账户状态保存账户存储树的根节点哈希值(在 storageRoot 字段)。每个账户都有一个账户存储树。

账户状态保存着每个以太坊账户的状态信息。账户状态同样保存着账户状态树的 storageRoot,后者包含了该账户的存储数据。

七、Ethereum – GHOST

GHOST协议最初版本

假定以太坊存在以下情况,A、B、C、D在四个分支上,最后,随着时间推移B所在链称为最长合法链,因此A、C、D区块都作废,但为了补偿这些区块所属矿工的工作,给这些区块一些“补偿”,并称其为“uncle block”(叔父区块)
规定E区块在发布时可以将A、C、D叔父区块包含进来,A、C、D叔父区块可以得到出块奖励的7/8,而为了激励E包含叔父区块,规定E每包含一个叔父区块可以额外得到1/32的出块奖励。为了防止E大量包含叔父区块,规定一个区块只能最多包含两个叔父区块,因此E在A、C、D中最多只能包含两个区块作为自己的出块奖励。

区块链技术与应用 - ETH基本原理

缺陷:
因为叔父区块最多只能包含两个,如图出现3个怎么办?
矿工自私,故意不包含叔父区块,导致叔父区块7/8出块奖励没了,而自己仅仅损失1/32。

Ghost协议新的版本

F为E后面一个新的区块。因为规定E最多包含两个叔父区块,所以假定E包含了C和D。此时,F也可以将A认为是自己的叔父区块。如果继续往下挖,F后的新区块仍然可以包含B同辈的区块(假定E、F未包含完)。这样,就有效的解决了上面提到的最初Ghost协议版本存在的陷阱。

区块链技术与应用 - ETH基本原理

对于M来说,无论包含哪个辈分的“叔父”,得到的出块奖励都是1/32出块奖励。 也就是说,叔父区块的定义是和当前区块在七代之内有共同祖先才可(合法的叔父只有6辈)。

区块链技术与应用 - ETH基本原理

以太坊中的奖励:

BTC:静态奖励(出块奖励)+ 动态奖励(交易费,占据比例很小)
ETH:静态奖励(出块奖励+包含叔父区块的奖励)+ 动态奖励(汽油费,占据比例很小,叔父区块没有)

区块链技术与应用 - ETH基本原理

如果对A->F作为一个整体给予出块奖励,这一定程度上鼓励了分叉攻击(降低了分叉攻击的成本,因为即使攻击失败也有奖励获得)。因此,ETH系统中规定,只认可A区块为叔父区块,给予其补偿,而其后的区块全部作废。

以太坊的真实数据

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

八、Ethereum – 挖矿算法

LiteCoin(莱特币)
莱特币的puzzle基于Scrypt。Scrypt为一个对内存性能要求较高的哈希函数,之前多用于计算机安全密码学领域。 莱特币挖矿算法基本思想设置一个很大的数组,按照顺序填充伪随机数。

比如有一个Seed为种子节点,通过Seed进行一些运算获得第一个数,之后每个数字都是通过前一个位置的值取哈希得到的。这样填充的特点是:这样的数组中取值存在前后依赖关系。

区块链技术与应用 - ETH基本原理

在需要求解Puzzle的时候,按照伪随机顺序,从数组中读取一些数,每次读取位置与前一个数有关。例如:第一次,从A位置读取其中数据,根据A中数据计算获得下一次读取位置B:第二次,从B位置读取其中数据,根据B中数据计算获得下一次读取位置C。

区块链技术与应用 - ETH基本原理

分析

如果数组足够大,对于挖矿矿工来说,必须保存该数组以便查询,否则每次不仅计算位置,还要根据seed计算整个数组数据,才能查询到对应位置的数据。这对于矿工来说,计算复杂度大幅度上升。

当然,矿工可以选择只保存一部分数据,例如:只保存奇数位置数据,偶数位置需要时再根据前一个奇数位置数据计算即可,从而对内存空间大小减少了一半(计算复杂度提高一点,但内存减少一半)。

核心思想:不能仅仅进行运算,通过增加其对内存的访问,从而实现对ASIC芯片不友好。

以太坊挖矿算法基本思想

以太坊中,设计了两个数据集,一大一小。小的为16MB的cache,大的数据集为1G的dataset(DAG)。其关系为,1G的数据集是通过16MB数据集生成而来的。为了便于验证,轻节点保存16MB的Cache进行验证即可,而矿工为了挖矿更快,减少重复计算则需要存储1GB大小的大数据集。
区块链技术与应用 - ETH基本原理
16MB的小cache数据生成方式与莱特币中生成方式较为类似。

  1. 通过seed进行一些运算获得第一个数,之后每个数字都是通过前一个位置的值取哈希获得的。

    莱特币:直接从数组中按照伪随机顺序读取一些数据进行运算
    以太坊:先生成一个更大的数组(注:以太坊中这两个数组大小并不固定,因为考虑到计算机内存不断增大,因此该两个数组需要定期增大)

  2. 大的DAG生成方式
    大的数组中每个元素都是从小数组中按照伪随机顺序读取一些元素,方法同莱特币相同。如第一次读取A位置数据,对当前哈希值更新迭代算出下一次读取位置B,在进行哈希值更新迭代计算出C位置元素。如此来回迭代读取256次,最终算出一个数作为DAG中第一个元素,如此类推,DAG中每个元素生成方式都依次类推。
区块链技术与应用 - ETH基本原理

分析
轻节点只保存小的cache,验证时进行计算即可。但对于挖矿来说,如果这样则大部分算力都花费在了通过Cache计算DAG上面,因此,其必须保存大的数组DAG以便于更快的挖矿。

以太坊挖矿过程

区块链技术与应用 - ETH基本原理

根据区块block header和其中nonce值计算一个初始哈希,根据其映射到某个初始位置A,读取A位置的数及其相邻的后一个位置A’上的数,根据该两个数进行运算,算得下一个位置B,读取B和B’位置上的数,依次类推,迭代读取64次,共读取128个数。最后,计算出一个哈希值与挖矿难度目标阈值比较,若不符合就重新更换nonce,重复以上操作知道最终计算哈希值符合难度要求。

伪代码理解以太坊挖矿算法

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
目前以太坊挖矿以GPU为主,可见其设计较为成功,这与以太坊设计的挖矿算法(Ethash)所需要的大内存具有很大关系。

1G的大数组与128k相比,差距8000多倍,即使是16MB与128K相比,也大了一百多倍,可见对内存需求的差距很大(况且两个数组大小是会不断增长的)。

当然,以太坊实现ASIC Resistance除了挖矿算法设计之外,还存在另外一个原因,即其预期从工作量证明(POW)转向权益证明(POS)

权益证明(POS:Proof of State)

权益证明:按照所占权益投票进行共识达成,类似股份制有限共识按照股份多少投票,权益证明不需要挖矿。

而这对于ASIC矿机厂商来说,就好比一把悬在头上的达摩克利斯之剑。因为ASIC芯片研发周期很长,成本很高,如果以太坊转入权益证明,这些投入的研发费用将全部白费(ASIC矿机只能用于挖特定的加密货币)

但实际上,以太坊目前仍然是POW挖矿共识机制。在设计之初,以太坊开发者就设想要从POW转向POS,并为了防止有矿工不愿意转埋下了一颗“难度炸弹”。但截至目前,以太坊仍然基于POW共识机制。

预挖矿(Pre-mining)

以太坊中采用的预挖矿的机制。这里“预挖矿”并不挖矿,而是在开发以太坊时,给开发者预留了一部分货币给开发者。以太坊的早期开发者,目前就很有钱了。

而比特币并未采用这一模式,所有比特币都是通过挖矿产生的。但早期挖矿难度容易,所有中本聪本人本来就有很多币(但没花)

和Pre-Mining对应,还有Pre-Sale,Pre-Sale指的是将预留的货币出售掉用于后续开发,类似于拉风投或众筹。目前,各类加密货币很多,存在一部分货币就在采用Pre-Sale来获取资金,如果此时买入,后续如果该货币取得成功,同样可以获得很大收益,但真正成功的货币只占少数,这就是其风险性。

以太坊的统计数据

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

九、Ethereum – 难度调度

比特币难度调整是每隔2016个区块调整难度,从而达到维持出块时间10min的目标。而以太坊则与之不同,每个区块都可能会进行难度调整。以太坊难度调整较为复杂,存在多个版本,网络上存在诸多不一致,这里遵循以代码逻辑为准的原则,从代码中查看以太坊难度调整算法。

以太坊难度调整

以太坊中区块难度调整算法如下图所示:
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

难度炸弹

根据以上以太坊难度调整算法可以看到,该算法可以很好地动态调整挖矿难度,从而保障系统整体出块时间维持在15s左右。但为什么要设置难度炸弹?

以太坊在设计之初就计划要逐步从POW(工作量证明)转向POS(权益证明),而权益证明不需要挖矿。该设计是为了促使矿工自愿转入POS。

从旁观者角度来看,挖矿消耗了大量电力、资金等,如果转入放弃挖矿,必然是一件好事。但从矿工的角度,花费了很大精力投入成本购买设备,突然被告知“不挖矿了”,这必然是一件很难接受的事情。而以太坊本身为一个分布式系统,其转入POS必须经过系统中大多数矿工认可才行,如果届时矿工联合起来转入POS,那么这一设计初衷就成了一江流水。因此,以太坊在设计之初便添加了难度炸弹。

区块链技术与应用 - ETH基本原理
数学上,指数函数是一个很可怕的东西。我们谈论一个算法,无论其时间复杂度还是空间复杂度,只要达到了指数级别,这个算法必然难以应用到大规模计算上。指数函数在前期增长相对缓慢,但在后期呈现“指数爆炸”,而这往往是我们无法通过升级硬件所能解决的。

可以看到,在以太坊早期时,区块号较小,难度炸弹计算所得值较小,难度调整级别基本上通过难度调整中自适应难度调整部分决定,而随着越来越多区块被挖出,难度炸弹的威力开始显露出来,这也就使得挖矿变得越来越难,从而迫使矿工愿意转入POS。

难度炸弹调整

实际上,在以太坊最初的设计中,并没有第二个公式。也就是说,最初就是简单的直接用区块编号除以100000。而在转入权益证明时间节点一再推迟后,以太坊系统采用了将区块编号回退三百万个区块的方法来降低挖矿难度。当然,为了保持公平,也将出块奖励从5个以太币减少到了3个以太币,这也是fake block number这一项出现的原因。
区块链技术与应用 - ETH基本原理

以太坊发展

区块链技术与应用 - ETH基本原理

  1. 难度计算公式
    bigTime为当前区块时间戳,bigParentTime为当前区块的父区块时间戳。
    区块链技术与应用 - ETH基本原理
  2. 基础部分计算
    区块链技术与应用 - ETH基本原理
  3. 难度炸弹计算
    区块链技术与应用 - ETH基本原理

以太坊实际统计数据(统计截至2018年)

  1. 以太坊挖矿难度变化曲线
    断崖式下跌是由于下调难度炸弹300W个区块
    区块链技术与应用 - ETH基本原理
  2. 以太坊出块时间变化曲线图
    区块链技术与应用 - ETH基本原理
  3. 两个真实区块信息
    difficulty为当前区块难度,total difficulty为当前区块链上所有区块难度相加。可见,最长合法链也就等同于最难合法链(难度最大合法链)
    区块链技术与应用 - ETH基本原理

十、Ethereum – 权益证明

比特币系统(POW机制)和以太坊目前都是使用的基于工作量的证明,这种共识机制受到了普遍的批评——浪费电

区块链技术与应用 - ETH基本原理
采用权益证明的加密货币一般会预留一部分货币给开发者,也会出售一部分货币用作货币的开发资金,将来按照权益证明的共识机制,每个人是按照资金的多少来进行投票。

基于工作量证明的共识系统从某种意义来说维护区块链安全的资源不是一个闭环(越挖矿,越安全)。虽然 某种虚拟货币的总市值很高,但是跟某些组织相比,虚拟货币的总市值是微乎其微的,所以要是某个组织恶意发动攻击,只需要足够的资金来购买挖矿设备,然后聚集到一半以上的算力就可以,这个例子是说现实世界对这种区块链系统仍具有威胁能力。

比特币这种比较主流的加密货币,抗攻击的能力是比较强的,因为系统的总算力是比较大的。但是对于AltCoin这种小的币种,遇到这种攻击将是毁灭性的,很可能这个币的价值会直线下滑,这对于系统开发者和早期的挖矿者造成的损失将是灾难性的,一个专门的词就是infanticide把它扼杀在摇篮里。

我们采用权益证明的一个好处就是:无论某些组织再有钱,都不能通过外界的矿机等东西来威胁系统,只有通过获得更多的加密货币,才能对货币系统产生威胁,这就是我们说的基于工作量证明的共识系统从某种意义来说维护区块链安全的资源不是一个闭环(越挖矿,越安全),基于权益证明的货币系统是一个闭环,只能在系统内部发动攻击。而一旦有人通过大量法币来购买货币,会造成货币价格大涨,所以对于货币开发者来说这不一定是一个坏事。

权益证明和工作量证明并不是互斥的,有的加密货币采用的是一种混合模型,它仍然是要挖矿的,但是挖矿的难度跟你占有的权益,你持有多少币是相关的。

将比拼的钱进行一定时间内冻结,使得这笔钱不能参与下一次区块的发布权。这种做法有时候叫做proof of deposit。

基于权益证明的共识机制该怎么设计有很多挑战,其中这种早期的权益证明遇到的一个挑战就是两边下注的问题(nothing at stake)

区块链技术与应用 - ETH基本原理

以太坊准备采用的权益证明

以太坊中准备采用的权益证明协议叫做Casper the Friendly Finality Gadget,该协议在过渡阶段也是要跟工作量证明混合使用的,为工作量证明提供叫做Finality,Finality是一种最终的状态,包含在Finality中的交易不会被取消。
单纯基于工作量证明就基于挖矿的交易是有可能被回滚的,就比如说,某个交易被写到区块链上,然后有人从前面开始分叉,挖出一条更长的分叉链,这个时候原来写入区块链的那个交易有可能就无效了,比特币当中规定要等六个确定区块,那个只是说等六个确定区块之后,发生回滚的可能性已经非常小了,但是有个某个恶意的攻击者,从前面开始分叉,只要他算力强到占到半数以上的算力,仍然有可能让这个分叉链变得比原来的链更长。

Casper协议引入了一个概念叫做验证者Validator,要想成为一个Validator必须要投入一定数量的以太币作为保证金,这个保证金会被系统锁定。Validator的职责是要推动系统达成共识,投票决定哪条链是最长合法链,投票的权益决定于保证金的数目大小

具体的做法是,混用的时候还是有人挖矿的,挖矿的时候,每挖出100个区块就作为一个epoch,然后决定能不能成为Finality,要进行一些投票,第一轮投票是一个Prepare Message,然后第二轮是Commit Message,Casper规定每一轮投票都要得到2/3的验证者才能通过,这事按照保证金的金额大小来算的

实际系统中不再区分这两个Message,而且这个epoch从原来的100个区块减少到50个区块,变成了每50个区块就是一个epoch,每个epoch只用一轮投票的就行了,这一轮投票对于上一个epoch来说是个Commit Message,对于下一个来说是一个Prepare Message,那么要连续两轮投票,两个epoch都得到2/3以上的多数,才算有效

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
验证者在投票的过程中要是

不投票:扣掉部分保证金
乱投票:比如有两边下注的行为,直接没收全部保证金

casper协议可以给挖矿挖出的区块链的某种状态做一个检查点:一个check point,那么这个check point是不是绝对安全的?通过这个验证者达成的finality有没有可能被推翻?

假如某个矿工要推翻一个finality是不可能的,为什么?
因为finality 是验证者投票投出来的,单纯是有恶意的矿工,无论算力有多强,假如没有验证者作为同伙,finality 是不可能被推翻的。

那么什么样的情况会造成finality被攻击成功的情况?
一定是有大量的验证者给两边的分叉都进行下注的情况下,因为casper协议要求每轮投票要求有2/3以上的验证者支持才能通过,如果出现这种情况,那么至少有1/3的验证者两边都投票了,这一旦发现这1/3的验证者的保证金将会被没收。

基于权益证明的共识机制与基于工作量的共识机制是完全不一样的。以太坊的设想是逐步将基于工作量证明过渡到基于权益证明,随着时间的推移,挖矿得到的奖励是越来越少的,权益得到的奖励是越来越多的,最后达到完全不用挖矿的境界。

十一、Ethereum – 智能合约

智能合约的本质是运行在区块链上的一段代码,代码的逻辑定义了智能合约的内容,智能合约的账户保存了合约当前的运行状态。

balance: 当前余额
nonce:交易次数
code: 合约代码
storage: 存储,数据结构是一棵MPT

智能合约的代码结构

区块链技术与应用 - ETH基本原理

Solidity是智能合约最常用的语言,语法上与JavaScript很接近,Solidity是面向对象的编程语言,这里的contract类似C++当中的类class,这里contract定义了很多状态变量,Solidity是强类型语言,这里的类型跟普通的编程语言像C++之类的是比较接近的,比如说uint(unsigned int)是无符号的整数,address类型是Solidity语言所持有的。

Solidity语言跟别的普通编程语言相比有一些特别之处:

比如mapping,mapping是一个哈希表,保存了从地址到unit的一个映射。Solidity语言中哈希表不支持遍历,如果想遍历哈希表里的所有元素,需要自己想办法记录哈希表中有哪些元素,这里使用bidders数组来记录的。Solidity语言中的数组可以是固定长度的,也可以是动态改变长度的,这里是一个动态改变长度的数组。如果想在数组里增加一个元素,就用push操作,bidders.push(bidder),新增加一个出价人在数组的末尾,要想知道这个数组有多少个元素,可以用bidders.length。如果是固定长度的数组,就要写明数组的长度,比如说address[1024],这个就是长度为1024的数组。

再往下是构造函数,构造函数只能有一个,Solidity语言中定义构造函数有两种方法:

  1. 一种方法就是像C++构造函数一样,定义一个与contract同名的函数,这个函数可以有参数,但是不能有返回值。
  2. 新版本Solidity语言更推荐用这个例子的方法,就用一个constructor来定义一个构造函数,这个构造函数只有在合约创建的时候会被调用一次。

最后是三个成员函数,三个函数都是public,说明其他账户可以调用这些函数。

账户调用

外部账户如何调用智能合约?

区块链技术与应用 - ETH基本原理

调用智能合约其实跟转账是类似的,比如说A发起一个交易转账给B:

  1. 如果B是一个普通的账户,那么这就是一个普通的转账交易,就跟比特币当中的转账交易是一样的。
  2. 如果B是一个合约账户的话,那么这个转账实际上是发起一次对B这个合约的调用,那么具体是调用B合约中的哪个函数呢,是在data域中说明的。

上图这个例子中,sender address是发起这个调用的账户的地址,to contract address是被调用的合约的地址,调用的函数时txdata,如果函数是有参数的话,那么参数的取值也是在data域里说明的,上面看的网上拍卖的例子中,三个成员函数都没有参数,但是有的成员函数是可以有参数的。

中间那一行是调用的参数,value是说发起调用的时候转过去多少钱,这里是0,这个调用的目的仅仅是为了调用它的函数,并不是真的要转账,所以value=0。gas used是这个交易花了多少汽油费,gas price是单位汽油的价格,gas limit是这个交易我最多愿意支付多少汽油费。

一个合约如何调用另一个合约中的函数?

方法一:直接调用
区块链技术与应用 - ETH基本原理

以太坊中规定一个交易只有外部账户才能够发起,合约账户不能自己主动发起一个交易。所以这个例子中需要有一个外部账户调用了合约B当中这个callAfooDirectly函数,然后这个函数再调用合约A当中的foo函数。

方法二:使用address类型的call()函数

区块链技术与应用 - ETH基本原理

address类型的call()函数,第一个参数是要调用函数的签名,然后后面跟的是调用的参数。

这种调用方法跟上一个调用的方法相比,一个区别是对于错误处理的不同,直接调用时,如果你调用了那个合约在执行过程中出现错误,那么会导致发起调用的这个合约也跟着一起回滚,在直接调用的例子中如果A在执行过程中出现什么异常,会导致B这个合约也跟着一起出错。

而这种address.call()这种形式如果在调用过程中,被调用的合约抛出异常,那么这个call函数会返回false,表明这个调用是失败的,但是发起调用的这个函数并不会抛出异常,而是可以继续执行。

方法三:代理调用delegatecall()

区块链技术与应用 - ETH基本原理

代码调用和call()这种方法基本上是一样的,一个主要的区别是delegatecall()不需要切换到被调用的合约的环境中去执行,而是在当前合约环境中执行就可以了,比如就用当前账户的账户余额存储之类的。

以太坊中凡是要接受外部转账的函数都需要标志为payable,否则的话你给这个函数转钱就引发错误处理抛出异常,如果你不需要接受外部转账,函数就不用写payable。具体

区块链技术与应用 - ETH基本原理

bid函数有一个payable,另外两个函数都没有。以太坊中规定如果这个合约账户要能接受外部转账的话,那么必须标注成payable。

方法四:fallback()函数

无参数无返回值,无函数名,fallback关键字并没有出现在函数名里面。

调用合约的时候,A调用B合约,要在转账交易的data域说明调用的是合约B中的哪个函数,如果A给B转了一笔钱,没有说明调用的是哪个函数,也就是data域是空的,这个时候缺省的就是调用这个fallback函数,这也是为什么叫fallback函数,因为没有别的函数可以调用了,就只能调用他。还有一种情况是你要调用的函数不存在,在你的data域里你说你要调用这个函数,实际合约当中没有这个函数,也是调用fallback函数,这也是为什么这个函数没有参数也没有返回值。

fallback函数也可能需要标注payable关键词,就如果fallback函数需要有接受转账的能力的话是需要写payable,一般情况都是写成payable,如果合约账户没有任何函数标志为payable,包括fallback函数也没有,那么这个合约没有任何能力可以接受外部的转账。如果有人往合约里转钱就会引发异常。
区块链技术与应用 - ETH基本原理

智能合约的创建

智能合约的创建是由某一个外部的账户发起一笔转账交易,转给0X0地址,然后把要发布的合约代码放到data域里面。

智能合约运行在EVM上。Java Virtual Machine(JVM)是为了增强可一致性,EVM也是类似的思想,通过加一层虚拟机,对智能合约的运行提供一致性的平台,所以EVM又叫world wide compute,EVM的寻址空间是非常大的,是256位的,像如之前讲的uint和signed int就是256位的,普通计算机是64位的。

区块链技术与应用 - ETH基本原理

汽油费

比特币和以太坊两种区块链模型的设计理念是有很大差别的,比特币的设计理念是简单,脚本语言的功能很有限,不支持循环.

停机问题已经从理论上证明不存在这样的算法能够对任意给定的输入程序判断出这个程序是否会停机,这是不可解的。

以太坊中如何解决的呢?
把这个问题推给发起交易的账户,以太坊引入了汽油费机制,你发起一个对智能合约的调用需要支付相应的汽油费。

区块链技术与应用 - ETH基本原理

交易的数据结构:

  1. AccountNonce是交易的序号,用于防止前面说到的replay attack(重放攻击),price是单位汽油的价格,Gaslimit是这个交易愿意支付的最大汽油量,相乘之后就是这个交易可能消耗的最大汽油费。
  2. recipient是收款人的地址,amount的转账金额,可以看到交易中汽油费跟转账金额是分开的。
  3. payload就是之前说的data域,用于存放调用的是合约中哪一个函数以及函数的参数取值是什么。

当一个全节点收到一个对智能合约的调用的时候,先按照这个调用给出的gas limit算出可能花掉的最大汽油费,然后一次性把汽油费从发起调用的账户中扣掉,然后再根据实际执行情况算出实际花了多少汽油费,汽油费不够会引起回滚。

区块链技术与应用 - ETH基本原理

错误处理

以太坊中的交易执行起来具有原子性,一个交易要么全部执行要么完全不执行,不会只执行一部分。这个交易既包含普通的转账交易也包含对智能合约的调用,所以如果在执行智能合约过程中出现任何错误,会导致整个交易的执行回滚,退回到开始执行之前的状态,就好像这个交易完全没有执行过。

  • 错误处理一: 之前所说的汽油费,如果这个交易执行完之后没有达到当初的gaslimit,那么多余的汽油费会被退回到这个账户里;相反的,如果执行到一半,gaslimit用完了,合约的执行要退回到开始执行之前的状态,而且这个时候已经消耗的汽油费是不退的。
  • 错误处理二:
    • assert语句和require语句,这里两个语句都是用来判断判断某种条件,如果条件不满足的话就会导致抛出异常。
    • assert语句一般来说是用来判断某种内部条件,和c语言中的类似;
    • reuire语句判断某种外部条件,比如说判断函数的输入是否符合要求,下图所给的例子是bid函数里,判断当前时间now是否小于等于拍卖结束时间,如果符合条件,继续执行,不符合,即拍卖时间已经结束了,这个时候就会抛出异常。
  • 错误处理三: revert语句无条件抛出异常,如果执行到revert语句,那么他自动的就会导致回滚,早期版本用的是throw语句,新版本的solidity建议改为revert语句。
区块链技术与应用 - ETH基本原理

嵌套调用

智能合约出现错误会导致回滚,那么如果是嵌套调用,一个智能合约调用另外一个智能合约,那么被调用的这个智能合约出现错误,是不是会导致发起调用的智能合约,也跟着一起回滚呢?所谓的连锁式回滚。

这个取决于调用这个智能合约的方式。如果是直接调用的话,会出现连锁式的回滚,这个交易都会回滚,如果调用方式使用call这种方式,就不会引起连锁式回滚,只会使当前的调用失败返回一个false的返回值。

有些情况下,从表面上看你并没有调用任何一个函数,比如说,你就是往一个账户里转钱,但是这个账户是合约账户的话,转账这个操作本身就有可能触发对函数的调用,因为有fallback()函数,这就是一种嵌套调用,一个合约往另外一个合约里转账,也有可能调用这个合约里的fallback函数。

区块链技术与应用 - ETH基本原理

Block Header中的GasLimit和GasUsed

Block Header中的GasLimit和GasUsed也是跟汽油费相关的,Block Header里面的GasUsed是这个区块里所有交易所消耗的汽油费加在一起。

发布区块需要消耗一定的资源,这个消耗的资源要不要有一个限制,比特币当中对于发布的区块也是有一个限制的,大小最多不能超过1M,因为发布的区块如果没有任何限制,有的矿工可能把特别多的交易全部打包到一个区块里面然后发布出去,那么这个超大的区块在区块链上会消耗很多资源,比特币交易是比较简单的,基本上可以用交易的字节数来衡量出这个交易消耗的资源有多少。但以太坊中如果这么规定是不行的,因为以太坊中智能合约的逻辑很复杂,有的交易可能从字节数上看是很小,但它消耗的资源可能很大,比如它可能调用别的合约之类的,所以要根据交易的具体操作来收费,这就是汽油费。

Block Header里面的GasLimit是这个区块里所有交易能够消耗的汽油的一个上限,不是说把区块里每个交易的GasLimit加在一起,如果那样的话,等于没有限制了,因为每个交易的GasLimit是发布这个交易的账户自己定的,定多少是自己说了算,但是这个区块中所有交易,实际能够消耗的汽油是有一个上限,不能无限的消耗,否则你也可能发布一个对资源消耗很大的区块,对整个系统的运行是没有好处的。

以太坊和比特币的区别:
比特币限制资源是按照大小来限制的,而且这个1M的上限是固定了的,是写死在协议里面的,有些人认为1M太小了,而且有的分叉币的产生就是为了提高这个上限。

以太坊中也有一个上限,这个GasLimit,但是每个矿工在发布区块的时候可以对GasLimit进行微调,可以在上一个区块的GasLimit的基础上上调或下调1/1024。如果出现像比特币那种情况,大家都觉得这个GasLimit设置的太小了,那轮到你发布区块的时候可以增加1/1024,以太坊的出块速度很快,十几秒就是一个新的区块,所以的话,如果大家都觉得当前的GasLimit太小,那么很快就可以翻一番。当然,也可能下调,有矿工认为GasLimit太大了需要下调,所以这种机制实际上求出的GasLimit,是所有矿工认为比较合理的GasLimit的一个平均值。

区块链技术与应用 - ETH基本原理

Receipt数据结构

区块链技术与应用 - ETH基本原理

智能合约支不支持多线程?多核并行处理。 Solidity不支持多线程,没有多线程的语句。

智能合约可以获得的信息

  1. 区块信息
    区块链技术与应用 - ETH基本原理
    智能合约的执行必须是确定性的,这也就导致了智能合约不能像通用的变成语言那样通过系统调用来得到一些环境信息,因为每个全节点的执行环境不是完全一样的,所以它只有通过一些固定的变量的值能够得到一些状态信息,上图就是智能合约能够得到区块链的一些信息。

  2. 调用信息
    区块链技术与应用 - ETH基本原理

地址类型

区块链技术与应用 - ETH基本原理

上图中第一个是个成员变量,剩下的都是成员函数。成员变量就是账户的余额balance,unit256是这个成员类型的类型,事宜Wei为单位的,是个很小的单位。

下面这些成员函数的话,有一点要注意的,这些成员函数的语义跟我们直观上的理解不是很一样,跟第一个成员变量balance也不太一样。

addr.balance是address这个地址上他的账户余额,那addr.transfer(12345)是什么意思呢?感觉像是addr这个账户往外转了12345个Wei,是不是这个意思?如果是这个意思的话,问题在于他只有一个参数,他只有转账的金额,没有说转给谁,所以addr.transfer(unit amount)是什么意思呢?并不是说addr这个账户往外转了多少钱,而是当前这个合约往addr这个地址里转入多少钱,这个addr是转入地址不是转出地址,转出的地址是哪一个?比如说这是个智能合约C,里面有一个函数f,它包含这条语句addr.transfer(12345),意思是说从C这个合约的账户往addr这个地址里转入12345这么多的钱。

addr.call也是一样的语句,并不是说addr这个合约账户发起了一个调用,而是说当前这个合约发起了一个调用,调的是addr这个合约。

delegatecall区别就是说不需要切换到被调用的函数的环境中,就用当前合约的余额,当前合约的存储这个状态去运行就可以了。

区块链技术与应用 - ETH基本原理

transfer和send的区别

区别1在于transfer会导致连锁型回滚,类似于调用那个函数直接调用的方法是一样的,失败的时候抛出异常,而send返回一个false,不会导致连锁式回滚。
区别2是transfer和send在发起调用的时候,只给了一点儿汽油,是2300个单位,非常少的,那么收到这个转账的合约基本上干不了别的事,只是写了一个log,而call是把当前这个调用剩下的所有汽油都发过去

拍卖的例子

区块链技术与应用 - ETH基本原理

如果要搞一个竞拍要写一个solidity程序,然后发布一个交易,把这个合约放到网上,别人怎么知道这个合约是需要自己线下宣传的,区块链是不管的,然后别人知道合约地址后进行竞拍,都是在区块链上通过转账交易执行的
智能合约的代码是储存在data域里面的,矿工把智能合约发布到区块链上之后返回给你一个合约的地址,然后这个合约就在区块链上了,所有人都可以调用。
任何人出价竞拍调用Bid函数的操作都需要矿工发布在区块链上。

黑客可以利用fallback实现整个拍卖的失败。

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理

改进版本

把前面的auctionend函数拆成两个函数,左边是withdraw右边是beneficiary。
withdraw是说不用循环了,每个竞拍失败的人自己调用withdraw函数把钱取出来。判断这个人是不是最高出价者,是的话不能退钱。判断账户余额是不是正的,amount就是账户余额,if 把账户余额转给msg.sender,就发起调用的人,然后把账户余额清0,免得下次再取钱。
pay2Beneficiary把最高出价给受益人。

区块链技术与应用 - ETH基本原理

改进版本的问题——重入攻击
区块链技术与应用 - ETH基本原理
这个递归重复到

  1. 拍卖合约上的余额不够了,不支持这样的转账语句,
  2. 汽油费不够了,每次递归调用还是消耗汽油费的,
  3. 调用栈溢出了

在右下角黑客合约的fallback函数判断一下拍卖合约的余额还足以支持转账,当前调用的剩余汽油msg.gas还有6000个单位以上,调用栈的深度不超过500,那么就再发起一轮攻击。

如何解决重入攻击?
可以先清0再转账,和第二版的右边写法一致,转账不成功再回复余额。

也可以不用call.value来转账,换成send或者transfer。
先清0再转账,send和transfer有一个特点就是转账的时候发送过去的汽油费只有2300个单位,不足以让接收的合约再发起新的调用,只够写一个log而已。
区块链技术与应用 - ETH基本原理

十二、Ethereum – TheDAO和美链

比特币实现了去中心化的货币,以太坊实现了去中心化的合约,有人想既然去中心化这么好,为什么不把所有的东西都改成去中心化呢?有人提出口号:let’s decentralize everything。DAO(Decentralized Autonomous Organization,去中心化的自治组织)就是在这个背景下产生的

工作原理有点像DAC(Decentralized Autonomous Corporation,去中心化的自治公司)

The DAO 2016年5月份开始众筹,但最后The DAO一共只存活了3个月。

区块链技术与应用 - ETH基本原理

上图是split DAO的代码,从withdrawRewardFor这个语句开始,首先把钱还给调用这个函数的人,然后把The DAO中的总金额减少相应的数量,再把调用者的账户清0。在上一节讲过,正确的操作是先把账户清0,然后再转账。黑客就是利用这个漏洞进行的重入攻击,转走了5千万美元的以太币,差不多1/3。

最后造成硬分叉,形成了ETH和ETC。

为了解决重放攻击,即在新链上的合法交易放到旧链上去同样是合法的,反过来也是一样。后来给两条链增加了chainID来解决

区块链技术与应用 - ETH基本原理
batchTransfer函数的实现
美链中有一个叫batchTransfer的函数,就是一次性向很多个接收者发送代币,然后把这些代币从调用这个函数的账户上扣掉。

区块链技术与应用 - ETH基本原理

batchTransfer函数有两个参数,第一个参数是数组,接受代币者的地址,函数中规定接收者的数目最多是20个,第二个参数value是转账的金额,先算一下总金额amount,recevier的数目和每个接受的代币计算了然后检查一下发起调用的账户msg.sender确实是有这么多代币的。之后把发起账户的代币数目减去amount,下面是一个循环给每一个接收者发送value这么多的代币。

上图红框中的乘法,当value值很大的时候可能会发生溢出,amount算出来可能是个很小的一个值,所以从调用者的代币中减的时候是很小一部分的代币,但还是每个receivers增加那么多value的代币,这样做造成了系统中凭空多发行了很多代币。

区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
区块链技术与应用 - ETH基本原理
反思
在进行数学运算的时候一定要考虑溢出的可能性。Solidity有一个safeMath库,里面提供的操作运算都会自动检测有没有出现溢出。

区块链技术与应用 - ETH基本原理

Solidity里面是不存在的,因为两个数都是256位的整数,整数先进行乘法,再进行除法。

十三、Ethereum – 反思

智能合约真的智能吗?

智能合约并没有用到人工智能,应该叫做“自动合约”,按照事先写好的代码自动执行某些操作。

现实世界中自动执行某些操作的例子是ATM取款机,物理世界的自动合约,插入银行卡输入密码,就会自动把钱给你。

智能合约其实并不智能,因为一旦写好之后就无法修改,实际上是一种代码合同。

智能合约是用来编写控制逻辑的,只有在那些互不信任的实体之间建立共识的操作,才需要写在智能合约里。

不可篡改性

不可篡改性是一把双刃剑。

优点:不可篡改性增加了合同的公信力,大家都只能按照合约中的规定去做,没有人能篡改。

缺点:如果规则中有漏洞,无法修复漏洞。结果就是无法终止交易,智能合约发布到区块链上也无法阻止别人对它的调用

没有什么是真的不可篡改的

The DAO事件中告诉我们,ETH可以硬分叉,即没有什么是绝对不可篡改的

去中心化的反思

去中心化并不是全自动化,让机器决定一切,不能有人为干预。去中心化并不是说已经制订的规则就不能修改,而是说对规则的修改要用去中心化的方法来完成。

TheDAO的硬分叉之所以能够成功,是因为大多数矿工认为以太坊团队做出的决定是符合公众利益的。

一个去中心化的系统必然是分布式的

分叉

一般认为分叉是件坏事,本来是一条链,分叉之后变成了两条链。但是分叉恰恰是去中心化的一种体现,在中心化的系统里可以放弃但是不能分叉。

十四、Ethereum – 总结

区块链应用的争议,现在社会上区块链的争议是非常大的,有很多对区块链的质疑其实也是有道理的,为什么会有那么多人会质疑这个技术,其中一个原因是区块链的概念被滥用了

区块链的模式就是类似git,一个远端一个本地共同维护一个主链

十五、Ethereum – Other

BTC

用户只保存最近的一些区块,当要用到先前产生的区块,再向别人要就可以,这向别人要的过程是什么?(2-区块链)
答:在网络上传播自己需要哪个块,然后拥有这些块的人会给他

可以要求转账的人发来哈希值,那转账的人和全节点的关系 是什么?(2-Merkle proof )
答:转账的人可以不是全节点,全节点可以转账,全节点在网络中是较少的

为了说明币的来源是从哪个交易来的而设置的指针是怎么存储工作的,怎么知道自己的来源交易所在的区块在哪里?(3-去中心化)转账交易怎么说明币的来源?(4-transaction-based ledger)
答:UTXO集合中的每个元素要给出产生这个输出的交易的哈希值,以及它在这个交易中是第几个输出。用这两个信息就可以定位到一个确定的交易中确定的输出。然后交易的输入脚本中给出了UTXO集合中的来源交易的哈希值和对应在来源交易的第几个输出,这样就提供了币的来源。

zero confirmation指交易刚发布出去,还没有写入区块链中的时候,就认为交易已经不可篡改了,这是为什么?(4-比特币安全性的分析)
答:这是个个人行为,自己觉得不能篡改了

发布了交易以后,矿工打包进区块,这样才算交易完成吗?那如果一直没被打包这个交易也当作没执行吗?(7-挖矿)
答:是的,只要不上链就不算交易完成,就需要一直等着

ETH

怎么检测前面的链上有多少叔父区块?
答:网络中会传播,如果有一个块产生别的节点都可以监听到。

合约账户要能接受外部转账的话必须标注为payable,那给外部转账就不需要吗?(9-solidity)
答:payable指的是可接受转账,给外部转账的话就说外部账户里标注了

外部账户调用智能合约,调用的函数是TXdata里面给出的要调用函数,如果这个函数有参数,那么其参数也在这里的data域里说明的,不是很明白TXdata里面的函数和data域里面的参数分别是什么。(9-solidity)
答:TXdata里面前四个是字节函数,后面的是参数。每个函数都会算出一个哈希值然后代表他自己,data域里面就是这个哈希值。

receive()与fallback()

receive() 
一个合约只能有一个receive函数,该函数不能有参数和返回值,需设置为external,payable;当本合约收到ether但并未被调用任何函数,未接受任何数据,receive函数被触发;

fallback() 
一个合约只能有一个receive函数,该函数不能有参数和返回值,需设置为external;可设置为payable;当本合约的其他函数不匹配调用,或调用者未提供任何信息,且没有receive函数,fallback函数被触发;

Require(), Assert(), Revert()的用法和区别

    1. revert的用法和throw很像,也会撤回所有的状态转变。但是它有两点不同
    --------------
    1.它允许你返回一个值;
    2.它会把所有剩下的gas退回给calle.调用起来就像这样子:
      revert(‘Something bad happened’);
      require(condition, ‘Something bad happened’);
    --------------

    2. require
    --------------
    验证一个用户的输入是否合法:require(input<20);                  
    验证一个外部协议的响应:require(external.send(amount));               
    判断执行一段语句的前置条件,验证合约执行前的状态: require(block.number > SOME_BLOCK_NUMBER) or require(balance[msg.sender]>=amount);
    require应该被最常使用到;一般用于函数的开头处。
    --------------

    3. assert
    --------------
    检查有没有上溢overflow或者是下溢underflow: ie. c = a+b; assert(c > b)             
    检查常数非变量(invariants):assert(this.balance >= totalSupply);             
    在完成变化后检查状态,避免本不应该发生的情况出现,如程序的bug;              
    assert不应该被经常利用到,一般用于函数结尾处。               
    验证改变后的状态;预防不应该发生的条件。             
    --------------
    
    基本上,require() 应该被用于函数中检查条件,assert() 用于预防不应该发生的情况,但不应该使条件错误。另外,除非认为之前的检查(用 require )会导致无法验证 overflow,否则不应该盲目使用 assert来检查 overflow

transfer()

Account.transfer(msg.value);           
合约发起方向Account地址转入msg.value个以太币                       
owner.transfer(address(this).balance)           
this代表的是合约地址;msg.sender是调用合约的账户地址;所以代表,合约里的钱转入合约的拥有者账户。                     

delegatecall()

两种底层调用方式 call 和 delegatecall的区别
call: 最常用的调用方式,调用后内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境。
delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。
简而言之,区别仅在于后者仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。

selfdestruct

析构函数和构造函数对应,构造函数是初始化数据,而析构函数是销毁数据。

selfdestruct(address recipient):destroy the current contract, sending its funds to the given Address

transferFrom()和approve()

function approve(address _spender, uint _value) returns (bool success)

此函数的调用方授权给定的地址可以从其地址中提款。

function transferFrom(address _from, address _to, uint _value) returns (bool success):

该函数允许智能合约自动执行转账流程并代表所有者发送给定数量的通证。例如:你可以与银行签订自 动代支付协议。 这就像使用transferFrom() :银行的机器会自动以你的名义进行转账支持。 有了这个函数, 合约就可以代表你自动发送通证到另一个地址,而无需你的干预。

transfer/call/send

address.transfer()
address.send()
address.call.value().gas()()

transfer与send相似,都为转账操作
transfer出错抛出异常
send、call出错不抛出异常,返回true或false
tansfer相对send更安全
send、call即便转账失败也会执行其后的代码
慎用call函数转账,容易发生重入攻击。

solidity里面的存储

每一个存储位是 32 个字节。根据 Solidity 优化规则,当变量所占空间小于 32 字节时,会与后面的变量共享空间

constant常量不写入存储

布尔类型占1个字节

状态变量的存储模型(Layout of State Variables in Storage)
大小固定的变量(除了映射,变长数组以外的所有类型)在存储(storage)中是依次连续从位置0开始排列的。如果多个变量占用的大小少于32字节,会尽可能的打包到单个storage槽位里,具体规则如下:

1. 在storage槽中第一项是按低位对齐存储(lower-order aligned)
2. 基本类型存储时仅占用其实际需要的字节。
3. 如果基本类型不能放入某个槽位余下的空间,它将被放入下一个槽位。
4. 结构体和数组总是使用一个全新的槽位,并占用整个槽(但在结构体内或数组内的每个项仍遵从上述规则)

交易所
https://www.coingecko.com/
https://etherscan.io/
以太坊开源项目
https://github.com/ethereum/go-ethereum
以太坊开发文档
https://goethereumbook.org/zh/
https://www.lanqiao.cn/library/go-ethereum/
https://ethereumdocch.readthedocs.io/zh/latest/index.html
https://ethereum.org/zh/developers/docs/
参考文献
https://www.cnblogs.com/klgzyq/tag/%E5%8C%BA%E5%9D%97%E9%93%BE/
https://www.cnblogs.com/coderzjz/p/14025979.html
https://www.zhihu.com/column/c_1401565487432552448
https://blog.csdn.net/Mu_Xiaoye/article/details/104299664
https://segmentfault.com/u/mackingjay
https://cloud.tencent.com/developer/article/1585653

正文完
 2
Chou Neil
版权声明:本站原创文章,由 Chou Neil 于2024-12-13发表,共计24308字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(2 条评论)
CK 评论达人 LV.1
2024-12-16 01:51:12 回复

这是一个很不错的学习ETH的网站 https://www.wtf.academy/,推荐

 Windows  Chrome  美国俄亥俄美国电话电报
CK 评论达人 LV.1
2024-12-16 01:52:34 回复

Mark

 Windows  Chrome  美国俄亥俄美国电话电报