Skip to content

Commit

Permalink
Add Gnosis chain deposit support
Browse files Browse the repository at this point in the history
  • Loading branch information
mksh committed Feb 19, 2023
1 parent 8da8348 commit 7c4b085
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 29 deletions.
2 changes: 1 addition & 1 deletion src/cli/existing_mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub fn subcommand<'a, 'b>() -> App<'a, 'b> {
.long("chain")
.required(true)
.takes_value(true)
.possible_values(&["goerli", "prater", "mainnet", "minimal"])
.possible_values(&crate::networks::SUPPORTED_NETWORKS)
.help(
r#"The name of Ethereum PoS chain you are
targeting. Use "mainnet" if you are
Expand Down
2 changes: 1 addition & 1 deletion src/cli/new_mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn subcommand<'a, 'b>() -> App<'a, 'b> {
.long("chain")
.required(true)
.takes_value(true)
.possible_values(&["goerli", "prater", "mainnet", "minimal"])
.possible_values(&crate::networks::SUPPORTED_NETWORKS)
.help(
r#"The name of Ethereum PoS chain you are
targeting. Use "mainnet" if you are
Expand Down
84 changes: 59 additions & 25 deletions src/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use eth2_keystore::keypair_from_secret;
use eth2_network_config::Eth2NetworkConfig;
use std::path::Path;
use types::{ChainSpec, Config, DepositData, Hash256, MainnetEthSpec, MinimalEthSpec, Signature};
use types::{ChainSpec, Config, DepositData, Hash256, MinimalEthSpec, Signature};

use crate::key_material::VotingKeyMaterial;
use crate::{key_material::VotingKeyMaterial, networks::NetworkSpec};

#[derive(Debug, Eq, PartialEq)]
pub enum DepositError {
Expand All @@ -23,7 +22,7 @@ pub(crate) fn keystore_to_deposit(
// withdrawal credentials
withdrawal_credentials: &[u8],
deposit_amount_gwei: u64,
network: String,
network: NetworkSpec,
chain_spec_file: Option<String>,
) -> Result<(DepositData, ChainSpec), DepositError> {
// Validate data input
Expand All @@ -41,22 +40,14 @@ pub(crate) fn keystore_to_deposit(
));
};

let network_str = network.as_str();
let spec;

if ["goerli", "prater", "mainnet"].contains(&network_str) {
spec = Eth2NetworkConfig::constant(network_str)
.unwrap()
.unwrap()
.chain_spec::<MainnetEthSpec>()
.unwrap();
} else if network_str == "minimal" {
let spec = if !network.is_public() {
// Loads custom configuration for private net
if chain_spec_file.is_none() {
return Err(DepositError::NoCustomConfig(
"Custom config for minimal network must be provided".to_string(),
));
}
spec = match Config::from_file(Path::new(chain_spec_file.unwrap().as_str())) {
match Config::from_file(Path::new(chain_spec_file.unwrap().as_str())) {
Ok(cfg) => cfg
.apply_to_chain_spec::<MinimalEthSpec>(&ChainSpec::minimal())
.unwrap(),
Expand All @@ -68,10 +59,8 @@ pub(crate) fn keystore_to_deposit(
}
}
} else {
return Err(DepositError::InvalidNetworkName(
"Unknown network name passed".to_string(),
));
}
network.into()
};

let credentials_hash = Hash256::from_slice(withdrawal_credentials);

Expand Down Expand Up @@ -105,7 +94,9 @@ mod test {
use types::PublicKey;

use super::keystore_to_deposit;
use crate::{key_material::VotingKeyMaterial, utils::get_withdrawal_credentials};
use crate::{
key_material::VotingKeyMaterial, networks::NetworkSpec, utils::get_withdrawal_credentials,
};
use std::{path::PathBuf, str::FromStr};
use test_log::test;

Expand All @@ -132,7 +123,7 @@ mod test {
&key_material,
withdrawal_creds.as_slice(),
32_000_000_000,
"mainnet".to_string(),
NetworkSpec::Mainnet,
None,
)
.unwrap();
Expand Down Expand Up @@ -172,7 +163,7 @@ mod test {
&key_material,
withdrawal_creds.as_slice(),
32_000_000_000,
"mainnet".to_string(),
NetworkSpec::Mainnet,
None,
)
.unwrap();
Expand Down Expand Up @@ -212,7 +203,7 @@ mod test {
&key_material,
&withdrawal_creds,
32_000_000_000,
"mainnet".to_string(),
NetworkSpec::Mainnet,
None,
)
.unwrap();
Expand Down Expand Up @@ -259,7 +250,7 @@ mod test {
&key_material,
&withdrawal_creds.as_slice(),
32_000_000_000,
"goerli".to_string(),
NetworkSpec::Goerli,
None,
)
.unwrap();
Expand Down Expand Up @@ -294,7 +285,7 @@ mod test {
&key_material,
&withdrawal_creds.as_slice(),
32_000_000_000,
"minimal".to_string(),
NetworkSpec::Minimal,
Some(manifest.to_str().unwrap().to_string()),
)
.unwrap();
Expand All @@ -311,4 +302,47 @@ mod test {
deposit_data.signature.to_string().as_str().strip_prefix("0x").unwrap()
);
}

#[test]
fn test_deposit_gnosis() {
let keystore = Keystore::from_json_str(KEYSTORE).unwrap();
let keypair = keystore.decrypt_keypair(PASSWORD).unwrap();
let key_material = VotingKeyMaterial {
keystore: Some(keystore.clone()),
keypair,
voting_secret: PlainText::from(
keystore
.decrypt_keypair(PASSWORD)
.unwrap()
.sk
.serialize()
.as_bytes()
.to_vec(),
),
withdrawal_keypair: None,
};
let withdrawal_creds = hex::decode(WITHDRAWAL_CREDENTIALS_ETH1).unwrap();
let (deposit_data, _) = keystore_to_deposit(
&key_material,
&withdrawal_creds.as_slice(),
32_000_000_000,
NetworkSpec::Gnosis,
None,
)
.unwrap();

// Signature asserted here is generated with
// https://github.com/gnosischain/validator-data-generator

// python ./staking_deposit/deposit.py existing-mnemonic --eth1_withdrawal_address=0x010000000000000000000000000000000000000001

// Please enter your mnemonic separated by spaces (" "): entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup
// Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here. [0]: 0
// Please choose how many new validators you wish to run: 1
// Please choose the (mainnet or testnet) network/chain name ['mainnet', 'ropsten', 'goerli', 'kiln', 'sepolia', 'gnosis', 'chiado']: [gnosis]: gnosis
assert_eq!(
"97cb6902975fc7cdcd685006ca972a22799aece787b9665af9e0b501e70a4db100cf280c99f1b20ea49c953b526b0e760b4a6d6a8d1092a6afbad3efdab8269384f2034c4fd97ebd8fc2101467b2fe6aeadcf5fcfaa11952be8d3939a55b10f7",
deposit_data.signature.to_string().as_str().strip_prefix("0x").unwrap()
);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod bls_to_execution_change;
pub mod cli;
pub(crate) mod deposit;
pub(crate) mod key_material;
pub mod networks;
pub(crate) mod seed;
pub(crate) mod utils;
pub mod validators;
Expand Down
77 changes: 77 additions & 0 deletions src/networks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use eth2_network_config::Eth2NetworkConfig;
use types::{ChainSpec, GnosisEthSpec, MainnetEthSpec};

use crate::DepositError;

/// String representation for all supported networks
pub static SUPPORTED_NETWORKS: [&str; 5] = ["goerli", "prater", "mainnet", "gnosis", "minimal"];

#[derive(Clone)]
pub enum NetworkSpec {
Mainnet,
Goerli,
Gnosis,
Minimal,
}

/// Collects network spec from user or library inputs
impl TryFrom<String> for NetworkSpec {
type Error = DepositError;

fn try_from(value: String) -> Result<Self, Self::Error> {
let network = match value.as_str() {
"mainnet" => NetworkSpec::Mainnet,
"goerli" => NetworkSpec::Goerli,
"prater" => NetworkSpec::Goerli,
"gnosis" => NetworkSpec::Gnosis,
"minimal" => NetworkSpec::Minimal,
_ => {
log::info!("Invalid network name passed: {value}");
return Err(DepositError::InvalidNetworkName(
"Unknown network name passed".to_string(),
));
}
};
Ok(network)
}
}

/// Convert to a string that is recognizeable by Lighthouse's
/// Eth2NetworkConfig::constant
impl From<NetworkSpec> for &'static str {
fn from(value: NetworkSpec) -> &'static str {
match value {
NetworkSpec::Mainnet => "mainnet",
NetworkSpec::Goerli => "goerli",
NetworkSpec::Gnosis => "gnosis",
NetworkSpec::Minimal => "minimal",
}
}
}

/// Convert to Lighthouse internal spec repr.
/// Works only for public networks, private net needs
/// to load file.
impl From<NetworkSpec> for ChainSpec {
fn from(value: NetworkSpec) -> ChainSpec {
match value {
NetworkSpec::Gnosis => Eth2NetworkConfig::constant(value.into())
.unwrap()
.unwrap()
.chain_spec::<GnosisEthSpec>()
.unwrap(),
_ => Eth2NetworkConfig::constant(value.into())
.unwrap()
.unwrap()
.chain_spec::<MainnetEthSpec>()
.unwrap(),
}
}
}

impl NetworkSpec {
/// Find out if the config for public network, or private net one
pub fn is_public(&self) -> bool {
!matches!(self, NetworkSpec::Minimal)
}
}
7 changes: 5 additions & 2 deletions src/validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::str::FromStr;

use crate::deposit::{keystore_to_deposit, DepositError};
use crate::key_material::{seed_to_key_material, VotingKeyMaterial};
use crate::networks::NetworkSpec;
use crate::seed::get_eth2_seed;
use crate::utils::get_withdrawal_credentials;
use bip39::{Mnemonic, Seed as Bip39Seed};
Expand Down Expand Up @@ -243,6 +244,8 @@ impl Validators {
let mut private_keys: Vec<String> = vec![];
let mut deposit_data: Vec<DepositExport> = vec![];

let network_spec = NetworkSpec::try_from(network)?;

for key_with_store in self.key_material.iter() {
if let Some(ks) = key_with_store.keystore.clone() {
keystores.push(ks);
Expand All @@ -260,7 +263,7 @@ impl Validators {
&(*key_with_store).clone(),
withdrawal_credentials.as_ref(),
deposit_amount_gwei,
network.clone(),
network_spec.clone(),
chain_spec_file.clone(),
)?;

Expand All @@ -278,7 +281,7 @@ impl Validators {
deposit_message_root: hex::encode(deposit.as_deposit_message().tree_hash_root()),
deposit_data_root: hex::encode(deposit.tree_hash_root()),
fork_version: hex::encode(chain_spec.genesis_fork_version),
network_name: network.clone(),
network_name: Into::<&'static str>::into(network_spec.clone()).to_string(),
deposit_cli_version: deposit_cli_version.clone(),
})
}
Expand Down

0 comments on commit 7c4b085

Please sign in to comment.