Skip to content

Commit

Permalink
Add JSON deposit data to create
Browse files Browse the repository at this point in the history
  • Loading branch information
paulhauner committed Aug 15, 2022
1 parent 71fd0b4 commit fdc2fd5
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions account_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ safe_arith = {path = "../consensus/safe_arith"}
slot_clock = { path = "../common/slot_clock" }
filesystem = { path = "../common/filesystem" }
sensitive_url = { path = "../common/sensitive_url" }
serde = { version = "1.0.116", features = ["derive"] }
serde_json = "1.0.58"

[dev-dependencies]
tempfile = "3.1.0"
42 changes: 41 additions & 1 deletion account_manager/src/validator/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const WALLET_NAME_FLAG: &str = "wallet-name";
pub const WALLET_PASSWORD_FLAG: &str = "wallet-password";
pub const DEPOSIT_GWEI_FLAG: &str = "deposit-gwei";
pub const STORE_WITHDRAW_FLAG: &str = "store-withdrawal-keystore";
pub const JSON_DEPOSIT_DATA_PATH: &str = "json-deposit-data-path";
pub const COUNT_FLAG: &str = "count";
pub const AT_MOST_FLAG: &str = "at-most";
pub const WALLET_PASSWORD_PROMPT: &str = "Enter your wallet's password:";
Expand Down Expand Up @@ -110,6 +111,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
.arg(
Arg::with_name(JSON_DEPOSIT_DATA_PATH)
.long(JSON_DEPOSIT_DATA_PATH)
.value_name("PATH")
.help(
"When provided, outputs a JSON file containing deposit data which \
is equivalent to the 'deposit-data-*.json' file used by the \
staking-deposit-cli tool.",
)
.takes_value(true),
)
}

