The goal of this document is to describe a mechanism by which a merge can happen quickly, with little modification to either the ethpow or beacon clients. The only change required on the ethpow side is that the client must have a communication channel with a trusted beacon node and must change its fork choice rule.
BeaconBlockBody
: application_block: bytes[<= 2**24]
BeaconState
: previous_application_block_hash: bytes32
(initialized to ZERO_HASH
)If state.previous_application_block_hash == ZERO_HASH
, then there are two valid options for the block.application_block
:
difficulty=1
If a beacon block chooses option (2), then the fork choice rule delays accepting the block until the ethpow node accepts it (regardless of whether or not an ethpow node considers it to be in the canonical chain). Additionally, the beacon node must ask the ethpow node for the totalDifficulty
of the block (the eth_getBlockByHash
RPC already does this), and the response must be >= TRANSITION_TOTAL_DIFFICULTY
.
Note that this requires the beacon node to be able to submit the application block body to the ethpow node by RPC. Additionally, the ethpow node must have a rule that PoW checks are disabled for ethpow blocks that originate from RPC requests from the beacon node.
Note also one subtlety, which explains why the extra criterion for option (2) must be a fork choice rule and not a validity condition: suppose that someone submits a beacon block B
containing application block A
, and at the time of receiving B
, the ethpow node does not know about parent(A)
and so cannot accept A
. However, five seconds later, the ethpow node learns about parent(A)
. At that point, the ethpow node should also accept A, and the beacon node should then accept B
. So an ineligible application block can later become eligible if the dependencies are satisfied after the fact.
If state.previous_application_block_hash != ZERO_HASH
, then a beacon block is valid if BOTH:
RLPDecode(block.application_block).parent_hash == state.previous_application_block_hash
If the beacon block is accepted in either case, we set state.previous_application_block_hash = sha3(block.application_block)
As mentioned in the previous section, the ethpow node must add an RPC submitBlock
mechanism, and if a block is submitted using this mechanism by the beacon node, PoW checks (including difficulty update formula checks) must be disabled.
Additionally, the ethpow node fork choice changes:
state.previous_application_block_hash == ZERO_HASH
, the fork choice is as normalstate.previous_application_block_hash != ZERO_HASH
, the fork choice is to select the head as being the ethpow block that is contained as the application_block
of the head beacon blocktotalDifficulty >= TRANSITION_TOTAL_DIFFICULTY
.We expect that an ethpow block will be included into the beacon chain within a minute of it becoming possible to do so (otherwise, there must be 5 malicious proposers in a row). At that point, there are very few possibilities for what ethpow blocks could be incuded: it must be the first transition block, a descendant or an uncle. In either case, the ethpow chain would not revert more than a few blocks.
Miners could do a “balance attack” and mine multiple chains, so there are many choices for what ethpow block to include. But ultimately, as long as there is at least one honest miner, eventually at least one block with totalDifficulty >= TRANSITION_TOTAL_DIFFICULTY
gets created. If multiple options appear simultaneously, the first proposer would pick one, and as soon as the ethpow fork choice repoints to the beacon fork choice, the ethpow chain would proceed smoothly inside the beacon chain.
If the beacon chain reorgs, then this would lead to the ethpow chain reorging. However, once the beacon chain stops reorging (or at least the blocks around the transition period stabilize), the ethpow fork choice would also stabilize, because the ethpow fork choice favors the chain embedded within the beacon chain if such a chain exists.
No. Rather, the minimal merge as described above would only be the first step, and a post-merge hard fork would simplify the protocol and clean out unnecessary bits and make the interaction between the application layer and the beacon chain more “natural”. Eventually we would go even further and, for example, replace the application_block: bytes
with application_txs: List[bytes]
) and even make the application state a “normal” part of the BeaconState
(even though it’s hashed in a non-SSZ way).
In fact, the minimal merge will make these things easier to work on, as it would no longer be needed to worry about merging two chains at the same time.
A post-merge hard fork can add support for withdrawals.