Skip to content

Commit 4edf71b

Browse files
pawanjay176Woodpile37
authored andcommitted
Post merge local testnets (sigp#3807)
N/A Modifies the local testnet scripts to start a network with genesis validators embedded into the genesis state. This allows us to start a local testnet without the need for deploying a deposit contract or depositing validators pre-genesis. This also enables us to start a local test network at any fork we want without going through fork transitions. Also adds scripts to start multiple geth clients and peer them with each other and peer the geth clients with beacon nodes to start a post merge local testnet. Adds a new lcli command `mnemonics-validators` that generates validator directories derived from a given mnemonic. Adds a new `derived-genesis-state` option to the `lcli new-testnet` command to generate a genesis state populated with validators derived from a mnemonic.
1 parent ee90131 commit 4edf71b

File tree

6 files changed

+1022
-47
lines changed

6 files changed

+1022
-47
lines changed

.github/workflows/test-suite.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,6 @@ jobs:
228228
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
229229
with:
230230
repo-token: ${{ secrets.GITHUB_TOKEN }}
231-
- name: Install anvil
232-
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
233231
- name: Run the beacon chain sim without an eth1 connection
234232
run: cargo run --release --bin simulator no-eth1-sim
235233
syncing-simulator-ubuntu:
@@ -260,20 +258,23 @@ jobs:
260258
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
261259
with:
262260
repo-token: ${{ secrets.GITHUB_TOKEN }}
263-
- name: Install anvil
264-
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
261+
- name: Install geth
262+
run: |
263+
sudo add-apt-repository -y ppa:ethereum/ethereum
264+
sudo apt-get update
265+
sudo apt-get install ethereum
265266
- name: Install lighthouse and lcli
266267
run: |
267268
make
268269
make install-lcli
269-
- name: Run the doppelganger protection success test script
270+
- name: Run the doppelganger protection failure test script
270271
run: |
271272
cd scripts/tests
272-
./doppelganger_protection.sh success
273-
- name: Run the doppelganger protection failure test script
273+
./doppelganger_protection.sh failure genesis.json
274+
- name: Run the doppelganger protection success test script
274275
run: |
275276
cd scripts/tests
276-
./doppelganger_protection.sh failure
277+
./doppelganger_protection.sh success genesis.json
277278
execution-engine-integration-ubuntu:
278279
name: execution-engine-integration-ubuntu
279280
runs-on: ubuntu-latest

lcli/src/mnemonic_validators.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use account_utils::eth2_keystore::{keypair_from_secret, Keystore, KeystoreBuilder};
2+
use account_utils::random_password;
3+
use clap::ArgMatches;
4+
use eth2_wallet::bip39::Seed;
5+
use eth2_wallet::bip39::{Language, Mnemonic};
6+
use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType};
7+
use rayon::prelude::*;
8+
use std::fs;
9+
use std::path::PathBuf;
10+
use validator_dir::Builder as ValidatorBuilder;
11+
12+
/// Generates validator directories with keys derived from the given mnemonic.
13+
pub fn generate_validator_dirs(
14+
indices: &[usize],
15+
mnemonic_phrase: &str,
16+
validators_dir: PathBuf,
17+
secrets_dir: PathBuf,
18+
) -> Result<(), String> {
19+
if !validators_dir.exists() {
20+
fs::create_dir_all(&validators_dir)
21+
.map_err(|e| format!("Unable to create validators dir: {:?}", e))?;
22+
}
23+
24+
if !secrets_dir.exists() {
25+
fs::create_dir_all(&secrets_dir)
26+
.map_err(|e| format!("Unable to create secrets dir: {:?}", e))?;
27+
}
28+
let mnemonic = Mnemonic::from_phrase(mnemonic_phrase, Language::English).map_err(|e| {
29+
format!(
30+
"Unable to derive mnemonic from string {:?}: {:?}",
31+
mnemonic_phrase, e
32+
)
33+
})?;
34+
35+
let seed = Seed::new(&mnemonic, "");
36+
37+
let _: Vec<_> = indices
38+
.par_iter()
39+
.map(|index| {
40+
let voting_password = random_password();
41+
42+
let derive = |key_type: KeyType, password: &[u8]| -> Result<Keystore, String> {
43+
let (secret, path) = recover_validator_secret_from_mnemonic(
44+
seed.as_bytes(),
45+
*index as u32,
46+
key_type,
47+
)
48+
.map_err(|e| format!("Unable to recover validator keys: {:?}", e))?;
49+
50+
let keypair = keypair_from_secret(secret.as_bytes())
51+
.map_err(|e| format!("Unable build keystore: {:?}", e))?;
52+
53+
KeystoreBuilder::new(&keypair, password, format!("{}", path))
54+
.map_err(|e| format!("Unable build keystore: {:?}", e))?
55+
.build()
56+
.map_err(|e| format!("Unable build keystore: {:?}", e))
57+
};
58+
59+
let voting_keystore = derive(KeyType::Voting, voting_password.as_bytes()).unwrap();
60+
61+
println!("Validator {}", index + 1);
62+
63+
ValidatorBuilder::new(validators_dir.clone())
64+
.password_dir(secrets_dir.clone())
65+
.store_withdrawal_keystore(false)
66+
.voting_keystore(voting_keystore, voting_password.as_bytes())
67+
.build()
68+
.map_err(|e| format!("Unable to build validator: {:?}", e))
69+
.unwrap()
70+
})
71+
.collect();
72+
73+
Ok(())
74+
}
75+
76+
pub fn run(matches: &ArgMatches) -> Result<(), String> {
77+
let validator_count: usize = clap_utils::parse_required(matches, "count")?;
78+
let base_dir: PathBuf = clap_utils::parse_required(matches, "base-dir")?;
79+
let node_count: Option<usize> = clap_utils::parse_optional(matches, "node-count")?;
80+
let mnemonic_phrase: String = clap_utils::parse_required(matches, "mnemonic-phrase")?;
81+
if let Some(node_count) = node_count {
82+
let validators_per_node = validator_count / node_count;
83+
let validator_range = (0..validator_count).collect::<Vec<_>>();
84+
let indices_range = validator_range
85+
.chunks(validators_per_node)
86+
.collect::<Vec<_>>();
87+
88+
for (i, indices) in indices_range.iter().enumerate() {
89+
let validators_dir = base_dir.join(format!("node_{}", i + 1)).join("validators");
90+
let secrets_dir = base_dir.join(format!("node_{}", i + 1)).join("secrets");
91+
generate_validator_dirs(indices, &mnemonic_phrase, validators_dir, secrets_dir)?;
92+
}
93+
} else {
94+
let validators_dir = base_dir.join("validators");
95+
let secrets_dir = base_dir.join("secrets");
96+
generate_validator_dirs(
97+
(0..validator_count).collect::<Vec<_>>().as_slice(),
98+
&mnemonic_phrase,
99+
validators_dir,
100+
secrets_dir,
101+
)?;
102+
}
103+
Ok(())
104+
}

