Telling failed actions apart from completed movements
Forensia separates calls that failed, calls undone by a failing parent, and calls not reported as failed. A call not reported as failed is still not a verified balance change.
A transaction that appears on a blockchain is not necessarily a payment that happened. Transactions can fail and still be recorded, with a fee paid and nothing moved. Reading the amount on a failed transaction as money sent is one of the most common mistakes in this work.
The reverse also catches people. A transaction can succeed overall while some of the steps inside it failed. The amounts on those inner steps were attempted, not delivered.
Forensia now labels each step by how it ended: whether it failed on its own, whether it was undone because something around it failed, or both. The plain summary of a transaction also says when a successful transaction contains failed steps.
#Plain languageThree ways a step can end up undone
Think of a furniture order with several items, where the shop only ships complete orders. If one item turns out to be unavailable and the shop cancels everything, the items that were already packed go back on the shelf. They were handled, but you never received them. If instead the shop drops only the missing item and ships the rest, the order completed with one line that did not.
Inside a transaction, a contract call can fail and take everything it started with it. The calling contract can also catch that failure and carry on, so the transaction as a whole still succeeds.
- The step failed itself. It reported an error.
- The step did not report an error, but a call that contains it failed, or the whole transaction failed. Its effects were rolled back with that parent.
- Both: the step reported its own error and something around it also failed.
In all three cases the amount shown on the step is an attempt. It is not a transfer that completed.
#How it worksWhere the outcome labels appear
- Search an EVM transaction hash. Overview shows a line titled Execution status for the outer transaction, read from its receipt.
- Read the paragraph below it. When a successful transaction has retained inner steps that failed or were rolled back, a separate highlighted sentence gives the count and says their displayed values are not completed transfers.
- Open Inspect transaction steps. Each step carries an execution label above its amount: Call reports an error, Rolled back with enclosing execution, or Call error and enclosing rollback.
- Read the amount label next to it. A failed step shows Attempted value. A delegated or read-only step shows Call-context value. A step not reported as failed shows Trace-reported value, and the outer transaction shows Transaction value.
- Open Read the activity timeline to see transactions grouped with headings. A group whose outer records all report failure is headed Failed transaction attempt. A group whose outer records disagree is headed Conflicting transaction records.
Three Ethereum transactions from the engineering notes show the cases. In the failed transaction 0x22c2fbc2dcbcfbd800a10cbd24fdff09563d8862559eb6e42651936052be80db, the first two nested calls each carry their own error and an enclosing rollback, and the deepest call has no error of its own and is rolled back with its parents.
In 0x8dcc17ca88769a57542f7a83a50ecdc869417cfe33ec394f83b9054477397663 the outer transaction succeeded, one nested call reports its own error, and the call inside it was rolled back with it. Overview keeps the successful outer label and adds that two internal steps failed or were rolled back. The transaction 0x1629640864947a3d95ae8988cb4e24ebad442aae7780ac8835ae6f9766f34018 is the review case for a failed attempt with nonzero value, which must not be described as transferred funds.
#Technical detailHow each outcome is derived from the receipt and the trace
The outer outcome comes from the receipt
The receipt from eth_getTransactionReceipt is accepted only if its transactionHash, blockHash and blockNumber equal those of the captured transaction and block, and its from and to match the transaction. A receipt with status 0x0 that still carries logs is rejected, because a failed transaction cannot leave events. Each log must repeat the same transaction hash, block hash and block number, must not be marked removed, and log indexes must be strictly increasing. If any of this fails, the lookup stops instead of interpreting.
| Receipt status | Outer outcome |
|---|---|
| 0x1 | Success |
| 0x0 | Failed; the stated value was not transferred |
| Anything else or absent | Unknown, which includes receipts from before the status field existed |
Each inner call comes from its trace frame
Inner outcomes are read from the frames of debug_traceTransaction with the callTracer. Two facts are computed per frame. Own failure: the frame has a non-empty error or revertReason field. Inherited failure: any enclosing frame failed. The walk starts at the root with inherited failure already set when the receipt reports a failed transaction, so every frame of a failed transaction is at least rolled back.
| Own failure | Inherited failure | Status | failureOrigin | Label on the step |
|---|---|---|---|---|
| No | No | success if the receipt says success, otherwise unknown | not set | Provider-reported call |
| Yes | No | failed | frame | Call reports an error |
| No | Yes | failed | ancestor | Rolled back with enclosing execution |
| Yes | Yes | failed | frame-and-ancestor | Call error and enclosing rollback |
This is the EVM rule for revert propagation: when a frame reverts, state changes made by that frame and by every call beneath it are discarded, while a parent that handles the failure can continue. The error text kept for a frame is cut at 300 characters. The three specific labels are shown only when the step links to a captured debug_traceTransaction source whose recorded request names the callTracer for this transaction hash. Without that, or for captures made before failureOrigin was recorded, the step keeps the generic label Failed execution.
Amount labels
| Step | Amount label |
|---|---|
| Failed CALL, CREATE or CREATE2 | Attempted value |
| DELEGATECALL, CALLCODE or STATICCALL, any outcome | Call-context value |
| Inner call with unknown outcome | Recorded value |
| Inner call not reported as failed | Trace-reported value |
| Outer transaction with a matching successful receipt | Transaction value |
| Token event in a matching successful receipt | Event-reported amount |
Consistency checks on the recorded outcomes
- A failureOrigin may appear only on a trace step whose status is failed, and only with one of the three values above.
- For every such step the validated hierarchy is walked upward. An ancestor must be failed exactly when the origin is ancestor or frame-and-ancestor.
- A failed parent with a child that is not failed, or an unknown parent with a child that claims success, invalidates the hierarchy.
- The saved-response check under Sources compares each recorded origin with the same call occurrence decoded again from the saved trace bytes. A disagreement is a conflict and blocks the report, saving and combined raw export.
When Overview adds the sentence about failed inner steps
The extra sentence is deliberately hard to trigger. It needs a receipt-confirmed successful outer transaction; a root step whose sender, recipient, raw value and receipt reference match the outer activity record; a valid hierarchy with no failure-origin issue; exactly one captured callTracer source for this hash; and every counted failed step linked to both that trace and the receipt, with no request conflict. If any condition fails, the sentence is left out. The records themselves stay available.
The number in that sentence is a count of retained frames that failed or were rolled back. It is not a count of separate errors, because one failing parent can roll back several calls beneath it.
Timeline headings
A timeline heading is derived from every retained outer record of a transaction, meaning its native, contract-call or deployment rows, not from whichever row arrived first. If any outer record is flagged as conflicting, or outer records report both success and failure, the heading is Conflicting transaction records and neither source is chosen as correct. The heading says Failed transaction attempt only when every outer record reports failure. Failures of inner calls never change the heading of the outer transaction.
#LimitsWhat these labels do not establish
- The outcomes are what a provider reported in a receipt and a call trace. Forensia does not re-execute the transaction.
- A step not reported as failed is not a verified balance change. A trace value is not checked against account balances, and nested values must not be added together.
- Transaction fees and the effects of a transaction’s authorization list are outside this explanation. A failed transaction can still cost its sender a fee.
- The call view is bounded to depth 6 and 64 retained calls. Finding no failed step in a bounded sample is never a statement that every call succeeded.
- If the source provides no call trace, only the outer outcome is known. Inner failures in that transaction are then not visible at all.
- A receipt with no recognized status leaves the outcome unknown. It is not treated as success.
- A failed or rolled-back step says nothing about why it was attempted. It does not show intent, theft or who controls an address.