Distribute Block Rewards
The following steps will guide you through the process of distributing block rewards on Berachain for both Validators and Reward Vaults.
How Block Rewards Are Distributed
Block rewards are distributed to both a Validator and Reward Vaults defined by the respective Validator's Reward Allocation.
Reward distribution does not happen automatically and requires that the distributeFor
function be called in the Distributor contract.
Who Can Distribute Block Rewards?
Anyone can trigger this function, as long as they have access to the proof generation endpoint provided by beacond
. Anyone can also execute the distributeFor
function for past blocks.
Distribution Considerations
There are a few points to consider with distribution:
- Distribute At Block N-1 - The Distributor contract can only distribute block rewards for the block that was last created and not the current block.
- Block Rewards Expiration - If rewards are not distributed before a block span of
8191
, the rewards are lost. - Foundation Trigger Fallback - The Berachain foundation has set up a service to call the
distributeFor
function periodically, including sometimes in amulticall
to ensure that block rewards are distributed. However, this fallback should be considered a last resort, and Validators should plan to set up a service to handle triggering the distribution themselves. - Block Reward Tracking - It is recommended that node operators track the block rewards they have distributed to ensure that they are not missing any rewards.
Requirements
Before you begin, ensure you have the following:
- A full node synced to the latest block with
beacond
and an execution client $BERA
to process the transaction- Foundry
Steps To Distribute Block Rewards Via Foundry
The following is a single bash command that can be run to distribute block rewards.
INFO
NOTE: If the transaction fails, it is possible that these block rewards have already been distributed.
# FROM: /
# STEP 1: Get Block(s) To Be Distributed
# ----------------------------------------------------
# Get recent finalized block's number from Exec Layer -> Get timestamp of its parent block
# Example: 1730756547
cast block finalized --rpc-url http://localhost:8545;
# [Expected Similar Output]:
# baseFeePerGas 7
# difficulty 0
# extraData 0xd883010e05846765746888676f312e32322e34856c696e7578
# gasLimit 30000000
# gasUsed 1362987
# hash 0xb576213ebb325ca33e9ceb47f2d1a813b193fb87aa46f82a31111e19bfafc130
# logsBloom 0x0000020000000000000000000000000010008002420000040000000020010400004000000000000000000001000000000004128000000000000000000028810104000008000000200020200800000001000040240000000000000c0001800000001001000200000000000040000408000402000800900080000000100000000000c40080010000000404040500a010000000000000000100200080400000000002042000040208400000200000000000180000000001000008200000008000000008000200000000000000000000000000000b010010080a00800000000020000010040000003800210000082000400088008000081000000000204110800001
# miner 0x0000000000000000000000000000000000000000
# mixHash 0x768a48d830a60995602bf4c788feeeb73dcb033f860a5f5fbd310dcc66f2da2b
# nonce 0x0000000000000000
# number 2340989 <---- WE NEED THIS
# ...
TARGET_BLOCK=`cast block finalized --rpc-url http://localhost:8545 | grep number | awk '{print $2}'`;
PARENT_BLOCK=`expr $TARGET_BLOCK + 1`;
# Get the next timestamp - we will generate the rewards by referencing the parent block
# Each second identifies a specific block; use the next second to reference the parent block.
TIMESTAMP=`cast block $PARENT_BLOCK --rpc-url http://localhost:8545 | grep timestamp | awk '{print $2}'`;
echo $TIMESTAMP;
# [Expected Similar Output]:
# 1734598728
# STEP 2: Generate Proofs For Block
# ----------------------------------------------------
# Sanity check - isTimestampActionable? Returns true if rewards have not yet been distributed for this block.
# NOTE: Distribute contract may differ
cast call "0x2C1F148Ee973a4cdA4aBEce2241DF3D3337b7319" "isTimestampActionable(uint64 timestamp) returns (bool success)" "$TIMESTAMP" --rpc-url http://localhost:8545;
# [Expected Output]:
# true
# This is in the scenario where `beacond` is running locally.
PROOF_JSON=`curl http://localhost:3500/bkit/v1/proof/block_proposer/t$TIMESTAMP`
echo $PROOF_JSON;
# STEP 3: Distribute Block Rewards
# ----------------------------------------------------
# Retrieve needed data
PROPOSER_INDEX=`echo $PROOF_JSON|jq -r '.beacon_block_header.proposer_index'`;
VAL_PUBKEY=`echo $PROOF_JSON|jq -r '.validator_pubkey'`;
PROPOSER_PROOF=`echo $PROOF_JSON|jq -j '.proposer_index_proof'|sed 's/"//g'|tr -d '\\n'`;
VAL_PUBKEY_PROOF=`echo $PROOF_JSON|jq -j '.validator_pubkey_proof'|sed 's/"//g'|tr -d '\\n'`;
# Execute tx after setting PK of wallet sending tx (or use --ledger if not using raw PK)
# Example: Transaction successfully executed. Gas used: 104467
WALLET_PRIVATE_KEY=<0xYOUR_WALLET_PRIVATE_KEY>;
cast send "0x2C1F148Ee973a4cdA4aBEce2241DF3D3337b7319" \
"distributeFor(uint64 nextTimestamp, uint64 proposerIndex, bytes calldata pubkey, bytes32[] calldata proposerIndexProof, bytes32[] calldata pubkeyProof)" \
"$TIMESTAMP" \
"$PROPOSER_INDEX" \
"$VAL_PUBKEY" \
"$PROPOSER_PROOF" \
"$VAL_PUBKEY_PROOF" \
--private-key $WALLET_PRIVATE_KEY \
--rpc-url http://localhost:8545;