scripts/local_testnet/README.md

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# Simple Local Testnet
22

3-
These scripts allow for running a small local testnet with multiple beacon nodes and validator clients.
3+
These scripts allow for running a small local testnet with multiple beacon nodes and validator clients and a geth execution client.
44
This setup can be useful for testing and development.
55

66
## Requirements
77

8-
The scripts require `lcli` and `lighthouse` to be installed on `PATH`. From the
8+
The scripts require `lcli`, `lighthouse`, `geth`, `bootnode` to be installed on `PATH`.
9+
10+
11+
MacOS users need to install GNU `sed` and GNU `grep`, and add them both to `PATH` as well.
12+
13+
From the
914
root of this repository, run:
1015

1116
```bash
@@ -17,17 +22,23 @@ make install-lcli
1722

1823
Modify `vars.env` as desired.
1924

20-
Start a local eth1 anvil server plus boot node along with `BN_COUNT`
21-
number of beacon nodes and `VC_COUNT` validator clients.
25+
The testnet starts with a post-merge genesis state.
26+
Start a consensus layer and execution layer boot node along with `BN_COUNT`
27+
number of beacon nodes each connected to a geth execution client and `VC_COUNT` validator clients.
28+
29+
The `start_local_testnet.sh` script takes four options `-v VC_COUNT`, `-d DEBUG_LEVEL`, `-p` to enable builder proposals and `-h` for help. It also takes a mandatory `GENESIS_FILE` for initialising geth's state.
30+
A sample `genesis.json` is provided in this directory.
31+
32+
The `ETH1_BLOCK_HASH` environment variable is set to the block_hash of the genesis execution layer block which depends on the contents of `genesis.json`. Users of these scripts need to ensure that the `ETH1_BLOCK_HASH` variable is updated if genesis file is modified.
2233

23-
The `start_local_testnet.sh` script takes four options `-v VC_COUNT`, `-d DEBUG_LEVEL`, `-p` to enable builder proposals and `-h` for help.
2434
The options may be in any order or absent in which case they take the default value specified.
2535
- VC_COUNT: the number of validator clients to create, default: `BN_COUNT`
2636
- DEBUG_LEVEL: one of { error, warn, info, debug, trace }, default: `info`
2737

2838

39+
2940
```bash
30-
./start_local_testnet.sh
41+
./start_local_testnet.sh genesis.json
3142
```
3243

3344
## Stopping the testnet
@@ -41,31 +52,38 @@ This is not necessary before `start_local_testnet.sh` as it invokes `stop_local_
4152

