Transaction Order Dependence

Defining Transaction Order Dependence

Transaction Order Dependence is concerned with the critical race condition of transactions. Oftentimes two parties will try to buy something at the same time, however only one can be granted the asset. But determining who gets that asset is where conflict can arise. Most blockchain transactions are executed using smart contracts where a miner will determine the order that the transactions are processed. When two transactions are submitted at very similar times, the miner typically selects the one with a greater transaction fee or "gas price". For almost all transactions, a transaction fee is incurred onto the party making the request. All transaction requests go into a Mempool, where they are waiting to be processed by the miner.

Malicious Intent

Many parties try and abuse this Transaction Order Dependence for their own personal gain. For example, for many smart contracts in order to get the reward of the contract a party must first solve a problem and give the answer to redeem the contract. However, some parties will intercept a transaction request that contains the answer from a different party. They will then submit this answer as their own. And if their gas price is higher, the miner may select their request from the mempool instead of the rightful party.


Another example is if a party requests to buy 10 of something from a seller. Once that request has been sent out, the seller can then request to change the price of that item. If the seller's request gets executed first, and he increases the price of the items, the buyer's request will involve paying more than he agreed to. The order of these events occurring are crucial to the outcome of these events.

Solutions Available:

Commit Reveal Scheme

The best solution for this vulnerability is the Commit Reveal Scheme. This method allows for parties to essentially claim a smart contract reward without sending in the plaintext answer. If a party wants to claim their reward, they first submit their solution that has been hashed with a number they generated (called a nonce or salt value). This ensures that no third party is able to intercept the answer and submit it for themselves. Then, the original party has a certain amount of time to submit the plain text answer along with their salt value. The smart contract compares the answer hashed with the salt value with the original hash. If they are the same, the reward is given. A third party can intercept the plaintext answer, however at this point it is too late. The smart contract will only grant the reward if the party has the correct salt value that hashes the answer to the original hash. Because this value was randomly generated, there is no way the malicious party can claim the reward for themselves.

Demonstration of Attack

The premise of this attack is for two different accounts to send requests to a contract at the same time. One of these accounts will be trying to buy whatever the contract is and the other will be trying to change the price of the contract. Because of how miners process transactions, if these two transactions are submitted in a short period of time, the one with the higher gas price will be processed first, regardless of who actually requested the transaction before the other.

Contract Layout

The contract TransactionOrdering (red arrow) will need to be deployed before anything happens. This contract is what the other address will interact with.

Also notice how the price and owner variable are public. This means any account can see the value of these variables.

The ownerOnly() modifier (yellow arrow) is essentially an auxiliary function. The other accounts will not be able to interact with this function. This functions ensures that the account wanting to change the price is the owner of the contract.

The TransactionOrdering() function (green arrow) is the constructor of the contract. It sets the initial owner as the address of the account who deployed the contract as well as set an initial price of 10 ETH.

The buy() and setPrice() functions (blue arrows) are the functions that the accounts will be able to interact with. An account can call the buy() function to buy whatever the contract is holding, as long as they send enough money to buy it. The setPrice() function only allows the owner to change the price of the contract to whatever they want.

To deploy the contract, make sure the current file is compiled and selected. Then, press deploy. Below you will see available functions and public variables.

Once the contract has been deployed you will be able to see the contract creation transactions in the "Transactions" tab in Ganache.

If you click on the owner and price, it will show the address of the current owner as well as the price of the contract

Then, swap accounts to a different address. Set the value to send to 10 ETH and press buy.

Then, swap back to the owner of the account and enter a price that is greater than 10 and press setPrice.

Notice now that the current price of the contract is 100 instead of the initial 10.

In the "Transactions" tab in Ganache, the two transactions that were just sent show up. The one on the bottom shows the account buying the contract for 10 ETH (it appears as 10000... because it is in Wei, not ETH) and a gas price of 25090. The top transaction shows the owner of the contract setting the price of the contract to 100 ETH. Notice how the gas price is 30301. If these transactions are submitted to the mempool and very close intervals, the top transaction will be processed first, even though it was really submitted second.

Solution

One solution is to have counter that increments every time the price of the contract is changed. If you make the counter visible to everyone, a potential buyer can see this counter before they buy the contract. In the buy method, require the buyer to not only give the correct currency amount but also the counter value when they purchase contract. Only approve the buy request if the given counter value equals the current counter value of the contract. This way if the owner of the contract tries and changes the buy amount, the counter will be incremented. Even if their transaction gets processed by the miner first, the buy transactions will never go through because the given counter and current counter don't match.

Contract Layout

This contract is very similar to the one above. The biggest difference the txCounter. In the setPrice() function (red arrow), the txCounter is incremented by one every time the price is changed. In the buy() function (yellow arrow), the transaction will only succeed if the given _txCounter parameter equals the contract txCounter. If they do not equal that means the price was changed.

Additionally, there are now to getter functions for price and txCounter that will allow the account get the current counter and price amounts.

First deploy the contract using a different account. The contract will appear under the deployed contracts. Notice the price is 10 and the txCounter is 0.

Then, swap accounts and input 10 ETH to be sent. Also, input the current txCounter into the buy function. Press the buy button

Swap back to the original account and set a price of 100. Press the setPrice button

Now, notice that the txCounter is 1 instead of 0.

Now, when the contract is bought and the inputted counter and txCounter don't match an error is throw and the transaction is not successful.

The transaction failed and "Price was changed" is given in the console which can be traced back in the code.

References

Duan, L., Sun, Y., Zhang, K., & Ding, Y. (2022). Multiple-layer security threats on the

Ethereum blockchain and their countermeasures. Security and Communication Networks,

2022, 1–11. https://doi.org/10.1155/2022/5307697


Nils Amiet. 2021. Blockchain Vulnerabilities in Practice. Digit. Threat.: Res. Pract. 2, 2, Article 8

(March 2021), 7 pages. https://doi.org/10.1145/3407230


Oualid, Z. “Transaction Order Dependence Attack in Smart Contract.” Get Secure World,

13 May 2022, https://www.getsecureworld.com/blog/transaction-order-dependence-attack-in-smart-contract/.