Block stuffing is a type of attack in blockchains where an attacker submits transactions that deliberately fill up the block’s gas limit and stall other transactions. To ensure inclusion of their transactions by miners, the attacker can choose to pay higher transaction fees. By controlling the amount of gas spent by their transactions, the attacker can influence the number of transactions that get to be included in the block.

To control the amount of gas spent by the transaction, the attacker utilizes a special contract. There is a function in the contract which takes as input the amount of gas that the attacker wants to burn. The function runs meaningless instructions in a loop, and either returns or throws an error when the desired amount is burned.

For example let’s say that the average gas price has been 5 Gwei in the last 10 blocks. In order to exert influence over the next block, the attacker needs to submit transactions with gas prices higher than that, say 100 Gwei. The higher the gas price, the higher the chance of inclusion by miners. The attacker can choose to divide the task of using 8,000,000 gas—current gas limit for blocks—into as many transactions as they want. This could be 80 transactions with 100,000 gas expenditure, or 4 transactions with 2,000,000 gas expenditure.

Deciding on how to divide the task is a matter of maximizing the chance of inclusion, and depends on the factors outline below.

Miners’ strategy for selecting transactions

Miners want to maximize their profit by including transactions with highest fees. In the current PoW implementation of Ethereum, mining the block takes significantly more time than executing the transactions. So let’s assume all transactions in the pool are trivially executed as soon as they arrive and miners know the amount of gas each one uses.

For miners, maximizing profit is an optimum packing problem. Miners want to choose a subset of the transaction pool that gives them maximum profit per block. Since there are at least tens of thousands of transactions in the pool at any given time, the problem can’t be solved by brute-forcing every combination. Miners use algorithms that test a feasible number of combinations and select the one giving the highest reward.

A block stuffer’s main goal is to target the selection process by crafting a set of transactions that has the highest chance of being picked up by miners in a way that will deplete blocks’ gas limits. They can’t devise a 100% guaranteed strategy since each miner can use a different algorithm, but they can find a sweet spot by testing out the whole network.

(In a PoS system, our assumptions would be wrong since executing transactions is not trivial compared to validating blocks. Validators would need to develop more complex strategies depending on the PoS implementation.)

The transactions the attacker wants to stall:

It could be so that the attacker wants to stall transactions with a specific contract. If the function calls to that contract use a distinctively high amount of gas, say between 300,000 and 500,000, then the attacker has to stuff the block in a way that targets that range.

For example, the attacker can periodically submit $n$ transactions $\{T_1, T_2,\dots, T_{n-1}, T_n\}$ with very high prices where

\[\sum\limits_{i=1}^{n} T_i^{\text{gas}} \approx 8,000,000.\]

If the attacker is targeting transactions within a range of $(R_\text{lower}, R_\text{upper})$, they can choose the first $n-1$ transactions to deplete $8,000,000 - R_\text{upper}$ gas in short steps, and submit $T_n$ to deplete the remaining $R_\text{upper}$ gas with a relatively higher price. Note that the revenue from including a single transaction is

\[\text{tx_fee} = \text{gas_price} \times \text{gas_usage}.\]

As gas usage decreases, the probability of being picked up by miners decreases, so prices should increase to compensate.

Example: Fomo3D

Fomo3D is a gambling game where players buy keys from a contract and their money goes into a pot. At the beginning of each round, a time counter is initiated which starts counting back from 24 hours. Each bought key adds 30 seconds to the counter. When the counter hits 0, the last player to have bought a key wins the majority of the pot and the rest is distributed to others. The way the pot is distributed depends on the team that the winner belongs to.

Key price increases with increasing key supply, which makes it harder and harder to buy a key and ensures the round will end after some point. In time, the stakes increase and the counter reduces to a minimum, like 2 minutes. At this point, the players pay both high gas and key prices to be “it” and win the game. Players program bots to buy keys for them, and winning becomes a matter of coding the right strategy. As you can understand from the subject, the first round was won through a block stuffing attack.

On August 22 2018, the address 0xa16…f85 won 10,469 ETH from the first round by following the strategy I outlined above. The winner managed to be the last buyer in block 6191896 and managed to stall transactions with Fomo3D until block 6191909 for 175 seconds, ending the round. Some details:

The user addresses above were scraped from the Ethereum transaction graph as being linked to a primary account which supplied them with funds. The contract addresses were scraped from 0-valued transactions sent from user addresses. These have a distance of 1, there may be other addresses involved with greater distances.

Below are details of the last 4 blocks preceding the end of the round. The rows highlighted with yellow are transactions submitted by the attacker. The crossed out rows are failed transactions. All transactions by the attacker were submitted with a 501 Gwei gas price, and stuffing a single block costed around 4 ETH. The calls to buy keys generally spend around 300,000~500,000 gas, depending on which function was called. Below, you see the successfully stuffed block 6191906.

Block 6191906
Idx From To Hash ETH sent Gas Price
[Gwei]
Gas Limit Gas Used ETH spent
on gas
0 0xF03…1f2 0x18e…801 0xb97…8e4 0 501.0 4,200,000 4,200,000 2.1042
1 0x87C…4eF 0x18e…801 0x96f…1b0 0 501.0 3,600,000 3,600,000 1.8036
2 0xf6E…059 0x18e…801 0x897…2b3 0 501.0 200,000 200,000 0.1002
Sum 0 1503.01 8,000,000 8,000,000 4.0080

