Skip to main content

Transaction Ordering in TRON and Ethereum: Why Energy Delegation Can Arrive Too Late

· 20 min read
Chris Gayle
Chris Gayle
Backend Engineer @CatFee

You purchase Energy before sending USDT. Why does the transfer still burn TRX—or fail because it runs out of Energy?

In a wallet, exchange, or payment backend, the implementation often looks straightforward: broadcast the resource delegation transaction, then broadcast the user's transfer. The requests are sequential, and the first one returns a successful response. What could go wrong?

The order in which an application submits transactions is not necessarily the order in which a block producer executes them.

A common explanation is that TRON has no transaction nonce, while Ethereum does. That identifies an important difference, but leaves out its scope. TRON executes transactions in block order; it simply lacks Ethereum's per-sender nonce constraint. Ethereum's nonce, in turn, constrains transactions from the same account. It does not automatically establish dependencies between different accounts.

For Energy services, the requirement is more specific: when the user's transaction starts executing, the resources it needs must already be available—and still sufficient.

Scope and source versions​

This article follows two specific releases so that the implementation details are reproducible: java-tron GreatVoyage-v4.8.0 and go-ethereum v1.15.11. These are reference versions, not a claim that every production node runs the same code. Client upgrades, network parameters, and block production policies must be checked separately.

The Ethereum discussion focuses on ordinary transactions sent by externally owned accounts (EOAs). It does not cover every path that can change an account's nonce, such as EIP-7702 authorizations. Algorithm sketches below are explanatory pseudocode rather than drop-in client implementations.

Product descriptions follow CatFee's integration documentation. Confirmation gates, resource reservations, and retry state machines are engineering recommendations in this article; they should not be read as guarantees provided by every product mode.

Three different meanings of transaction order​

Let P be a resource provider and U a user. Consider two transactions:

  • D: signed by P, delegating Energy to U.
  • T: signed by U, calling the USDT contract to transfer tokens.

The application needs D → T. But “order” can refer to three different things:

Type of orderWhat determines itWhat it establishes
Submission orderThe wallet or application backendWhich request the application submitted first
Node arrival and selection orderEach node's local view and the producer's selection policyWhich transaction a particular node saw or selected first
Execution order within a blockThe transaction list in the accepted blockWhich state transition happens first

An RPC endpoint may acknowledge D before accepting T, while the eventual block producer has yet to receive D. Different propagation paths and node queues can change the order in which those transactions arrive.

Even inclusion in the same block is not enough. Their positions matter: [D, T] and [T, D] can produce different resource usage and fee outcomes. Comparing block heights alone misses this distinction.

Inside TRON: ordered execution without a sender nonce​

Timestamps and TAPOS do not assign a place in the queue​

In protocol/src/main/protos/core/Tron.proto, Transaction.raw contains a block reference, expiration time, contract payload, timestamp, and fee limit. It does not contain a monotonically increasing sender nonce for ordinary transactions. See the raw transaction definition.

Three fields are particularly easy to misinterpret:

  • timestamp records transaction timing; it does not establish a network-wide first-come, first-served order.
  • ref_block_bytes and ref_block_hash support TAPOS block-reference validation; they do not express a dependency on a preceding application transaction.
  • expiration limits transaction validity; an earlier expiration does not entitle a transaction to execute first.

Manager.validateTapos() checks the referenced block, validateCommon() checks transaction size and expiration, and processTransaction() also performs duplicate and signature checks. This processing path has no equivalent of Ethereum's requirement that a transaction nonce match the sender's current account nonce. See the TAPOS and common validation code and the transaction processing entry point.

Giving D an earlier timestamp than T, or referencing the same block in both transactions, therefore does not establish a D → T dependency.

A local queue does not establish global order​

java-tron does have local ordering policies. In this release, Manager initializes the pending and re-push queues as LinkedBlockingQueue instances when transaction sorting is disabled. When sorting is enabled, it uses PriorityBlockingQueue instances with a comparator. See the queue initialization code.

A local FIFO queue describes operations on that particular queue. It does not mean that every node has the same queue, or that transactions reach the application, RPC node, and block producer in the same order. An internal ordering field is not a protocol-level account sequence number that applications can rely on.

