以太坊黃皮書(shū)詳解(一)

寫(xiě)在篇頭

本文是對(duì)以太坊的黃皮書(shū)的解析,并參照go-ethereum中的實(shí)現(xiàn),將相應(yīng)的代碼也列了出來(lái)。黃皮書(shū)中使用了大量的公式將以太坊的一些流程和狀態(tài)都公式化了。看不看得懂公式對(duì)理解的影響不大。本文中對(duì)公式進(jìn)行了解析。嫌麻煩的可以跳過(guò)每部分公式解析的部分。

一、區(qū)塊鏈范型

以太坊本質(zhì)是一個(gè)基于交易的狀態(tài)機(jī)(transaction-based state machine)。其以初始狀態(tài)(genesis state) 為起點(diǎn),通過(guò)執(zhí)行交易來(lái)到達(dá)新的狀態(tài)。

\begin{equation} {\sigma}_{t+1} \equiv \Upsilon(\boldsymbol{\sigma}_{t}, T) \tag{1} \end{equation}

公式1表示t+1時(shí)的狀態(tài),是由t時(shí)的狀態(tài)經(jīng)過(guò)交易T轉(zhuǎn)變而來(lái)。轉(zhuǎn)變函數(shù)為\Upsilon
如下圖所示

ethereumtransaction.png

\begin{equation} \boldsymbol{\sigma}_{t+1} \equiv {\Pi}(\boldsymbol{\sigma}_{t}, B) \tag{2} \end{equation}

\begin{equation} B \equiv (..., (T_0, T_1, ...) ) \tag{3} \end{equation}

\begin{equation} \Pi(\boldsymbol{\sigma}, B) \equiv {\Omega}(B,{\Upsilon}(\Upsilon(\boldsymbol{\sigma}, T_0), T_1) ...) \tag{4} \end{equation}

公式2-4是從區(qū)塊的角度來(lái)描述狀態(tài)的轉(zhuǎn)化過(guò)程。
公式2: t+1時(shí)的狀態(tài),是由t時(shí)的狀態(tài)經(jīng)過(guò)區(qū)塊B轉(zhuǎn)變而來(lái)。轉(zhuǎn)變函數(shù)為{\Pi}

公式3: 區(qū)塊B是包含了一系列交易T的集合。

公式4: 區(qū)塊的狀態(tài)轉(zhuǎn)變函數(shù){\Pi},相當(dāng)于逐條的執(zhí)行交易的狀態(tài)轉(zhuǎn)變\Upsilon,然后完成所有交易轉(zhuǎn)變后再經(jīng)過(guò){\Omega}進(jìn)行一次狀態(tài)轉(zhuǎn)換。(這個(gè)地方{\Omega}實(shí)際上是給礦工挖坑獎(jiǎng)勵(lì)。)

在以太坊中的實(shí)際情況就是區(qū)塊驗(yàn)證和執(zhí)行的過(guò)程。

  • 逐一的執(zhí)行交易(也就是使用交易轉(zhuǎn)變函數(shù)操作\Upsilon狀態(tài)集)。實(shí)際就是交易比方是A向B轉(zhuǎn)10ether,則A賬戶值-10,B賬戶值+10。(當(dāng)然執(zhí)行過(guò)程中還有g(shù)as消耗,這個(gè)后面詳述)
  • 等整個(gè)block交易執(zhí)行完畢后,需要對(duì)礦工進(jìn)行獎(jiǎng)勵(lì)。也就是需要使用{\Omega}進(jìn)行一次狀態(tài)轉(zhuǎn)換。

1.1 貨幣

以太坊中有以下四種單位的貨幣。以太坊中的各種計(jì)算都是以Wei為單位的。 (看有的地方好像有更多種單位,我這邊是直接按照黃皮書(shū)走的)

Multiplier Name
100 Wei
1012 Szabo
1015 Finney
1018 Ether

1.2 分叉

以太坊的正確運(yùn)行建立在其鏈上只有一個(gè)鏈?zhǔn)怯行У模腥硕急仨氁邮芩碛卸鄠€(gè)狀態(tài)(或多個(gè)鏈)會(huì)摧毀這個(gè)系統(tǒng),因?yàn)樗谀膫€(gè)是正確狀態(tài)的問(wèn)題上不可能得到統(tǒng)一結(jié)果。如果鏈分叉了,你有可能在一條鏈上擁有10個(gè)幣,一條鏈上擁有20個(gè)幣,另一條鏈上擁有40個(gè)幣。在這種場(chǎng)景下,是沒(méi)有辦法確定哪個(gè)鏈才是最”有效的“。不論什么時(shí)候只要多個(gè)路徑產(chǎn)生了,一個(gè)”分叉“就會(huì)出現(xiàn)。
為了確定哪個(gè)路徑才是最有效的以及防止多條鏈的產(chǎn)生,以太坊使用了一個(gè)叫做“GHOST協(xié)議(GHOST protocol.)”的數(shù)學(xué)機(jī)制。

簡(jiǎn)單來(lái)說(shuō),GHOST協(xié)議就是讓我們必須選擇一個(gè)在其上完成計(jì)算最多的路徑。一個(gè)方法確定路徑就是使用最近一個(gè)區(qū)塊(葉子區(qū)塊)的區(qū)塊號(hào),區(qū)塊號(hào)代表著當(dāng)前路徑上總的區(qū)塊數(shù)(不包含創(chuàng)世紀(jì)區(qū)塊)。區(qū)塊號(hào)越大,路徑就會(huì)越長(zhǎng),就說(shuō)明越多的挖礦算力被消耗在此路徑上以達(dá)到葉子區(qū)塊。

