以太坊黄皮书-区块、状态和交易

4.1世界状态(state of world) :世界状态( state)是在地址( 160 位的标识符)和账户状态(序列化为 RLP 的数据结构,详见附录 B)的映射。虽然世界状态没有直接储存在区块链上,但会假定在实施过程中会将这个映射维护在一个修改过的 Merkle Patricia 树上(即 trie,详见附录 D)。这个 trie 需要一个简单的后端数据库去维护字节数组到字节数组的映射;我们称这个后端数据库为状态数据库。它有一系列的好处: 第一,这个结构的根节点是基于密码学依赖于所有内部数据的,它的哈希可以作为整个系统状态的一个安全标识;第二,作为一个不变的数据结构,我们可以通过简单地改变根节点哈希来召回任何一个先前的状态(在根节点哈希已知的条件下)。因为我们在区块链中储存了所以这样的根节点哈希值,所以我们可以很容易地恢复到特定的历史状态。
账户的状态\sigma [a](state of world)包含以下四个字段:

nonce: 账户发出的交易数量或者由这个账户所创建的合约数量(当这个账户有相关的EVM代码时)。\sigma[a]_{n}表示状态\sigma中地址a的nonce值。

balance:\sigma[a]_{b}表示账户a拥有多少Wei

storageRoot:保存了账户的存储内容的Merkle Patricia树的根节点的256位哈希值,这个树中保存的是256位整数键值的Keccak256位哈希值到256位整数值的RLP编码的映射。这个哈希定义为\sigma[a]_{s}

codeHash:与账户相关的EVM代码的哈希值,这个是以太坊部署智能合约的哈希值。当这个地址接收到一个消息调用时,这些代码会被调用,创建后不可更改(也就是说合约创建后不可修改)。状态数据库中包含所有这样的代码片段哈希,以便后续使用。这个哈希可以定义为\sigma[a]_{c},然后用b表示代码,则有KEC(b) = \sigma[a]_{c}

因为我通常希望所指的并不是Trie的根哈希,而是其中所保存的键值对集合,所以我做了一个更方便的定义:

(6) TRIE(L^*_{I}(\sigma[a]_{s})) \equiv \sigma[a]_{s}

Trie中的键值对集合函数L^*_{I}被定义为适用于基础函数L_{I}中所有元素的变换:

(7)L_{I}((k,v)) \equiv (KEC(k), RLP(v))

其中:(8)k \ \epsilon \ \mathbb{B}_{32} \quad \wedge \quad v\ \epsilon \ \mathbb{N}

需要说明的是\sigma[a]_{s}不应算作这个账户的“物理”成员”,不会参与之后的RLP序列化

如果codeHash字段是一个空字符串的Keccak-256哈希,也就是说\sigma[a]_{c} = KEC(()),那么这个节点则表示一个简单账户,就是说这个账户没有与EVM相关的合约代码,这个账户没有部署合约,也就是“非合约”账户

因此我们可以定义一个世界状态函数L_{S}

(9)\quad L_{S}(\sigma)\equiv{p(a):\sigma[a]\neq\o }

其中(10)\quad p(a) \equiv (KEC(a), RLP((\sigma[a]_{n},\sigma[a]_{b},\sigma[a]_{s},\sigma[a]_{c})))

函数L_{S}Trie函数一起用来提供一个世界状态的简短标识(哈希)。我们假定:

(11)\forall a:\sigma[a] = \o \ \vee \ (a \ \epsilon \ \mathbb{B}_{20} \ \wedge \ v(\sigma[a]))

其中,v 是账户有效性验证函数

v(x) \equiv\ x_{n}\ \epsilon \ \mathbb{N}_{256}\wedge x_{b} \ \epsilon \ \mathbb{N}_{256} \ \wedge x_{s} \ \epsilon \ \mathbb{B}_{32} \wedge x_{c} \ \epsilon \ \mathbb{B}_{32}

如果一个账户没有代码,它将是empty,且nonce和balance均为0:

(13)EMPTY(\sigma,a)\equiv \ \sigma[a]_{c} = KEC(()) \vee\sigma[a]_{n} = 0 \wedge \sigma[a]_{b} = 0

即使所谓预编译合约也可能处于empty状态。这是因为它们的账户状态并不总是包含可以表述它们的行为的代码。

当一个账户不存在或为empty时,就表示它dead(死了):

(14)DEAD(\sigma,a) \equiv \ \sigma[a] = \o \vee EMPTY(\sigma,a)

4.2交易

交易(符号,T)是个单一的加密学签名的指令,通常由以太坊系统之外的操作者创建。我们假设外部的操作者是人,软件工具则用于构建和散播。这里的交易有两种类型:一种表现为消息调用,另一种则通过EVM代码创建新的账户(称为“合约创建”)。这两种类型的交易都有一些共同的字段:

