Wie kann eine DApp eine Fork- oder Chain-Reorganisation mit web3.js oder zusätzlichen Bibliotheken erkennen?

Nehmen Sie ein Beispiel für eine Abstimmungs-DApp. Ein Benutzer klickt auf eine Abstimmungsschaltfläche, dann wird hinter den Kulissen eine Transaktion in der Blockchain abgebaut, und schließlich teilt die DApp dem Benutzer mit, dass seine Stimme aufgezeichnet wurde.

Jetzt gibt es aus irgendeinem Grund eine Kettenreorganisation (möglicherweise hat der Knoten des Benutzers die Netzwerkverbindung verloren und wiederhergestellt).

Wie kann eine DApp web3.js verwenden, um dies zu erkennen, damit sie überprüfen kann, ob die Transaktion des Benutzers rückgängig gemacht wurde und ob der Benutzer seine Stimme erneut abgeben muss? Löst web3.js ein Ereignis aus, um die DApp zu benachrichtigen? Gibt es Code-Snippets, z. B. auf welches Ereignis zu hören ist und wie? Oder gibt es Bibliotheken mit Anwendungsbeispielen?

Antworten (6)

Hier ist Code, der eine bestimmte Anzahl von Blöcken wartet und überprüft, ob die Transaktionsquittung noch gültig ist. Wenn ein Fork auftritt und die Wiedergabe fehlschlägt, sollte die Empfangsprüfung fehlschlagen und der Rückruf wird mit dem Fehlersatz aufgerufen.

Ich habe dies nur auf Erfolg und Timeout-Fehler getestet, ich habe es nicht auf einem tatsächlichen Fork der Blockchain getestet, weil ich noch nicht herausgefunden habe, wie ich dies in einem Test-Framework zuverlässig veranlassen kann. Schätzen Sie alle Hinweise, wie das geht.

Gemäß der Frage werden nur web3.js-Aufrufe und keine Bibliotheken verwendet. Ich muss Ihnen sagen, Rückrufe statt Versprechungen zu verwenden, ist sehr schmerzhaft für mich ;-P

Ich habe die Validierung der Transaktion mehrerer RPC-Knoten nicht implementiert, aber es gibt einen Hinweis im Code, wo dies zu tun ist. Wahrscheinlich möchten Sie dazu mindestens Async.join verwenden, was eine externe Bibliothek wäre.

 //
 // @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();
 };

[EDIT 1] - kein Gas ist größer oder gleich, nicht größer ...

Ich habe hochgestimmt. Scheint das Beste, was in diesem Stadium getan werden kann; @euri10 muss das Kopfgeld vergeben oder es wird automatisch nach bestimmten Regeln vergeben meta.stackexchange.com/questions/16065/…
"Scheint das Beste zu sein, was in diesem Stadium getan werden kann". Wenn es etwas fehlt, würde ich es gerne wissen. Ich habe diesen Code in der Hoffnung gepostet, dass ich Feedback dazu bekommen könnte, was falsch ist. Es könnte in web3.js oder Ether-Pudding integriert werden, wenn wir einen Ort wollen, an dem es jeder verwenden kann. Wenn ja, hätte ich weitere Funktionen, die ich hinzufügen würde (wie tx_receipt und tx_info in einer Ergebnisstruktur kombinieren).
Ich habe mehr nachgedacht und bin auch zufrieden :) Ich hoffe, es kann bei Ihrer Planung integriert werden.
@PaulS Was ist, wenn die Transaktion erfolgreich mit verbrauchtem Gas = bereitgestelltem Gas abgebaut wird?
Wenn Sie feststellen können, dass eine Transaktion mit verbrauchtem Gas abgebaut wurde = bereitgestellt, dann gibt es eine Frage, die beantwortet werden muss. Die Community hat keine Antwort gegeben und ich weiß nicht, was es sein würde ... im Grunde ist es ein Ethereum-'Bug'
Am Ende der Antwort wird eine erfolgreich abgebaute Transaktion angezeigt, bei der verwendet == gesendet wurde. Ich habe mich nicht genug damit beschäftigt, um herauszufinden, was programmatisch zur Differenzierung beitragen könnte. ethereum.stackexchange.com/questions/6002/transaction-status/…
Wie wählt man einen guten blockCount aus? ist es nur raten?
Ich denke, es gibt andere Fragen zur Auswahl einer Blockanzahl. Ich denke, es kommt auf den zugrunde liegenden Wert an. Wenn es sich um einen Gegenwert von 10.000 USD handelt, würde ich eine größere Anzahl von Blöcken als den Gegenwert von 1 USD warten. Sie könnten in die Geschichte von Ethereum gehen und die schlechteste vorübergehende Gabel finden und sehen, wie lange sie gedauert hat. Die Wahrscheinlichkeit, dass ein Fork Ihre Transaktion zerstört hat, nimmt exponentiell ab, je mehr Blöcke danach abgebaut werden.
@Paul, was ist der Wert hinter 'web3s'? Bedeutet Knoten hier Konto, das diesem Knoten zugewiesen ist? (Ich sehe, es scheint nur eine 'web3'-Klasseninstanz zu sein)
Im Moment implementiert dieser Code nur eine einzelne Knotenverifizierung, aber wenn Sie es mit großen Wertbeträgen zu tun haben, möchten Sie Ihre Transaktion über mehrere Knoten verifizieren. web3s ist eine Liste von Entitäten der Klasse web3.js, die vermutlich jeweils mit verschiedenen Knoten verbunden sind. Wenn ich für Transaktionen mit hohem Wert wäre, hätte ich Knoten von drei verschiedenen Implementierungen in drei verschiedenen Cloud-Zonen, die sich keine Sicherheitsvorrichtungen teilen.