二、 區(qū)塊、狀態(tài)與交易

2.1 世界狀態(tài)

以太坊中的世界狀態(tài)指地址(Address)與賬戶狀態(tài)(Account State)的集合。世界狀態(tài)并不是存儲(chǔ)在鏈上,而是通過(guò)Merkle Patricia tree來(lái)維護(hù)。
賬戶狀態(tài)(Account State)包含四個(gè)屬性。

  • nonce: 如果賬戶是一個(gè)外部擁有賬戶,nonce代表從此賬戶地址發(fā)送的交易序號(hào)。如果賬戶是一個(gè)合約賬戶,nonce代表此賬戶創(chuàng)建的合約序號(hào)。用\boldsymbol{\sigma}[a]_{\mathbf{n}}來(lái)表示。
  • balance:此地址擁有Wei的數(shù)量。1Ether=10^18Wei。用\boldsymbol{\sigma}[a]_{\mathbf{b}}來(lái)表示。
  • storageRoot: 理論上是指Merkle Patricia樹(shù)的根節(jié)點(diǎn)256位的Hash值。用\boldsymbol{\sigma}[a]_{\mathbf{s}}來(lái)表示。公式6中有介紹。
  • codeHash:此賬戶EVM代碼的hash值。對(duì)于外部擁有賬戶,codeHash域是一個(gè)空字符串
    的Hash值。對(duì)于合約賬戶,就是代碼的Hash作為codeHash保存。用\boldsymbol{\sigma}[a]_{\mathbf{c}}來(lái)表示。
// github.com/ethereum/go-ethereum/core/state/state_object.go
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

關(guān)于storageRoot的補(bǔ)充
\begin{equation} {\small TRIE}\big(L_{I}^*(\boldsymbol{\sigma}[a]_{\mathbf{s}})\big) \equiv \boldsymbol{\sigma}[a]_{\mathrm{s}} \tag{6} \end{equation}

\begin{equation} L_{I}\big( (k, v) \big) \equiv \big({\small KEC}(k), {\small RLP}(v)\big) \tag{7} \end{equation}

\begin{equation} k \in \mathbb{B}_{32} \quad \wedge \quad v \in \mathbb{N} \tag{8} \end{equation}

公式6, 由于有些時(shí)候我們不僅需要state的hash值的trie,而是需要其對(duì)應(yīng)的kv數(shù)據(jù)也包含其中。所以以太坊中的存儲(chǔ)State的樹(shù),不僅包含State的hash,同時(shí)也包含了存儲(chǔ)這個(gè)賬戶的address的hash和它對(duì)應(yīng)的data也就是其Account的值的數(shù)據(jù)對(duì)的集合。這里storageRoot實(shí)際上是這樣的樹(shù)的根節(jié)點(diǎn)hash值。

公式7,指state對(duì)應(yīng)的kv數(shù)據(jù)的RLP的形式化表示,是k的hash值作為key,value是v的RLP表示。也就是以太坊中實(shí)際存儲(chǔ)的state是賬戶address的hash ({\small KEC}(k)),與其數(shù)據(jù)Account內(nèi)容的RLP ({\small RLP}(v))

公式8,指公式7中的k是32的字符數(shù)組。這個(gè)是由KECCAK256算法保證的。

注:原本公式8中要求的是v是個(gè)正整數(shù),但是我看來(lái)下代碼和下文公式10,感覺(jué)這里的v都應(yīng)該是Account的內(nèi)容

一些符號(hào)化定義

以太坊中的賬戶有兩類,一類是外部賬戶,一類是合約賬戶。其中外部賬戶被私鑰控制且沒(méi)有任何代碼與之關(guān)聯(lián)。合約賬戶,被它們的合約代碼控制且有代碼與之關(guān)聯(lián)。以下幾個(gè)公式定義了賬戶的各種狀態(tài)。
\begin{equation} L_{S}(\boldsymbol{\sigma}) \equiv \{ p(a): \boldsymbol{\sigma}[a] \neq \varnothing \} \tag{9} \end{equation}
其中
\begin{equation} p(a) \equiv \big({\small KEC}(a), {\small RLP}\big( (\boldsymbol{\sigma}[a]_{\mathrm{n}}, \boldsymbol{\sigma}[a]_{\mathrm{b}}, \boldsymbol{\sigma}[a]_{\mathrm{s}}, \boldsymbol{\sigma}[a]_{\mathrm{c}}) \big) \big) \tag{10} \end{equation}

\begin{equation} \forall a: \boldsymbol{\sigma}[a] = \varnothing \vee (a \in \mathbb{B}_{20} \wedge v(\boldsymbol{\sigma}[a])) \tag{11} \end{equation}

\begin{equation} v(x) \equiv {x}_{\mathrm{n}} \in \mathbb{N}_{256} \wedge {x}_{\mathrm{b}} \in \mathbb{N}_{256} \wedge {x}_{\mathrm{s}} \in \mathbb{B}_{32} \wedge {x}_{\mathrm{c}} \in \mathbb{B}_{32} \tag{12} \end{equation}

\begin{equation} \mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a) \quad \equiv \quad \boldsymbol{\sigma}[a]_{\mathrm{c}} = {\small KEC} \big(()\big) \wedge \boldsymbol{\sigma}[a]_{\mathrm{n}} = 0 \wedge \boldsymbol{\sigma}[a]_{\mathrm{b}} = 0 \tag{13} \end{equation}

