Lifecycle of a Transaction
Transactions
are constructed by users to express the intent of performing actions in the network. Once in the network, transactions are converted into Receipts
, which are messages exchanged between network nodes.
On this page, we will explore the lifecycle of a transaction, from its creation to its final status.
To dig deeper into transaction routing, we recommend reading the nearcore documentation
Receipts & Finality
Let's walk through the lifecycle of a complex transaction and see how it is processed by the network using blocks as time units.
Block #1: The Transaction Arrives
After a transaction arrives, the network takes one block to validate it and transform it into a single Receipt
that contains all the actions to be executed.
While creating the Receipt
, the signer
gets $NEAR deducted from its balance to pay for the gas and any attached NEAR.
If the signer
and receiver
coincide - e.g. the signer
is adding a Key - the Receipt
is immediately processed in this first block and the transaction is considered final.
Block #2: The Receipt is Processed
If the signer
and receiver
differs - e.g. the signer
transfers NEAR to the receiver
- the Receipt
is processed in a second block.
During this process a FunctionCall
could span a cross-contract call, creating one or multiple new Receipts
.
Block #3...: Function Calls
Each Receipt
created from the function call take an additional block to be processed. Notice that, if those Receipts
are FunctionCall
they could spawn new Receipts
and so on.
Final Block: Gas is Refunded
A final Receipt
is processed in a new block, refunding any extra gas paid by the user.
A transaction is considered final when all its receipts are processed.
Most transactions will just spawn a receipt to process the actions, and a receipt to refund the gas, being final in 1-3 blocks (~1-3 seconds):
- One block if the
signer
andreceiver
coincide - e.g. when adding a key - Three blocks if the
signer
andreceiver
differ, since the first block creates theReceipt
, and the last reimburses gas
Function calls might take longer, as they can spawn multiple receipts. Network congestion can also increase the time to process a receipt and, thus, a transaction.
Transaction Status
As the Receipts
of a Transaction
are processed, they get a status:
Success
: the actions on the receipt were executed successfullyFailed
: an action on the receipt failedUnknown
: the receipt is not known by the network
If an action in a Receipt
fails, all the actions in that Receipt
are rolled back. Notice that we are talking about the Receipt
status, and not the Transaction
status.
The status of a transaction is determined by its first receipt, which contains all its actions. If any of the actions in the first receipt fail, the transaction is marked as failed.
Notice that, it could happen that a transaction is marked as successful, but some of its receipt fails. This happens when a FunctionCall
successfully spawns a new receipt, but the consequent function call fails. In this case, the transaction is marked as successful because the original function call was successful.
See the examples below for more details.
Status Examples
Example: Transaction with Transfer
bob.near
creates a transaction to transfer 10 NEAR toalice.near
- The transaction is converted into a receipt
- The conversion fails because
bob.near
does not have enough balance - The transaction is marked as failed ⛔
Example: Deploying a Contract
bob.near
creates a transaction to:- create the account
contract.bob.near
- transfer 5 NEAR to
contract.bob.near
- deploy a contract in
contract.bob.near
- create the account
- The transaction is transformed into one receipt
- The account is created, the money transfer and the contract deployed
- The transaction is marked as successful ✅
Example: Deploying a Contract Fails
bob.near
creates a transaction to:- create the account
contract.bob.near
- transfer 5 NEAR to
contract.bob.near
- deploy a contract in
contract.bob.near
- create the account
- The transaction is transformed into one receipt
- The account is created, but the transfer fails because
bob.near
does not have enough balance - The whole process is reverted (i.e. no account is created)
- The transaction is marked as successful ⛔
Example: Calling a Function
bob.near
creates a transaction to call the functioncross-call
incontract.near
- The transaction is transformed into one receipt
- The function
cross-call
creates a promise to call the functionexternal-call
inexternal.near
- The function finishes correctly and the transaction is marked as successful ✅
- A new receipt is created to call the function
external-call
inexternal.near
- The function
external-call
fails - The original transaction is still marked as successful ✅ because the first receipt was successful
You can check the status of a transaction using the NearBlocks explorer