Как DApp может обнаружить форк или реорганизацию цепочки с помощью web3.js или дополнительных библиотек?

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

Теперь по какой-то причине происходит реорганизация цепочки (возможно, нода пользователя потеряла и восстановила подключение к сети).

Как DApp может использовать web3.js для обнаружения этого, чтобы он мог проверить, была ли транзакция пользователя отменена и нужно ли пользователю снова отправить свой голос? Запускает ли web3.js событие для уведомления DApp? Есть ли фрагменты кода, например, какое событие прослушивать и как? Или есть библиотеки с примерами их использования?

Ответы (6)

Вот код, который ожидает указанное количество блоков и проверяет, что квитанция транзакции все еще действительна. Если происходит разветвление и воспроизведение не удается, проверка получения должна завершиться неудачно, и обратный вызов будет выполнен с установленным значением Error.

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

Согласно вопросу, он использует только вызовы web3.js и не использует библиотеки. Я должен сказать вам, что использование обратных вызовов вместо промисов для меня очень болезненно ;-P

Я не реализовал проверку транзакции на нескольких узлах RPC, но в коде есть примечание о том, где это сделать. Вы, вероятно, захотите использовать для этого хотя бы Async.join, который будет внешней библиотекой.

 //
 // @method awaitBlockConsensus
 // @param web3s[0] is the node you submitted the transaction to,  the other web3s 
 //    are for cross verification, because you shouldn't trust one node.
 // @param txhash is the transaction hash from when you submitted the transaction
 // @param blockCount is the number of blocks to wait for.
 // @param timout in seconds 
 // @param callback - callback(error, transaction_receipt) 
 //
 exports.awaitBlockConsensus = function(web3s, txhash, blockCount, timeout, callback) {
   var txWeb3 = web3s[0];
   var startBlock = Number.MAX_SAFE_INTEGER;
   var interval;
   var stateEnum = { start: 1, mined: 2, awaited: 3, confirmed: 4, unconfirmed: 5 };
   var savedTxInfo;
   var attempts = 0;

   var pollState = stateEnum.start;

   var poll = function() {
     if (pollState === stateEnum.start) {
       txWeb3.eth.getTransaction(txhash, function(e, txInfo) {
         if (e || txInfo == null) {
           return; // XXX silently drop errors
         }
         if (txInfo.blockHash != null) {
           startBlock = txInfo.blockNumber;
           savedTxInfo = txInfo;
           console.log("mined");
           pollState = stateEnum.mined;
         }
       });
     }
     else if (pollState == stateEnum.mined) {
         txWeb3.eth.getBlockNumber(function (e, blockNum) {
           if (e) {
             return; // XXX silently drop errors
           }
           console.log("blockNum: ", blockNum);
           if (blockNum >= (blockCount + startBlock)) {
             pollState = stateEnum.awaited;
           }
         });
     }
    else if (pollState == stateEnum.awaited) {
         txWeb3.eth.getTransactionReceipt(txhash, function(e, receipt) {
           if (e || receipt == null) {
             return; // XXX silently drop errors.  TBD callback error?
           }
           // confirm we didn't run out of gas
           // XXX this is where we should be checking a plurality of nodes.  TBD
           clearInterval(interval);
           if (receipt.gasUsed >= savedTxInfo.gas) {
             pollState = stateEnum.unconfirmed;
             callback(new Error("we ran out of gas, not confirmed!"), null);
           } else {
             pollState = stateEnum.confirmed;
             callback(null, receipt);
           }
       });
     } else {
       throw(new Error("We should never get here, illegal state: " + pollState));
     }

     // note assuming poll interval is 1 second
     attempts++;
     if (attempts > timeout) {
       clearInterval(interval);
       pollState = stateEnum.unconfirmed;
       callback(new Error("Timed out, not confirmed"), null);
     }
   };

   interval = setInterval(poll, 1000);
   poll();
 };

[РЕДАКТИРОВАТЬ 1] - из газа больше или равно, не больше...