\begin{equation} \mathtt{\tiny DEAD}(\boldsymbol{\sigma}, a) \quad\equiv\quad \boldsymbol{\sigma}[a] = \varnothing \vee \mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a) \tag{14} \end{equation}

公式9,定義了函數(shù)L_{S},意思是若賬戶a不為空,則返回賬戶p(a)

公式10,定義p(a),p(a)其實(shí)就是我們上面公式7,解釋kv對(duì)的時(shí)候的kv對(duì)。包括address的hash值,以及Account內(nèi)容的RLP結(jié)果。

公式11與公式12,對(duì)賬戶a做了定義,表示賬戶要么為空,要么就是一個(gè)a為20個(gè)長(zhǎng)度的字符,其nonce值為小于2256的正整數(shù),balance值為小于2256的正整數(shù),storageRoot為32位的字符,codeHash為32的字符。

公式13,定義了空賬戶。若一個(gè)賬戶,其地址為空字符,并且該賬戶nonce值為0,balance值也為0.

公式14,定義了死賬戶,死賬戶要么為空\varnothing,要么是一個(gè)EMPTY賬戶。

2.2 交易

  • nonce: 與發(fā)送該交易的賬戶的nonce值一致。用T_{\mathrm{n}}表示。
  • gasPrice: 表示每gas的單價(jià)為多少wei。用T_{\mathrm{p}}表示。
  • gasLimit:執(zhí)行該條交易最大被允許使用的gas數(shù)目。用T_{\mathrm{g}}表示。
  • to:160位的接受者地址。當(dāng)交易位創(chuàng)建合約時(shí),該值位空。用T_{\mathrm{t}}表示。
  • value:表示發(fā)送者發(fā)送的wei的數(shù)目。該值為向接受者轉(zhuǎn)移的wei的數(shù)目,或者是創(chuàng)建合約時(shí)作為合約賬戶的初始wei數(shù)目。用T_{\mathrm{v}}表示。
  • v,r,s: 交易的簽名信息,用以決定交易的發(fā)送者。分別用T_{\mathrm{w}},T_{\mathrm{r}},T_{\mathrm{s}}表示。
  • init:如果是創(chuàng)建合約的交易,則init表示一段不限長(zhǎng)度的EVM-Code用以合約賬戶初始化的過(guò)程。用T_{\mathrm{i}}表示。
  • data: 調(diào)用合約的交易,會(huì)包含一段不限長(zhǎng)度的輸入信息,用T_{\mathrmauggxm6}表示。
// github.com/ethereum/go-ethereum/core/types/transaction.go
type Transaction struct {
    data txdata
    // caches
    hash atomic.Value
    size atomic.Value
    from atomic.Value
}
type txdata struct {
    AccountNonce uint64          `json:"nonce"    gencodec:"required"`
    Price        *big.Int        `json:"gasPrice" gencodec:"required"`
    GasLimit     uint64          `json:"gas"      gencodec:"required"`
    Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
    Amount       *big.Int        `json:"value"    gencodec:"required"`
    Payload      []byte          `json:"input"    gencodec:"required"`

    // Signature values
    V *big.Int `json:"v" gencodec:"required"`
    R *big.Int `json:"r" gencodec:"required"`
    S *big.Int `json:"s" gencodec:"required"`

    // This is only used when marshaling to JSON.
    Hash *common.Hash `json:"hash" rlp:"-"`
}

一些符號(hào)化定義
\begin{equation} L_{T}(T) \equiv \begin{cases} (T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{i}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{if} \; T_{\mathrm{t}} = \varnothing\\ (T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbfmw7s21s}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{otherwise} \end{cases} \tag{15} \end{equation}

\begin{equation} \begin{array} \quad T_{\mathrm{n}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{v}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{p}} \in \mathbb{N}_{256} & \wedge \\ T_{\mathrm{g}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{w}} \in \mathbb{N}_5 & \wedge & T_{\mathrm{r}} \in \mathbb{N}_{256} & \wedge \\ T_{\mathrm{s}} \in \mathbb{N}_{256} & \wedge & T_{\mathbfgoxjslh} \in \mathbb{B} & \wedge & T_{\mathbf{i}} \in \mathbb{B} \end{array} \tag{16} \end{equation}

\begin{equation} \mathbb{N}_{\mathrm{n}} = \{ P: P \in \mathbb{N} \wedge P < 2^n \} \tag{17} \end{equation}

\begin{equation} T_{\mathbf{t}} \in \begin{cases} \mathbb{B}_{20} & \text{if} \quad T_{\mathrm{t}} \neq \varnothing \\ \mathbb{B}_{0} & \text{otherwise}\end{cases} \tag{18} \end{equation}
以太坊中根據(jù)交易中的to值是否為空,可以判斷交易是創(chuàng)建合約還是執(zhí)行合約。

公式15表示,如果to的值T_{\mathrm{t}}為空,則交易是創(chuàng)建合約的交易,需要有init數(shù)據(jù)。交易的RLP形式化可以表示為nonce T_{\mathrm{n}},gasPrice T_{\mathrm{p}}, gasLimit T_{\mathrm{g}}, to T_{\mathrm{t}},value T_{\mathrm{v}}init T_{\mathrm{i}},“v ,r, s” T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}。如果T_{\mathrm{t}}不為空,則交易是執(zhí)行合約的交易,需要有data數(shù)據(jù)。交易的RLP形式化可以表示為nonce T_{\mathrm{n}},gasPrice T_{\mathrm{p}}, gasLimit T_{\mathrm{g}}, to T_{\mathrm{t}},value T_{\mathrm{v}}data T_{\mathrmlq5kvjc},“v ,r, s” T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}

