Как я могу надежно вызвать форк блокчейна для целей тестирования?

Я хотел бы надежно вызвать форк в приватной тестовой сети, чтобы проверить поведение внеблокчейнового кода, взаимодействующего с контрактами. (см. связанные вопросы о том, почему я хотел бы это сделать). Вероятно, будет полезно для автоматического тестирования реализации geth и других узлов.

Под надежным я подразумеваю воспроизводимый, каждый раз с одним и тем же результатом, так что я могу запустить это в автоматизированной тестовой системе.

Аналогичный вопрос появился на форумах Эфириума.

Какую операционную систему вы используете? Решение может зависеть от типа вашей ОС.
Виртуальные машины Linux поверх OSX, но я могу запускать контейнеры Docker на чем угодно.

Ответы (1)

Решение

  • Настройте 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, где я заменил текст [::] на IP-адрес моего компьютера. Вот как ./data1/static-nodes.jsonвыглядит мой:

    [
      "enode://3941d48d95d4782f8b4fb7561d78642d2e53e478e5c8d3087e6e6023f5931aca3024a9679628fff775b9ddafd11d8d48f84a502fe815619be80f16b54cb1c077@192.168.1.14:30302"
    ]
    
  • Второй экземпляр geth использует файл ./data2/static-nodes.jsonс информацией об эноде, полученный с помощью admin.nodeInfoкоманды из первого экземпляра geth.

  • Чтобы имитировать форк, я блокирую 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

Чтобы просмотреть трассировку P2P-соединения между экземплярами geth, выполните команду 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, соединения P2P между экземплярами geth разрываются примерно через 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()скрипт, который указан внизу этой страницы — в левом столбце показан номер блока, а в правом столбце показаны первые 4 символа адреса майнера в монетной базе.

Выполнение команды checkBlock(1401, 10000)в первом экземпляре geth дает следующий результат:

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

Выполнение команды checkBlock(1390, 10000)во втором экземпляре geth дает следующие результаты:

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

В первом экземпляре гета после форка я перевел 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 в 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()

Для указанного диапазона блоков этот скрипт напечатает следующую информацию: номер блока, сложность блока, изменение сложности блока, средняя сложность блока, прошедшее время, изменение времени, среднее время и первые 4 символа открытого ключа майнера coinbase. .

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
кажется разумным, я примерно так и думал. Я мог бы попробовать нечто подобное, используя Docker. Мой босс просто решил, что это имеет более низкий приоритет, поэтому я не собираюсь отмечать ответ, пока не заработаю, через несколько месяцев...
Кстати, двойная трата исчезает для взаимодействий в сети, как вы описываете, но не для вне сети. то есть, как и в биткойнах, я отправлю 100 эфиров в магазин спортивных товаров Боба и получу хороший дождевик, а затем взломаю последний майнинг-узел в 15-секундном окне, я могу отправить эти 100 эфиров в магазин шляп Алисы, а также получить красивую шляпу. для одного и того же эфира, и либо Боб, либо Алиса без куртки или шляпы. Важны внечейн-взаимодействия. Ожидание N блоков для распространения изменения состояния гарантирует Бобу и Алисе, что изменение неприкосновенно.
Если сеть разветвляется, майнерам потребуется больше времени для решения / добычи блоков. Алгоритм регулировки сложности должен скорректировать сложность, чтобы скорректировать среднее время блока, я думаю, вы должны быть в состоянии обнаружить разветвление, проверяя изменения в среднем. время блока и / или снижение сложности. Я пока не знаю, сколько времени/сколько блоков потребуется, чтобы регулировка сложности вступила в силу.
Браузер блокчейна уже имеет обнаружение дяди, и есть ответ stackexchange, чтобы вы могли увидеть, была ли ваша транзакция удалена, поэтому не уверен, что мне нужно беспокоиться об обнаружении форков.
Хорошо, я верю, что это работает. Большое спасибо, это потрясающе. Дайте мне знать, если вам нужна помощь с чем-либо, я в долгу перед вами.
Убрал --devиз gethвызовов, так как p2p отключен в режиме dev