Ob es dafür in web3 eine Funktion gibt oder nicht, kann ich nicht sagen. Was ich weiß, ist, dass Geth und Mist eine Transaktionswiedergabe haben. Das bedeutet, dass im Falle einer Reorganisation Transaktionen verarbeitet werden, die während der Reorganisation „verloren gegangen“ sind, sodass der Status theoretisch immer noch derselbe sein sollte.

Hilfreiche Antwort, die ich positiv bewertet habe. Ich habe von der Wiederholung gehört und es wäre hilfreich, Lösungen zu sehen, die vor Mist verfügbar sind, sowie die Mist-Empfehlung und die Verwendung seiner APIs/Ereignisse für den Umgang damit, da Mist anscheinend zumindest Ereignisse bereitstellen sollte die DApp, dass es gab: 1) ein reorg 2) die tx-Wiedergabe war erfolgreich (vielleicht kann die Wiedergabe aus irgendeinem Grund auch fehlschlagen). Eine DApp sollte sich dieser Ereignisse auch dann bewusst sein, wenn sie dem Benutzer stillschweigend keine Änderungen anzeigt (während sie auf eine erfolgreiche Wiedergabe hofft).
Dies setzt voraus, dass Sie Ihrem lokalen Knoten vertrauen. Ich denke, Sie sollten davon ausgehen, dass Knoten gehackt werden. Also am Ende denke ich, dass wir eine Lösung im Bitcoin-Wallet-Stil brauchen, bei der Sie zB 16 Blöcke warten, bis sie abgebaut werden, und überprüfen, ob der Transaktionshash immer noch gut ist.
(und machen Sie dasselbe mit mehreren verschiedenen Knoten verschiedener Implementierungen)
Was genau bedeutet das? Angenommen, es gibt 12 ehrliche Blöcke, aber ein böswilliger Knoten schürft heimlich 24 leere Blöcke, veröffentlicht sie dann sofort und verursacht einen 12-Block-Fork - Wie werden die Transaktionen in den jetzt 12 fehlenden Blöcken "wiederholt"? Wollen Sie damit sagen, dass alle diese Transaktionen in den nächsten 12 Blöcken enthalten sein werden?

Derzeit glaube ich nicht, dass es eine Möglichkeit gibt, dies zu tun. Derzeit sagen die Dokumente, dass Sie nur 12 Blöcke warten sollen, um sicherzustellen, dass kein Hard Fork aufgetreten ist, und getCode () verwenden. https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethcontract

In der Web3-API, Abschnitt Contract Events , heißt es, dass das dem Callback übergebene Objekt ein removedFeld hat. Wenn Sie auf Ihr Ereignis warten und eine Reorganisation auftritt, sollten Sie durch ein Ereignis benachrichtigt werden, removeddas auf gesetzt ist true.

Ich habe das nie versucht, aber wenn ich das Dokument richtig verstanden habe, sollte es funktionieren.

Danke, ich sehe, es wurde 2017 zum Wiki hinzugefügt . Ein Beispiel würde helfen, diese Antwort zu verbessern.

Ja, es gibt eine Möglichkeit:

Wenn ein Fork auftritt, ändert sich der Hash des Blocks (oder Zustands), sodass Sie nur den Hash des letzten Blocks (oder Zustands) abrufen müssen, bevor Sie eine Transaktion einreichen. Überwachen Sie dann weiterhin eingehende Block-Hashes, um zu überprüfen, ob die Kette noch gültig ist. Nach 10-20 Bestätigungen können Sie diesen Überwachungsprozess beenden und die Transaktion als dauerhaft gespeichert betrachten.

Vereinfachte Schrittfolge wäre:

  1. Bevor Sie dies eth_sendRawTransactiontun: eth_blockNumber, dann eth_getBlockByNumberund speichern Sie den Hash des Blocks (oder Zustands)
  2. Senden Sie die Transaktion miteth_sendRawTransaction
  3. Fragen Sie in einer Schleife neue Blöcke ab und verbinden Sie sie mit dem Hash des Blocks, den Sie kurz vor dem eth_sendRawTransactionAufruf abgerufen haben. Wenn ein Block angekommen ist, der eine fortlaufende Nummer hat und nicht mit dem Hash des übergeordneten Blocks übereinstimmt, ist ein Fork aufgetreten, und Sie können Ihrem Benutzer eine Nachricht anzeigen.

Sie können den Hash von Block oder den Hash von State verwenden, es spielt keine Rolle, beide Werte ändern sich beim Fork-Ereignis. Sie sollten auch bedenken, dass während der Verarbeitung Ihrer Transaktion viele Ketten vorhanden sein können, sodass Sie überprüfen müssen, ob Ihre Transaktion in der längsten Kette gespeichert ist. Dies ist die Grundidee, aber natürlich kann die Implementierung komplexer sein, als ich beschrieben habe.

Web3 bietet eine sehr elegante Möglichkeit, dies zu tun. Ich bin mir nicht sicher, warum die anderen Antworten diese Lösung nicht vorgeschlagen haben - es gibt eine Möglichkeit, auf Kettenreorgs zu hören, die Ereignisse ungültig machen:

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");
    });

Aus den web3-Dokumenten :

„changed“ gibt zurück Objekt: Wird bei jedem Ereignis ausgelöst, das aus der Blockchain entfernt wurde. Das Ereignis erhält die zusätzliche Eigenschaft „removed: true“.

NB: Derzeit scheint es nicht möglich zu sein, etwas Ähnliches in EthersJS zu tun; nur in Web3.js.