公式16,是對(duì)交易的各個(gè)字段限制的符號(hào)化定義。其意思是nonce、value、gasPrice、gasLimit以及特殊的用來(lái)驗(yàn)證簽名的r和s 都是小于2256的正整數(shù),用來(lái)驗(yàn)證簽名的v (T_{\mathrm{w}})是小于25的正整數(shù)。而init和data都是未知長(zhǎng)度的字符數(shù)組。

公式17,是對(duì)\mathbb{N}_{\mathrm{n}}的定義,即小于2n的正整數(shù)。

公式18,是對(duì)交易中的to字段的符號(hào)化定義,當(dāng)其不為空的時(shí)候,是20位的字符,為空的時(shí)候是0位字符。

2.3 區(qū)塊

以太坊中的一個(gè)區(qū)塊由區(qū)塊頭Header,以及交易列表B_{\mathbf{T}},以及ommerblock的header集合B_{\mathbf{U}}三部分組成。

Header包括以下字段。

  • parentHash: 父節(jié)點(diǎn)的hash值。用H_{\mathrm{p}}表示。
  • ommersHash: uncle節(jié)點(diǎn)的hash值,這塊是跟GHOST相關(guān)的,用H_{\mathrm{o}}表示。
  • beneficiary: 礦工address,用H_{\mathrm{c}}表示。
  • stateRoot: 當(dāng)所有交易都執(zhí)行完畢后的世界狀態(tài)樹(shù)的根節(jié)點(diǎn),用H_{\mathrm{r}}表示。
  • transactionsRoot:交易列表的根節(jié)點(diǎn),用H_{\mathrm{t}}表示。
  • receiptsRoot:收據(jù)的根節(jié)點(diǎn),用H_{\mathrm{e}}表示。
  • logsBloom:日志過(guò)濾器,用H_{\mathrm{b}}表示。這個(gè)暫時(shí)沒(méi)細(xì)看,不太確定。
  • difficulty:區(qū)塊難度,根據(jù)上一個(gè)區(qū)塊的難度以及時(shí)間戳算出來(lái)的值,用H_{\mathrm9h1pbjs}表示。
  • number:區(qū)塊號(hào),用H_{\mathrm{i}}表示。
  • gasLimit: 區(qū)塊的gas數(shù)量限制,即區(qū)塊中交易使用掉的gas值不應(yīng)該超過(guò)該值。用H_{\mathrm{l}}表示。
  • gasUsed: 區(qū)塊使用掉的gas數(shù)量,用H_{\mathrm{g}}表示。
  • timestamp:時(shí)間戳,用H_{\mathrm{s}}表示。
  • extraData:額外的數(shù)據(jù),合法的交易對(duì)長(zhǎng)度有限制,用H_{\mathrm{x}}表示。
  • mixHash: 與nonce一起用作工作量證明,用H_{\mathrm{m}}表示。
  • nonce:與mixHash一起用作工作量證明,用H_{\mathrm{n}}表示。
ethereumblock.png
// github.com/ethereum/go-ethereum/core/types/block.go
// "external" block encoding. used for eth protocol, etc.
type extblock struct {
    Header *Header
    Txs    []*Transaction
    Uncles []*Header
}

// Header represents a block header in the Ethereum blockchain.
type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
    Coinbase    common.Address `json:"miner"            gencodec:"required"`
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
    Number      *big.Int       `json:"number"           gencodec:"required"`
    GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
    GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`
    Extra       []byte         `json:"extraData"        gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}
// Receipt represents the results of a transaction.
type Receipt struct {
    // Consensus fields
    PostState         []byte `json:"root"`
    Status            uint   `json:"status"`
    CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
    Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
    Logs              []*Log `json:"logs"              gencodec:"required"`

    // Implementation fields (don't reorder!)
    TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
    ContractAddress common.Address `json:"contractAddress"`
    GasUsed         uint64         `json:"gasUsed" gencodec:"required"`
}

區(qū)塊的符號(hào)化定義
\begin{equation} B \equiv (B_{H}, B_{\mathbf{T}}, B_{\mathbf{U}}) \tag{19} \end{equation}
公式19,表示區(qū)塊由三部分組成,區(qū)塊頭B_{H},交易列表B_{\mathbf{T}},以及ommerblock的header集合B_{\mathbf{U}}

2.3.1 交易收據(jù)

以太坊中為了將交易的信息進(jìn)行編碼,以方便索引以及查找或者零知識(shí)證明等相關(guān)的東西,為每條交易定義了一定收據(jù)。對(duì)于第i個(gè)交易,其收據(jù)用B_R[i]表示。
每條收據(jù)都是一個(gè)四元組,包括區(qū)塊當(dāng)前累計(jì)使用的gas值R_{\mathrm{u}},交易執(zhí)行產(chǎn)生的log R_{\mathrm{l}},日志過(guò)濾器R_{\mathrm{b}},以及狀態(tài)碼R_{\mathrm{z}}
\begin{equation} R \equiv (R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}}, R_{\mathrm{z}}) \tag{20} \end{equation}

\begin{equation} L_{R}(R) \equiv (0 \in \mathbb{B}_{256}, R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}}) \tag{21} \end{equation}

\begin{equation} R_{\mathrm{z}} \in \mathbb{N} \tag{22} \end{equation}

\begin{equation} R_{\mathrm{u}} \in \mathbb{N} \quad \wedge \quad R_{\mathrm{b}} \in \mathbb{B}_{256} \tag{23} \end{equation}