nonce: 由交易发送者发出的交易的数量,由T_{n}表示。

gasPrice: 为执行这个交易所需要进行的计算步骤消耗的每单位gas的价格,以Wei为单位,由T_{p}表示。

gasLimit: 用于执行这个教育的最大gas数量。这个值必须在交易开始前设置,且设定后不能再增加,由T_{g}表示。

to :160位的消息调用接收者地址;对与合约创建交易,用\o表示\mathbb{B}_{0}的唯一成员。此字段由T_{t}表示

value:转移到接收者账户的Wei的数量;对于合约创建,则代表给新建合约地址的初始捐款。由T_{v}表示。

v,r,s: 与交易签名相符的若干数值,用于确定交易的发送者,由T_{w},T_{r},T_{s}表示(详见附录F)

此外,合约创建还包括以下字段:

init:不限制大小的字节数组,用来指定初始化程序的EVM代码,由T_{i}表示。init是EVM代码片段;它将返回body,这是这个账户每次接收到消息调用时会执行的代码(通过一个交易或者代码的内部执行)。init仅会在合约创建时被执行一次,然后就会被丢弃。

与此相对,一个消息调用交易包括:

data:一个不限制大小的字节数组,用来指定消息调用的输入数据,由T_{d}表示。

附录F详细描述了将交易映射到发送者的函数S。这种映射通过SECP-256k1曲线的ECDSA算法实现,使用交易哈希(除去后3个签名字段)作为数据来进行签名。目前我们先简单地使用S(T)表示指定交易T 的发送者。