Block 6191907 was a close call for the winner, because their transactions picked up for the block did not amount up to 8,000,000 and the other transaction was a call to Fomo3D by an opponent to buy keys. Note that it has a gas price of 5559 Gwei, which means either the bot or person who submitted the transaction was presumably aware of the attack. The transaction failed due to low gas limit, presumably due to a miscalculation by the bot or the person.

Block 6191907
Idx From To Hash ETH sent Gas Price
[Gwei]
Gas Limit Gas Used ETH spent
on gas
0 0x32A…370 0xA62…Da1 0x5e7…be1 0.0056 5559.7 379,000 379,000 2.1071
1 0xC6A…3E2 0x18e…801 0xb8b…40c 0 501.0 3,900,000 3,900,000 1.9539
2 0xD27…642 0x18e…801 0xbcf…c62 0 501.0 3,300,000 3,300,000 1.6533
3 0x00c…776 0x18e…801 0xf30…337 0 501.0 400,000 400,000 0.2004
Sum 0.0056 7062.71 7,979,000 7,979,000 5.9147

Transactions in block 6191908 belonged to the attacker except for one irrelevant transfer. This block is also considered successfully stuffed, since the 7,970,000 gas usage by the attacker leaves no space for a call to buy keys.

Block 6191908
Idx From To Hash ETH sent Gas Price
[Gwei]
Gas Limit Gas Used ETH spent
on gas
0 0xD27…642 0x18e…801 0x74a…9b1 0 501.0 3,300,000 3,300,000 1.6533
1 0x7Dd…c4c 0x18e…801 0x48c…222 0 501.0 2,700,000 2,700,000 1.3527
2 0x3C3…f27 0x18e…801 0x01b…4aa 0 501.0 1,800,000 1,800,000 0.9018
3 0xa94…eb8 0x18e…801 0x776…d43 0 501.0 170,000 170,000 0.0851
4 0xbFd…1b4 0x663…d31 0x3a6…ba1 0.05 100.0 21,000 21,000 0.0021
Sum 0.05 2104.01 7,991,000 7,991,000 3.9950

By block 6191909, the counter has struck zero—more like current UTC time surpassed the round end variable stored in the contract—and any call to Fomo3D would be the one to end the round and distribute the pot. And the first transaction in the block is—wait for it—a call to Fomo3D to buy keys by the opponent whose transaction failed a few blocks earlier, submitted with 5562 Gwei. So the guy basically paid 1.7 ETH to declare the attacker the winner!

Block 6191909
Idx From To Hash ETH sent Gas Price
[Gwei]
Gas Limit Gas Used ETH spent
on gas
0 0x32A…370 0xA62…Da1 0xa14…012 0.0056 5562.2 379,000 304,750 1.6950
1 0xC96…590 0x18e…801 0xf47…9ca 0 501.0 2,200,000 37,633 0.0188
2 0xb1D…aEF 0x18e…801 0xe4c…edb 0 501.0 1,400,000 37,633 0.0188
3 0x18D…A9A 0x18e…801 0xf3a…995 0 501.0 800,000 37,633 0.0188

Another thing to note is that the attacker probably crafted the spender contract to stop the attack when the round has ended, presumably to cut costs. So the 37,633 gas used by the contract are probably to call the Fomo3D contract to check round status. All these point out to the fact that the attacker is an experienced programmer who knows their way around Ethereum.

Here, you can see the details of the 100 blocks preceding the end of the round, with the additional information of ABI calls and events fired in transactions.

Since the end of the first round, 2 more rounds ended with attacks similar to this one. I didn’t analyze all of them because it’s too much for this post, but here are some details if you want to do it yourselves.

Round Address winning the pot Winner’s last tx before the end of the round Block containing that tx Tx ending the round Block containing that tx Tx where winner withdraws prize Amount won [ETH] Contract used for block stuffing
1 0xa169df5ed3363cfc4c92ac96c6c5f2a42fccbf85 0x7a06d9f11e650fbb2061b320442e26b4a704e1277547e943d73e5b67eb49c349 6191896 0xa143a1ee36e1065c3388440ef7e7b38ed41925ca4799c8a4d429fa3ee1966012 6191909 0xe08a519c03cb0aed0e04b33104112d65fa1d3a48cd3aeab65f047b2abce9d508 10,469 0x18e1b664c6a2e88b93c1b71f61cbf76a726b7801
2 0x18a0451ea56fd4ff58f59837e9ec30f346ffdca5 0x0437885fa741f93acfdcda9f5a2e673bb16d26dd22dfc4890775efb8a94fb583 6391537 0x87bf726bc60540c6b91cc013b48024a5b8c1431e0847aadecf0e92c56f8f46fd 6391548 0x4da4052d2baffdc9c0b82d628b87d2c76368914e33799032c6966ee8a3c216a0 3,264 0x705203fc06027379681AEf47c08fe679bc4A58e1
3 0xaf492045962428a15903625B1a9ECF438890eF92 0x88452b56e9aa58b70321ee8d5c9ac762a62509c98d9a29a4d64d6caae49ae757 6507761 0xe6a5a10ec91d12e3fec7e17b0dfbb983e00ffe93d61225735af2e1a8eabde003 6507774 0xd7e70fdf58aca40139246a324e871c84d988cfaff673c9e5f384315c91afa5e4 376 0xdcC655B2665A675B90ED2527354C18596276B0de

A thing to note in the following rounds is that participation in the game and amount of pot gradually decreased, presumably owing to the fact that the way of beating the game has been systematized. Although anyone can attempt such an attack, knowing how it will be won takes the “fun” factor out of it.

Credit: Although I’ve found previous instances of the term “block stuffing” online, Nic Carter is the first one to use it in this context.