区块链

区块链就是一个具有共享状态的密码性安全交易的单机(cryptographically secure transactional singleton machine with shared-state)。

  • “密码性安全(Cryptographically secure)”是指用一个很难被解开的复杂数学机制算法来保证数字货币生产的安全性。将它想象成类似于防火墙的这种。它们使得欺骗系统近乎是一个不可能的事情(比如:构造一笔假的交易,消除一笔交易等等)。
  • “交易的单机(Transactional singleton machine)”是指只有一个权威的机器实例为系统中产生的交易负责任。换句话说,只有一个全球真相是大家所相信的。
  • “具有共享状态(With shared-state)”是指在这台机器上存储的状态是共享的,对每个人都是开放的。

    以太坊实现了区块链的这个范例。

以太坊

  • 定义:以太坊的目的是基于脚本、竞争币和链上元协议(on-chain meta-protocol)概念进行整合和提高,使得开发者能够创建任意的基于共识的、可扩展的、标准化的、特性完备的、易于开发的和协同的应用。
  • 实际上,以太坊,Ethereum是一个分布式的计算机,有许多的节点,其中的每一个节点,都会执行字节码(其实就是智能合约),然后把结果存在区块链上。由于整个网络是分布式的,且应用就是一个个的状态组成,存储了状态就有了服务;所以它就能永不停机,没有一个中心化的结点(没有任何一个节点说了算,去中心化的),任何第三方不能干预。

一些概念

  1. 以太坊的本质就是一个基于交易的状态机(transaction-based state machine)。在计算机科学中,一个状态机是指可以读取一系列的输入,然后根据这些输入,会转换成一个新的状态出来的东西。
  2. 根据以太坊的状态机,我们从创世纪状态(genesis state)开始。这差不多类似于一片空白的石板,在网络中还没有任何交易的产生状态。当交易被执行后,这个创世纪状态就会转变成最终状态。在任何时刻,这个最终状态都代表着以太坊当前的状态。
  3. 以太坊的状态有百万个交易。这些交易都被“组团”放到一个区块中。一个区块包含了一系列的交易,每个区块都与它的前一个区块链接起来。
  4. 为了让一个状态转换成下一个状态,交易必须是有效的。为了让一个交易被认为是有效的,它必须要经过一个验证过程,此过程也就是挖矿。挖矿就是一组节点(即电脑)用它们的计算资源来创建一个包含有效交易的区块出来。
  5. 任何在网络上宣称自己是矿工的节点都可以尝试创建和验证区块。世界各地的很多矿工都在同一时间创建和验证区块。每个矿工在提交一个区块到区块链上的时候都会提供一个数学机制的“证明”,这个证明就像一个保证:如果这个证明存在,那么这个区块一定是有效的。为了让一个区块添加到主链上,一个矿工必须要比其他矿工更快的提供出这个“证明”。通过矿工提供的一个数学机制的“证明”来证实每个区块的过程称之为工作量证明(proof of work)。

深入地了解一下以太坊系统主要组成部分:

  • 账户(accounts)
  • 状态(state)
  • 损耗和费用(gas and fees)
  • 交易(transactions)
  • 区块(blocks)
  • 交易执行(transaction execution)
  • 挖矿(mining)
  • 工作量证明(proof of work)

以太坊账户

以太坊的全局“共享状态”是有很多小对象(账户)来组成的,这些账户可以通过消息传递来与对方进行交互。每个账户都有一个与之关联的状态(state)和一个20字节的地址(address)。在以太坊中一个地址是160位的标识符,用来识别账户。

以太坊有两种类型的账户:

  • 外部账户(由私钥控制的)
  • 合约账户(由合约代码控制)。

两种账户都可以发起交易,后者被动发送。

外部拥有账户与合约账户的比较

  • 一个外部拥有账户可以通过创建和用自己的私钥来对交易进行签名,来发送消息给另一个外部拥有账户或合约账户。在两个外部拥有账户之间传送的消息只是一个简单的价值转移。但是从外部拥有账户到合约账户的消息会激活合约账户的代码,允许它执行各种动作。(比如转移代币,写入内部存储,挖出一个新代币,执行一些运算,创建一个新的合约等等)。

  • 不像外部拥有账户,合约账户不可以自己发起一个交易。相反,合约账户只有在接收到一个交易之后(从一个外部拥有账户或另一个合约账户处),为了响应此交易而触发一个交易。

因此,在以太坊上任何的动作,总是被外部拥有账户触发的交易所发动的。

账户状态

账户状态有四个组成部分,不论账户类型是什么,都存在这四个组成部分:

  • nonce:如果账户是一个外部拥有账户,nonce代表从此账户地址发送的交易序号。如果账户是一个合约账户,nonce代表此账户创建的合约序号
  • balance: 此地址拥有Wei的数量。1Ether=10^18Wei
  • storageRoot: Merkle Patricia树的根节点Hash值(我们后面在解释Merkle树)。Merkle树会将此账户存储内容的Hash值进行编码,默认是空值
  • codeHash:此账户EVM(以太坊虚拟机,后面细说)代码的hash值。对于合约账户,就是被Hash的代码并作为codeHash保存。对于外部拥有账户,codeHash域是一个空字符串的Hash值

gas和费用
在以太坊中一个比较重要的概念就是费用(fees),由以太坊网络上的交易而产生的每一次计算,都会产生费用—没有免费的午餐。这个费用是以”gas”来支付。
Gas就是用来衡量在一个具体计算中要求的费用单位。gas price就是你愿意在每个gas上花费Ether的数量,以“gwei”进行衡量。“Wei”是Ether的最小单位,1Ether=10^18Wei,1gwei=1,000,000,000 Wei。
对每个交易,发送者设置gas limit和gas price。gas limit和gas price就代表着发送者愿意为执行交易支付的Wei的最大值。

消息和交易

最基本的概念,一个交易就是指被外部拥有账户生成的加密签名的一段指令,序列化之后提交给区块链。

有两种类型的交易:消息通信(message calls)和合约创建(contract creations)(也就是交易产生一个新的以太坊合约)。
不管什么类型的交易,都包含:

  • nonce:发送者发送交易数的计数
  • gasPrice:发送者愿意支付执行交易所需的每个gas的Wei数量
  • gasLimit:发送者愿意为执行交易支付gas数量的最大值。此值设置之后在任何计算完成之前就会被提前扣掉
  • to:接收者的地址。在合约创建交易中,合约账户的地址还没有存在,所以值先空着
  • value:从发送者转移到接收者Wei的数量。在合约创建交易中,value作为新建合约账户的开始余额
  • v,r,s:用于产生标识交易发送者的签名
  • init(只有在合约创建交易中存在):用来初始化新合约账户的EVM代码片段。init值会执行一次,然后就会被丢弃。当init第一次执行的时候,它返回一个账户代码体,也就是永久与合约账户关联的一段代码。
  • data(可选域,只有在消息通信中存在):消息通信中的输入数据(也就是参数)。例如,如果智能合约就是一个域名注册服务,那么调用合约可能就会期待输入参数:域名和IP地址

区块

所有的交易都被组成一个”块”。一个区块链包含了一系列这样链在一起的区块。
在以太坊中,一个区块包含:

  • 区块头
  • 关于包含在此区块中交易集的信息
  • 与当前块的ommers相关的一系列其他区块头

ommers解释

