Blockchain

我如何可靠地誘導區塊鏈分叉用於測試目的?

  • June 12, 2019

我想在私有測試網上可靠地誘導一個分叉,以便我可以測試與合約互動的非區塊鏈程式碼的行為。(請參閱有關我為什麼要這樣做的相關問題)。也可能對 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

引用自:https://ethereum.stackexchange.com/questions/2851