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
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
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 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.
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.
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!
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.
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.