Manager.generateBlock() makes this distinction concrete. It selects candidates from pendingTransactions and rePushTransactions, checks the production deadline and block size, processes each candidate, and adds eligible transactions to the block. A transaction that does not fit in the remaining space can be skipped so that smaller transactions can still be included. Processing exceptions can also prevent inclusion. See the block generation loop.

Block construction therefore depends on the producer's local candidate set and current state. It does not promise to preserve the application's submission order.

Passing this processing path must also be distinguished from a successful contract call. A transaction can be included with a failed execution result. Whether the USDT transfer actually happened is a separate question answered by its execution result.

Once a block is accepted, its transaction order matters​

Manager.processBlock() iterates over block.getTransactions() and calls processTransaction() for each entry. TRON's state transitions consequently follow a defined transaction order. See the block processing loop.

Block order [D, T]: S0 --execute D--> S1  --execute T--> S2
Block order [T, D]: S0 --execute T--> S1' --execute D--> S2'

If D successfully updates U's resource state, a subsequent T can execute against that updated state. Both transactions can be in the same block; delegation and transfer do not inherently require separate blocks.

TRON supports ordered execution. Its ordinary broadcast interface does not enforce the application's desired dependency between these two transactions.

Inside Ethereum: how nonces constrain a sender's transactions​

The constraint is enforced during state transition validation​

In Geth's core/state_transition.go, preCheck() reads the sender's account nonce and compares it with the transaction nonce. In simplified form:

expected = state.nonce(transaction.sender)

if transaction.nonce > expected:
return ErrNonceTooHigh
if transaction.nonce < expected:
return ErrNonceTooLow

continue with balance, fee, and other checks

This check determines whether a transaction is valid for the current state. It is enforced beyond wallet conventions or transaction pool policy. See the nonce validation code.

Suppose Alice's current account nonce is 42, and she signs:

A: sender = Alice, nonce = 42
B: sender = Alice, nonce = 43

Even if a node receives B first, it cannot execute B as Alice's next valid ordinary transaction while her account nonce is still 42. The state transition would return ErrNonceTooHigh. Whether the node retains B in its pool while waiting for a preceding transaction depends on its pool policy and capacity.

Geth selects across accounts while preserving nonce order within each account​

In miner/ordering.go, transactionsByPriceAndNonce maintains a list of transactions ordered by nonce for each account. It then places the next candidate from each account into a price heap. Shift() advances an account's next transaction to the head when selection moves forward. See the selection structure and Shift implementation.

Transactions from different accounts can compete on effective tips and other selection criteria. A later transaction from the same account cannot bypass an unmet nonce requirement merely by offering a higher tip.

Other block builders can use different selection policies, but the resulting block must still pass execution-layer validation. Geth applies block transactions sequentially and propagates execution-layer validation errors to the calling validation path. See the block transaction processing code.

Consuming a nonce does not mean the preceding operation succeeded​

For ordinary contract calls, Geth increments the sender's nonce before invoking the EVM. A validly included transaction consumes that nonce even if its contract execution subsequently reverts. See the nonce update and EVM call.

Using nonce 42 for A and 43 for B therefore establishes account sequencing. It does not mean “execute B only if A's intended operation succeeds.” Replacement transactions introduce another distinction: the transaction that ultimately consumes nonce 42 may not be the original A.

Workflows involving approvals, swaps, or payments still need to inspect preceding receipts or enforce the required conditions in contract code.

Separate accounts do not share a nonce sequence​

In the Energy example, provider P signs D, while user U signs T. They are different senders.

Consider an analogous Ethereum workflow: a service first funds a user with ETH, then the user sends tokens. The two accounts' nonces do not establish an ordering relationship between those transactions. If the user already has enough ETH, the token transfer may execute first. If the user lacks funds, its executability depends on the balance state—not on the provider's nonce imposing a sequence on the user's account.

PropertyOrdinary TRON transactionsOrdinary Ethereum EOA transactions
Execute in block transaction orderYesYes
Preserve network-wide broadcast orderNo guaranteeNo guarantee
Constrain consecutive transactions through a sender nonceNo equivalent ordinary transaction nonceYes
Automatically establish application dependencies across sendersNoNot provided by nonces
Require the preceding application operation to succeedRequires additional logicRequires additional logic