\begin{equation} O \equiv (O_{\mathrm{a}}, ({O_{\mathbf{t}}}_0, {O_{\mathbf{t}}}_1, ...), O_{\mathbf2pkwhu5}) \tag{24} \end{equation}

\begin{equation} O_{\mathrm{a}} \in \mathbb{B}_{20} \wedge \forall_{t \in O_{\mathbf{t}}}: t \in \mathbb{B}_{32} \wedge O_{\mathbfny0991z} \in \mathbb{B} \tag{25} \end{equation}
公式20對(duì)區(qū)塊頭中的收據(jù)作了定義,收據(jù)是個(gè)四元組,四元組定義如前文。

公式21,收據(jù)的RLP形式化表示為L_{R}(R)。其中0 \in \mathbb{B}_{256}在之前版本的協(xié)議中是交易執(zhí)行之前的stateRoot。現(xiàn)在被替換為0.

公式22,表示R_{\mathrm{z}}狀態(tài)碼是正整數(shù)。

公式23對(duì)收據(jù)中當(dāng)前累計(jì)使用的gas值R_{\mathrm{u}},和日志過(guò)濾器R_{\mathrm{b}}進(jìn)行了描述。顯然累計(jì)gas值R_{\mathrm{u}}是一個(gè)正整數(shù)。而日志過(guò)濾器R_{\mathrm{b}}是256位字符。

公式24對(duì)交易執(zhí)行的日志R_{\mathrm{l}}進(jìn)行了解釋。

公式25對(duì)其限制進(jìn)行了描述。R_{\mathrm{l}}是日志條目的序列。日志條目需要包括紀(jì)錄日志者的地址,以及日志話題分類,以及實(shí)際數(shù)據(jù)。日志條目用O來(lái)表示,用O_{\mathrm{a}}表示日志紀(jì)錄者的address,用O_{\mathrm{t}}來(lái)表示一些列32位字符的日志主題(log topics),用O_{\mathrmkorysen}來(lái)表示字符數(shù)據(jù)。其中日志紀(jì)錄者的address O_{\mathrm{a}}是20位字符,每一個(gè)日志分類話題O_{\mathrm{t}}是一個(gè)32位字符,而日志數(shù)據(jù)O_{\mathrmj0btuef}是未知長(zhǎng)度的字符。

公式26-30.對(duì)日志過(guò)濾函數(shù)做了定義,這塊涉及到東西是數(shù)據(jù)操作層面的,不影響對(duì)流程的理解。暫不作解釋。

2.3.2 整體的合法性

上面介紹過(guò)了區(qū)塊包含區(qū)塊頭,區(qū)塊交易列表,以及區(qū)塊的ommer區(qū)塊的頭三部分。以太坊中判斷一個(gè)區(qū)塊是否合法,首先需要對(duì)區(qū)塊整體上做合法性判斷。見(jiàn)公式31

  • 其區(qū)塊頭的狀態(tài)樹(shù)的根stateRoot也就是 H_{\mathrm{r}},是否確實(shí)是狀態(tài)樹(shù)的根。
  • 其區(qū)塊頭的ommerHash也就是 H_{\mathrm{o}},是否與區(qū)塊的ommer區(qū)塊的頭部分的hash值一致。
  • 其區(qū)塊頭中的transactionRoot也就是H_{\mathrm{t}},f即區(qū)塊中交易的樹(shù)的根,是否和區(qū)塊存儲(chǔ)的交易列表中的交易一一對(duì)應(yīng)。一一對(duì)應(yīng)關(guān)系見(jiàn)公式32,是將交易所在列表中的索引的RLP作為鍵,交易內(nèi)容v的RLP作為值的鍵值對(duì)。
  • 其區(qū)塊頭中的recieptRoot也就是H_{\mathrm{e}},即區(qū)塊中收據(jù)的樹(shù)的根,是否和區(qū)塊存儲(chǔ)的交易列表一一對(duì)應(yīng),是不是每條交易都有一條相應(yīng)的收據(jù)。一一對(duì)應(yīng)關(guān)系見(jiàn)公式32,是將收據(jù)所在列表中的索引的RLP作為鍵,收據(jù)內(nèi)容v的RLP作為值的鍵值對(duì)。
  • 區(qū)塊頭中l(wèi)ogsBloom也就是H_{\mathrm{b}},是否包含了區(qū)塊的交易的所有日志。
  • 執(zhí)行該區(qū)塊之前的狀態(tài)樹(shù)的根節(jié)點(diǎn),是否與其父區(qū)塊的中的根節(jié)點(diǎn)一致。見(jiàn)公式33.

\begin{equation} \begin{array} {}H_{\mathrm{r}} &\equiv& \mathtt{\small TRIE}(L_S(\Pi(\boldsymbol{\sigma}, B))) & \wedge \\ {}H_{\mathrm{o}} &\equiv& \mathtt{\small KEC}(\mathtt{\small RLP}(L_H^*(B_{\mathbf{U}}))) & \wedge \\ {}H_{\mathrm{t}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{T}} \rVert, i \in \mathbb{P}: \quad p (i, L_{T}(B_{\mathbf{T}}[i]))\}) & \wedge \\ {}H_{\mathrm{e}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{R}} \rVert, i \in \mathbb{P}: \quad p(i, {L_{R}}(B_{\mathbf{R}}[i]))\}) & \wedge \\ {}H_{\mathrm{b}} &\equiv& \bigvee_{\mathbf{r} \in B_{\mathbf{R}}} \big( \mathbf{r}_{\mathrm{b}} \big) \end{array} \tag{31} \end{equation}

\begin{equation} p(k, v) \equiv \big( \mathtt{\small RLP}(k), \mathtt{\small RLP}(v) \big) \tag{32} \end{equation}

\begin{equation} \mathtt{\small TRIE}(L_{S}(\boldsymbol{\sigma})) = {P(B_H)_H}_{\mathrm{r}} \tag{33} \end{equation}

2.3.3 序列化

對(duì)區(qū)塊,以及區(qū)塊頭的序列化表示如下。

\begin{equation} \quad L_{H}(H) \equiv ({}H_{\mathrm{p}}, H_{\mathrm{o}}, H_{\mathrm{c}}, H_{\mathrm{r}}, H_{\mathrm{t}}, H_{\mathrm{e}}, H_{\mathrm{b}}, H_{\mathrm9fs5oso}, H_{\mathrm{i}}, H_{\mathrm{l}}, H_{\mathrm{g}}, H_{\mathrm{s}}, H_{\mathrm{x}}, H_{\mathrm{m}}, H_{\mathrm{n}} ) \tag{34} \end{equation}

\begin{equation} \quad L_{B}(B) \equiv \big( L_{H}(B_{H}), L_{T}^*(B_{\mathbf{T}}), L_{H}^*({B_{\mathbf{U}}}) \big) \tag{35} \end{equation}

\begin{equation} {f^*}\big( (x_0, x_1, ...) \big) \equiv \big( f(x_0), f(x_1), ... \big) \quad \tag{36} \end{equation}

\begin{equation} \begin{array}[t]{lclclcl} {H_{\mathrm{p}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{o}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{c}} \in \mathbb{B}_{20} & \wedge \\ {H_{\mathrm{r}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{t}} \in \mathbb{B}_{32} & \wedge & {H_{\mathrm{e}}} \in \mathbb{B}_{32} & \wedge \\ {H_{\mathrm{b}}} \in \mathbb{B}_{256} & \wedge & H_{\mathrm61sm8c6} \in \mathbb{N} & \wedge & {H_{\mathrm{i}}} \in \mathbb{N} & \wedge \\ {H_{\mathrm{l}}} \in \mathbb{N} & \wedge & H_{\mathrm{g}} \in \mathbb{N} & \wedge & {H_{\mathrm{s}}} \in \mathbb{N}_{256} & \wedge \\ {H_{\mathrm{x}}} \in \mathbb{B} & \wedge & H_{\mathrm{m}} \in \mathbb{B}_{32} & \wedge & {H_{\mathrm{n}}} \in \mathbb{B}_{8} \end{array} \tag{37} \end{equation}

公式34是區(qū)塊頭的序列化表示。

公式35是區(qū)塊的序列化表示,即分別將區(qū)塊頭序列化,區(qū)塊的交易列表,ommer區(qū)塊頭序列化。其中交易序列化函數(shù)L_T見(jiàn)公式15.

公式36表示列表序列化和當(dāng)個(gè)序列化的關(guān)系,列表的序列化,就是把列表中的元素分別序列化,然后將結(jié)果組成列表。

公式37是對(duì)區(qū)塊頭中屬性的限制規(guī)定:

  • 各種hash值都是32位字符。包括parentHash H_{\mathrm{p}},ommerHash H_{\mathrm{o}}, stateRoot H_{\mathrm{r}}, transactionRoot H_{\mathrm{t}}, RecieptRoot H_{\mathrm{e}}, mixHash H_{\mathrm{m}}
  • 受益人也就是挖礦的人的地址beneficiary H_{\mathrm{c}},是20位字符。
  • 日志過(guò)濾器logBoom H_{\mathrm{b}},是256位字符。
  • 難度,區(qū)塊號(hào),gas限制,用掉的gas等都是正整數(shù)。difficulty H_{\mathrmxqy0jwj}, Number H_{\mathrm{i}},gasLimit H_{\mathrm{l}} gasUsed H_{\mathrm{g}}
  • 時(shí)間戳timestamp H_{\mathrm{s}}是個(gè)大的正整數(shù),其值小于2256
  • 額外的數(shù)據(jù)extraData H_{\mathrm{x}}是未知長(zhǎng)度字符。
  • nonce H_{\mathrm{n}}是8位的字符。

2.3.4 區(qū)塊頭的合法性

確定區(qū)塊是否合法除了整體性的合法校驗(yàn),還需要對(duì)區(qū)塊頭進(jìn)行更進(jìn)一步的校驗(yàn)。主要校驗(yàn)規(guī)則有以下幾點(diǎn):

  • parentHash正確。即parentHash與其父區(qū)塊的頭的hash一致。
  • number為父區(qū)塊number值加一。
  • difficuilty難度正確。區(qū)塊合理的難度跟父區(qū)塊難度,以及當(dāng)前區(qū)塊時(shí)間戳和父區(qū)塊時(shí)間戳間隔以及區(qū)塊編號(hào)有關(guān)。難度可以起到一定的調(diào)節(jié)出塊時(shí)間的作用。,可以看出當(dāng)出塊變快(也就是出塊間隔變小之后)難度會(huì)增加,相反難度會(huì)減小。
  • gasLimit和上一個(gè)區(qū)塊的差值在規(guī)定范圍內(nèi)。
  • gasUsed小于等于gasLimit
  • timestamp時(shí)間戳必須大于上一區(qū)塊的時(shí)間戳。
  • mixHash和nonce必須滿足PoW。
  • extraData最多為32個(gè)字節(jié)。
// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
// See YP section 4.3.4. "Block Header Validity"
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
    // Ensure that the header's extra-data section is of a reasonable size
      // 驗(yàn)證extraData的長(zhǎng)度
    if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
        return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
    }
    // Verify the header's timestamp
    // 驗(yàn)證時(shí)間戳是否超過(guò)大小限制,是否過(guò)大,是否大于上一區(qū)塊的時(shí)間戳等
    if uncle {
        if header.Time.Cmp(math.MaxBig256) > 0 {
            return errLargeBlockTime
        }
    } else {
        if header.Time.Cmp(big.NewInt(time.Now().Add(allowedFutureBlockTime).Unix())) > 0 {
            return consensus.ErrFutureBlock
        }
    }
    if header.Time.Cmp(parent.Time) <= 0 {
        return errZeroBlockTime
    }

      // 驗(yàn)證難度是否正確
    // Verify the block's difficulty based in it's timestamp and parent's difficulty
    expected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)

    if expected.Cmp(header.Difficulty) != 0 {
        return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
    }
    // Verify that the gas limit is <= 2^63-1
    cap := uint64(0x7fffffffffffffff)
     //驗(yàn)證gasLimit是否超了上限
    if header.GasLimit > cap {
        return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
    }
     //驗(yàn)證已用的gas值是否小于等于gasLimit
    // Verify that the gasUsed is <= gasLimit
    if header.GasUsed > header.GasLimit {
        return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
    }

    // Verify that the gas limit remains within allowed bounds
    //判斷gasLimit與父區(qū)塊的gasLimit差值是否在規(guī)定范圍內(nèi)
    diff := int64(parent.GasLimit) - int64(header.GasLimit)
    if diff < 0 {
        diff *= -1
    }
    limit := parent.GasLimit / params.GasLimitBoundDivisor

    if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
        return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
    }
    // Verify that the block number is parent's +1
        //驗(yàn)證區(qū)塊號(hào),是否是父區(qū)塊號(hào)+1
    if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
        return consensus.ErrInvalidNumber
    }
    // Verify the engine specific seal securing the block
     //驗(yàn)證PoW
    if seal {
        if err := ethash.VerifySeal(chain, header); err != nil {
            return err
        }
    }
    // If all checks passed, validate any special fields for hard forks
    if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
        return err
    }
    if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
        return err
    }
    return nil
}