Я проголосовал. Кажется лучшим, что можно сделать на данном этапе; @euri10 должен присудить награду, иначе она будет присуждена автоматически в соответствии с определенными правилами meta.stackexchange.com/questions/16065/…
«Кажется, лучшее, что можно сделать на данном этапе». Если чего-то не хватает, я хотел бы знать. Я разместил этот код в надежде получить отзывы о том, что не так. Его можно интегрировать в web3.js или ether-pudding, если мы хотим, чтобы каждый мог его использовать. Если это так, у меня есть дополнительные функции, которые я бы добавил (например, объединение tx_receipt и tx_info в одну структуру результата)
Думал больше, и я тоже доволен :) Надеюсь, это можно будет интегрировать, как вы планируете.
@PaulS, что, если транзакция успешно добыта с использованным газом = предоставленным газом?
Если вы можете найти способ сказать, что транзакция была произведена с использованием газа = при условии, то есть вопрос, на который нужно ответить. Сообщество не дало ответа, и я не знаю, каким он будет... в основном это "ошибка" Эфириума.
внизу ответа показана успешная транзакция, где используется == отправлено. Я недостаточно вникал в это, чтобы понять, что можно сделать программно, чтобы различать. ethereum.stackexchange.com/questions/6002/transaction-status/…
Как выбрать хороший blockCount? это просто догадки?
Я думаю, что есть и другие вопросы о том, как выбрать количество блоков. Я думаю, что это зависит от базовой стоимости. если это эквивалентно 10 000 долларов США, я бы подождал большее количество блоков, чем эквивалент 1 доллара США. Вы можете заглянуть в историю Эфириума, найти худший временный форк и посмотреть, как долго он просуществовал. Существует экспоненциально убывающая вероятность того, что форк уничтожит вашу транзакцию, чем больше блоков будет добыто впоследствии.
@ Пол, какова ценность «web3s»? Означает ли узел здесь учетную запись, назначенную этому узлу? (Понятно, кажется, это просто экземпляр класса "web3")
На данный момент этот код просто реализует проверку одного узла, но если вы имеете дело с большими суммами, вы захотите проверить свою транзакцию на нескольких узлах. web3s — это список сущностей класса web3.js, каждая из которых предположительно подключена к разным узлам. Если бы это был я для транзакций с высокой стоимостью, у меня были бы узлы трех разных реализаций в трех разных облачных зонах, которые не использовали бы какие-либо устройства безопасности.

Я не могу комментировать, есть ли такая функция в web3. Что я знаю точно, так это то, что у Geth и Mist есть повтор транзакций. Это означает, что в случае реорганизации он будет обрабатывать транзакции, которые были «потеряны» во время реорганизации, поэтому теоретически состояние должно оставаться прежним.

Полезный ответ, за который я проголосовал. Я слышал о воспроизведении, и было бы полезно увидеть решения, доступные до Mist, а также рекомендации Mist и использование его API/событий для работы с ним, поскольку кажется, что Mist должен как минимум предоставлять события для DApp, который был: 1) реорганизация 2) повтор tx был успешным (возможно, повтор также может по какой-то причине не сработать). DApp по-прежнему должен знать об этих событиях, даже если он молча не показывает пользователю никаких изменений (пока он надеется на успешное воспроизведение).
Это предполагает, что вы доверяете своему локальному узлу node. Я думаю, вы должны предположить, что узлы будут взломаны. Так что, в конце концов, я думаю, нам нужно решение, более похожее на биткойн-кошелек, в котором вы, например, ждете 16 блоков для майнинга и проверяете, что хэш транзакции все еще хорош.
(и сделать то же самое с несколькими разными узлами разных реализаций)
Что именно это значит? Скажем, есть 12 честных блоков, но злонамеренный узел тайно добывает 24 пустых блока, а затем публикует их сразу и вызывает форк из 12 блоков. Как будут «воспроизведены» транзакции в 12 отсутствующих блоках? Вы хотите сказать, что все эти транзакции будут включены в следующие 12 блоков?

В настоящее время я не думаю, что есть способ сделать это. В настоящее время в документах говорится, что нужно просто подождать 12 блоков, чтобы убедиться, что хард-форк не произошел, и использовать getCode(). https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethcontract

В web3 API, в разделе Contract Events, сказано, что объект, передаваемый обратному вызову, имеет removedполе. Если вы прослушиваете свое событие и происходит реорганизация, вы должны быть уведомлены событием, в котором removedустановлено значение true.

Я никогда не пробовал это, но если правильно понял документ, он должен работать.

Спасибо, я вижу, что он был добавлен в вики в 2017 году. Пример поможет улучшить этот ответ.

Да, есть способ сделать это:

Когда происходит форк, хэш блока (или состояния) изменится, поэтому все, что вам нужно сделать, это получить хеш последнего блока (или состояния) перед отправкой транзакции. Затем продолжайте отслеживать хэши входящих блоков, чтобы убедиться, что цепочка все еще действительна. После 10-20 подтверждений вы можете остановить этот процесс мониторинга и считать транзакцию постоянно хранимой.

Упрощенная последовательность шагов будет такой:

  1. Перед выполнением eth_sendRawTransactiondo: eth_blockNumber, затем eth_getBlockByNumberсохраните хэш блока (или состояния)
  2. Отправить транзакцию сeth_sendRawTransaction
  3. В цикле запросите новые блоки и соедините их с хешем блока, полученного непосредственно перед eth_sendRawTransactionвызовом. Если прибыл блок, который имеет последовательный номер и не совпадает с хэшем родительского блока, то произошло разветвление, и вы можете показать своему Пользователю сообщение.

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

Web3 позволяет сделать это очень элегантно. Я не уверен, почему другие ответы не предложили это решение - есть способ прослушать реорганизацию цепочки, аннулирующую события:

myContract.events.MyVoteEvent()
    .on("data", async (error, event) => {
        console.log("vote received");
    })
    .on("changed", async (error, event) => { // Called when event is no longer valid
        console.log("vote was invalidated due to reorg");
    });

Из документов web3 :

«changed» возвращает Object: срабатывает при каждом событии, которое было удалено из блокчейна. Событие будет иметь дополнительное свойство «removed: true».

NB: на данный момент кажется, что сделать подобное в EthersJS невозможно; только в Web3.js.