I would like to reliably induce a fork on a private testnet so that I can test the behavior of off-blockchain code that interacts with contracts. (see related questions as to why I would want to do that). Would probably useful for automated testing of geth and other node implementations as well.
By reliable, I mean repeatable, with the same result every time, so that I can run this in an automated test system.
Best Answer
The Solution
admin.addPeer(...)
command in geth to add peer nodes to the geth's list of peers, but there does not seem to be any commands to remove these peers.The test details follows:
The Test
The first geth instance uses the P2P port 30301 with the following command:
The second geth instance uses the P2P port 30302 with the following command:
The first geth instance uses the file
./data1/static-nodes.json
to find the second geth instance. This file contains the enode information obtained using theadmin.nodeInfo
command from the second geth instance, where I replace the text [::] with the IP address of my computer. Here is what my./data1/static-nodes.json
looks like:The second geth instance uses the file
./data2/static-nodes.json
with the enode information obtained using theadmin.nodeInfo
command from the first geth instanceTo simulate the fork, I block the TCP ports 30301 and 30302 using the following commands:
To allow the geth instances to reconnect as peers, I remove the blocking rules on TCP ports 30301 and 30302 using the following commands:
The P2P Connection And Disconnection
To view the trace of the P2P connection between the geth instances, run the command
admin.verbosity(6)
in the geth console.When the ports are unblocked
Node 1 shows the following message:
And node 2 shows the following message:
When the iptables rules are created to block the P2P ports, the P2P connections between the geth instances are dropped after about 1 minute.
Node 1 shows the following messages:
And node 2 shows the following messages:
The Fork
When the P2P connection is blocked, the blockchain is forked with the first geth instance mining on it's separate copy of the blockchain, and the second geth instance mining on it's separate copy of the blockchain.
Below you will see the blockchain forks at block #1405. I've created a
checkBlock()
script that is listed at the bottom this page - the left hand column shows the block number and the right hand column shows the first 4 characters of the miner's coinbase address.Running the command
checkBlock(1401, 10000)
in the first geth instance produces the following result:Running the command
checkBlock(1390, 10000)
in the second geth instance produces the following results:Spending From The Same Account During The Fork
In both geth instances, I had the second account
eth.accounts[1]
set to the same private / public key.Before the fork, the account balances were the same from both geth instances:
In the first geth instance after the fork, I transferred 7 ethers from
eth.accounts[1]
toeth.accounts[0]
.In the second geth instance after the fork, I transferred 6 ethers from
eth.accounts[1]
toeth.accounts[0]
.After I unblocked the P2P port, both geth instances synchronised their blockchains (presumably to the longest/highest difficulty chain) with the result that both geth instances reported a balance of 4.09916 in
eth.accounts[1]
.Did Difficulty Adjust Downwards When The Blockchain Forked?
Yes in the case of the second geth instance, as can be seen from block #1410 below:
I was expecting difficulty do adjust downwards because the combined blockchain had two miners, whereas the individual forked blockchain was being built by one miner. To keep the time between blocks the same (on average), difficulty would have to adjust down.
Additional Things
~/ForkIt/etc/CustomGenesis.json
checkBlocks() script
For the specified range of blocks, this script will print the following information: block number, block difficulty, change in block difficulty, average block difficulty, elapsed time, change in time, average time and the first 4 characters of the miner's coinbase public key.
The results look like the following (the first line difference information will always be incorrect):