Try   HackMD

Besu Deposit Contract Address Postmortem

An analysis of the spec changes and the evolution of the Besu codebase leading to the omission of the DEPOSIT_CONTRACT_ADDRESS for Holesky and Sepolia.

This ultimately lead to a supermajority chain split on Holesky since Geth and Nethermind had a similar bug: https://github.com/ethereum/pm/blob/master/Pectra/holesky-postmortem.md

The point of this postmortem is not to assign blame, but rather help understand how we might avoid similar problems in the future.

Potential Contributing Factors

General

Besu specific

  • Besu defaults to Mainnet address if not present in config
  • Besu uses custom genesis config for automated tests rather than loading public testnet config
  • Various PR authors/reviewers spanning nearly 3 years - no single Besu dev championing the feature

Learnings

Timeline

Oct 2020

https://eth2book.info/capella/part2/deposits-withdrawals/contract/

The deposit contract was deployed on October the 14th, 2020, at 09:22:52 UTC to Ethereum address
0x00000000219ab540356cbb839cbe05303d7705fa.

Oct 2021

https://github.com/eth-clients/sepolia/

Sepolia launched (without deposit contract)

June 2022

Sepolia deposit contract deployed on Jun-09-2022 02:58:14 PM UTC to 0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D

December 2022

https://github.com/ethereum/EIPs/pull/6110
Initial spec only includes Mainnet DEPOSIT_CONTRACT_ADDRESS

At this point Sepolia deposit contract existed but Holesky network did not exist.

At time of writing (March 2025) the spec still only contains the Mainnet DEPOSIT_CONTRACT_ADDRESS

June 2023

https://github.com/hyperledger/besu/pull/5295 (EIP-6110: Add deposit validation and apis)

  • DEFAULT_DEPOSIT_CONTRACT_ADDRESS added:
  public static final Address DEFAULT_DEPOSIT_CONTRACT_ADDRESS =
      Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa");
  • Wired up for "experimental EIPs fork", not yet wired up for Prague:
final Address depositContractAddress =
        genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS);
  • Explicit genesis config for Mainnet omitted - not a bug as default would be used.
  • Explicit genesis config for Sepolia omitted - this would have been a bug if Sepolia had gone through Pectra before Holesky.
  • Holesky did not exist yet in order for configuration to be added!
  • Explicit genesis config was added as part of a custom test genesis file.

Sept 2023

https://github.com/eth-clients/holesky

Holesky launched with DEPOSIT_CONTRACT_ADDRESS predeployed in genesis to
0x4242424242424242424242424242424242424242

Besu did not add the depositContractAddress to the Holesky config that EIP-6110 would use (neither Mainnet, Sepolia nor Goerli had it either).

March 2024

http://github.com/hyperledger/besu/pull/6783 (Adding engine_getPayloadV4 and engine_newPayloadV4)

  • DEFAULT_DEPOSIT_CONTRACT_ADDRESS wired up for Prague fork
final Address depositContractAddress =
        genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS);

May 2024 - Oct 2024

Various PRs (implementing various spec changes) by various people which refactored the location of the DEFAULT_DEPOSIT_CONTRACT_ADDRESS and wiring/loading code.
These refactorings didn't have a significant impact on the root cause, but potentially contributed to diluting the knowledge and understanding of the system.

https://github.com/hyperledger/besu/pull/7068 (EIP-7685 General purpose execution layer requests)
https://github.com/hyperledger/besu/pull/7647 (Add consolidationRequestContract in jsonGenesisConfig)
https://github.com/hyperledger/besu/pull/7771 (Change requests to use flat encoding)