Skip to content

Instantly share code, notes, and snippets.

@GalloDaSballo
Last active November 4, 2023 21:28
Show Gist options
  • Save GalloDaSballo/0a33a8b07e681d49c8228b6f8e9c9bd2 to your computer and use it in GitHub Desktop.
Save GalloDaSballo/0a33a8b07e681d49c8228b6f8e9c9bd2 to your computer and use it in GitHub Desktop.

Cost of DOS for 10 minutes

Note: this may be wrong, but hopefully it's a start

Output

blocks_dossed 51
fee 406236221416.0 ## 406 GWEI
total_paid 1.0941377978739001e+20 ## 100 ETH

Script

"""

		# check if the base fee is correct
		if INITIAL_FORK_BLOCK_NUMBER == block.number:
			expected_base_fee_per_gas = INITIAL_BASE_FEE
		elif parent_gas_used == parent_gas_target:
			expected_base_fee_per_gas = parent_base_fee_per_gas
		elif parent_gas_used > parent_gas_target:
			gas_used_delta = parent_gas_used - parent_gas_target
			base_fee_per_gas_delta = max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
			expected_base_fee_per_gas = parent_base_fee_per_gas + base_fee_per_gas_delta
		else:
"""


INITIAL_BASE_FEE = 1000000000 ## 1 gwei
ELASTICITY_MULTIPLIER = 2
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8

GAS_TARGET  = 15e6
GAS_USED = 30e6
GAS_LIMIT = 30e6

MINUTES_TO_DOS = 10
SECONDS_PER_BLOCK = 12
BLOCKS_TO_DOS = MINUTES_TO_DOS  * 60 / SECONDS_PER_BLOCK