L_{T}(T) \equiv\left\{\begin{matrix} (T_{n},T_{p},T_{g},T_{t},T_{v},T_{i},T_{w},T_{r},T_{s}) & i f \ T_{t} = \o\\ (T_{n},T_{p},T_{g},T_{t},T_{v},T_{d},T_{w},T_{r},T_{s}) & otherwise \end{matrix}\right.
在这里,我们假设除了任意长度的字节数组T_{i}T_{d}以外,所有变量都是作为整数来进行RLP编码的、

(16)T_{n} \ \epsilon \ \mathbb{N}_{256} \quad \wedge \quad T_{v} \ \epsilon \ \mathbb{N}_{256} \quad T_{p} \ \epsilon \ \mathbb{N}_{256} \quad \wedge \quad T_{g} \ \epsilon \ \mathbb{N}_{256} \quad \wedge \\ \quad T_{w} \ \epsilon \ \mathbb{N}_{5} \quad \wedge T_{r} \ \epsilon \ \mathbb{N}_{256} \quad \wedge \quad T_{s} \ \epsilon \ \mathbb{N}_{256} \quad \wedge \quad T_{d} \ \epsilon \ \mathbb{B} \quad \wedge \quad T_{i} \ \epsilon \ \mathbb{B} \quad

其中,\mathbb{N}_{n} = {P:P\ \epsilon \ \mathbb{N} \wedge \ P < \ 2^n}

地址哈希T_{t}稍微有些不同:它是一个02字节的地址哈希值,或者当创建合约时(将等于\o)是RLP空字节序列,所以是\mathbb{B}_{0}的成员:

(18)T_{t} \ \epsilon \ \left\{\begin{matrix} \mathbb{B}_{20} & if \ T_{t} \neq \o\\ \mathbb{B}_{0}& otherwise \end{matrix}\right.

4.3区块

在以太坊中,区块由以下部分组成的:一些相关信息片段组成的集合(成为block header,即区块头);组成区块的交易T和其他一些区块头U(这是一些父区块与当前区块的爷爷辈区块相同的区块,这样的区块成为ommers^2)。区块头包含的信息如下:

parentHash:父区块头的Keccak256位哈希,由H_{p}表示。

ommersHash:当前区块的ommers列表的Keccak256位哈希,由H_{o}表示。

beneficiary:成功挖到这个区块所得到的所有交易费的160位接收地址,由H_{c}表示。

stateRoot:所有交易被执行完且区块定稿后的状态树(state trie)根节点的Keccak256位哈希

transactionsRoot:由当前区块中所包含的所有交易所组成的树结构(transaction trie)根节点的Keccak256位哈希,由H_{t}表示。

receiptsRoot:由当前区块中所包含的所有交易的收据所组成的树结构(transaction trie)根节点的Keccak256位哈希。由H_{e}表示。

logsBloom: 由当前区块中所有交易的收据数据中的可索引信息(产生日志的地址和日志主题)组成的Bloom过滤器,由H_{b}表示。

difficulty:当前区块难度水平的纯量值,它可以根据前一个区块的难度水平和时间戳计算得到,由H_{d}表示。

number:当前区块的祖先的数量,由H_{i}表示。创世区块的这个数量为0.

gasLimit:目前每个区块的gas开支上限,由H_{l}表示。

gasUsed:当前区块的所有交易所用掉的gas之和,由H_{g}表示。

timestamp:当前区块初始化时的Unix时间戳,由H_{s}表示。

extraData:与当前区块相关的任意字节数据,但必须在32字节以内,由H_{x}表示。

minHash:一个256位的哈希值,用来与nonce一起证明当前区块已经承载了足够的计算量。

nonce:一个64位的值,用来与minHash一起证明当前区块已经承载了足够量的计算量,由H_{n}表示。

区块的另外两个组成部分是ommer区块头(与以上格式相同)列表B_{U}和一系列的交易B_{T}。我们可以表示一个区块B

(19) B \equiv (B_{H},B_{T},B_{U})

4.3.1交易收据

为了能使交易信息对零知识证明、索引和搜索都是有用的,我们将每个交易执行过程中一些特定信息编码为交易收据。我们以B_{R}[i]表示第i个交易的收据,并把收据信息保存在一个以索引为键的树(index_keyed_trie)中,树的根节点用H_{e}保存到区块头中。

交易数据R是一个包含四个条目的元组:包含交易数据的区块中当交易发生后的累积gas使用量R_{u};交易过程中创建的日志集合R_{l};由这些日志信息所构成的Bloom过滤器R_{b}和交易的状态代码R_{z}

(20)\quad R \equiv (R_{u},R_{b},R_{l},R_{z})

函数L_{R}是将交易收据转换为RLP序列化字节数组的预处理函数:

(21)\quad L_{R}(R) \equiv (0 \ \epsilon \ \mathbb{B}_{256},R_{u},R_{b},R_{l})

其中,0 \ \epsilon \ \mathbb{B}_{256} 代替了先前协议版本中的交易前状态根(the pre-transaction state root)。我们要求状态代码R_{z}是一个整数。

(22) \quad R_{z} \ \epsilon \ \mathbb{N}

我们要求累积的gas使用量R_{u}是一个正整数,且日志的BloomR_{b}是一个2048位(256字节)数据的哈希:

(23)\quad R_{u} \ \epsilon \ \mathbb{N} \quad \wedge \quad R_{b} \ \epsilon \ \mathbb{B}_{256}

R_{l} 是一系列的日志项(O_{0},O_{1},..)。一个日志项O是一个记录了日志产生者的地址O_{a};一系列32字节的日志主题O_{t}和一些字节数据O_{d}所组成的元组:

(24) \quad O \equiv (O_{a},(O_{t0},O_{t1},\cdots),O_{d})

(25) \quad O_{a} \ \epsilon \ \mathbb{B}_{20} \quad \wedge \quad \forall_{t \epsilon O_{t}}: t \ \epsilon \mathbb{B}_{32} \quad \wedge \quad O_{d} \ \epsilon \ \mathbb{B}

我们定义Bloom过滤器函数M将一个日志项精简为一个256字节哈希:

(26)\quad M(O) \equiv \forall_{t \epsilon {O_{a}}\cup O_{t}}(M_{3:2048(T)})

其中M_{3:2048}(x:x \ \epsilon \ \mathbb{B}) 是一个特别的Bloom过滤器,它通过设置2048位中的3位数值来给定一个随机的字节序列。这是通过取得对一个字节序列中的前三对字节的Keccak-256哈希值的低11位数据实现的:

(27) M_{3:2048}(x \ : \ x \ \epsilon \ \mathbb{B}) \equiv \ y : y \ \epsilon \ \mathbb{B}_{256} \quad where:

...

Homestread难度值参数\zeta_{2}被用来影响出块时间的动态平衡,它已经通过Buterin[2015]提出的EIP-2实现了。在Homestead版本中,难度符号\epsilon会越来越快地使难度值缓慢增长(每10万个区块),从而增加区块时间差别,也为向权益证明(proof-of-stake)的切换增加了时间压力。这个效果就是所谓的“难度炸弹”("difficulty bomb")或("冰河时期")(“ice age”)在由Schoedon and Buterin[2017]提出的EIP-649中进行了解释,并用来推迟早先在EIP-2中的实现。\zeta_{2}也在EIP-100通过使用x(即上面公式中的矫正参数)和分母9进行了修改,用来达到Buterin[2016]提出的对包含叔区块(uncle blocks)在内的平均出块时间的调整效果。最终,在拜占庭版本中,伴随EIP-649,我们通过伪造一个区块号H^' _{i}来延迟冰河时期的来临。这是通过用实际区块号减去300万来获得的。换句话说,就是减少\epsilon和区块的时间间隔,来为权益证明的开发争取更多的时间并防止网络被“冻结”。

猜你喜欢

转载自blog.csdn.net/liudaoqiang_tj/article/details/81435529
今日推荐