The Anatomy of a Block Stuffing Attack
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:
-
Fomo3D Long contract address: 0xA62…Da1
-
Last transaction by the winner before the attack starts: 0x7a0…349
-
Transaction ending the round and firing the
onBuyAndDistribute
event which distributes the pot: 0xa14…012 -
Transaction where winner withdraws the prize: 0xe08…508
-
Contract used for block stuffing: 0x18e…801
-
Other addresses possibly belonging to the winner: 0x3c3…f27 , 0xc6a…3e2 , 0x81e…0ac , 0xc96…590 , 0xd27…642 , 0x18d…a9a , 0x87c…4ef , 0x9da…0cf , 0x7dd…c4c , 0xb1d…aef , 0x1a6…d56 , 0xf6e…059 , 0x1dd…a69 , 0x061…63b , 0x00c…776 , 0xa94…eb8 , 0xf03…1f2
-
Other contracts possibly deployed by the winner for testing prior to the attack: 0xaf1…eae , 0x0c0…5ad , 0x88e…d04 , 0x4f4…6f8 , 0x487…4e5
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.
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.
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.
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!
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.