The race between Energy delegation and a USDT transfer​

Delegation becomes useful through a state change​

DelegateResourceActuator updates delegation records and the receiving account's resource-related state, including acquiredDelegatedFrozenV2BalanceForEnergy. Those updates affect the account data used to calculate available resources. See the delegation implementation.

Obtaining a delegation transaction hash, receiving a broadcast acknowledgment, seeing successful execution on-chain, and observing available resources at the required confirmation level are separate milestones.

A receiving node may already have processed a pending transaction locally. That local state is not a substitute for the state against which every block producer will execute the transaction. Applications need to know which node, chain branch, and confirmation level their reads represent.

The transfer's execution budget depends on the state available at execution​

VMActuator.getAccountEnergyLimitWithFixRatio() calculates an execution budget using the account's available resources, balance, and fee_limit. ReceiptCapsule.payEnergyBill() handles resource consumption and charges for any shortfall. Other branches, including resource contributions from the contract creator, also affect the outcome. See the execution budget calculation and Energy billing code.

fee_limit is not an Ethereum-style priority tip. Increasing it changes the permitted execution budget; it does not instruct a node to wait for a delegation or place the transaction after another transaction. Setting it very low does not make a node wait for Energy either.

If T executes before D, it cannot use resources that D has yet to provide:

State when T executesPossible outcome
The user already has sufficient resourcesThe transfer succeeds without relying on the later delegation
Resources are insufficient, but the TRX balance and execution budget cover the shortfallThe transfer may succeed while incurring an Energy fee
Available resources and the permitted paid budget are insufficientExecution may fail, for example with OUT_OF_ENERGY; some transactions may instead be rejected during other validation stages

Late delegation does not always mean failure, and failure does not necessarily mean no fees were charged. The actual receipt determines the cost; there is no single TRX burn amount that describes every case.

A later delegation does not retroactively change an executed transfer or automatically refund the resource fees it incurred.

Correct ordering alone may still leave the transfer short of Energy​

Delegated resources are available to the receiving account. They are not inherently reserved for a particular USDT transaction hash. Another transaction, X, can consume them first:

D: Delegation makes resources available.
X: Another contract call from the same user account consumes resources.
T: The intended transfer starts with insufficient resources remaining.

A reliable system must account for both availability and concurrent consumption. Broadcasting the delegation first does not fully address either requirement.

Practical approaches and the guarantees they provide​

Gate the transfer on observed chain state​

For applications that can tolerate additional latency, the most direct approach is to make broadcasting conditional on resource availability:

  1. Hold the signed transfer T without broadcasting it through another endpoint.
  2. Broadcast delegation D and retain its hash.
  3. Verify that D executed successfully and reached the application's required confirmation level.
  4. Check U's available resources against a consistent view of chain state, accounting for other in-flight transactions.
  5. Broadcast T once those conditions are met, then track its execution result.

A transaction appearing in a FullNode's latest block provides a different level of assurance from inclusion in a solidified block. Waiting for stronger confirmation adds latency. Releasing the transfer based on the latest block requires handling reorganizations that could remove the delegation from the canonical chain. The confirmation policy should match the application's tolerance for that risk.

Resource checks must also measure what is available to spend. For example, consider EnergyLimit, EnergyUsed, and in-flight consumption together, rather than checking only the total resource limit. Estimates should account for contract state changes, dynamic Energy costs, and Bandwidth requirements.

Provision resources ahead of demand and coordinate concurrency by account​

For busy hot wallets, maintaining a resource buffer can move replenishment out of the critical path of an individual transfer. Replenishment thresholds should reflect transaction throughput and the time needed to make additional resources available.

A useful capacity-planning approximation is:

Target resource buffer ≈
peak transaction rate × replenishment latency × estimated usage per transaction
+ safety margin

This is an operational model, not a protocol formula. Size the buffer using measured workloads and include transactions that have been submitted but are not yet confirmed.

