Wie kann der Transaktionsstatus aus einem ausgelösten Fehler erkannt werden, wenn das Gas genau dasselbe sein kann wie das Gas, das für eine erfolgreiche Transaktion verwendet wird?

Diese Frage entstand aus der Beantwortung des Transaktionsstatus .

Im folgenden Beispiel sende ich Gas in Höhe von 21.000 (der Betrag, der für eine reguläre Transaktion erforderlich ist). In dieser Situation gas == gasUsedkann und so nicht verwendet werden, um festzustellen, ob der Vertrag eine Ausnahme ausgelöst hat?

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1.2345, "ether"), gas: 21000})
"0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3"
> eth.getTransaction("0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 21000,
  gasPrice: 20000000000,
  hash: "0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3",
  input: "0x",
  nonce: 55,
  to: "0x4d5bbe7fbc80933ffa90ece988a764e41ee6d018",
  transactionIndex: null,
  value: 1234500000000000000
}
> eth.getTransactionReceipt("0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3")
{
  blockHash: "0xf0af8236ceec7ad1839d67c9934ab062a8d95fa1f88b06139f97dbdfbd1cd842",
  blockNumber: 2234,
  contractAddress: null,
  cumulativeGasUsed: 21000,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 21000,
  logs: [],
  root: "3280f47a0de1149ad5c5fda421faaf95f303da8a77e83c8ec6ac2b3d8ca27abc",
  to: "0x4d5bbe7fbc80933ffa90ece988a764e41ee6d018",
  transactionHash: "0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3",
  transactionIndex: 0
}

Eine Transaktion kann so eingestellt werden, dass das Gas genau das gleiche ist wie das Gas, das verwendet wird, indem die web3.eth.estimateGas- Funktion verwendet wird, um zunächst das erforderliche Gas zu schätzen.

Wie kann also der Transaktionsstatus aus einem ausgelösten Fehler erkannt werden, wenn das Gas genau dasselbe sein kann wie das Gas, das für eine erfolgreiche Transaktion verwendet wird?



BEARBEITEN 15.06.2016 – Erklären der Unterschiede zu „ Woher weiß ich, wann mir das Benzin ausgegangen ist?“

Bei dieser Frage WISSEN wir bereits, wie wir programmatisch feststellen können, dass uns das Benzin ausgegangen ist. Und wir wissen bereits, dass diese Methode zum Erkennen einer fehlerhaften Transaktion nicht zuverlässig ist, ebenso wie gas == gasUsedeine gültige Bedingung für eine nicht fehlerhafte Transaktion.

Diese Frage stellt sich die Frage, welche zuverlässigen Alternativen es gibt, um eine fehlerhafte Transaktion zu erkennen, wenn die Situation „kein Benzin mehr“ nicht zuverlässig ist.

Aus meiner Sicht kein Duplikat - siehe EDIT 15.06.2016 oben.

Antworten (4)

Zusammenfassung

Ich habe eine Testtransaktion (#1) gesendet, um zunächst herauszufinden, wie viel Gas eine erfolgreiche Smart-Contract-Transaktion verbraucht. Aus den Ergebnissen dieser Transaktion ergibt sich, dass das für eine erfolgreiche Transaktion erforderliche Gas 26747 beträgt.

Ich habe dann diese 26747 Gasmenge in meiner voraussichtlich erfolgreichen Transaktion (Nr. 2) verwendet. Und gasUsed == gas.

Ich verwende dann dieselbe Gasmenge von 26747 in meiner Transaktion, die erfolglos bleiben wird (Nr. 3). Und gasUsed == gas.

Die einzige zuverlässige Möglichkeit, die ich bisher gefunden habe, um einfach zu überprüfen, ob eine Smart-Contract-Transaktion erfolgreich ist, besteht darin debug.traceTransaction, die letzte Fehlermeldung zu verwenden und zu überprüfen. Wenn dies der Fall ist "", ist kein Fehler aufgetreten. Wenn dies "Out of gas"oder ist "invalid jump destination (PUSH1) 2", ist ein Fehler aufgetreten.

Und hier ist ein kurzer Code, um den Status Ihrer Transaktion zu bestimmen.

> var status = debug.traceTransaction("0xd23219e2ea10528b245deb9e47993cae2ffd3ffe9fc27aeb808e94fc4e75d37b")
undefined
> if (status.structLogs.length > 0) {
  console.log(status.structLogs[status.structLogs.length-1].error)
}
"invalid jump destination (PUSH1) 2"

Der obige Rückgabewert ist, ""wenn keine Fehler vorliegen oder "Out of gas"wenn Ihnen das Benzin ausgeht.

UPDATE 29.07.2016 - Der geth --fastBlockchain-Download enthält nicht die Informationen für debug.traceTransaction(...)die Anzeige.



Einzelheiten

Ich verwende das folgende Beispiel für einen Smart Contract, der eine Ausnahme auslöst, wenn _value < 12345:

contract TestStatus {
    uint public value;
    function setValue(uint256 _value) {
        value = _value;
        if (_value < 12345) {
            throw;
        }
    }
}

Ich habe die Quelle reduziert auf:

var testStatusSource='contract TestStatus { uint public value; function setValue(uint256 _value) { value = _value; if (_value < 12345) { throw; } }}'

Ich habe den Vertrag kompiliert und in die Blockchain eingefügt:

var testStatusCompiled = web3.eth.compile.solidity(testStatusSource);

var testStatusContract = web3.eth.contract(testStatusCompiled.TestStatus.info.abiDefinition);
var testStatus = testStatusContract.new({
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code, gas: 1000000}, 
  function(e, contract) {
    if (!e) {
      if (!contract.address) {
        console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + 
          " waiting to be mined...");
      } else {
        console.log("Contract mined! Address: " + contract.address);
        console.log(contract);
      }
  }
})
Contract transaction send: TransactionHash: 0x4de11cc54484333036f45a2441563d6badae43d2e2789e0b113b6703a582a879 waiting to be mined...
undefined
...
Contract mined! Address: 0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c
[object Object]