pub fn cli_run<T: EthSpec>(
Expand Down Expand Up @@ -140,6 +152,9 @@ pub fn cli_run<T: EthSpec>(
let count: Option<usize> = clap_utils::parse_optional(matches, COUNT_FLAG)?;
let at_most: Option<usize> = clap_utils::parse_optional(matches, AT_MOST_FLAG)?;

let json_deposit_data_path: Option<PathBuf> =
clap_utils::parse_optional(matches, JSON_DEPOSIT_DATA_PATH)?;

// The command will always fail if the wallet dir does not exist.
if !wallet_base_dir.exists() {
return Err(format!(
Expand Down Expand Up @@ -212,6 +227,8 @@ pub fn cli_run<T: EthSpec>(
)
})?;

let mut json_deposit_data = Some(vec![]).filter(|_| json_deposit_data_path.is_some());

for i in 0..n {
let voting_password = random_password();
let withdrawal_password = random_password();
Expand Down Expand Up @@ -241,7 +258,7 @@ pub fn cli_run<T: EthSpec>(
)
})?;

ValidatorDirBuilder::new(validator_dir.clone())
let validator_dir = ValidatorDirBuilder::new(validator_dir.clone())
.password_dir(secrets_dir.clone())
.voting_keystore(keystores.voting, voting_password.as_bytes())
.withdrawal_keystore(keystores.withdrawal, withdrawal_password.as_bytes())
Expand All @@ -250,9 +267,32 @@ pub fn cli_run<T: EthSpec>(
.build()
.map_err(|e| format!("Unable to build validator directory: {:?}", e))?;

if let Some(json_deposit_data) = &mut json_deposit_data {
let standard_deposit_data_json = validator_dir
.standard_deposit_data_json(&spec)
.map_err(|e| format!("Unable to create standard JSON deposit data: {:?}", e))?;
json_deposit_data.push(standard_deposit_data_json);
}

println!("{}/{}\t{}", i + 1, n, voting_pubkey.as_hex_string());
}

// If configured, create a single JSON file which contains deposit data information for all
// validators.
if let Some(json_deposit_data_path) = json_deposit_data_path {
let json_deposit_data =
json_deposit_data.ok_or("Internal error: JSON deposit data is None")?;

let mut file = fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&json_deposit_data_path)
.map_err(|e| format!("Unable to create {:?}: {:?}", json_deposit_data_path, e))?;

serde_json::to_writer(&mut file, &json_deposit_data)
.map_err(|e| format!("Unable write JSON to {:?}: {:?}", json_deposit_data_path, e))?;
}

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions common/validator_dir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ tree_hash = "0.4.1"
hex = "0.4.2"
derivative = "2.1.1"
lockfile = { path = "../lockfile" }
serde = { version = "1.0.116", features = ["derive"] }
eth2_serde_utils = "0.1.1"

[dev-dependencies]
tempfile = "3.1.0"
65 changes: 64 additions & 1 deletion common/validator_dir/src/validator_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use deposit_contract::decode_eth1_tx_data;
use derivative::Derivative;
use eth2_keystore::{Error as KeystoreError, Keystore, PlainText};
use lockfile::{Lockfile, LockfileError};
use serde::{Deserialize, Serialize};
use std::fs::{read, write, File};
use std::io;
use std::path::{Path, PathBuf};
use tree_hash::TreeHash;
use types::{DepositData, Hash256, Keypair};
use types::*;

/// The file used to save the Eth1 transaction hash from a deposit.
pub const ETH1_DEPOSIT_TX_HASH_FILE: &str = "eth1-deposit-tx-hash.txt";
Expand Down Expand Up @@ -41,6 +42,8 @@ pub enum Error {
Eth1DepositRootMismatch,
#[cfg(feature = "unencrypted_keys")]
SszKeypairError(String),
DepositDataMissing,
ConfigNameUnspecified,
}

/// Information required to submit a deposit to the Eth1 deposit contract.
Expand All @@ -54,6 +57,26 @@ pub struct Eth1DepositData {
pub root: Hash256,
}

/// The structure generated by the `staking-deposit-cli` which has become a quasi-standard for
/// browser-based deposit submission tools (e.g., the Ethereum Launchpad and Lido).
///
/// We assume this code as the canonical definition:
///
/// https://github.com/ethereum/staking-deposit-cli/blob/76ed78224fdfe3daca788d12442b3d1a37978296/staking_deposit/credentials.py#L131-L144
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct StandardDepositDataJson {
pub pubkey: PublicKeyBytes,
pub withdrawal_credentials: Hash256,
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub amount: u64,
pub signature: SignatureBytes,
#[serde(with = "eth2_serde_utils::bytes_4_hex")]
pub fork_version: [u8; 4],
pub eth2_network_name: String,
pub deposit_message_root: Hash256,
pub deposit_data_root: Hash256,
}

/// Provides a wrapper around a directory containing validator information.
///
/// Holds a lockfile in `self.dir` to attempt to prevent concurrent access from multiple
Expand Down Expand Up @@ -203,6 +226,46 @@ impl ValidatorDir {
root,
}))
}

/// Calls `Self::eth1_deposit_data` and then builds a `StandardDepositDataJson` from the result.
///
/// The provided `spec` must match the value that was used to create the deposit, otherwise
/// an inconsistent result may be returned.
pub fn standard_deposit_data_json(
&self,
spec: &ChainSpec,
) -> Result<StandardDepositDataJson, Error> {
let deposit_data = self.eth1_deposit_data()?.ok_or(Error::DepositDataMissing)?;

let domain = spec.get_deposit_domain();
let deposit_message_root = deposit_data
.deposit_data
.as_deposit_message()
.signing_root(domain);

let deposit_data_root = deposit_data.deposit_data.tree_hash_root();

let DepositData {
pubkey,
withdrawal_credentials,
amount,
signature,
} = deposit_data.deposit_data;

Ok(StandardDepositDataJson {
pubkey,
withdrawal_credentials,
amount,
signature,
fork_version: spec.genesis_fork_version,
eth2_network_name: spec
.config_name
.clone()
.ok_or(Error::ConfigNameUnspecified)?,
deposit_message_root,
deposit_data_root,
})
}
}

/// Attempts to load and decrypt a Keypair given path to the keystore.
Expand Down

0 comments on commit fdc2fd5

Please sign in to comment.