-
-
Published
Linked with GitHub
# How to create a shadowfork
## Introduction:
![shadowfork](https://storage.googleapis.com/ethereum-hackmd/upload_218accdf38cf256ef05f50d64d4a3ba0.png)
### Theory:
#### General info:
- A chain consists of an Execution layer(EL) and a Consensus layer(CL)
- The EL is responsible for state (transactions, history, etc) and the CL is responsible for consensus (agreement on chain head) at a high level
- The EL is driven by the CL. i.e, The CL decides what the head of the chain is and the EL must accept that decision (mostly)
- The CL asks the EL to produce blocks, these blocks are placed in the EL chain as well as `execution_payloads` in the CL slot. This links the CL slot to the EL chain (leaving out caveats here)
- When the CL sends the request to produce a block, it also sends information about the block to build on top of
#### Shadowfork:
- Sync a known chain to the head of the chain. E.g, we sync Holesky till both the EL and CL are fully synced
- Stop the CL beacon node. This means the EL will get no new messages about the chain and will forever stall at its current head. Ensure all the ELs on your planned shadowfork are stalled at the same head (helps to use a script to achieve this)
- Get the latest block by using the `eth_getBlockByNumber` from any EL (since they are all on the same head)
- Now generate a new CL genesis state, with a genesis timestamp in the future, pre-populated validators in the genesis state and the latest EL block from the earlier step as the `execution_payload`
- Copy the EL genesis config for that network, i.e Holesky
- In case a non-mainline fork is needed, e.g Holesky hasn't gone through Cancun but the shadowfork should go through it. Then define the cancun fork in the CL config.yaml as well as the EL genesis files
- Clear the CL data on all the nodes, this includes removing their data directories. Leave the EL nodes as they are
- Start the nodes again, but this time using the custom testnet flags instead of the named testnet flag, e.g: use `--testnet-dir=/network-config` (and its variations) instead of `--holesky`. The custom testnet files should be the ones from earlier
- Ensure that the pre-populated validators are up and configured to use the new chain
- Once the genesis time is hit, the CL will start sending requests to build blocks on top of the latest canonical EL block. This new block however will not be found in the canonical chain. i.e, If the block had stalled at block 1000, then the CL will request block 1001 to be built and this block 1001 will be different from canonical Holesky
At this point, we have created a forked chain (i.e, Holesky). This forked chain will inherit the entire state of the canonical Holesky chain (i.e, the past contracts, transactions) but the blocks will diverge at one point. We will have the canonical chain and the shadowfork. They will continue to stay peered on the gossip layer, so the canonical transactions will still be included in the shadowfork for a few days (Caution while executing transactions, they make leak to the canonical chain!).
### Practical steps:
- We'll be talking about shadowforking Holesky in this guide
- Setup 10 instances on a cloud provider of your choice
- Sync the EL and CL (checkpoint sync CL) to Holesky, use whichever client you prefer (note: Erigon sync takes longer)(You can use snap or whatever sync mode is default on the client)
- Once the EL and CL are fully synced, stop the CL on all the 10 instances at the same slot height/time (preferably with a simple script). All ELs will automatically stall at the same height
- Check that all the EL are at the same height by calling the `eth_getBlockByNumber` API call (retry the process if some EL is on a different height, or use the setHead feature)
- Clone [this](https://github.com/ethpandaops/ethereum-genesis-generator/blob/master/config-example/values.env) repository and edit the `values.env` file
- Set the values required for your shadowfork (Usually you want to change the mnemonic, # of validators, genesis timestamp and fork epoch)
- Don't forget to enable the value `SHADOW_FORK_RPC`, set this to the JSON-RPC of one of your stalled ELs (this will fetch the latest block and embed it as the `execution_payload`)
- Run the genesis tool with docker:
```
mkdir output && docker run --rm -it -u $UID -v $PWD/output:/data \
-v $PWD/config-example:/config \
ethpandaops/ethereum-genesis-generator:latest all
```
- This will create a folder called `output` with the genesis files for your shadowfork
- Now clear everything related to the CL on all of your 10 instances (Leave the EL alone). The CL data directory and process must be completely removed
- Copy over the genesis files found in the `output` directory
- Start the CL and EL in custom testnet mode, using the generated `output` files as the configs
- At the specific genesis timestamp, the CL will send a request to build a block on top of the latest head of the EL, If this block is proposed and propagated - then your shadowfork is successful!
#### Helpers:
- Script to stop beacon node at a certain slot
```bash=
#!/bin/bash
# Define the URL from a Jinja2 variable
URL="{{ beacon_url }}"
# Poll the URL in a loop
while true; do
# Fetch the slot value
SLOT_VALUE=$(curl -s $URL | jq -r '.data[].header.message.slot')
# Check if the slot value has reached "{{ slot_to_stop_at }}"
if [[ $SLOT_VALUE == "{{ slot_to_stop_at }}" ]]; then
echo "Slot value has reached $SLOT_VALUE !"
echo "Stopping beacon node..."
sleep 2
docker stop beacon
# Exit the loop
break
else
echo "Current slot value: $SLOT_VALUE"
fi
# Wait for a while before polling again
sleep 10
done
```