def main():
    blocks_dossed = 0
    fee = INITIAL_BASE_FEE
    total_paid = 0
    while (blocks_dossed <= BLOCKS_TO_DOS):
      gas_used_delta = GAS_USED - GAS_TARGET
      base_fee_per_gas_delta = max(fee * gas_used_delta // GAS_TARGET // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
      expected_base_fee_per_gas = fee + base_fee_per_gas_delta
      fee = expected_base_fee_per_gas
      total_paid += fee * GAS_USED
      blocks_dossed += 1
    
    print("blocks_dossed", blocks_dossed)
    print("fee", fee)
    print("total_paid", total_paid)



main()
@0xmonrel
Copy link

0xmonrel commented Nov 3, 2023

I think we can make this ~1/100 the price by being block builders and buying entire blocks when MEV-boost is used. We can limit the increase of the base fee by consuming less than the gas target when we buy MEV-boosted blocks.

I have expanded on your script to calculate the price of DOS/censorship if we account for MEV-boost.

The following parameters are used:

INITIAL_BASE_FEE = 18 GWEI
PRIORITY_FEE = 2 (Priority fee set to 100% of base fee)
MEV_BOOST_PAYMENT = 1e18 (Average MEV-boost payment in the last 6 months is ~0.06 ETH so this is ~20x the average)

Output

Minutes: 15
blocks_dossed: 76
fee: 66.0 GWEI
total_boost_payment: 63.0
total_paid: 250.0
Total in dollar at 1800 $/ETH: 450347.0

Script

import random

INITIAL_BASE_FEE = 18000000000 ## 18 gwei
ELASTICITY_MULTIPLIER = 2
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8

GAS_TARGET  = 15e6
GAS_LIMIT = 30e6

PRIORTY_FEE = 2 # 2 - Sets priority fee at 100% of base fee 
MEV_BOOST_PAYMENT = 1e18 # 1 ETH in MEV-boost payment for each block (this is ~20x the 6month average) 

MINUTES_TO_DOS = 15
SECONDS_PER_BLOCK = 12
BLOCKS_TO_DOS = MINUTES_TO_DOS  * 60 / SECONDS_PER_BLOCK

def main():
    blocks_dossed = 0
    fee = INITIAL_BASE_FEE
    total_paid = 0
    total_boost_payment = 0
    parent_gas_used = 30e6
    while (blocks_dossed <= BLOCKS_TO_DOS):
      mevBoost = True if random.randint(1,10) <9 else False  # ~93% of validators use MEV-boost

      if not mevBoost:
         gas_used_delta = parent_gas_used - GAS_TARGET
         base_fee_per_gas_delta = max(fee * gas_used_delta // GAS_TARGET // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
         expected_base_fee_per_gas = fee + base_fee_per_gas_delta
         fee = expected_base_fee_per_gas
         total_paid += fee*PRIORTY_FEE*30e6 
         blocks_dossed += 1
         parent_gas_used = 30e6
         
      else: #If it is a MEV-boosted block, we buy the block and consume only 10e6

         gas_used_delta = parent_gas_used - GAS_TARGET
         base_fee_per_gas_delta = fee*gas_used_delta // GAS_TARGET // BASE_FEE_MAX_CHANGE_DENOMINATOR
         expected_base_fee_per_gas = fee - base_fee_per_gas_delta
         fee = expected_base_fee_per_gas
         total_paid += fee * 10e6  + MEV_BOOST_PAYMENT
         total_boost_payment += MEV_BOOST_PAYMENT 
         blocks_dossed += 1
         parent_gas_used = 10e6
    
    print("Minutes:", MINUTES_TO_DOS)
    
    print("blocks_dossed:", blocks_dossed)
    print("fee:", str(fee//1e9) + " GWEI")

    print("total_boost_payment:", total_boost_payment//1e18)
    print("total_paid:", total_paid//1e18)
    print("Total in dollar at 1800 $/ETH:", 1800 * total_paid//1e18)


main()

@GalloDaSballo
Copy link
Author

Looks wrong cause you're assuming all MEV Booster will accept your DOS via TIP instead of just letting the fee grow and get a shitton more ETH

@GalloDaSballo
Copy link
Author

See this: https://gist.github.com/GalloDaSballo/5a0eb205d72df5b0d033b55f3baaa927

You have to add the binomial distribution of having the few MEV Builders that would also accept a loss

It can be done for free, but the odds are unlikely even if Lido was a single entity

@0xmonrel
Copy link

0xmonrel commented Nov 3, 2023

Why would they have to accept a loss? We would be offering the highest payment since the transaction to consume all the gas is not in the mempool when a MEV-boosted validator is selected. As block builders we will submit our block through all available relayers and offer the highest payment and be selected purely on that basis.

@GalloDaSballo
Copy link
Author

You're assuming that the half-empty block + backdoor deal will be accepted vs the exponential cost of EIP1559
Those builders would be accepting a loss if they kept accepting your bundles instead of buying new bundles with full blocks (which raise base fee)

@0xmonrel
Copy link

0xmonrel commented Nov 3, 2023

I am assuming that we are builders ourselves so we are offering our bids to the validators. MEV-boosted validators accept blocks based on standardized block level scoring which we would most likely win with our large payment. There is no backdoor deal, we are simply winning the MEV-boost auction.

@GalloDaSballo
Copy link
Author

You're saying that you can get people to get paid less than what they could, I'm calling cap

@0xmonrel
Copy link

0xmonrel commented Nov 3, 2023

What? I am saying that I am paying them MORE that is why we win the block.

Maybe I did not explain this well enough:
Block proposers and committees are known at least 6.4min ahead of time.

Attacker knows if validator uses MEV-boost or not.

  1. For each MEV-boosted validator the attacker wins the block by paying the highest MEV-payment fee.

  2. If MEV-boost is not used, attacker send transaction to consume all gas with a high priority fee which would be the highest profit for the validator.

Observe that the 2) is not available when MEV-boost is used so it can not be included in other block builders blocks.

For each block we offer the highest profit for the validator.

@GalloDaSballo
Copy link
Author

The statement you're making assumes the fact that all blocks would be built by you because you'd be winning the MEV-Boost auction.

The reality of what you're saying is that you'd be turning the MEV-Boost auction into a blind auction, where your strategy is to bid X ETH per block.

This is logically equivalent to PGA auctions, which is how Ethereum used to work before EIP1559, so we have proof of the fact that in those scenarios people would just raise their Gas Fee until the DOS is avoided.

The assumption that because you can overpay once or a few times to DOS a block, that that would translate to a network DOS is simply unfounded as you'd have to ignore the facts that:

  • Apes would just raise fees, as they are fee insensitive, meaning you'd actually have people overbid you
  • The MEV that you're denying would over time make the ETH fee you're paying worth less, meaning you'd actually have people overbid you
  • The queue of transaction would cause Priority Fees to skyrocket as a way to compensate for the DOS, meaning you'd actually have people overbid you

Block space in Ethereum is elastic only up to 30MLN Gas, but demand is not driven by blockspace but the underlying value of the network activity. You're asserting that people would just sit back and let you run the network for minutes on end, instead of just PGAing to get the value they want, which is a leap of faith level assumption I disagree with.

PS: Send this to EF they will pay you a $250k bounty if you're right

@0xmonrel
Copy link

0xmonrel commented Nov 4, 2023

Okay, I see where my confusion comes from. Your script was linked in the context of a DOS of a transaction from a specific account and not a network wide DOS. As in we can include all other transactions other than a specific transaction from a certain address.

A censorship attack can work exactly because it can include all but one transaction and as a result not drive up the gas fees tremendously. I should have paid more attention to the differences between a network DOS and censorship/single tx DOS before commenting here, sorry about that!

Here is a script calculating the cost of a censorship attack when MEV-boosted blocks have 55% gas utilization:

Output

Minutes: 15
blocks_dossed: 76
fee: 182.0 GWEI
total_boost_payment: 64.0
total_paid: 197.0
Total in dollar at 1800 $/ETH: 354803.0

Script

import random

INITIAL_BASE_FEE = 18000000000 ## 18 gwei
ELASTICITY_MULTIPLIER = 2
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8

GAS_TARGET  = 15e6
GAS_LIMIT = 30e6

PRIORTY_FEE = 2 # 2 - Sets priority fee at 100% of base fee 
MEV_BOOST_PAYMENT = 1e18 # 1 ETH in MEV-boost payment for each block (this is ~20x the 6month average) 

MINUTES_TO_DOS = 15
SECONDS_PER_BLOCK = 12
BLOCKS_TO_DOS = MINUTES_TO_DOS  * 60 / SECONDS_PER_BLOCK

def main():
    blocks_dossed = 0
    fee = INITIAL_BASE_FEE
    total_paid = 0
    total_boost_payment = 0
    parent_gas_used = 30e6
    while (blocks_dossed <= BLOCKS_TO_DOS):
      mevBoost = True if random.randint(1,10) <9 else False  # ~93% of validators use MEV-boost

      if not mevBoost:
         gas_used_delta = parent_gas_used - GAS_TARGET
         base_fee_per_gas_delta = max(fee * gas_used_delta // GAS_TARGET // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
         expected_base_fee_per_gas = fee + base_fee_per_gas_delta
         fee = expected_base_fee_per_gas
         total_paid += fee*PRIORTY_FEE*30e6 
         blocks_dossed += 1
         parent_gas_used = 30e6
         
      else: #If it is a MEV-boosted block, we buy the block and consume only 10e6

         gas_used_delta = parent_gas_used - GAS_TARGET
         base_fee_per_gas_delta = max(fee * gas_used_delta // GAS_TARGET // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
         expected_base_fee_per_gas = fee + base_fee_per_gas_delta
         fee = expected_base_fee_per_gas
         total_paid += fee * 15e6  + MEV_BOOST_PAYMENT
         total_boost_payment += MEV_BOOST_PAYMENT 
         blocks_dossed += 1
         parent_gas_used = 15e6

   
    print("Minutes:", MINUTES_TO_DOS)
    
    print("blocks_dossed:", blocks_dossed)
    print("fee:", str(fee//1e9) + " GWEI")

    print("total_boost_payment:", total_boost_payment//1e18)
    print("total_paid:", total_paid//1e18)
    print("Total in dollar at 1800 $/ETH:", 1800 * total_paid//1e18)


main()



Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment