以太坊解析:默克尔树、世界状态、交易及其他
olwww
发表于 2022-12-26 06:51:47
60
0
0
在讨论以太坊的主要数据对象之前,我想先向各位简要介绍一下默尔克树到底是什么,以使得它得以发挥作用的属性特征。
黄皮书中假设由定制的默克尔-帕特里夏树维护世界状态和交易。附录 D 描述了这个数据结构。2 D, ~! u# y, \/ ^, i
默克尔-帕特里夏树有许多有意思的属性,如果你想更深入地了解其在以太坊中的应用,我推荐你阅读这篇文章。1 W! ~% ]9 [+ J( |7 G3 s
在默克尔树中,由叶子节点保存区块数据的哈希,而由非叶子节点保存其子节点的哈希。
- e! b U- B b3 Q; Y' c2 J
-默克尔树示意图(包括节点以及他们之间的关系)-5 |- H3 q: d$ h
7 W3 ~: q/ t" W, O
默克尔树所指向数据的任何改动都会引起节点哈希的变化。由于每一个父节点中所保存的哈希值都取决于子节点所包含的数据,所以子节点中数据的变更都会引起父节点哈希的变化。并且这样的影响是连锁反应,从叶子节点直达根节点的。因此对叶子节点所指向数据的改动会引起根节点所保存哈希的变化。由上述结构特征,我们可以引申出两条重要的属性:
1 H+ H5 F* h* q- S2 ~, s- m3 x
在判断两棵默克尔树所指向数据是否完全相同时,我们不需要比较每个叶子节点,而只需比较根节点所保存的哈希。4 e" n* Y" W* j# T1 F
8 }8 A y' U8 m4 \
在判断特定数据是否被树所指向时,我们可以使用 默克尔证明 技术。此处不对该技术作过多介绍,只需知道这是证明数据存在于默克尔树中的一种简单、高效的方法。% e! O' X0 K( e, A5 v: K& }2 [! o
& v9 ?/ N; m4 f7 E) L' l$ ]
第一种属性的重要之处在于,我们能够仅利用根节点的哈希值,就标示某一时刻整棵树所指向的数据。这意味着仅通过保存根节点的哈希值就能标示区块(无需储存区块链中所有的数据),且维护数据的不可篡改。# J) K3 W# x# Z$ w7 D: u5 }
- d# G3 J% y. h6 T% q( m' K
至此我们理清了默克尔树中根节点哈希的作用,下面来介绍以太坊中的主要对象。
世界状态
世界状态是地址(账户)到账户状态的映射。虽然世界状态不保存在区块链上,但在黄皮书的描述中,世界状态也由树来保存数据(此树也被称为状态数据库或者状态树)。世界状态可以被视作为随着交易的执行而持续更新的全局状态。以太坊就像一个去中心化的计算机,世界状态则是这台电脑的硬盘。
: K! V, P( |8 f) c
以太坊中所有的账户信息都体现在世界状态之中,并由世界状态树保存。如果你想知道某一账户的余额,或者某智能合约当前的状态,就需要通过查询世界状态树来获取该账户的具体状态信息。下文中我也会简要介绍这些信息是如何存储的。
& ] j' Q7 R9 O [( p1 ]6 R1 [
-世界状态树与账户存储-
-世界状态树与账户存储-
3 \9 `; d" c4 D, v
账户状态: ]% s! L, J% l- Y
以太坊中有两种账户类型:外部所有账户(Externally Owned Accounts 简称 EOA)以及合约账户。我们用来互相收发以太币、部署智能合约的账户就是 EOA 账户,而部署智能合约时自动生成的账户则是合约账户。每一个智能合约都有其独一无二的以太坊账户。- D) ^& U; _0 w3 V! T( [; |
% w3 n4 Q' v e; c3 @
账户状态反映了一个以太坊账户的各项信息。例如,它存储了当前账户以太币的余额信息、当前账户发送过的交易数量…每一个账户都有账户状态。! o% e9 ]' P8 V# I/ X
下面就来看看账户状态中都包括什么:4 v4 w! h9 d- Z3 B+ I5 ?
nonce7 I l9 _* N3 Z2 }
从此地址发送出去的交易数量(如果当前为 EOA 账户)或者此账号产生的合约创建操作(现在先别管合约创建操作是什么)。8 a3 R. R$ W; W+ o2 \2 z3 \: |
7 T! w& g5 K: _1 B9 p
balance
- l- F; ?0 X9 c# _0 }- e; g+ f
此账号所拥有的以太币数量(以 Wei 计量)。- T1 F+ Q G n! r0 y
storageRoot- V2 P5 }' \/ r6 W2 [
+ i% d: }. l3 p+ C8 }+ q8 g# f
账户存储树的根节点哈希值(稍后介绍账户存储是什么)。3 h! R( U; Z: J4 h
codeHash
对于合约账户,就是此账户存储 EVM 代码的哈希值。对于 EOA 账户,此处留空。; p t% O. `# o. m7 F. `7 n, w3 `
' v% o5 O2 ~0 N: m
账户状态中不容忽视的一个细节是,上述对象在内的所有对象都可变(除了 codeHash)。举例来说,当一个账户向其他账户发送以太币时,除了 nonce 会增加,账户的余额也会相应改变。7 `+ H" C/ C* l, o3 U
而 codeHash 的不可变性使得,如果部署了有漏洞的智能合约,也无法修复更新此合约。对应的,只能部署一个新合约(而有漏洞的版本会一直存在于区块链上)。这也是为什么使用 Truffle 进行智能合约的开发和部署十分必要,并且用 Solidity 编程时要遵循 最佳实践 的要求。
账户存储树是保存与账户相关联数据的结构。该项只有合约账户才有,而在 EOA 中, storageRoot 留空、 codeHash 则是一串空字符串的哈希值。所有智能合约的数据都以 32 字节映射的形式保存在账户存储树中。此处不再赘述账户状态树如何维持合约数据。如果读者对其内部实现感兴趣,强烈建议阅读这篇文章。账户状态中的 storageRoot 区域负责维持账户存储树根节点哈希值。
& q$ B! b) l! f" q* I- ]7 B
-账户状态与账户存储树-" S0 ?+ \; z9 X1 [
交易! ?7 |7 M3 B% I8 n1 M# b
1 q# ]* v) T; a( Y% T; u
交易推动当前状态到下一状态的转变。在以太坊中有三种交易:, Z" I g4 _" n4 z5 R0 Z
EOA 之间传输值的交易(例如,改变发送方和接收方余额大小)。
& F- [/ B1 x3 a9 A5 ?7 I- I
发送消息来调用合约的交易(例如,通过发送消息调用来触发 setter 方法,以设置合约中的值)。5 `2 G# ?% _1 Y1 r
用于部署合约的交易(由此创建了合约账户)。
(从技术角度来讲,前两种交易是一样的…它们都是通过消息调用来改变账户状态的交易,只不过一个是 EOA 账户,一个是合约账户。此处将交易分为三种是为了方便读者的理解。)5 O" T0 x" g$ O4 b% A" ]; D
交易由以下部分组成:
. P3 e( H# o8 G! Q, w
nonce/ M' t0 c2 w8 y' h% @( k
% @/ G/ \2 y' o- \0 y7 F9 d
此账户发出的交易序号数(校对注:可以粗略理解为“这是该账户的第几笔交易”)。
( r/ j- C2 O! f: H, `( c
gasPrice [/ R$ |/ p% _& C3 I
: C' D- _, z/ h
执行此交易、进行计算时为每单位 gas 所支付的费用(以 Wei 计量)。
, v" c5 ?! {( G) ?
gasLimit
; _# L) W3 y, p
执行此交易时可以使用的最大 gas 数量。
to5 M/ M' |' ~' A2 U6 }$ x9 I
如果此交易用于传送以太币,此处为接收以太币的 EOA 地址。
" v. P1 {8 {, g! w# \6 g
如果此交易用于向合约发送消息(例如,调用智能合约中的方法),此处为合约的地址。4 d5 @ v$ e( U; m" G
U1 c: k$ x2 `/ c* K
如果此交易用于创建合约,此处值为空。
value/ T- U8 x* O" R& y2 C
如果此交易用于收发以太币,此处为接收账户以 Wei 计量的代币数量。) {- `& N" r4 x/ _" b, ?
6 b& t; P, l! B* v7 B
如果此交易用于发送对合约的消息调用,此处为向接收此消息智能合约所给付的 Wei 数量。- x5 W I0 @% ?4 M9 l9 N
如果此交易用于创建合约,此处为合约初始化时账户存放的以 Wei 计量的以太币数量。 _ D+ m9 W: ?
/ T% f+ N; A1 g& g. K4 j
v, r, s: }) |2 N( f! R+ X, ^
& M' P& D2 `& X
在交易的密码学签名中用到的值,可以用于确定交易的发送方。
data(只用于价值传输以及向智能合约发送消息调用)
1 O `3 \$ k- j. X% N
发送消息调用时附带的输入数据(例如,假设你想要执行智能合约中的 setter 方法,数据区就应该包括 setter 方法的标识符,以及你想要设定的参数值)。
9 f/ d \+ F7 L
init(只用于合约创建)
用于初始化合约的 EVM 代码。
. p+ y" @/ h' v8 x' q! D
别想着一下子就把这些概念消化完… 必须对以太坊的内部机理有更深的认识才真正理解、使用像 data 区、init 区这样的概念。
相信不出你的意料,区块中所有的交易也是存储在默克尔树中的。并且这棵树的根节点哈希值由区块头保存!下面我们就来剖析一下以太坊区块结构。1 H- u3 d" m, K+ h1 H' ~
* k& j) ^9 k+ Z4 m
区块5 Z! K3 a8 j" ?7 y' G
; e6 h# z- ~8 T3 o
区块分为两部分,即区块头和区块体。
% D, O+ v5 f; `! _# a$ w+ b# I1 ?
区块头就是以太坊中的区块链部分。它保存了前一个区块(也可称为父区块)的哈希值,通过区块头的连接形成了一条由密码学背书的链。: j2 e9 j6 @7 }8 n4 k, \1 v; X
区块体包含了此区块中记录的一系列交易,以及叔块(ommer)区块头列表。如果想要进一步了解叔块,我推荐阅读这篇文章。
2 X6 [& V4 y) ~5 x$ {8 t9 f' a
-以太坊区块的抽象示意图-
) t8 F% b. i$ ]5 O# ^( ^4 C% [
下面就来介绍区块头包括哪些部分。7 F' P/ ^+ m) M3 T( R3 h% A* c
parentHash
. @& v# r( [1 _- @
前一个区块的区块头哈希值。每个区块都包含前序区块的哈希值,一路可回溯至链上的创世块。这也就是维护数据不会被篡改的结构设计(任何对前序区块的篡改都会影响后续所有区块的哈希值)。& D* @" y, R7 K- ~3 L
/ Q: c1 Y2 ]$ J3 G
ommersHash- N+ E9 p; W0 G ~; Q. r3 K# L
叔块头以及部分区块体的哈希值。
4 d3 j0 |6 [( x9 S3 M3 \
beneficiary; n4 y G# Q" q. ^7 C
1 c/ o. O1 ^0 u' {# v) Z4 I
因为挖到此区块而获得收益的以太坊账户。" L* y; m7 l% X# V/ E
6 G! T, y# Y$ K K
stateRoot9 j# R8 Q7 t( K y7 J, x5 u
$ J* A1 C z; E
世界状态树的根节点哈希值(在所有交易被执行后)。
; ]& w8 Q* O+ p {
transactionsRoot
交易树根节点的哈希值。这棵树包含了区块体的所有交易。
J) w$ K0 S/ S ], b7 ~
receiptsRoot
( Y) }, V) [8 x
每当交易执行时,以太坊都会生成对应结果的交易收据。此处就是这个交易收据树的根节点哈希。7 x( p" N) n! e5 V
$ V9 z/ D3 W. s' Q4 m+ G- F. n
logsBloom
布隆过滤器,用于判断某区块的交易是否产生了某日志(如果对这方面感兴趣,可以查阅 Stack Overflow 的这个答案)。这避免了在区块中存储日志信息(节省了大量空间)。8 Y, v0 d% g" |% K1 z
! u; L- l: L4 y
difficulty. P2 [ ?- R% p0 K' [
此区块的难度值。这是当前区块挖矿难度的度量值(此处不对此概念的细节和计算作介绍)。
number
前序区块的总数。这标示了区块链的高度(即区块链上有多少区块)。创世区块的 number 为 0 。8 M* T, }/ a7 ]6 i
/ H5 M: [8 J: D0 ?" Z2 X% ?
gasLimit$ j) r, F) \! {3 E6 g
每一个交易都需要消耗 gas 。gas limit 标示了该区块所记录的所有交易可以使用的 gas 总量。这是限制区块内交易数量的一种手段。" I: c. Y$ P/ u& ` f! m; Y4 b
# y& [& T" _# u' V& S n% j- ~8 W) y
gasUsed
区块中各条交易所实际消耗的 gas 总量。0 k6 e$ `% _& _7 j
7 {' P' s# Y( ]
timestamp" L6 U7 M, c6 Y; R
区块创建时的 Unix 时间戳。谨记由于以太坊网络去中心化的特性,我们不能信任这个值,特别是撰写智能合约、涉及到时间相关的商业逻辑时不能依靠这个值。+ J! p0 C( i7 H, @/ N, u& ^
4 ]( @' S4 r5 s# q
extraData
( K* [! _* r5 x1 c1 M- y
能输入任何东西的不定长字节数组。当矿工创建区块时,可以在这个区域添加任何东西。
mixHash
. C, ^' G/ D" ^* l+ C3 ^/ ?
用于验证一个区块是否被真正记录到链上的哈希值(如果想要真正理解这个概念,建议阅读这篇文章 Ethash proof-of-work function )。
nonce3 @) M# D! k9 f; m2 C
+ @; X. H, U5 s4 b9 T5 Q
和 mixHash 一样,用于验证区块是否被真正记录到链上的值。
成为第一个吐槽的人