16. 以太坊虛擬機 (The Ethereum Virtual Machine)
以太坊虛擬機,簡稱 EVM,是以太坊協議和運作的心臟。 正如您可能從名稱中猜到的那樣,它是一個計算引擎,與或者是其他字節碼編譯(bytecode-complied)的編程語言(如Java)的直譯器,或是像 Microsoft .NET Framework的虛擬機,並沒有太大差別。 在本章中,我們將會詳細介紹 EVM, 在乙太坊狀態更新的執行脈落之中,介紹包含指令集 (instruction set), 結構以及運作。
16.1. 什麼是以太坊虛擬機 (EVM) ?
以太坊虛擬機 (EVM) 是以太坊的一部份,用來處理智能合約的部署以及執行。當外部擁有帳戶 (EOA) 送交易至其它接收者時,若是交易中只用到 value 欄位時,就不需要動到以太坊虛擬機的運算。但是實際上來說,大部份的情形都會需要作到狀態 (state) 的更新,而狀態更新就會運用 EVM 的計算。從高階的角度來看,以太坊區塊鏈上所執行的 EVM 可以被想成一個全域的去中心化電腦,此電腦上面包有數以百萬計的可執行物件,每個物件都擁有永久的數據儲存空間。
EVM 是一個類圖靈完備的狀態機; 用“類”來形容是因為EVM上所有執行程序都有計算步數的限制,因為啟動智能合約之執行時,會設定 Gas 的總量限制。有鑒於此,停機問題(halting problem)就被解決了,所有程式文本之執行都有其終點。當惡意或意外的不當程式被執行時,程序本來將會永遠無法停止,但因為有 Gas 的限制,所以能夠避免該狀況;所以說,以太坊平台遇到這些不當程式時,並不會停機。
EVM 具有基於堆疊的體系結構,將所有記憶體內(in-memory) 的值存儲在堆疊中。它的字(word)大小為256位元(主要用於簡化原生散列和橢圓曲線操作),並具有多個可尋址的數據組件:
-
一個不可變的程序代碼 (program code) ROM,加載了可執行的智能合約字節碼 (bytecode)
-
揮發性記憶體 (volatile memory) 中每個位置都明確初始化為零
-
作為以太坊狀態的一部分的永久存儲器,也是零初始化的
在執行期間還有一組可用的環境變量和數據。我們將在本章後面詳細介紹這些內容。
以太坊虛擬機 (EVM) 架構與執行脈落 顯示以太坊虛擬機架構及執行脈落
16.1.1. 與現有的虛擬機科技作比較
-
虛擬機(Virtual Machine)(Virtualbox, QEMU, 雲計算)
-
Java 虛擬機(VM)
虛擬機技術(如Virtualbox和QEMU / KVM)與EVM的不同之處在於它們的目的是提供管理程式功能,或者處理客戶作業系統與底層主機作業系統和硬體之間的系統調用,任務調度和資源管理的軟體抽象。
然而,Java VM(JVM)規範的某些方面確實包含與EVM的相似之處。從高級概述來看,JVM 旨在提供與底層主機作業系統或硬體無關的運行時環境,從而實現各種系統的兼容性。在JVM上運行的高級程式語言(如Java或Scala)被編譯到相應的指令集 Bytecode 中。這與編譯要在EVM上運行的Solidity源檔案相當。
16.1.2. EVM機器語言( Bytecode 操作)
EVM機器語言分為特定的指令集組,例如算術運算,邏輯和比較運算,控制流,系統調用,堆疊操作和儲存器操作。除典型的 Bytecode 操作外,EVM還必須管理帳戶資訊(即地址和餘額),當前gas價格和區塊資訊。
- 通用堆疊操作
-
堆疊和記憶體管理的操作碼指令:
POP // 項目出棧 PUSH // 項目入棧 MLOAD // 將項目加載到記憶體中 MSTORE // 在記憶體中儲存項目 JUMP // 改變程式計數器的位置 PC // 程式計數器 MSIZE // 活動的記憶體大小 GAS // 交易可用的gas數量 DUP // 複製棧項目 SWAP // 交換棧項目
- 通用系統操作
-
執行程式的系統的操作碼指令:
CREATE // 創建新的帳戶 CALL // 在帳戶間傳遞消息的指令 RETURN // 執行停機 REVERT // 執行停機,恢復狀態更改 SELFDESTRUCT // 執行停機,並標記帳戶為刪除的
- 算術運算
-
通用算術運算程式碼指令:
添加//添加 MUL //乘法 SUB //減法 DIV //整數除法 SDIV //有符號整數除法 MOD // Modulo(剩餘)操作 SMOD //簽名模運算 ADDMOD //模數加法 MULMOD //模數乘法 EXP //指數運算 STOP //停止操作
- 環境操作碼
-
處理執行環境資訊的通用操作碼:
ADDRESS //當前執行帳戶的地址 BALANCE //帳戶餘額 CALLVALUE //執行環境的交易值 ORIGIN //執行環境的原始地址 CALLER //執行調用者的地址 CODESIZE //執行環境程式碼大小 GASPRICE //gas價格狀態 EXTCODESIZE //帳戶的程式碼大小 RETURNDATACOPY //從先前的記憶體調用輸出的數據的副本
16.1.3. 狀態
與任何計算系統一樣,狀態概念也很重要。就像CPU跟蹤執行過程一樣,EVM必須跟蹤各種組件的狀態以支持交易。這些組件的狀態最終會推動總體區塊鏈的變化程度。這方面導致將以太坊描述為_基於交易的狀態機_,包含以下組件:
- World State
-
160位地址識別碼和帳戶狀態之間的映射,在不可變的_Merkle Patricia Tree_資料結構中維護。
- Account State
-
包含以下四個組件:
-
nonce:表示從該相應帳戶發送的交易數量的值。
-
balance:帳戶地址擁有的_wei_的數量。
-
storageRoot:Merkle Patricia Tree根節點的256位雜湊值。
-
codeHash:各個帳戶的EVM程式碼的不可變雜湊值。
-
- Storage State
-
在EVM上運行時維護的帳戶特定狀態資訊。
- Block State
-
交易所需的狀態值包括以下內容:
-
blockhash:最近完成的塊的雜湊值。
-
coinbase:收件人的地址。
-
timestamp:當前塊的時間戳。
-
number:當前塊的編號。
-
difficulty:當前區塊的難度。
-
gaslimit:當前區塊的gas限制。
-
- Runtime Environment Information
-
用於使用交易的資訊。
-
gasprice:當前gas價格,由交易發起人指定。
-
codesize:交易程式碼庫的大小。
-
caller:執行當前交易的帳戶的地址。
-
origin:當前交易原始發件人的地址。
-
狀態轉換使用以下函數計算:
- 以太坊狀態轉換函數
-
用於計算_valid state transition_。
- 區塊終結狀態轉換函數
-
用於確定最終塊的狀態,作為挖礦過程的一部分,包含區塊獎勵。
- 區塊級狀態轉換函數
-
應用於交易狀態時的區塊終結狀態轉換函數的結果狀態。
16.1.4. 將Solidity編譯為EVM Bytecode
可以通過命令行完成將Solidity源檔案編譯為EVM Bytecode 。有關其他編譯選項的列表,只需運行以下命令:
$ solc --help
使用_—opcodes_命令行選項可以輕鬆實現生成Solidity源檔案的原始操作碼流。此操作碼流會遺漏一些資訊(_—asm_選項會生成完整資訊),但這對於第一次介紹是足夠的。例如,編譯範例Solidity檔案_Example.sol_並將操作碼輸出填充到名為_BytecodeDir_的目錄中,使用以下命令完成:
$ solc -o BytecodeOutputDir --opcodes Example.sol
或
$ solc -o BytecodeOutputDir --asm Example.sol
以下命令將為我們的範例程式生成 Bytecode 二進制檔案:
$ solc -o BytecodeOutputDir --bin Example.sol
生成的輸出操作碼檔案將取決於Solidity源檔案中包含的特定合約。我們的簡單Solidity檔案_Example.sol_ [simple_solidity_example]只有一個名為“example”的合約。
pragma solidity ^0.4.19;
contract example {
address contractOwner;
function example() {
contractOwner = msg.sender;
}
}
如果查看_BytecodeDir_目錄,你將看到操作碼檔案_example.opcode_(請參閱[simple_solidity_example]),其中包含“example”合約的EVM機器語言操作碼指令。在文字編輯器中打開_example.opcode_檔案將顯示以下內容:
PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH1 0xE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST CALLER PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP PUSH1 0x35 DUP1 PUSH1 0x5B PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x60 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 JUMP 0xb9 SWAP14 0xcb 0x1e 0xdd RETURNDATACOPY 0xec 0xe0 0x1f 0x27 0xc9 PUSH5 0x9C5ABCC14A NUMBER 0x5e INVALID EXTCODESIZE 0xdb 0xcf EXTCODESIZE 0x27 EXTCODESIZE 0xe2 0xb8 SWAP10 0xed 0x
使用_—asm_選項編譯範例會在_BytecodeDir_目錄中生成一個檔案 example.evm。這包含詳細的EVM機器語言說明:
/* "Example.sol":26:132 contract example {... */
mstore(0x40, 0x60)
/* "Example.sol":74:130 function example() {... */
jumpi(tag_1, iszero(callvalue))
0x0
dup1
revert
tag_1:
/* "Example.sol":115:125 msg.sender */
caller
/* "Example.sol":99:112 contractOwner */
0x0
dup1
/* "Example.sol":99:125 contractOwner = msg.sender */
0x100
exp
dup2
sload
dup2
0xffffffffffffffffffffffffffffffffffffffff
mul
not
and
swap1
dup4
0xffffffffffffffffffffffffffffffffffffffff
and
mul
or
swap1
sstore
pop
/* "Example.sol":26:132 contract example {... */
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x0
codecopy
0x0
return
stop
sub_0: assembly {
/* "Example.sol":26:132 contract example {... */
mstore(0x40, 0x60)
0x0
dup1
revert
auxdata: 0xa165627a7a7230582056b99dcb1edd3eece01f27c9649c5abcc14a435efe3bdbcf3b273be2b899eda90029
}
--bin 選項產生以下內容:
60606040523415600e57600080fd5b336000806101000a81548173 ffffffffffffffffffffffffffffffffffffffff 021916908373 ffffffffffffffffffffffffffffffffffffffff 160217905550603580605b6000396000f3006060604052600080fd00a165627a7a7230582056b99dcb1e
讓我們檢查前兩條指令(參考[common_stack_opcodes]):
PUSH1 0x60 PUSH1 0x40
這裡我們有_mnemonic_“PUSH1”,後跟一個值為“0x60”的原始字節。這對應於EVM指令,該操作將操作碼之後的單字節解釋為文字值並將其推入堆疊。可以將大小最多為32個字節的值壓入堆疊。例如,以下 Bytecode 將4字節值壓入堆疊:
PUSH4 0x7f1baa12
第二個push操作碼將“0x40”儲存到堆疊中(在那裡已存在的“0x60”之上)。
接下來的兩個指令:
MSTORE CALLVALUE
MSTORE是一個堆疊/記憶體操作(參見[common_stack_opcodes]),它將值保存到記憶體中,而CALLVALUE是一個環境操作碼(參見[common_environment_opcodes]),它返回正在執行的消息調用的存放值。
16.1.5. 執行EVM Bytecode
16.1.6. Gas,會計
對於每個交易,都有一個關聯的_gas-limit_和_gas-price_,它們構成了EVM執行的費用。這些費用用於促進交易的必要資源,例如計算和儲存。gas還用於創建帳戶和智能合約。
16.1.7. 圖靈完備性和gas
簡單來說,如果系統或程式語言可以解決你輸入的任何問題,它是_圖靈完備的_。這在以太坊黃皮書中討論過:
It is a quasi-Turing complete machine; the quasi qualification comes from the fact that the computation is intrinsically bounded through a parameter, gas, which limits the total amount of computation done.
ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
雖然EVM理論上可以解決它收到的任何問題,但gas可能會阻止它這樣做。這可能在以下幾個方面發生:
1)在以太坊開採的塊具有與之相關的gas限制; 也就是說,區塊內所有交易所使用的總gas不能超過一定限度。 2)由於gas和gas價格齊頭並進,即使取消了gas限制,高度複雜的交易也可能在經濟上不可行。
但是,對於大多數用例,EVM可以解決提供給它的任何問題。
16.1.8. Bytecode 與運行時 Bytecode
編譯合約時,你可以獲得_合約 Bytecode _或_運行時 Bytecode _。
合約 Bytecode 包含實際上最終位於區塊鏈上的 Bytecode _以及_將 Bytecode 放在區塊鏈上並運行合約構造函數所需的 Bytecode 。
另一方面,運行時 Bytecode 只是最終位於區塊鏈上的 Bytecode 。這不包括初始化合約並將其放在區塊鏈上所需的 Bytecode 。
讓我們以前面創建的簡單`Faucet.sol`合約為例。
// Version of Solidity compiler this program was written for
pragma solidity ^0.4.19;
// Our first contract is a faucet!
contract Faucet {
// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// Limit withdrawal amount
require(withdraw_amount <= 100000000000000000);
// Send the amount to the address that requested it
msg.sender.transfer(withdraw_amount);
}
// Accept any incoming amount
function () public payable {}
}
要獲得合約 Bytecode ,我們將運行`solc --bin Faucet.sol`。如果我們只想要運行時 Bytecode ,我們將運行`solc --bin-runtime Faucet.sol`。
如果比較這些命令的輸出,你將看到運行時 Bytecode 是合約 Bytecode 的子集。換句話說,運行時 Bytecode 完全包含在合約 Bytecode 中。
16.1.9. 反彙編 Bytecode
反彙編EVM Bytecode 是瞭解高級別Solidity在EVM中的作用的好方法。你可以使用一些反彙編程式來執行此操作:
-
Porosity 是一個流行的開源反編譯器:https://github.com/comaeio/porosity
-
Ethersplay 是Binary Ninja的EVM插件,一個反彙編程式:https://github.com/trailofbits/ethersplay
-
IDA-Evm 是IDA的EVM插件,另一個反彙編程式:https://github.com/trailofbits/ida-evm
在本節中,我們將使用 Binary Ninja 的 Ethersplay 插件。
在獲取Faucet.sol的運行時 Bytecode 後,我們可以將其提供給Binary Ninja(在匯入Ethersplay插件之後)以查看EVM指令。
當你將交易發送到智能合約時,交易首先會與該智能合約的調度員(dispatcher)進行交互。調度程式讀入交易的數據欄位並將其發送到適當的函數。
在熟悉的MSTORE指令之後,我們在編譯的Faucet.sol合約中看到以下創建:
PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3f JUMPI
"PUSH1 0x4" 將0x4置於堆疊頂部,棧初始為空。“CALLDATASIZE”獲取接收到的交易的calldata的大小(以字節為單位)並將其推送到堆疊中。當前堆疊如下所示:
| Stack |
|---|
0x4 |
length of calldata from tx (msg.data) |
下一條指令是“LT”,是“小於(less than)”的縮寫。LT指令檢查堆疊上的頂部項是否小於堆疊上的下一項。在我們的例子中,它檢查CALLDATASIZE的結果是否小於4個字節。
為什麼EVM會檢查交易的calldata是否至少為4個字節?因為函數識別碼的工作原理。每個函數由其keccak256雜湊的前四個字節標識。通過將函數的名稱和它所採用的參數放入keccak256雜湊函數,我們可以推匯出它的函數識別碼。在我們的合約中,我們有:
keccak256("withdraw(uint256)") = 0x2e1a7d4d...
因此,“withdraw(uint256)”函數的函數識別碼是0x2e1a7d4d,因為它們是結果雜湊的前四個字節。函數識別碼總是4個字節長,所以如果發送給合約的交易的整個數據欄位小於4個字節,那麼除非定義了_fallback函數_,否則沒有交易可能與之通信的函數。因為我們在Faucet.sol中實現了這樣的fallback函數,所以當calldata的長度小於4個字節時,EVM會跳轉到此函數。
如果msg.data欄位少於4個字節,LT將彈出堆疊的前兩個值並將1推到其上。否則,它會推入0。在我們的例子中,讓我們假設發送給我們的合約的transaciton的msg.data欄位_was_少於4個字節。
“PUSH1 0x3f”指令將字節“0x3f”壓入堆疊。在此指令之後,堆疊如下所示:
| Stack |
|---|
1 |
0x3f |
下一條指令是“JUMPI”,代表“jump if”。它的工作原理如下:
jumpi(label, cond) // Jump to "label" if "cond" is true
在我們的例子中,“label”是0x3f,這是我們的fallback函數存在於我們的智能合約中的地方。“cond”參數為1,它來自之前LT指令的結果。要將整個序列放入單詞中,如果交易數據少於4個字節,則合約將跳轉到fallback函數。
我們來看一下調度員的核心程式碼塊。假設我們收到的長度大於4個字節的calldata,“JUMPI”指令不會跳轉到回退函數。相反,程式碼執行將遵循下一條指令:
PUSH1 0x0 CALLDATALOAD PUSH29 0x1000000... SWAP1 DIV PUSH4 0xffffffff AND DUP1 PUSH4 0x2e1a7d4d EQ PUSH1 0x41 JUMPI
“PUSH1 0x0”將0壓入堆疊,否則為空。“CALLDATALOAD”接受發送到智能合約的calldata中的索引作為參數,並從該索引讀取32個字節,如下所示:
calldataload(p) // call data starting from position p (32 bytes)
由於0是從PUSH1 0x0命令傳遞給它的索引,因此CALLDATALOAD從字節0開始讀取32字節的calldata,然後將其推送到堆疊的頂部(在彈出原始0x0之後)。在“PUSH29 0x1000000 …”指令之後,堆疊如下所示:
| Stack |
|---|
32 bytes of calldata starting at byte 0 |
0x1000000… (29 bytes in length) |
“SWAP1”用它後面的_第i個_元素交換堆疊頂部元素。在這裡,它與密鑰數據交換0x1000000 … 新堆疊如下所示:
| Stack |
|---|
0x1000000… (29 bytes in length) |
32 bytes of calldata starting at byte 0 |
下一條指令是“DIV”,其工作方式如下:
div(x, y) // x / y
在這裡,x = 32字節的calldata從字節0開始,y = 0x100000000 …(總共29個字節)。你能想到調度員為什麼要進行劃分嗎?這是一個提示:我們從索引0開始從calldata讀取32個字節。該calldata的前四個字節是函數識別碼。
我們之前推送的0x100000000 ….長度為29個字節,由開頭的1組成,後跟全0。將我們的32字節的calldata除以此0x100000000 ….將只留下從索引0開始的callataload的_topmost 4字節_這四個字節 - 從索引0開始的calldataload中的前四個字節 - 是函數識別碼,並且這就是EVM如何提取該欄位。
如果你不清楚這一部分,可以這樣想:在base10,1234000/1000 = 1234。在base16中,這沒有什麼不同。不是每個地方都是10的倍數,它是16的倍數。正如在我們的較小的例子中除以103(1000)只保留最頂部的數字,將我們的32字節基數16值除以1629做同樣的事。
DIV(函數識別碼)的結果被推送到堆疊上,我們的新堆疊如下:
| Stack |
|---|
function identifier sent in msg.data |
由於“PUSH4 0xffffffff”和“AND”指令是冗餘的,我們可以完全忽略它們,因為堆疊在完成後將保持不變。“DUP1”指令複製堆疊上的1st項,這是函數識別碼。下一條指令“PUSH4 0x2e1a7d4d”將抽取(uint256)函數的計算函數識別碼推送到堆疊。堆疊現在看起來如下:
| Stack |
|---|
function identifier sent in msg.data |
function identifier sent in msg.data |
0x2e1a7d4d |
下一條指令“EQ”彈出堆疊的前兩項並對它們進行比較。這是調度程式完成其主要工作的地方:它比較交易的msg.data欄位中發送的函數識別碼是否與withdraw(uint256)匹配。如果它們相等,則EQ將1推入堆疊,這最終將用於跳轉到fallback函數。否則,EQ將0推入堆疊。
假設發送給我們合約的交易確實以withdraw(uint256)的函數識別碼開頭,我們的新棧看起來如下:
| Stack |
|---|
function identifier sent in msg.data |
1 |
接下來,我們有“PUSH1 0x41”,這是withdraw(uint256)函數在合約中的地址。在此指令之後,堆疊如下所示:
| Stack |
|---|
function identifier sent in msg.data |
1 |
0x41 |
接下來是JUMPI指令,它再次接受堆疊上的前兩個元素作為參數。在這種情況下,我們有“jumpi(0x41,1)”,它告訴EVM執行跳轉到withdraw(uint256)函數的位置。
16.2. EVM工具參考
-
[ByteCode To Opcode Disassembler](https://etherscan.io/opcode-tool) (用於檢查/調試編譯是否完整運行,如果源程式碼未發佈則可用於逆向工程) == 共識
以太坊網路中的共識是指多個節點或代理在給定的時間點就區塊鏈狀態達成一致的能力。這與傳統的定義爲個人或羣體之間的一般協議的共識密切相關但不同。在這裏,社區必須解決在技術上(在網路內)和社交上達成共識的挑戰(以確保協議不會分叉或破裂)。本章將概述建立共識的一些技術方法。
當涉及區塊鏈上分散記錄保存和驗證的核心功能時,單獨依靠信任來確保添加到帳本的資訊是正確的可能會成爲問題。這種挑戰在去中心化網路中更爲明顯,因爲沒有中央實體來決定什麼應該和不應該被視爲是真實的。缺乏一箇中央決策實體是區塊鏈受歡迎程度的主要吸引力之一,因爲系統能夠抵抗審查制度,並且無需對許可或資訊獲取權限的依賴。然而,這些好處可能帶來成本,因爲如果沒有可信的仲裁員,任何分歧,欺騙或差異都需要使用數學,經濟或社會技術進行協調。因此,分散的系統更有抵禦攻擊的能力,但在應對變化時卻不那麼果斷。
獲得共識和信任資訊的能力將對區塊鏈技術作爲資產類別和技術的未來採用和實用具有重要意義。爲了應對這一挑戰並保持權力下放的重要性,社區不斷嘗試不同共識模式,我們將在本章中探討。
16.3. 共識度量
共識度量是可測量的數據,區塊鏈網路的節點必須在該數據上達成一致,以便爲每個塊中包含的數據建立並保持一致。在區塊鏈技術中,每次將新塊添加到鏈中時,每個網路節點都會測量並批准一致性度量。作爲共識度量的結果,區塊鏈充當了從一個確定可驗證的事實延伸到下一個事實的真理鏈。由於共識度量,區塊鏈協議的節點變爲 迷你公證人 mini-notaries,能夠從真實的節點中立即分辨出區塊鏈的錯誤副本,並將該事實報告給整個網路。這些措施是必需的,以便阻止通過提交包含虛假資訊的區塊來欺騙網路不良行爲者。由於一致性度量,區塊鏈不僅建立了的完整性,而且長期保持不變。共識度量有多種形式,但對於此討論而言,最重要的兩種是基於風險的度量和基於工作量的度量。
16.4. 基於Hash的度量
通常稱爲工作量證明(PoW)度量,這些度量建立了共識,因爲使用它們的協議將計算機設置爲查找難題的答案。找到適合網路參數的雜湊的難題要求節點提交處理能力並使用電力與其他節點競爭以提出有效的雜湊。爲了便於說明,可以考慮超級計算機,它的唯一工作就是在整數空間中搜索質數。現在考慮由普通計算機組成的整個網路。這些計算機放在一起時,可以說具有超級計算機的組合計算能力。這個計算機網路的唯一工作類似於搜索另一種稱爲SHA-256雜湊的數字的可能數字。這些數字具有獨特的屬性,就像質數一樣,儘管在生成符合網路設定標準的雜湊方面存在很大困難,但在發現時可以輕鬆驗證它們。想象計算雜湊和驗證雜湊的一種方法是使用拼圖遊戲類比。這個難題非常困難且耗時,但是一眼就能看出它是否已經完成。
當準確計算SHA-256雜湊值時,它們可用作證明已使用一定量的計算能力來查找數字的證明。最新的質數是asciimath:[2^(77,232,917)- 1]。它是由計算機發現的,就像計算機發現的SHA-256雜湊一樣。SHA-256雜湊比新質數更容易找到,但是找到雜湊的固有難點在於基於雜湊的度量得出它們的能力。
每個SHA-256雜湊都有64個十六進制字符。例如,這裏是單詞“yank”的SHA-256雜湊。
“yank”(SHA-256)= 45F1B9FC8FD5F760A2134289579DF920AA55830F2A23DCF50D34E16C1292D7E0
將其與三個字母“yan”的SHA-256雜湊:
“yan”(SHA-256)= 281ACA1A80B52620BD717E8B14A0386B8ADA92AE859AC2C8A2AC222EFA02EDBB
兩個字母“ya”的SHA-256雜湊:
“ya”(SHA-256)= 6663103A3E47EFC879EA31FA38458BE23BE0CE0895F3D8B27B7EA19A1120B3D4
單個字母“y”的SHA-256雜湊:
“y”(SHA-256)= A1FCE4363854FF888CFF4B8E7875D600C2682390412A8CF79B37D0B11148B0FA
16.5. 基於雜湊的度量驗證
如果你對足夠多的隨機短語進行了雜湊,有點像打字機上的猴子,最終你會發現一個匹配特定模式的雜湊。在雜湊爲“ya”的情況下,請注意它以模式“666”開頭。這類似於比特幣,但比特幣要求找到與以“000”開頭的模式匹配的雜湊值。可以通過將前一個區塊中的資訊插入到SHA-256雜湊演算法中來創建的任何雜湊,並用於創建下一個塊,只要它在數字中具有正確數量的前導零,網路就會認可,並且區塊獎勵將是你的。
由於想要挖掘比特幣的人數不斷增加,因此每秒尋找SHA-256雜湊值的算力總是越來越多。比特幣軟體通過自動調整共識度量的難度來處理這種意外事件,增加前導零的數量以形成共識。這可以保證新塊的創建時間與前面的塊大致相同。對於比特幣網路,爲十分鐘,但可以輕鬆更改。在以太坊中,平均區塊生成時間約爲10秒。
17. 基於風險的度量
通常稱爲Proof-of-Stake(PoS)度量,這些度量基於以下事實建立共識:選擇創建無效區塊的每個人都會失去比通過創建有效區塊獲得的更多的東西。該度量是通過關於鏈內數據的共識創造的,而不是關於鏈外數據的共識。基於雜湊的共識度量主要關注SHA-256雜湊的質量和精確性。基於風險的度量主要關注任何特定節點在添加新塊時所承擔的風險。
所有節點在這裏達成一致的度量是哪些節點創建了正確的塊,哪些節點沒有。在某種程度上,這已經內建在比特幣協議中。比特幣協議假定正確的塊是大多數節點正在挖掘的塊。它假設礦工不會選擇不正確的區塊,因爲這不利於挖掘壞區塊。
另一方面,基於風險的鏈依賴於對未能創建網路中其他節點認可的塊的創建者的快速,即時和不可逆轉的影響。通過強制執行資源丟失的風險,基於雜湊的度量(也依賴於不想浪費資源的人員)過程可以以更簡單的方式進行縮短和實施。目前正在研究在以太坊中實現這種共識度量模型。
工作量證明是一種共識協議,它將網路中的有效區塊鏈視爲創建計算成本最高的鏈。這裏提到的計算工作是將所有塊添加到當前區塊鏈所必須完成的工作。這項工作由網路節點完成,並且必須在計算上很困難,以便使工作變得非常重要,但也必須是可行的,以便在經過合理的努力之後可以實現。最終,網路將依賴於提供此PoW的節點以維持區塊鏈,因此,網路的最佳利益是需要合理的PoW。
在以太坊網路以及許多其他區塊鏈網路中,獲取PoW需要找到要添加到區塊鏈的塊的雜湊。這個雜湊是通過雜湊由塊的數據和隨機數組成的字串獲得的(創建此字串的方法可能會有所不同,但整個過程是相同的)。該雜湊必須小於某個閾值(由網路的難度確定),並且一旦節點發現產生該雜湊的隨機數,則接受相應的塊並將其添加到區塊鏈中。
找到這個有效雜湊的方法是修改nonce,通常將其初始化爲零並在每次迭代時遞增,直到產生低於網路閾值的雜湊。此過程稱爲挖掘。由於挖掘中使用的雜湊函數的性質,找到有效隨機數的唯一方法是通過暴力搜索,即檢查隨機數的每個可能值,直到找到滿足網路要求的雜湊。因此,提供有效的隨機數被認爲是PoW。
17.1. PoS
權益證明(PoS,Proof-of-Stake)是公共區塊鏈的一類共識演算法,它依賴於驗證者在網路中的經濟利益。在基於工作量證明(PoW)的公共區塊鏈(例如比特幣和以太坊的當前實現)中,該演算法獎勵解密加密謎題的參與者,以便驗證交易並創建新的塊(即挖掘)。在基於PoS的公共區塊鏈中(例如以太坊即將發佈的Casper實現),一組驗證者輪流對下一個塊進行建議和投票,每個驗證者的投票權重取決於其存款的大小(即賭注)。PoS的顯着優勢包括安全性,降低集中風險和能源效率。
通常,權益證明演算法如下。區塊鏈跟蹤一組驗證者,任何持有區塊鏈基本密碼貨幣的人(在以太坊的情況下是ether)都可以通過發送一種特殊類型的交易來將其以太幣鎖定到存款中,從而成爲驗證者。然後,通過所有當前驗證者都可以參與的一致性演算法來完成創建和同意新塊的過程。
有許多種共識演算法,以及許多方法可以爲參與共識演算法的驗證人分配獎勵,因此有許多“口味”的權益證明。從演算法的角度來看,有兩種主要類型:基於鏈的權益證明和BFT風格的權益證明。
-
在基於鏈的證明中,演算法在每個時隙中僞隨機地選擇一個驗證者(例如,每個10秒的週期可能是一個時隙),併爲該驗證者分配創建單個塊的權限,這個塊必須指向一些前一個塊(通常是前一個最長鏈末端的塊),因此隨着時間的推移,大多數塊會聚成一個不斷增長的鏈。
-
在BFT風格的股權證明中,驗證者被隨機分配提出區塊的權利,但是通過多輪過程來確定哪個區塊是規範的,其中每個驗證者在每輪中發送對某個特定區塊的“投票”,在流程結束時,所有(誠實和在線)驗證者永久同意任何給定的塊是否屬於鏈條的一部分。請注意,塊可能仍然鏈接在一起; 關鍵的區別在於塊上的共識可以在一個塊內,並且不依賴於它之後的鏈的長度或大小。
17.1.1. PoA
授權證明(PoA)是PoS一致性演算法的子集,主要由測試網和私有或聯盟網路使用。在基於PoA的區塊鏈中,交易有效性最終由一組經批准的鏈上帳戶確定,稱爲“授權節點”。確定授權節點的標準是通過網路治理結構中編寫的方法確定性地決定的。
PoA被廣泛認爲是達成共識的最快途徑,但依賴於驗證節點尚未受到損害的假設。非驗證參與者可以像公共以太網那樣訪問和使用網路(通過利用p2p交易,合約,帳戶等)
PoA共識依賴於驗證者的聲譽和過去的表現。這個想法是驗證者節點將其身份/聲譽放到我的身上。私人聯盟網路的一個重要方面是鏈上地址與已知的現實世界身份之間的聯繫。因此,我們可以說驗證節點正在盯着他們的“身份”或“聲譽”(而不是他們的經濟持有)。這爲驗證者創建了一定程度的問責制,最適合企業,私有或測試網路。
PoA目前由測試網路Kovan(PoA網路)使用,並且可以在Parity中輕鬆配置用於私人聯盟網路。
17.1.2. DPoS
代理權益證明(DPoS)是一種經過修改的權益證明形式,網路參與者投票選舉一系列代表(也稱爲證人)來驗證和保護區塊鏈。這些代表有點類似於PoA中的權威節點,除非他們的權限可能被選民撤銷。
在DPoS共識中,與PoS一樣,投票權重與用戶注入的投注金額成正比。這就產生了一個場景,即較多token持有者比較少token的持有者擁有更多的投票權。從遊戲理論的角度來看,這是有道理的,因爲那些具有更多經濟的“遊戲中的皮膚”的人自然會有更大的動力來選出最有效的代表證人。
此外,代表證人會收到驗證每個區塊的獎勵,因此被激勵保持誠實和有效 - 以免被替換。然而,有一些方法可以使“賄賂”變得相當合理; 例如,交易所可以提供存款利率(或者更加含糊地,使用交易所自己的資金建立一個很好的界面和功能),交易所運營商可以使用大量存款進行DPoS共識投票。。