以太坊是一个基于交易的状态机,其区块链中的每个区块就对应一个状态,每产生一个区块,以太坊中的状态就会转换到下一个状态。通过状态转换使得运行以太坊中的所有节点保持数据的一致性。以太坊中的区块链分布式存在,但与传统的分布式却截然不同。
从创世块开始,不断产生的交易持续刷新系统当前状态,每当产生一个区块,系统状态就发生一次转换。
区块由三大部分组成:区块头(Block Header),叔块(Uncle),交易列表(tx_List)。如下图所示。
看一下一个实际的区块信息:
"blocks" : [
{
"blockHeader" : {
"bloom" : "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000",
"coinbase" : "0x3535353535353535353535353535353535353535",
"difficulty" : "0x020000",
"extraData" : "",
"gasLimit" : "0x05f5e100",
"gasUsed" : "0x014fa1",
"hash" : "0x39f4659b079e257df8fd7e699528531e97a6b8a442ca0d11200c4a2f7433c483",
"mixHash" : "0x7379f33af4ae2db7e293f808a165135d0b1a99572cc96fb9f7d17ef64a751969",
"nonce" : "0x8e08d7aabeee8773",
"number" : "0x01",
"parentHash" : "0xadbef3bf0b3b7b14f6e7b1a45d240ecc863543a279a86c23f60170e8e7a6bcc3",
"receiptTrie" : "0xb21660268480338c0cd0613358315359b619bd527d5850949c4863cddaec316b",
"stateRoot" : "0xde4ce9b5b2f88ab1680962c64281224b1743bdf94bd6a9e390ea779ff616c1f7",
"timestamp" : "0x03e8",
"transactionsTrie" : "0x56445ba866f3e41851154fb8700dcec8556a178f1833021e030b8a47b494769d",
"uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
},
"rlp" : "0xf90308f901f9a0adbef3bf0b3b7b14f6e7b1a45d240ecc863543a279a86c23f60170e8e7a6bcc3a01dcc4de8dec75d7aab85b56 7b6ccd41ad312451b948a7413f0a142fd40d49347943535353535353535353535353535353535353535a0de4ce9b5b2f88ab168096 2c64281224b1743bdf94bd6a9e390ea779ff616c1f7a056445ba866f3e41851154fb8700dcec8556a178f1833021e030b8a47b4947 69da0b21660268480338c0cd0613358315359b619bd527d5850949c4863cddaec316bb901000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000083020000018405f5e10083014fa18203e880a07379f33af4a e2db7e293f808a165135d0b1a99572cc96fb9f7d17ef64a751969888e08d7aabeee8773f90108f90105460183030d4094c305c9010 78781c232a2a521c2af7980f8385ee980b8a430c8d1da0000000000000000000000000000000000000000000000000000000000000 0200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000 0000000000000000000000001000000000000000000000000000000000000000000000000000000000000000230644e72e131a029b 85045b68181585d2833e84879b9709143e1f593f00000001ba021a28cc82b40931239f8653ffa5300e1a506c0ef7fb79a663772caf e6558ab44a075af23441f7f176a2770af41142c77b671391209b15d59144e7a1332179b5e14c0",
"transactions" : [
{
"data" :
"0x30c8d1da0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000230644e72e131a029b85045b68181585d2833e84879b 9709143e1f593f0000000",
"gasLimit" : "0x030d40",
"gasPrice" : "0x01",
"nonce" : "0x46",
"r" : "0x21a28cc82b40931239f8653ffa5300e1a506c0ef7fb79a663772cafe6558ab44",
"s" : "0x75af23441f7f176a2770af41142c77b671391209b15d59144e7a1332179b5e14",
"to" : "0xc305c901078781c232a2a521c2af7980f8385ee9",
"v" : "0x1b",
"value" : "0x00"
}
],
"uncleHeaders" : []
}
]
以太坊中每个区块的结构和比特币中每个区块的结构却不同,以太坊中每个区块的结构比比特币种区块的结构更加复杂,太坊中区块的区块头(Block Header)结构定义如下图所示。
区块头中每个字段的意义如下:
名称 | 类型 | 意义 |
---|---|---|
parentHash | common.Hash | 父区块的哈希值 |
UncleHash | common.Hash | 叔父区块列表的哈希值 |
Coinbase | common.Address | 打包该区块的矿工的地址,用于接收矿工费 |
Root | common.Hash | 状态树的根哈希值 |
TxHash | common.Hash | 交易树的根哈希值 |
ReceiptHash | common.Hash | 收据树的根哈希值 |
Bloom | Bloom | 交易收据日志组成的Bloom过滤器 |
Difficulty | *Big.Int | 本区块的难度 |
Number | *Big.Int | 本区块块号,区块号从0号开始算起 |
GasLimit | uint64 | 本区块中所有交易消耗的Gas上限 |
GasUsed | uint64 | 本区块中所有交易使用的Gas之和 |
Time | *big.Int | 区块产生的unix时间戳 |
Extra | []byte | 区块的附加数据 |
MixDigest | common.Hash | 哈希值,与Nonce的组合用于工作量计算 |
Nonce | BlockNonce | 区块产生时的随机值 |
和比特币中的区块链一样,以太坊中的区块链仍然使用了parentHash连接前一个区块,通过这个字段,所有的区块最终都能回溯到创始块。
区块头中比较重要的三个字段是Root、TxHash和ReceiptHash三个哈希值。其中Root表示以太坊网络中全部有过转账交易的账户形成的Merkle partial Tree(MPT)的根哈希值。为什么是全部有个转账交易的账户呢,因为我们可以随便创建一个以太坊账户,但是没有发生交易的话,以太坊节点也不会知道这个账户的存在。TxHash是本区块中所有交易形成的merkle tree的根哈希值,ReceiptHash是本区块中所有收据信息构成的merkle tree 的哈希值。这其中涉及到的Merkle tree的相关概念可以参考比特币区块链中的数据结构,里面有merkle tree数据结构的介绍。接下来重点介绍Merkle Partial Tree。
比特币是基于交易的分布式账本,而以太坊是基于账户的分布式账本,相比于比特币,以太坊设计的更加复杂。以太坊中会对所有有过转账交易的账户都会建立一个用户状态树,每个运行以太坊客户端的节点都会在本地维护一个用户状态树,当有新的区块打包到区块链上之后,所有的以太坊节点(挖出区块的节点除外)的就会执行最新区块中的交易,对本地用户的状态树进行维护,得到新的用户状态树。用户状态树的信息并不保存在区块中,区块中仅仅保存了最新的状态树组成的根哈希值。
关于MPT的介绍,网上资料众多,但我认为比较容易理解的文章,可参见以太坊MPT原理,和这篇Understand ethereum’s trie,这两篇是网上对于MPT的解释比较到位的文章。
值得说明的是,以太坊中状态树之间的指针式哈希指针,这样从底层的value建立哈希指针,再对其父节点建立哈希指针,以此类推,直到最终的状态树merkle root。
每次发布新的区块链时,每个存有状态树的节点就会根据区块中的交易更新对应的账户树中的内容,每个节点根据区块上的交易独立的修改状态树,最后生成状态树的根哈希值,随后用本地生成的状态树哈希值和新发布区块中的状态树的Root字段进行比较,如果相同,表示账户状态和发布区块的节点的账户状态保持了一致。这也是以太坊中每个节点独立运行却又能维持区块链数据的一致性的方法。
状态树的变化可以用下图所示。每个状态发生改变时,都会新建一个节点,但是原有的状态树仍然会保存,保存原有状态的原因是因为以太坊出快时间是十几秒,在十几秒钟很容易出现多个合法的区块,就会出现分叉,分叉的区块最终合并之后,有一些分叉链上的节点的状态就需要回退,保存历史记录就是为了方便回退。