我如何可靠地誘導區塊鏈分叉用於測試目的?
我想在私有測試網上可靠地誘導一個分叉,以便我可以測試與合約互動的非區塊鏈程式碼的行為。(請參閱有關我為什麼要這樣做的相關問題)。也可能對 geth 和其他節點實現的自動化測試有用。
可靠,我的意思是可重複的,每次都得到相同的結果,這樣我就可以在自動化測試系統中執行它。
乙太坊論壇上也出現了類似的問題。
解決方案
- 在您的專用網路上設置 2 個連接的 geth 實例並開始探勘它們。
- geth 中有一個
admin.addPeer(...)
命令可以將對等節點添加到 geth 的對等節點列表中,但似乎沒有任何命令可以刪除這些對等節點。- 要模擬分叉,請阻止兩個 geth 實例用於其對等連接的網路埠。
- 在 Linux 環境下,可以使用**iptables命令來阻塞網路埠。**在 Mac OSX 或 Windows 上使用等效的防火牆。
- 兩個斷開連接的 geth 實例都將繼續探勘各自的區塊鏈副本。
- 在兩個斷開連接的節點上分別發送一些交易,並在一個地址上雙花。
- 通過刪除埠阻止規則重新連接 geth 實例。
- 隨著最長/最高難度的區塊鏈成為真正的來源,並且分叉區塊鏈的一份副本被丟棄,雙花應該會消失。
- 這個過程應該是可重複的,儘管當發送的交易被包含在單獨的(最終是單一的)區塊鏈中時探勘區塊的時間在不同的測試中是不同的。
- (我將把測試留到另一天,以檢查當 geth 實例再次開始通信時協議如何決定使用哪個區塊鏈)。
測試詳情如下:
考試
- 我將在同一台電腦上執行兩個 geth 實例
- 第一個 geth 實例通過以下命令使用 P2P 埠 30301:
user@Kumquat:~/ForkIt$ geth --datadir ./data1 \ --genesis ~/ForkIt/etc/CustomGenesis.json \ --networkid 8888 --nodiscover --mine --minerthreads 1 \ --port 30301 --maxpeers 10 console
- 第二個 geth 實例使用 P2P 埠 30302 和以下命令:
user@Kumquat:~/ForkIt$ geth --datadir ./data2 \ --genesis ~/ForkIt/etc/CustomGenesis.json \ --networkid 8888 --nodiscover --mine --minerthreads 1 \ --port 30302 --maxpeers 10 console
- 第一個 geth 實例使用該文件
./data1/static-nodes.json
來查找第二個 geth 實例。admin.nodeInfo
此文件包含使用命令從第二個 geth 實例獲取的 enode 資訊,我在其中替換了文本$$ :: $$用我電腦的IP地址。這是我的
./data1/static-nodes.json
樣子:[ "enode://3941d48d95d4782f8b4fb7561d78642d2e53e478e5c8d3087e6e6023f5931aca3024a9679628fff775b9ddafd11d8d48f84a502fe815619be80f16b54cb1c077@192.168.1.14:30302" ]
- 第二個 geth 實例使用文件,其中包含使用命令從第一個 geth 實例
./data2/static-nodes.json
獲取的 enode 資訊admin.nodeInfo
- 為了模擬分叉,我使用以下命令阻止 TCP 埠 30301 和 30302:
user@Kumquat:~/ForkIt$ sudo iptables -A INPUT -p tcp --dport 30301 -j DROP user@Kumquat:~/ForkIt$ sudo iptables -A INPUT -p tcp --dport 30302 -j DROP
- 為了允許 geth 實例作為對等點重新連接,我使用以下命令刪除了 TCP 埠 30301 和 30302 上的阻塞規則:
user@Kumquat:~/ForkIt$ sudo iptables -D INPUT -p tcp --dport 30301 -j DROP user@Kumquat:~/ForkIt$ sudo iptables -D INPUT -p tcp --dport 30302 -j DROP
P2P連接和斷開
要查看 geth 實例之間的 P2P 連接跟踪,請
admin.verbosity(6)
在 geth 控制台中執行命令。當埠暢通時
- 節點 1 顯示以下消息:
I0412 10:11:47.379824 12467 peer.go:173] Peer 3941d48d95d4782f 192.168.1.14:30302 broadcasted 0 message(s)
- 節點 2 顯示以下消息:
I0412 10:11:58.480153 12478 peer.go:173] Peer e0b2addf8107866c 192.168.1.14:35257 broadcasted 0 message(s)
當創建 iptables 規則以阻止 P2P 埠時,geth 實例之間的 P2P 連接會在大約 1 分鐘後斷開。
- 節點 1 顯示以下消息:
I0412 10:12:59.080325 12467 server.go:431] new task: static dial 3941d48d95d4782f 192.168.1.14:30302 I0412 10:12:59.080407 12467 dial.go:209] dialing enode://3941d48d95d4782f8b4fb7561d78642d2e53e478e5c8d3087e6e6023f5931aca3024a9679628fff775b9ddafd11d8d48f84a502fe815619be80f16b54cb1c077@192.168.1.14:30302 I0412 10:13:14.080977 12467 dial.go:212] dial error: dial tcp 192.168.1.14:30302: i/o timeout > admin.peers []
- 節點 2 顯示以下消息:
I0412 10:20:28.784346 12478 server.go:431] new task: static dial e0b2addf8107866c 192.168.1.14:30301 I0412 10:12:58.780403 12478 dial.go:209] dialing enode://e0b2addf8107866c0e33a56f51cf800f2625ea0f4f70097ce6420b941d215c55c5404bb856d94911964865e8ac640b7ce2a2426afbf01d16d85e8f26f053a070@192.168.1.14:30301 I0412 10:13:13.780693 12478 dial.go:212] dial error: dial tcp 192.168.1.14:30301: i/o timeout > admin.peers []
叉子
當 P2P 連接被阻塞時,區塊鏈被分叉,第一個 geth 實例在其區塊鏈的單獨副本上探勘,第二個 geth 實例在其區塊鏈的單獨副本上探勘。
您將在下面看到區塊 #1405 的區塊鏈分叉。我創建了一個
checkBlock()
列在本頁底部的腳本 - 左列顯示塊號,右列顯示礦工 coinbase 地址的前 4 個字元。在第一個 geth 實例中執行命令
checkBlock(1401, 10000)
會產生以下結果:1401 182434 0 Infinity 0 0 NaN 909f 1402 182523 89 364957.0 1 1 1.0 909f 1403 182612 178 273784.5 9 8 4.5 909f 1404 182523 89 243364.0 41 32 13.7 8d15 1405 182612 178 228176.0 51 10 12.8 8d15 <--- THE FORK 1406 182523 89 219045.4 69 18 13.8 8d15 1407 182612 178 212973.2 78 9 13.0 8d15 1408 182701 267 208648.6 90 12 12.9 8d15 1409 182612 178 205394.0 117 27 14.6 8d15 1410 182701 267 202872.6 127 10 14.1 8d15
在第二個 geth 實例中執行命令
checkBlock(1390, 10000)
會產生以下結果:1401 182434 0 Infinity 0 0 NaN 909f 1402 182523 89 364957.0 1 1 1.0 909f 1403 182612 178 273784.5 9 8 4.5 909f 1404 182523 89 243364.0 41 32 13.7 8d15 1405 182612 178 228176.0 51 10 12.8 909f <--- THE FORK 1406 182523 89 219045.4 87 36 17.4 909f 1407 182434 0 212943.5 135 48 22.5 909f 1408 182523 89 208597.7 145 10 20.7 909f 1409 182434 0 205327.2 192 47 24.0 909f 1410 182345 -89 202773.7 237 45 26.3 909f
分叉期間從同一賬戶消費
在這兩個 geth 實例中,我將第二個帳戶
eth.accounts[1]
設置為相同的私鑰/公鑰。在分叉之前,兩個 geth 實例的賬戶餘額是相同的:
web3.fromWei(eth.getBalance(eth.accounts[1]), "ether") 10.09958
在分叉後的第一個 geth 實例中,我將 7 個乙太幣從 轉移
eth.accounts[1]
到eth.accounts[0]
.> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(7, "ether")}) "0xe442a4e325ff6be2fbb35ba1f381e56559df722ebc307c6ad57f3580d6b97412" ... > web3.fromWei(eth.getBalance(eth.accounts[1]), "ether") 3.09916
在分叉後的第二個 geth 實例中,我將 6 個乙太幣從 轉移
eth.accounts[1]
到eth.accounts[0]
.> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(6, "ether")}) "0xa06a6a141f5ab19e0be266244eb6c07c5b9bece6dc308f5fd6244dee51606fa6" ... > web3.fromWei(eth.getBalance(eth.accounts[1]), "ether") 4.09916
在我解封 P2P 埠後,兩個 geth 實例同步了它們的區塊鏈(可能是最長/最高難度的鏈),結果兩個 geth 實例報告了 4.09916 in 的餘額
eth.accounts[1]
。區塊鏈分叉時難度是否向下調整?
是的,在第二個 geth 實例的情況下,從下面的塊 #1410 可以看出:
> checkBlocks(1401,10000); 1401 182434 0 Infinity 0 0 NaN 909f 1402 182523 89 364957.0 1 1 1.0 909f 1403 182612 178 273784.5 9 8 4.5 909f 1404 182523 89 243364.0 41 32 13.7 8d15 1405 182612 178 228176.0 51 10 12.8 909f 1406 182523 89 219045.4 87 36 17.4 909f 1407 182434 0 212943.5 135 48 22.5 909f 1408 182523 89 208597.7 145 10 20.7 909f 1409 182434 0 205327.2 192 47 24.0 909f 1410 182345 -89 202773.7 237 45 26.3 909f 1411 182256 -178 200721.9 275 38 27.5 909f 1412 182168 -266 199035.2 306 31 27.8 909f 1413 182256 -178 197636.9 313 7 26.1 909f 1414 182168 -266 196447.0 358 45 27.5 909f 1415 182256 -178 195433.4 366 8 26.1 909f 1416 182168 -266 194549.0 388 22 25.9 909f 1417 182256 -178 193780.7 397 9 24.8 909f 1418 182344 -90 193107.9 407 10 23.9 909f 1419 182433 -1 192514.9 418 11 23.2 909f 1420 182344 -90 191979.6 465 47 24.5 909f 1421 182255 -179 191493.4 491 26 24.6 909f
我預計難度會向下調整,因為組合區塊鏈有兩個礦工,而單個分叉區塊鍊是由一個礦工建構的。為了保持塊之間的時間相同(平均),難度必須向下調整。
額外的東西
~/ForkIt/etc/CustomGenesis.json
{ "alloc": { }, "nonce": "0x8888888888888888", "difficulty": "0x020000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x8888888888888888888888888888888888888888", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x888888" }
checkBlocks() 腳本
對於指定的區塊範圍,該腳本將列印以下資訊:區塊號、區塊難度、區塊難度變化、平均區塊難度、經過時間、時間變化、平均時間和礦工coinbase公鑰的前4個字元.
function checkBlocks(firstBlock, lastBlock) { var i; var firstTimestamp; var prevTimestamp; var prevDifficulty; var totalDifficulty = 0; for (i = firstBlock; i < 10000; i++) { var block = eth.getBlock(i); if (i == firstBlock) { firstTimestamp = block.timestamp; prevTimestamp = firstTimestamp; prevDifficulty = block.difficulty; } if (block == null) break; totalDifficulty = +totalDifficulty + +block.difficulty; var averageDifficulty = totalDifficulty / (i - firstBlock); var averageTime = (block.timestamp - firstTimestamp) / (i - firstBlock); console.log(block.number + "\t" + block.difficulty + "\t" + (block.difficulty - prevDifficulty) + "\t" + averageDifficulty.toFixed(1) + "\t" + (block.timestamp - firstTimestamp) + "\t" + (block.timestamp - prevTimestamp) + "\t" + averageTime.toFixed(1) + "\t" + block.miner.substr(2, 4)); prevTimestamp = block.timestamp; } }
結果如下所示(第一行差異資訊總是不正確的):
> checkBlocks(1401,10000); 1401 182434 0 Infinity 0 0 NaN 909f 1402 182523 89 364957.0 1 1 1.0 909f 1403 182612 178 273784.5 9 8 4.5 909f 1404 182523 89 243364.0 41 32 13.7 8d15 1405 182612 178 228176.0 51 10 12.8 8d15 1406 182523 89 219045.4 69 18 13.8 8d15 1407 182612 178 212973.2 78 9 13.0 8d15 1408 182701 267 208648.6 90 12 12.9 8d15 1409 182612 178 205394.0 117 27 14.6 8d15