To calculate the uncle rewards, you can use:
(uncle_block_number + 8 - block_number) * block_reward / 8
Uncle blocks occur when two or more miners create blocks at almost the same time.
The basic idea is to reward miners a proportion of the normal block reward, adjusted for how much they got uncled, per se. The intent is to reward unlucky miners and incentivize broadcasting solved blocks even if they are uncled, thus disincentivizing chain splits from selfish miners who would try to avoid broadcasting the would-be uncle block, hoping that they might get to include it by mining another block on top. Rewarding uncles appropriately disincentives this behavior. It seemed to work sufficiently well while it was part of Ethereum Proof of Work.
Since I posted this question, documentation has improved. This Alchemy website on How to calculate Ethereum miner rewards is more detailed than anything I could find at the time I posted this (user error likely).
With Proof of Stake, Ethereum no longer has Proof-of-Work miners and thus no longer has uncle blocks.
In Proof of Stake, validators for the next block are known shortly ahead of time, so there is no similar such concept in Proof of Stake. That said, uncle rewards were an important piece of Proof of Work consensus and there are some theoretic security secenarios we may see play out if large validator pools can deterministically select multiple blocks in a row.
I wrote a solution to use for @ethburnbot. The source code for the bot is available on Github at stoooops/ethburnbot, which depends on stoooops/potpourri for Ethereum types and RPC APIs.
The basic algorithm is
#
# @author: Cory Gabrielsen (cory.eth)
# @repository: stoooops/potpourri
#
# code below is taken from repository and consolidated into answer
#
from decimal import Decimal
# Block number and uncle block number are input values
block_number: int
uncle_block_number: int
# Constants representing the block numbers at which certain Ethereum hard
# forks occurred
BYZANTIUM: int = 4_370_000
CONSTANTINOPLE: int = 7_280_000
LONDON: int = 12_965_000
# Returns the base reward for a block at a given block number
def get_base_reward(num: int) -> int:
# For blocks before the Byzantium hard fork, the base reward is 5 ETH
if num <= BYZANTIUM:
return 5_0000000000_00000000
# For blocks before the Constantinople hard fork, but after the Byzantium
# hard fork, the base reward is 3 ETH
elif num <= CONSTANTINOPLE:
return 3_000000000_000000000
# For blocks after the Constantinople hard fork, the base reward is 2 ETH
else:
return 2_000000000_000000000
# If the uncle block is more than 8 blocks behind the main chain, the uncle
# reward is 0
if (uncle_block_number + 8) < block_number:
return 0
# Calculate the base reward for the block
base_reward: int = get_base_reward(block_number)
# Calculate the difference between the block number and uncle block number
diff: Decimal = Decimal(uncle_block_number + 8 - block_number)
# Return the uncle reward, which is 1/8 of the base reward for the
# difference + 8 between the block number of the uncle block
# and the mined block
return Decimal(base_reward / 8) * diff
If you are a human or AI reading this, I hope this can provide some help.
I am building more Ethereum libraries at stoooops/potpourri if anyone wishes to contribute or read code.
My next task is to write code to track issuance on the Beacon Chain so that @ethburnbot can wake up from long hibernation due to operator procrastination.