“ommer”到底是什么? ommer就是一个区块的父区块与当前区块父区块的父区块是相同的。让我们快速了解一下ommers是用来干嘛的,并且为什么一个区块需要为ommers包含区块头。
由于以太坊的构造,它的区块生产时间(大概15秒左右)比其他的区块链例如Bitcoin(大概10分钟左右)要快很多。这使得交易的处理更快。但是,更短的区块生产时间的一个缺点就是:更多的竞争区块会被矿工发现。这些竞争区块同样也被称为“孤区块”(也就是被挖出来但是不会被添加到主链上的区块)。
Ommers的目的就是为了帮助奖励矿工纳入这些孤区块。矿工包含的ommers必须是有效的,也就是ommers必须是往上数6代之内或更小范围内父区块的子区块。 一个孤区块在第6个子区块之后,这种陈旧的孤区块将不会再被引用(因为包含老旧的交易会使事情变得复杂一点)。
Ommer区块会收到比全区块少一点的奖励。不管怎样,依然存在激励来让矿工们纳入孤区块并能从中获得一些报酬。

区块头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> eth.getBlock(1)
{
difficulty: 131072,
extraData: "0xd783010509846765746887676f312e352e31856c696e7578",
gasLimit: 3144658,
gasUsed: 1910026,
hash: "0xc0b29bc36695a0437fbc8886bf37f845582169062bc6d62885a5fe626f7e295e",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x5a1d4c5249f27ef473a6ab3b715f03313fd1f560",
mixHash: "0x591c1f3f5a0ded2059f1e587c160f9bf7a0d68d26d59becb30d6522877f2adfc",
nonce: "0x4212740f3207045d",
number: 1,
parentHash: "0x33a09ce307ed33581a1a0e2f854de4e55d6a5be246a08408c76da9dd5959869a",
receiptsRoot: "0x68c0e9f576c9d919b9246071b48e216bb664175e3449a41e97cdc9faa63af2be",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 7601,
stateRoot: "0x162deb8236f6a1ac694e24fb88bd9c1665c90135b6535a6281a2f6ae43b19f19",
timestamp: 1523541484,
totalDifficulty: 131073,
transactions: ["0x21db4aef57dab234ec12f864be40cd4e8a598262ffdb02a9e1d3b14b2a200b9a"],
transactionsRoot: "0x7319b6173c48ecb69021575df2b8cc508c2fe1f3645cf63ad9f07125f35d4226",
uncles: []
}

parentHash:父区块头的Hash值(这也是使得区块变成区块链的原因)
ommerHash:当前区块ommers列表的Hash值
beneficiary:接收挖此区块费用的账户地址
stateRoot:状态树根节点的Hash值(回忆一下我们之前所说的保存在头中的状态树以及它使得轻客户端认证任何关于状态的事情都变得非常简单)
transactionsRoot:包含此区块所有交易的Merkle树的根节点Hash值
receiptsRoot:包含此区块所有交易收据的Merkle树的根节点Hash值
logsBloom:由日志信息组成的一个Bloom过滤器 (一种数据结构)
difficulty: 此区块的难度级别
number:当前区块的计数(创世纪块的区块序号为0,对于每个后续区块,区块序号都增加1)
gasLimit:每个区块的当前gas limit
gasUsed: 此区块中交易所用的总gas量
timestamp:此区块成立时的unix的时间戳
extraData:与此区块相关的附加数据
mixHash:一个Hash值,当与nonce组合时,证明此区块已经执行了足够的计算
nonce:一个Hash值,当与mixHash组合时,证明此区块已经执行了足够的计算

交易收据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> eth.getTransactionReceipt("0x21db4aef57dab234ec12f864be40cd4e8a598262ffdb02a9e1d3b14b2a200b9a")
{
blockHash: "0xc0b29bc36695a0437fbc8886bf37f845582169062bc6d62885a5fe626f7e295e",
blockNumber: 1,
contractAddress: "0xc94dbcd3480d6c2c92bd303382ed3c3a3308194a",
cumulativeGasUsed: 1910026,
from: "0x5a1d4c5249f27ef473a6ab3b715f03313fd1f560",
gasUsed: 1910026,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
root: "0x8f0fb37f003cb64fcbde2adefd1adcf1d588833241fad6e14b9439d26c36bf05",
to: null,
transactionHash: "0x21db4aef57dab234ec12f864be40cd4e8a598262ffdb02a9e1d3b14b2a200b9a",
transactionIndex: 0
}

交易收据包含着日志信息的交易收据的根Hash值保存在头中。 就像你在商店买东西时收到的收据一样,以太坊为每笔交易都产生一个收据。像你期望的那样,每个收据包含关于交易的特定信息,这些信息为:

  • 区块序号(blockNumber)
  • 区块Hash值(blockHash)
  • 交易Hash值(transactionHash)
  • 当前交易使用了的gas(gasUsed)
  • 在当前交易执行完之后当前块使用的累计gas(cumulativeGasUsed)
  • 执行当前交易时创建的日志(logsBloom)
  • 等等

难度调整

区块的难度是被用来在验证区块时加强一致性。创世纪区块的难度是131,072,有一个特殊的公式用来计算之后的每个块的难度。如果某个区块比前一个区块验证的更快,以太坊协议就会增加区块的难度。
区块的难度影响nonce,它是在挖矿时必须要使用工作量证明算法计算出的一个Hash值。
区块难度和nonce之间的关系用数学形式表达就是:

1
n <= (2^256/Hd)

Hd代表的是难度。
找到符合难度阈值的nonce唯一方法就是使用工作量证明算法来列举所有的可能性。找到解决方案预期时间与难度成正比—难度越高,找到nonce就越困难,因此验证一个区块也就越难,这又相应地增加了验证新块所需的时间。所以,通过调整区块难度,协议可以调整验证区块所需的时间。
另一方面,如果验证时间变的越来越慢,协议就会降低难度。这样的话,验证时间自我调节以保持恒定的速率—平均每15s一个块。

以太坊的应用场景

一般来讲,以太坊之上有三种应用。第一类是金融应用,为用户提供更强大的用他们的钱管理和参与合约的方法。包括子货币,金融衍生品,对冲合约,储蓄钱包,遗嘱,甚至一些种类的全面的雇佣合约。第二类是半金融应用,这里有钱的存在但也有很重的非金钱的方面,一个完美的例子是为解决计算问题而设的自我强制悬赏。最后,还有在线投票和去中心化治理这样的完全的非金融应用。

以太坊客户端P2P协议

以太坊客户端 P2P 协议是一个相当标准的加密货币协议,并且能够容易地为其它加密货币使用;仅有的改动是引入了由 Yonatan
Sompolinsky 和 Aviv Zohar 在 2013 年 12 月首次引入的“幽灵“协议(“Greedy Heaviest Observed Subtree” (GHOST) protocol);
该协议的引入动机和实现细节将在后面作详细介绍。以太坊客户端基本上是被动的;如果没有被触发,它自己做的仅有工作是调用网
络守护进程维护连接及定期发送消息索要以当前区块为父区块的区块。然而,该客户端同时会更强大;与只存储与块链相关的有限数
据的 bitcoind 不同,以太坊客户端将同时扮演一个功能完整的区块浏览器的后台的角色。

当客户端收到一个消息时,它将执行以下布骤:

  1. 哈希该数据,并且检查该数据与其哈希是否已经接收过,如果是,退出,否则将数据发送给数据分析器。
  2. 确认数据类型。如果该数据项是一个交易,如果交易合法则将其加入本地交易列表,加入当前区块并发布至网络。如果该数据项是一个消息,作出回应。如果该数据项是一个区块,转入步骤 3。
  3. 检查区块中的“父区块“参数是否已存储于数据库中。如果没有,退出。
  4. 检查该区块头以及其“叔区块列表”中所有区块头中的工作量证明是否合法,如有任意一个非法,退出。
  5. 检查“叔区块列表”中每一个区块的区块头以确定其是否以该区块的“祖父区块”为父区块。如有任何否,退出。注意叔区块头并不必须在数据库中;他们只需有共同的父区块并有合法的工作量证明。
  6. 检查区块中的时间戳是否最后至未来 15 分钟并且在其父区块的时间戳之后。检查该区块的难度与区块号码匹配。如任何检查失败,退出。
  7. 由该区块的父区块的状态开始,加上该区块中的每一笔合法交易。最后,加上矿工奖励。如果结果状态树的根哈希与区块头中的状态根不匹配,退出。如匹配,将该区块加入数据库并前进至下一步。
  8. 为新区块确定 TD(block) (“总难度”)。TD 由 TD(genesis_block) = 0 及 TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty 递归定义。如新区块拥有比现区块更高的总难度,则新区块将成为“现区块“并进入下一步,否则,退出。
  9. 如果新区块被改动,向其中加入交易列表中的所有交易,废除交易列表中的所有变为不合法的交易,将该区块及这些交易向全网重新广播。