Ich habe eine Transaktion gesendet, die keinen Fehler bei der Bestimmung des erforderlichen Gases verursacht:

# Not an error
testStatus.setValue(123456, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 41747
});
...
> eth.getTransactionReceipt("0x727cb3c6846bbb05c8437e97aa32db39a252426b09ed837a76bb364748ce73c4")
{
  blockHash: "0x293b4d357d72615c409ba37a6430253d64b4646e0366ac987d1364a258cf81fa",
  blockNumber: 2465,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "fbb29f9eff2340aa8d7df7feec74671c2b693fb9e7b6ec3b710ca6d64813da0e",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0x727cb3c6846bbb05c8437e97aa32db39a252426b09ed837a76bb364748ce73c4",
  transactionIndex: 0
}

Das benötigte Gas ist 26747.

Ich habe eine Transaktion gesendet value=123456, die NICHT fehlschlägt, und gas==gasUsed:

testStatus.setValue(123456, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 26747
});
0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1

Ich verwende debug.traceTransactionum zu bestätigen, dass bei dieser Transaktion kein Fehler aufgetreten ist.

> var status = debug.traceTransaction("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
undefined
> status.structLogs[status.structLogs.length-1].error
""

Und in dieser Situationgas(26747) == gasUsed(26747)

eth.getTransaction("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
{
  blockHash: "0x961277e2bebfe8332a23d240672b2658eced493b70fd145c99991c3c8651adcc",
  blockNumber: 2475,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 26747,
  gasPrice: 20000000000,
  hash: "0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1",
  input: "0x55241077000000000000000000000000000000000000000000000000000000000001e240",
  nonce: 66,
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionIndex: 0,
  value: 0
}
eth.getTransactionReceipt("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
{
  blockHash: "0x961277e2bebfe8332a23d240672b2658eced493b70fd145c99991c3c8651adcc",
  blockNumber: 2475,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "f850e1e5c99352f47e3409e4e9f966a49c13152e63aaa7bb3765f0b37bfe09e5",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1",
  transactionIndex: 0
}

Ich habe dann eine Transaktion gesendet, bei value=123der das fehlgeschlagen ist, und gas==gasUsed:

testStatus.setValue(123, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 26747
});

0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a
> var status = debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
undefined
> status.structLogs[status.structLogs.length-1].error
"invalid jump destination (PUSH1) 2"

> eth.getTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  blockHash: "0x22fb5fcaef27dd017efcde8c3f78df7f5168c505210f5d08872a9c2877146044",
  blockNumber: 2484,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 26747,
  gasPrice: 20000000000,
  hash: "0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a",
  input: "0x55241077000000000000000000000000000000000000000000000000000000000000007b",
  nonce: 67,
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionIndex: 0,
  value: 0
}
> eth.getTransactionReceipt("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  blockHash: "0x22fb5fcaef27dd017efcde8c3f78df7f5168c505210f5d08872a9c2877146044",
  blockNumber: 2484,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "ab51ab81c19eb8da7f8d0216d6c2f1d8946e88842b758ff0af13c28ff7e181b4",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a",
  transactionIndex: 0
}

Der einzige einfache und zuverlässige Weg, den ich gefunden habe, um festzustellen, ob eine intelligente Vertragstransaktion erfolgreich war oder fehlgeschlagen ist, ist die Verwendung von debug.traceTransaction.

Hier ist die Ausgabe debug.traceTransactionfür die letzte fehlgeschlagene Transaktion.

debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  gas: 26747,
  returnValue: "",
  structLogs: [{
      depth: 1,
      error: "",
      gas: 5280,
      gasCost: 3,
      memory: null,
      op: "PUSH1",
      pc: 0,
      stack: [],
      storage: {}
  }, {
      depth: 1,
      error: "",
      gas: 5277,
      gasCost: 3,
      memory: null,
      op: "PUSH1",
      pc: 2,
      stack: ["0000000000000000000000000000000000000000000000000000000000000060"],
      storage: {}
  }, {
  ...
  }, {
      depth: 1,
      error: "invalid jump destination (PUSH1) 2",
      gas: 129,
      gasCost: 8,
      memory: ["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060"],
      op: "JUMP",
      pc: 66,
      stack: ["0000000000000000000000000000000000000000000000000000000055241077", "0000000000000000000000000000000000000000000000000000000000000022", "000000000000000000000000000000000000000000000000000000000000007b"],
      storage: {
        0000000000000000000000000000000000000000000000000000000000000000: "000000000000000000000000000000000000000000000000000000000000007b"
      }
  }]
}

