From 5b984ad3942e8bd442f7499bedb0bea410fcf0b9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 1 Apr 2020 17:40:32 +1100 Subject: [PATCH] Add lcli tool for checking deposit data (#940) * Add check-deposit-data tool * Update help text * Update function name --- Cargo.lock | 11 +-- eth2/utils/deposit_contract/Cargo.toml | 2 +- eth2/utils/deposit_contract/src/lib.rs | 78 +++++++++++++++++++-- lcli/Cargo.toml | 2 + lcli/src/check_deposit_data.rs | 31 ++++++++ lcli/src/helpers.rs | 13 ++++ lcli/src/main.rs | 26 +++++++ tests/eth1_test_rig/src/lib.rs | 10 +-- validator_client/src/validator_directory.rs | 4 +- 9 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 lcli/src/check_deposit_data.rs diff --git a/Cargo.lock b/Cargo.lock index e8b4341181..d8883594b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -879,7 +879,7 @@ name = "deposit_contract" version = "0.1.0" dependencies = [ "eth2_ssz 0.1.2", - "ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ethabi 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "tree_hash 0.1.1", @@ -1240,16 +1240,15 @@ dependencies = [ [[package]] name = "ethabi" -version = "9.0.1" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1839,6 +1838,7 @@ name = "lcli" version = "0.1.0" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "deposit_contract 0.1.0", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "environment 0.1.0", "eth1_test_rig 0.1.0", @@ -1853,6 +1853,7 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "state_processing 0.1.0", + "tree_hash 0.1.1", "types 0.1.0", "web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -5226,8 +5227,8 @@ dependencies = [ "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" +"checksum ethabi 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97652a7d1f2504d6c885c87e242a06ccef5bd3054093d3fb742d8fb64806231a" "checksum ethabi 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebdeeea85a6d217b9fcc862906d7e283c047e04114165c433756baf5dce00a6c" -"checksum ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "965126c64662832991f5a748893577630b558e47fa94e7f35aefcd20d737cef7" "checksum ethbloom 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3932e82d64d347a045208924002930dc105a138995ccdc1479d0f05f0359f17c" "checksum ethbloom 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "32cfe1c169414b709cf28aa30c74060bdb830a03a8ba473314d079ac79d80a5f" "checksum ethereum-types 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62d1bc682337e2c5ec98930853674dd2b4bd5d0d246933a9e98e5280f7c76c5f" diff --git a/eth2/utils/deposit_contract/Cargo.toml b/eth2/utils/deposit_contract/Cargo.toml index 1d28546dab..c3698ba1b1 100644 --- a/eth2/utils/deposit_contract/Cargo.toml +++ b/eth2/utils/deposit_contract/Cargo.toml @@ -14,4 +14,4 @@ serde_json = "1.0" types = { path = "../../types"} eth2_ssz = { path = "../ssz"} tree_hash = { path = "../tree_hash"} -ethabi = "9.0" +ethabi = "11.0" diff --git a/eth2/utils/deposit_contract/src/lib.rs b/eth2/utils/deposit_contract/src/lib.rs index c7ce827b1c..d85c89e678 100644 --- a/eth2/utils/deposit_contract/src/lib.rs +++ b/eth2/utils/deposit_contract/src/lib.rs @@ -1,14 +1,31 @@ use ethabi::{Contract, Token}; -use ssz::Encode; +use ssz::{Decode, DecodeError as SszDecodeError, Encode}; use tree_hash::TreeHash; -use types::DepositData; +use types::{DepositData, Hash256, PublicKeyBytes, SignatureBytes}; pub use ethabi::Error; +#[derive(Debug)] +pub enum DecodeError { + EthabiError(ethabi::Error), + SszDecodeError(SszDecodeError), + MissingField, + UnableToGetBytes, + MissingToken, + InadequateBytes, +} + +impl From for DecodeError { + fn from(e: ethabi::Error) -> DecodeError { + DecodeError::EthabiError(e) + } +} + pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000; pub const DEPOSIT_GAS: usize = 4_000_000; pub const ABI: &[u8] = include_bytes!("../contracts/v0.10.1_validator_registration.json"); pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.10.1_validator_registration.bytecode"); +pub const DEPOSIT_DATA_LEN: usize = 420; // lol pub mod testnet { pub const ABI: &[u8] = @@ -17,7 +34,7 @@ pub mod testnet { include_bytes!("../contracts/v0.10.1_testnet_validator_registration.bytecode"); } -pub fn eth1_tx_data(deposit_data: &DepositData) -> Result, Error> { +pub fn encode_eth1_tx_data(deposit_data: &DepositData) -> Result, Error> { let params = vec![ Token::Bytes(deposit_data.pubkey.as_ssz_bytes()), Token::Bytes(deposit_data.withdrawal_credentials.as_ssz_bytes()), @@ -32,6 +49,40 @@ pub fn eth1_tx_data(deposit_data: &DepositData) -> Result, Error> { function.encode_input(¶ms) } +pub fn decode_eth1_tx_data( + bytes: &[u8], + amount: u64, +) -> Result<(DepositData, Hash256), DecodeError> { + let abi = Contract::load(ABI)?; + let function = abi.function("deposit")?; + let mut tokens = + function.decode_input(bytes.get(4..).ok_or_else(|| DecodeError::InadequateBytes)?)?; + + macro_rules! decode_token { + ($type: ty, $to_fn: ident) => { + <$type>::from_ssz_bytes( + &tokens + .pop() + .ok_or_else(|| DecodeError::MissingToken)? + .$to_fn() + .ok_or_else(|| DecodeError::UnableToGetBytes)?, + ) + .map_err(DecodeError::SszDecodeError)? + }; + }; + + let root = decode_token!(Hash256, to_fixed_bytes); + + let deposit_data = DepositData { + amount, + signature: decode_token!(SignatureBytes, to_bytes), + withdrawal_credentials: decode_token!(Hash256, to_bytes), + pubkey: decode_token!(PublicKeyBytes, to_bytes), + }; + + Ok((deposit_data, root)) +} + #[cfg(test)] mod tests { use super::*; @@ -54,14 +105,27 @@ mod tests { } #[test] - fn basic() { + fn round_trip() { let spec = &E::default_spec(); let keypair = generate_deterministic_keypair(42); - let deposit = get_deposit(keypair, spec); + let original = get_deposit(keypair, spec); - let data = eth1_tx_data(&deposit).expect("should produce tx data"); + let data = encode_eth1_tx_data(&original).expect("should produce tx data"); - assert_eq!(data.len(), 420, "bytes should be correct length"); + assert_eq!( + data.len(), + DEPOSIT_DATA_LEN, + "bytes should be correct length" + ); + + let (decoded, root) = decode_eth1_tx_data(&data, original.amount).expect("should decode"); + + assert_eq!(decoded, original, "decoded should match original"); + assert_eq!( + root, + original.tree_hash_root(), + "decode root should match original root" + ); } } diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index a3ec8909ba..7dcf4f83d7 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -25,3 +25,5 @@ web3 = "0.8.0" eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" } dirs = "2.0" genesis = { path = "../beacon_node/genesis" } +deposit_contract = { path = "../eth2/utils/deposit_contract" } +tree_hash = { path = "../eth2/utils/tree_hash" } diff --git a/lcli/src/check_deposit_data.rs b/lcli/src/check_deposit_data.rs new file mode 100644 index 0000000000..af2b6fb478 --- /dev/null +++ b/lcli/src/check_deposit_data.rs @@ -0,0 +1,31 @@ +use crate::helpers::{parse_hex_bytes, parse_u64}; +use clap::ArgMatches; +use deposit_contract::{decode_eth1_tx_data, DEPOSIT_DATA_LEN}; +use tree_hash::TreeHash; +use types::EthSpec; + +pub fn run(matches: &ArgMatches) -> Result<(), String> { + let rlp_bytes = parse_hex_bytes(matches, "deposit-data")?; + let amount = parse_u64(matches, "deposit-amount")?; + + if rlp_bytes.len() != DEPOSIT_DATA_LEN { + return Err(format!( + "The given deposit-data is {} bytes, expected {}", + rlp_bytes.len(), + DEPOSIT_DATA_LEN + )); + } + + let (deposit_data, root) = decode_eth1_tx_data(&rlp_bytes, amount) + .map_err(|e| format!("Invalid deposit data bytes: {:?}", e))?; + + let expected_root = deposit_data.tree_hash_root(); + if root != expected_root { + return Err(format!( + "Deposit data root is invalid. Expected {:?}, but got {:?}. Perhaps the amount is incorrect?", + expected_root, root + )); + } + + Ok(()) +} diff --git a/lcli/src/helpers.rs b/lcli/src/helpers.rs index 7916c26e21..6f7014f3a6 100644 --- a/lcli/src/helpers.rs +++ b/lcli/src/helpers.rs @@ -83,3 +83,16 @@ pub fn parse_fork_opt(matches: &ArgMatches, name: &'static str) -> Result