校驗(yàn)區(qū)塊號(hào)和區(qū)塊hash的符號(hào)化表示
\begin{equation} P(H) \equiv B': \mathtt{\tiny KEC}(\mathtt{\tiny RLP}(B'_{H})) = {H_{\mathrm{p}}} \tag{39} \end{equation}

\begin{equation} H_{\mathrm{i}} \equiv P(H)_{H_{\mathrm{i}}} + 1 \tag{40} \end{equation}

公式39表示,parentHash值應(yīng)該為父節(jié)點(diǎn)Header的hash值。

公式40表示,number為父節(jié)點(diǎn)number + 1.

難度計(jì)算的符號(hào)化表示
\begin{equation} D(H) \equiv \begin{cases} D_0 & \text{if} \quad H_{\mathrm{i}} = 0\\ \text{max}\!\left(D_0, {P(H)_{H}}_{\mathrmjlkg84m} + {x}\times{\varsigma_2} + {\epsilon} \right) & \text{otherwise}\\ \end{cases} \tag{41} \end{equation}

\begin{equation} D_0 \equiv 131072 \tag{42} \end{equation}

\begin{equation} {x} \equiv \left\lfloor\frac{P(H)_{H_{\mathrmybdrw6b}}}{2048}\right\rfloor \tag{43} \end{equation}

\begin{equation} {\varsigma_2} \equiv \text{max}\left( y - \left\lfloor\frac{H_{\mathrm{s}} - {P(H)_{H}}_{\mathrm{s}}}{9}\right\rfloor, -99 \right) \tag{44} \end{equation}

\begin{equation*} y \equiv \begin{cases} 1 & \text{if} \, \lVert P(H)_{\mathbf{U}}\rVert = 0 \\ 2 & \text{otherwise} \end{cases} \end{equation*}

\begin{equation} \epsilon \equiv \left\lfloor 2^{ \left\lfloor H'_{\mathrm{i}} \div 100000 \right\rfloor - 2 } \right\rfloor \tag{45} \end{equation}

\begin{equation} H'_{\mathrm{i}} \equiv \max(H_{\mathrm{i}} - 3000000, 0) \tag{46} \end{equation}

公式41-46為difficulty的計(jì)算方法。

公式41,42表示,當(dāng)區(qū)塊編號(hào)為0的時(shí)候其難度值是固定好的,在這里用D_0表示,其值為131072.對(duì)于其他區(qū)塊,其難度值需要根據(jù)其父區(qū)塊難度值以及一些其他因素,出塊的間隔時(shí)間,區(qū)塊編號(hào)等有關(guān)進(jìn)行調(diào)節(jié)的,若小于D_0,則難度值調(diào)整為D_0

公式43,調(diào)節(jié)系數(shù)(the adjustment factor )x的定義。

公式44,難度系數(shù)(diculty parameter){\varsigma_2}的定義。該系數(shù)主要與出塊間隔時(shí)間有關(guān),當(dāng)間隔大的時(shí)候,系數(shù)變大,難度也會(huì)相應(yīng)變大,當(dāng)間隔小的時(shí)候,系數(shù)變小,難度也會(huì)變小。使得區(qū)塊鏈在整體上出塊時(shí)間是趨于穩(wěn)定的。其中y值根據(jù)父節(jié)點(diǎn)的uncle節(jié)點(diǎn)是否為空而有所區(qū)別,可以看出當(dāng)父節(jié)點(diǎn)的uncle不為空的時(shí)候,y值為2,說(shuō)明當(dāng)前的分叉程度較大,適當(dāng)調(diào)大難度,一定程度上會(huì)減少分叉。

公式45,46,“difficulty bomb”, or “ice age” {\epsilon}的定義。(看說(shuō)明好像是為了將來(lái)切PoS共識(shí)的時(shí)候,調(diào)節(jié)難度用)

// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
    return CalcDifficulty(chain.Config(), time, parent)
}

// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
    next := new(big.Int).Add(parent.Number, big1)
    switch {
    case config.IsByzantium(next):
        return calcDifficultyByzantium(time, parent)
    case config.IsHomestead(next):
        return calcDifficultyHomestead(time, parent)
    default:
        return calcDifficultyFrontier(time, parent)
    }
}

// 黃皮書(shū)中的介紹的是Byzantium難度協(xié)議,所以這里只給出相應(yīng)的代碼。其他幾種難度調(diào)節(jié)協(xié)議只是參數(shù)值上有區(qū)別。
// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time given the
// parent block's time and difficulty. The calculation uses the Byzantium rules.
func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int {
    // https://github.com/ethereum/EIPs/issues/100.
    // algorithm:
    // diff = (parent_diff +
    //         (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
    //        ) + 2^(periodCount - 2)

    bigTime := new(big.Int).SetUint64(time)
    bigParentTime := new(big.Int).Set(parent.Time)

    // holds intermediate values to make the algo easier to read & audit
    x := new(big.Int)
    y := new(big.Int)

    // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9
    x.Sub(bigTime, bigParentTime)
    x.Div(x, big9)
    if parent.UncleHash == types.EmptyUncleHash {
        x.Sub(big1, x)
    } else {
        x.Sub(big2, x)
    }
    // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99)
    if x.Cmp(bigMinus99) < 0 {
        x.Set(bigMinus99)
    }
    // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
    y.Div(parent.Difficulty, params.DifficultyBoundDivisor)
    x.Mul(y, x)
    x.Add(parent.Difficulty, x)

    // minimum difficulty can ever be (before exponential factor)
    if x.Cmp(params.MinimumDifficulty) < 0 {
        x.Set(params.MinimumDifficulty)
    }
    // calculate a fake block number for the ice-age delay:
    //   https://github.com/ethereum/EIPs/pull/669
    //   fake_block_number = min(0, block.number - 3_000_000
    fakeBlockNumber := new(big.Int)
    if parent.Number.Cmp(big2999999) >= 0 {
        fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, big2999999) // Note, parent is 1 less than the actual block number
    }
    // for the exponential factor
    periodCount := fakeBlockNumber
    periodCount.Div(periodCount, expDiffPeriod)

    // the exponential factor, commonly referred to as "the bomb"
    // diff = diff + 2^(periodCount - 2)
    if periodCount.Cmp(big1) > 0 {
        y.Sub(periodCount, big2)
        y.Exp(big2, y, nil)
        x.Add(x, y)
    }
    return x
}

gasLimit限制的符號(hào)化表示
\begin{eqnarray} \nonumber & & H_{\mathrm{l}} < {P(H)_{H_{\mathrm{l}}}} + \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber & & H_{\mathrm{l}} > {P(H)_{H_{\mathrm{l}}}} - \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber & & H_{\mathrm{l}} \geqslant 5000 \tag{47} \end{eqnarray}

公式47表示區(qū)塊的gasLimit必須大于等于5000,且其和上一個(gè)區(qū)塊的gasLimit差值不超過(guò)\left\lfloor\frac{P(H)_{H_l}}{1024}\right\rfloor

時(shí)間戳的符號(hào)化表示

\begin{equation} H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}} \tag{48} \end{equation}

公式48表示當(dāng)前區(qū)塊的時(shí)間戳必須大于父區(qū)塊的時(shí)間戳。(代碼中要求區(qū)塊的時(shí)間戳不能比當(dāng)前時(shí)間大15秒以上)

mixHash和nonce相關(guān)符號(hào)化表示

\begin{equation} n \leqslant \frac{2^{256}}{H_{\mathrmxz14sdo}} \quad \wedge \quad m = H_{\mathrm{m}} \tag{49} \end{equation}

公式49表示,nonce值和mixHash需要滿足PoW。

區(qū)塊頭驗(yàn)證的符號(hào)化表示

\begin{eqnarray} \tag{50} V(H) & \equiv & n \leqslant \frac{2^{256}}{H_{\mathrmfzsc8db}} \wedge m = H_{\mathrm{m}} \quad \wedge \\ \nonumber & & H_{\mathrmua2g040} = D(H) \quad \wedge \\ \nonumber& & H_{\mathrm{g}} \le H_{\mathrm{l}} \quad \wedge \\ \nonumber& & H_{\mathrm{l}} < {P(H)_{H}}_{\mathrm{l}} + \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber& & H_{\mathrm{l}} > {P(H)_{H}}_{\mathrm{l}} - \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber& & H_{\mathrm{l}} \geqslant 5000 \quad \wedge \\ \nonumber& & H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}} \quad \wedge \\ \nonumber& & H_{\mathrm{i}} = {P(H)_{H}}_{\mathrm{i}} +1 \quad \wedge \\ \nonumber& & \lVert H_{\mathrm{x}} \rVert \le 32 \end{eqnarray}

相關(guān)的含義見(jiàn)本小節(jié)開(kāi)始部分對(duì)區(qū)塊頭驗(yàn)證的那幾點(diǎn)。

圖片來(lái)源于網(wǎng)絡(luò)侵刪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。