Und hier ist ein kurzes Skript, um zu überprüfen, ob die Transaktion erfolgreich war oder fehlgeschlagen ist:

> var status = debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
undefined
> status.structLogs[status.structLogs.length-1].error
"invalid jump destination (PUSH1) 2"
Positiv bewertet, nette, einfache Verwendung von Geth's debug.traceTransaction.
Insgesamt, wenn ( (eth.getTransactionReceipt($transaction_id) != null) && (status.structLogs.length == 0) ) , könnte ich dann schließen, dass die Transaktion bestanden wurde?
Bei einer Transaktion zu einem Vertrag werden die vm-Anweisungen des Vertrags ausgeführt und die Schritte werden in status.structLogs angezeigt, sodass status.structLogs.length immer > 0 ist. Bei einer Transaktion zu einem anderen Nicht-Vertragskonto status.structLogs. Länge wird 0 sein.
Gute Lösung, aber für die Produktionsumgebung können wir das Debug wirklich aktivieren. Was wird der Overhead sein?. Wenn der lokale Knoten nicht der Mining-Knoten ist, bekommen wir dann etwas von der traceTransaction zurück?
Der Overhead besteht darin, dass Sie die debug.traceTransaction(...)Informationen nicht aus dem schnell synchronisierten Teil der Blockchain abrufen können – der Overhead besteht aus Speicherplatz und der Zeit für die Synchronisierung.
Meine debug.traceTransaction(<transactionID>) gibt ein leeres structLogs zurück: []. Was könnte der Grund sein? @BokkyPooBah
Ihr Knoten sollte mit der vollständigen Archivknotenoption synchronisiert werden. Testen Sie mit einer aktuellen Transaktion und wenn es funktioniert, aber eine ältere Transaktion nicht funktioniert, liegt das Problem darin, dass Ihr Knoten schnell synchronisiert wurde.
status.structLogs[status.structLogs.length-1].errorzurück: "{}". Kann ich daraus schließen, dass die Transaktion bestanden wurde? @BokkyPooBah
Die Ergebnisse von debug.traceTransactionkönnen sich geändert haben. Hier ist ein TX mit einem Fehler während des TokenCard ICO. var status = debug.traceTransaction("0xe686a5fd36ec7921c847b9285c9ea09724bc6065c7374462a448f0d384adad82");. status.structLogs[status.structLogs.length-1].errorkehrt zurück {}. Aber status.structLogs[status.structLogs.length-1].opkehrt zurück"Missing opcode 0xfd"
Ja debug.traceTransaction wurde aktualisiert. Wenn status.structLogs[status.structLogs.length-1].op returns=> "Missing opcode 0xfd"könnte ich also daraus schließen, dass die Transaktion ausgelöst wird? Ich möchte nur eine Kennung haben, um zu überprüfen, ob die Transaktion ausgelöst wird. @BokkyPooBah
Was wäre, wenn: op:JUMP and error: {}.. bedeutet es auch Transaktionswurf? @BokkyPooBah
op:RETURN and error: null ist eine gültige Transaktion? @BokkyPooBah

Seit Block 4370000 (Byzanz) wurde den Quittungen ein statusKennzeichen hinzugefügt.