智能合约

合约的创建

回忆一下在以太坊中,有两种账户类型:合约账户和外部拥有账户。当我们说一个交易是“合约创建”,是指交易的目的是创建一个新的合约账户。
为了创建一个新的合约账户,我们使用一个特殊的公式来声明新账户的地址。然后我们使用下面的方法来初始化一个账户:

  • 设置nonce为0
  • 如果发送者通过交易发送了一定量的Ether作为value,那么设置账户的余额为value
  • 将存储设置为0
  • 设置合约的codeHash为一个空字符串的Hash值

一旦我们完成了账户的初始化,使用交易发送过来的init code(查看”交易和消息”章节来复习一下init code),实际上就创造了一个账户。init code的执行过程是各种各样的。取决于合约的构造器,可能是更新账户的存储,也可能是创建另一个合约账户,或者发起另一个消息通信等等。
当初始化合约的代码被执行之后,会使用gas。交易不允许使用的gas超过剩余gas。如果它使用的gas超过剩余gas,那么就会发生gas不足异常(OOG)并退出。如果一个交易由于gas不足异常而退出,那么状态会立刻恢复到交易前的一个点。发送者也不会获得在gas用完之前所花费的gas。
不过,如果发送者随着交易发送了Ether,即使合约创建失败Ether也会被退回来。
如果初始化代码成功的执行完成,最后合约创建的花费会被支付。这些是存储成本,与创建的合约代码大小成正比(再一次,没有免费的午餐)。如果没有足够的剩余gas来支付最后的花费,那么交易就会再次宣布gas不足异常并中断退出。
如果所有的都正常进行没有任何异常出现,那么任何剩余的未使用gas都会被退回给原始的交易发送者,现在改变的状态才被允许永久保存。

消息通信

消息通信的执行与合约创建比较类似,只不过有一点点区别。
由于没有新账户被创建,所以消息通信的执行不包含任何的init code。不过,它可以包含输入数据,如果交易发送者提供了此数据的话。一旦执行,消息通信同样会有一个额外的组件来包含输出数据,如果后续执行需要此数据的话组件就会被使用。
就像合约创建一样,如果消息通信执行退出是因为gas不足或交易无效(例如栈溢出,无效跳转目的地或无效指令),那么已使用的gas是不会被退回给原始触发者的。相反,所有剩余的未使用gas也会被消耗掉,并且状态会被立刻重置为余额转移之前的那个点。
没有任何方法停止或恢复交易的执行而不让系统消耗你提供的所有gas,直到最新的以太坊更新。例如,假设你编写了一个合约,当调用者没有授权来执行这些交易的时候抛出一个错误。在以太坊的前一个版本中,剩余的gas也会被消耗掉,并且没有任何gas退回给发送者。但是拜占庭更新包括了一个新的“恢复”代码,允许合约停止执行并且恢复改变的状态而不消耗剩余的gas,此代码还拥有返回交易失败原因的能力。如果一个交易是由于恢复而退出,那么未使用的gas就会被退回给发送者。

参考

  1. 以太坊白皮书(中文)
  2. 以太坊(Ethereum):下一代智能合约和去中心化应用平台
  3. (转)Merkle Tree(默克尔树)算法解析
  4. 以太坊开发入门,完整入门篇
  5. How does Ethereum work, anyway?