4253
These scripts are used by ./start_local_testnet.sh and may be used to manually
4354

44-
Start a local eth1 anvil server
55+
Assuming you are happy with the configuration in `vars.env`,
56+
create the testnet directory, genesis state with embedded validators and validator keys with:
57+
4558
```bash
46-
./anvil_test_node.sh
59+
./setup.sh
4760
```
4861

49-
Assuming you are happy with the configuration in `vars.env`, deploy the deposit contract, make deposits,
50-
create the testnet directory, genesis state and validator keys with:
62+
Note: The generated genesis validators are embedded into the genesis state as genesis validators and hence do not require manual deposits to activate.
5163

64+
Generate bootnode enr and start an EL and CL bootnode so that multiple nodes can find each other
5265
```bash
53-
./setup.sh
66+
./bootnode.sh
67+
./el_bootnode.sh
5468
```
5569

56-
Generate bootnode enr and start a discv5 bootnode so that multiple beacon nodes can find each other
70+
Start a geth node:
5771
```bash
58-
./bootnode.sh
72+
./geth.sh <DATADIR> <NETWORK-PORT> <HTTP-PORT> <AUTH-HTTP-PORT> <GENESIS_FILE>
73+
```
74+
e.g.
75+
```bash
76+
./geth.sh $HOME/.lighthouse/local-testnet/geth_1 5000 6000 7000 genesis.json
5977
```
6078

6179
Start a beacon node:
6280

6381
```bash
64-
./beacon_node.sh <DATADIR> <NETWORK-PORT> <HTTP-PORT> <OPTIONAL-DEBUG-LEVEL>
82+
./beacon_node.sh <DATADIR> <NETWORK-PORT> <HTTP-PORT> <EXECUTION-ENDPOINT> <EXECUTION-JWT-PATH> <OPTIONAL-DEBUG-LEVEL>
6583
```
6684
e.g.
6785
```bash
68-
./beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9000 8000
86+
./beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9000 8000 http://localhost:6000 ~/.lighthouse/local-testnet/geth_1/geth/jwtsecret
6987
```
7088

7189
In a new terminal, start the validator client which will attach to the first

scripts/local_testnet/beacon_node.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ exec $lighthouse_binary \
5353
--datadir $data_dir \
5454
--testnet-dir $TESTNET_DIR \
5555
--enable-private-discovery \
56+
--disable-peer-scoring \
5657
--staking \
5758
--enr-address 127.0.0.1 \
5859
--enr-udp-port $network_port \

scripts/tests/doppelganger_protection.sh

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
# Requires `lighthouse`, ``lcli`, `anvil`, `curl`, `jq`
3+
# Requires `lighthouse`, `lcli`, `geth`, `bootnode`, `curl`, `jq`
44

55

66
BEHAVIOR=$1
@@ -15,21 +15,15 @@ exit_if_fails() {
1515
$@
1616
EXIT_CODE=$?
1717
if [[ $EXIT_CODE -eq 1 ]]; then
18-
exit 111
18+
exit 1
1919
fi
2020
}
21+
genesis_file=$2
2122

2223
source ./vars.env
2324

2425
exit_if_fails ../local_testnet/clean.sh
2526

26-
echo "Starting anvil"
27-
28-
exit_if_fails ../local_testnet/anvil_test_node.sh &> /dev/null &
29-
ANVIL_PID=$!
30-
31-
# Wait for anvil to start
32-
sleep 5
3327

3428
echo "Setting up local testnet"
3529

@@ -41,28 +35,31 @@ exit_if_fails cp -R $HOME/.lighthouse/local-testnet/node_1 $HOME/.lighthouse/loc
4135
echo "Starting bootnode"
4236

4337
exit_if_fails ../local_testnet/bootnode.sh &> /dev/null &
44-
BOOT_PID=$!
38+
39+
exit_if_fails ../local_testnet/el_bootnode.sh &> /dev/null &
4540

4641
# wait for the bootnode to start
4742
sleep 10
4843