eth.getTransactionReceipt(transactionHash)gibt ein statusFeld zurück, das einen Wert hat, 0wann eine Transaktion fehlgeschlagen ist und 1wann die Transaktion erfolgreich war.

Vor Block 4370000

Der Quittungsstatus ist null, und um festzustellen, dass kein Gas mehr vorhanden ist, muss die Transaktion über das EVM verarbeitet werden, was einige Block-Explorer tun .

Out of Gas kann im Allgemeinen aufgrund der Implikationen des Halteproblems nicht statisch (ohne Ausführung der Transaktion) erkannt werden .

Selbst bei der einfachsten Transaktion, die genau 21.000 Gas hat und verbraucht, muss die Transaktion ausgeführt werden, da der Empfänger den ihm zugesandten Ether durch eine Fallback-Funktion ablehnen könnte. Im Allgemeinen muss der Code in einer Fallback-Funktion ausgeführt werden, um zu bestimmen, ob er eine „Out of Gas“-Ausnahme vermeidet.

gas == gasUsedist eine gute praktische Heuristik für Gasmangel, da es sicherer ist, eine angemessene Menge Gas mehr zu liefern, als web3.eth.estimateGasungenutztes Gas zurückerstattet wird.

Ich glaube nicht, dass das Halteproblem hier zutrifft; die Transaktion wurde bereits vom Netzwerk ausgeführt; es könnte den Grund für das Anhalten angeben, wenn das Protokoll dafür eine Bestimmung enthält.

Hier ist mein Python-Code, um dies mit Populus und web3.py zu überprüfen :

from web3 import Web3
from populus.utils.transactions import wait_for_transaction_receipt

class TransactionConfirmationError(Exception):
    """A transaction was not correctly included in blockchain."""


def confirm_transaction(web3: Web3, txid: str, timeout=60) -> dict:
    """Make sure a transaction was correctly performed.

    Confirm that

    * The transaction has been mined in blockchain

    * The transaction did not throw an error (used up all its gas)

    http://ethereum.stackexchange.com/q/6007/620

    :raise TransactionConfirmationError: If we did not get it confirmed in time
    :return: Transaction receipt
    """

    try:
        receipt = wait_for_transaction_receipt(web3, txid, timeout)
    except Timeout as e:
        raise TransactionConfirmationError("Could not confirm tx {} within timeout {}".format(txid, timeout)) from e

    tx = web3.eth.getTransaction(txid)

    if tx["gas"] == receipt["gasUsed"]:
        raise TransactionConfirmationError("Transaction failed (out of gas, thrown): {}".format(txid))

    return receipt
Warum fügen Sie die debug.traceTransaction(...)Überprüfung nicht zu Ihrem Code hinzu - Sie erhalten dann den genauen Fehler, und Sie können auch Nicht-Fehlerbedingungen erkennen, bei denen gas==gasUsed ?
OK. Wird die Antwort aktualisieren, um den genauen Fehler zu erhalten.

Persönlich habe ich eine andere Problemumgehung als die Methode traceTransaction verwendet. Es besteht einfach darin, dass am Ende der Transaktionen (Vertragsfunktion) ein Ereignisprotokoll (z. B. Completed()) aufgerufen wird. Der Ablauf ist dann:

  • Senden Sie die Transaktion und erhalten Sie den TxHash
  • setTimeout(maxtoWait, callback) starten
  • Registrieren Sie sich bei „Completed()“ an dieser Vertragsadresse von currentBlock bis currentBlock+5 (oder mehr) mit Rückruf
  • Bei Rückruf,
    • wenn durch Timeout aufgerufen (keine Argumente), dann ist tx fehlgeschlagen (oder wurde angenommen!)
    • Überprüfen Sie, ob log.transactionHash Ihrem TX entspricht
    • Wenn ja, dann war die Transaktion erfolgreich
    • Wenn nein, prüfen Sie, ob log.blockNumber >= initialBlock+5 ist, dann ist die Transaktion fehlgeschlagen.

Es ist komplexer, verwendet aber nicht traceTransaction, das ich als Debugging-Funktion und nicht als Produktionsumgebung betrachte.

Dies ist eine gute Möglichkeit, wenn Sie vor seiner Bereitstellung Zugriff auf den Quellcode des Smart Contracts haben. In meiner Arbeit, die forensische Abrechnung (dh Bilanzierung nach der Tat) macht, ist Ihre Methode nicht möglich. Aber wie ich schon sagte, es ist eine gute Möglichkeit, dies zu tun, wenn Sie die Kontrolle über die Quelle haben.