An application queue or resource reservation ledger can coordinate multiple transfers from one address. A local lock alone cannot prevent transactions submitted elsewhere from consuming the same account's resources. Use a common broadcast path where possible and reconcile reservations with on-chain state.

Use a shared broadcast path and a short delay to reduce the race window​

Submitting D and T through the same endpoint, with a short delay between them, can reduce timing differences introduced by separate propagation paths.

But sleep(2000) establishes only that two seconds have elapsed. It does not prove that a block containing D has been accepted. Queue backlogs, propagation delays, block timing, or validation failures can all outlast that interval.

This technique can complement other controls. If the requirement is “never release the transfer before resources are confirmed,” the release condition must be based on chain state rather than elapsed time alone.

Coordinate with a block producer or encode an explicit dependency​

A dedicated relationship with the party constructing a block can support simulation and an agreed inclusion order such as [D, T]. The guarantee then depends on that service's terms: which producers participate, whether partial inclusion is allowed, how failures are handled, and what happens if transactions reach other broadcast paths.

In the Ethereum ecosystem, Flashbots provides bundle submission and simulation interfaces, including configurable treatment of reverting transactions. These services operate at a different layer from account nonces, and submitting a bundle does not guarantee inclusion. See the Flashbots bundle API.

The ordinary TRON transaction path examined here provides no equivalent guarantee merely because an application places two transactions in an array. Although Transaction.raw.contract is a repeated field, this version of Manager.processTransaction() requires exactly one top-level contract entry. See the contract count check.

Appending a DelegateResourceContract and a TriggerSmartContract to the same transaction is therefore not a way to obtain atomic execution. A custom contract can combine operations under its control and validate their dependencies, but that design still has to address caller identity, asset authorization, and the resources needed to begin execution. It cannot automatically combine arbitrary existing user transactions without changes.

At the protocol level, explicit predecessor references or atomic transaction bundles are possible areas for further design. Such proposals would need to define dependency expiration, predecessor failure and replacement behavior, authorization across accounts, and limits on node resource usage. Those are protocol design decisions, not hidden options in today's ordinary broadcast request.

CatFee Seamless Energy: resource preparation at the broadcast boundary​

Most wallets and applications would prefer not to maintain their own per-transfer logic for resource quotes, orders, propagation delays, and failure handling. CatFee Seamless Energy addresses that integration work: the application constructs and signs transactions as usual, then submits them to a dedicated endpoint that coordinates resource preparation and broadcasting. See the Seamless Energy overview.

The responsibilities are divided as follows:

Wallet or application
Construct and sign T
↓
CatFee broadcast endpoint
Identify resource requirements, prepare resources, and forward the original T
according to the configured policy
↓
TRON network
Validate, include, and execute the transaction
↓
Application
Determine the transfer outcome from its execution receipt

The transaction's addresses, amount, contract parameters, and signature remain unchanged, and the application retains control of its private keys. Systems that configure broadcast and query endpoints separately can keep their existing query endpoint and route broadcasts through CatFee. API key authentication uses the CF-NODE-KEY header as described in the integration guide.

The documented timing behavior matters here. CatFee's FAQ describes an approximately two-second wait before forwarding the original transaction to give resources time to become available. That is a coordination measure for resource provisioning and propagation. It does not add account nonces, consensus-enforced bundles, or finality guarantees to TRON. See the Seamless Energy FAQ.

The FAQ also describes failure handling: if Energy estimation, ordering, or delegation broadcasting fails, the original transaction is not forwarded. An exception applies when the CatFee account has insufficient balance and the customer has explicitly configured the service to continue broadcasting. Applications seeking tight control over unexpected TRX burn should align that setting with their cost policy.

Seamless Energy reduces the resource orchestration an application has to implement. The final transaction outcome still depends on on-chain execution. Applications with stricter confirmation requirements should verify the applicable release conditions rather than infer them from the product name.

CatFee Transaction Handoff: bring delegation into your own transaction pipeline​

Teams that already operate nodes, transaction queues, and confirmation policies may want more direct control. Transaction Handoff provides a different integration model.