44+
echo "Starting local execution nodes"
45+
46+
exit_if_fails ../local_testnet/geth.sh $HOME/.lighthouse/local-testnet/geth_datadir1 7000 6000 5000 $genesis_file &> geth.log &
47+
exit_if_fails ../local_testnet/geth.sh $HOME/.lighthouse/local-testnet/geth_datadir2 7100 6100 5100 $genesis_file &> /dev/null &
48+
exit_if_fails ../local_testnet/geth.sh $HOME/.lighthouse/local-testnet/geth_datadir3 7200 6200 5200 $genesis_file &> /dev/null &
49+
50+
sleep 20
51+
4952
echo "Starting local beacon nodes"
5053

51-
exit_if_fails ../local_testnet/beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9000 8000 &> /dev/null &
52-
BEACON_PID=$!
53-
exit_if_fails ../local_testnet/beacon_node.sh $HOME/.lighthouse/local-testnet/node_2 9100 8100 &> /dev/null &
54-
BEACON_PID2=$!
55-
exit_if_fails ../local_testnet/beacon_node.sh $HOME/.lighthouse/local-testnet/node_3 9200 8200 &> /dev/null &
56-
BEACON_PID3=$!
54+
exit_if_fails ../local_testnet/beacon_node.sh -d debug $HOME/.lighthouse/local-testnet/node_1 9000 8000 http://localhost:5000 $HOME/.lighthouse/local-testnet/geth_datadir1/geth/jwtsecret &> beacon1.log &
55+
exit_if_fails ../local_testnet/beacon_node.sh $HOME/.lighthouse/local-testnet/node_2 9100 8100 http://localhost:5100 $HOME/.lighthouse/local-testnet/geth_datadir2/geth/jwtsecret &> /dev/null &
56+
exit_if_fails ../local_testnet/beacon_node.sh $HOME/.lighthouse/local-testnet/node_3 9200 8200 http://localhost:5200 $HOME/.lighthouse/local-testnet/geth_datadir3/geth/jwtsecret &> /dev/null &
5757

5858
echo "Starting local validator clients"
5959

6060
exit_if_fails ../local_testnet/validator_client.sh $HOME/.lighthouse/local-testnet/node_1 http://localhost:8000 &> /dev/null &
61-
VALIDATOR_1_PID=$!
6261
exit_if_fails ../local_testnet/validator_client.sh $HOME/.lighthouse/local-testnet/node_2 http://localhost:8100 &> /dev/null &
63-
VALIDATOR_2_PID=$!
6462
exit_if_fails ../local_testnet/validator_client.sh $HOME/.lighthouse/local-testnet/node_3 http://localhost:8200 &> /dev/null &
65-
VALIDATOR_3_PID=$!
6663

6764
echo "Waiting an epoch before starting the next validator client"
6865
sleep $(( $SECONDS_PER_SLOT * 32 ))
@@ -71,15 +68,17 @@ if [[ "$BEHAVIOR" == "failure" ]]; then
7168

7269
echo "Starting the doppelganger validator client"
7370

74-
# Use same keys as keys from VC1, but connect to BN2
71+
# Use same keys as keys from VC1 and connect to BN2
7572
# This process should not last longer than 2 epochs
7673
timeout $(( $SECONDS_PER_SLOT * 32 * 2 )) ../local_testnet/validator_client.sh $HOME/.lighthouse/local-testnet/node_1_doppelganger http://localhost:8100
7774
DOPPELGANGER_EXIT=$?
7875

7976
echo "Shutting down"
8077

8178
# Cleanup
82-
kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $ANVIL_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID
79+
killall geth
80+
killall lighthouse
81+
killall bootnode
8382

8483
echo "Done"
8584

@@ -98,7 +97,6 @@ if [[ "$BEHAVIOR" == "success" ]]; then
9897
echo "Starting the last validator client"
9998

10099
../local_testnet/validator_client.sh $HOME/.lighthouse/local-testnet/node_4 http://localhost:8100 &
101-
VALIDATOR_4_PID=$!
102100
DOPPELGANGER_FAILURE=0
103101

104102
# Sleep three epochs, then make sure all validators were active in epoch 2. Use
@@ -144,7 +142,10 @@ if [[ "$BEHAVIOR" == "success" ]]; then
144142

145143
# Cleanup
146144
cd $PREVIOUS_DIR
147-
kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $ANVIL_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID $VALIDATOR_4_PID
145+
146+
killall geth
147+
killall lighthouse
148+
killall bootnode
148149

149150
echo "Done"
150151

@@ -153,4 +154,4 @@ if [[ "$BEHAVIOR" == "success" ]]; then
153154
fi
154155
fi
155156

156-
exit 0
157+
exit 0

0 commit comments

Comments
 (0)