CatFee returns a signed resource delegation transaction that has not been broadcast. The integrating application decides when and how to broadcast it. The application receives a transaction it can schedule, rather than only an order status to monitor. See the Transaction Handoff API overview and delegation transaction creation documentation.

This lets a team manage delegation D and user transaction T within one application state machine, with common policies for node selection, confirmation thresholds, expiration, and failure recovery.

For applications requiring confirmation before release, the following is a recommended integration workflow—not a default guarantee supplied by the API:

  1. Estimate the user's resource requirements and create a Transaction Handoff order.
  2. Persist the order ID, client order ID, signed delegation payload, and transaction hash.
  3. Broadcast the delegation transaction D returned by CatFee.
  4. Wait for successful execution and the confirmation level required by the application.
  5. Check available resources, accounting for other in-flight transactions from the same address.
  6. Verify that T has not expired, then broadcast the original signed user transaction.
  7. Track resource provisioning, order settlement, and the user's transaction outcome separately.

Orders are created through POST /v1/mate/open/transaction. When querying an order, distinguish the order ID from the delegation transaction hash. If a creation request has an uncertain outcome, query using the original client_order_id before creating another order with a new identifier. See the creation endpoint and order lookup documentation.

Transaction Handoff does not alter TRON's consensus rules. Broadcasting both signed transactions immediately, one after the other, still leaves the race described earlier. Its value is control: the application owns the delegation broadcast step and can enforce its own release conditions around it.

The documentation distinguishes payment from the partner's CatFee balance from end-user payment, which requires separate activation. Resource provisioning, fee settlement, and the user's application transaction should remain distinct states in the integration. See the Transaction Handoff solution guide.

Integration requirementApproach to evaluateWhat the application still owns
Keep the existing signing flow while reducing per-transfer resource orchestrationCatFee Seamless EnergyEndpoint configuration, failure policy, receipts, and retry handling
Control delegation broadcasting and choose the required confirmation levelCatFee Transaction HandoffDelegation broadcasts, confirmation gates, concurrent resource budgeting, and transaction state management
Reduce the latency of replenishing resources for each transfer from a busy addressMaintain a resource buffer with a replenishment serviceCapacity planning, threshold monitoring, and accounting for in-flight usage

Production reliability depends on retries and observability​

An ordering strategy needs a recovery model before it is ready for production.

A broadcast timeout is an unknown outcome, not proof of failure. The node may have accepted the transaction—or it may already be in a block—even though the response never arrived. Query the original hash first. If rebroadcasting is appropriate, reuse the same signed transaction while it remains valid. Constructing a new transfer before resolving the original can lead to duplicate payments.

Waiting for resources also consumes part of a transaction's validity window. Once a signed transaction expires, a broadcast service cannot change its expiration and retain the original signature. The signing party must construct and sign a new transaction. Link the old and new hashes in the application's idempotency records, and establish that the old transaction has not executed and can no longer execute before proceeding.

Capture enough evidence to distinguish an ordering race from other causes:

  • The delegation and user transaction hashes, broadcast responses, and submission times.
  • Their block heights, block hashes, positions within the block, and confirmation status.
  • The user's execution result and receipt fields such as energy_usage_total, energy_fee, and net_fee.
  • Resource snapshots before broadcast, estimates, and other in-flight transactions from the same account.

If T precedes D, investigate ordering directly. If D executed first but T still burned TRX, investigate the delegated amount, estimation error, concurrent usage, resource reclamation, and contract resource-sharing rules. Transaction order is one possible cause, not a diagnosis for every unexpected charge.

Integration testing should exercise [D, T], [T, D], and [D, X, T] in a controlled environment. It should also cover lagging nodes, request timeouts, expired transactions, and successful delegation followed by a failed user operation. The test is whether the state machine responds correctly to each outcome, not simply whether one transfer succeeds under normal conditions.

Reliable Energy infrastructure connects resource provisioning to transaction execution. Understanding the limits of account nonces helps teams identify which dependencies the protocol enforces and which their application must manage. CatFee Seamless Energy centralizes common resource preparation at the broadcast endpoint; Transaction Handoff gives teams with their own transaction infrastructure control over delegation scheduling. Both approaches serve the same practical requirement: make the required resources available before the target transaction executes.