Update testnet tooling (#1001)

* Add progress on new deposits

* Add deposited command to account manager

* Remove old lcli::helpers mod

* Clean clap_utils

* Refactor lcli deposit contract commands to use IPC

* Make testnet optional for environment

* Use dbg formatting for deploy address

* Add command to generate bootnode enr

* Ensure lcli returns with 1 on error

* Ensure account manager returns 1 on error

* Disallow deposits to the zero address

* Update web3 in eth1 crate

* Ensure correct lighthouse dir is created

* Reduce deposit gas requirement

* Update cargo.lock

* Add progress on new deposits

* Add deposited command to account manager

* Remove old lcli::helpers mod

* Clean clap_utils

* Refactor lcli deposit contract commands to use IPC

* Add command to generate bootnode enr

* Ensure lcli returns with 1 on error

* Ensure account manager returns 1 on error

* Update web3 in eth1 crate

* Update Cargo.lock

* Move lcli out of main install script

* Change --limit to --at-least

* Change --datadir to --validator-dir

* Remove duplication in docs
This commit is contained in:
Paul Hauner 2020-04-19 12:20:43 +10:00 committed by GitHub
parent f9e8dad1fb
commit 7b86c9a08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 711 additions and 304 deletions

View File

@ -74,3 +74,12 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Typecheck benchmark code without running it - name: Typecheck benchmark code without running it
run: make check-benches run: make check-benches
install-lcli:
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Build lcli via Makefile
run: make install-lcli

91
Cargo.lock generated
View File

@ -6,6 +6,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"bls 0.2.0", "bls 0.2.0",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"deposit_contract 0.2.0", "deposit_contract 0.2.0",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0", "environment 0.2.0",
@ -22,7 +23,7 @@ dependencies = [
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0", "types 0.2.0",
"validator_client 0.2.0", "validator_client 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -493,6 +494,18 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "clap_utils"
version = "0.1.0"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"eth2_ssz 0.1.2",
"eth2_testnet_config 0.2.0",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0",
]
[[package]] [[package]]
name = "clear_on_drop" name = "clear_on_drop"
version = "0.2.3" version = "0.2.3"
@ -890,6 +903,16 @@ dependencies = [
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "derive_more"
version = "0.99.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.8.1" version = "0.8.1"
@ -1079,7 +1102,7 @@ dependencies = [
"toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tree_hash 0.1.1", "tree_hash 0.1.1",
"types 0.2.0", "types 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1091,7 +1114,7 @@ dependencies = [
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0", "types 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1224,6 +1247,20 @@ dependencies = [
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "ethabi"
version = "9.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"error-chain 0.12.2 (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.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ethabi" name = "ethabi"
version = "11.0.0" version = "11.0.0"
@ -1790,6 +1827,18 @@ dependencies = [
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "jsonrpc-core"
version = "14.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "keccak" name = "keccak"
version = "0.1.0" version = "0.1.0"
@ -1824,10 +1873,12 @@ name = "lcli"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"deposit_contract 0.2.0", "deposit_contract 0.2.0",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0", "environment 0.2.0",
"eth1_test_rig 0.2.0", "eth1_test_rig 0.2.0",
"eth2-libp2p 0.2.0",
"eth2_ssz 0.1.2", "eth2_ssz 0.1.2",
"eth2_testnet_config 0.2.0", "eth2_testnet_config 0.2.0",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2339,6 +2390,7 @@ dependencies = [
"account_manager 0.0.1", "account_manager 0.0.1",
"beacon_node 0.2.0", "beacon_node 0.2.0",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0", "environment 0.2.0",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
@ -4706,6 +4758,7 @@ dependencies = [
"tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"tree_hash 0.1.1", "tree_hash 0.1.1",
"types 0.2.0", "types 0.2.0",
"web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -4909,6 +4962,34 @@ dependencies = [
"websocket 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "websocket 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "web3"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethereum-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (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.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-uds 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"websocket 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "webpki" name = "webpki"
version = "0.21.2" version = "0.21.2"
@ -5173,6 +5254,7 @@ dependencies = [
"checksum db-key 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b72465f46d518f6015d9cf07f7f3013a95dd6b9c2747c3d65ae0cce43929d14f" "checksum db-key 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b72465f46d518f6015d9cf07f7f3013a95dd6b9c2747c3d65ae0cce43929d14f"
"checksum derivative 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135" "checksum derivative 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135"
"checksum derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe" "checksum derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe"
"checksum derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7"
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
@ -5187,6 +5269,7 @@ dependencies = [
"checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" "checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
"checksum ethabi 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97652a7d1f2504d6c885c87e242a06ccef5bd3054093d3fb742d8fb64806231a" "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 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.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 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" "checksum ethereum-types 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62d1bc682337e2c5ec98930853674dd2b4bd5d0d246933a9e98e5280f7c76c5f"
@ -5243,6 +5326,7 @@ dependencies = [
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" "checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" "checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
"checksum jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581" "checksum jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581"
"checksum jsonrpc-core 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25525f6002338fb4debb5167a89a0b47f727a5a48418417545ad3429758b7fec"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
@ -5509,6 +5593,7 @@ dependencies = [
"checksum wasm-bindgen-test-macro 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cf2f86cd78a2aa7b1fb4bb6ed854eccb7f9263089c79542dca1576a1518a8467" "checksum wasm-bindgen-test-macro 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cf2f86cd78a2aa7b1fb4bb6ed854eccb7f9263089c79542dca1576a1518a8467"
"checksum wasm-timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aa3e01d234bb71760e685cfafa5e2c96f8ad877c161a721646356651069e26ac" "checksum wasm-timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aa3e01d234bb71760e685cfafa5e2c96f8ad877c161a721646356651069e26ac"
"checksum web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" "checksum web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
"checksum web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0631c83208cf420eeb2ed9b6cb2d5fc853aa76a43619ccec2a3d52d741f1261"
"checksum web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "076f34ed252d74a8521e3b013254b1a39f94a98f23aae7cfc85cda6e7b395664" "checksum web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "076f34ed252d74a8521e3b013254b1a39f94a98f23aae7cfc85cda6e7b395664"
"checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" "checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef"
"checksum webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" "checksum webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4"

View File

@ -5,6 +5,7 @@ members = [
"eth2/state_processing", "eth2/state_processing",
"eth2/types", "eth2/types",
"eth2/utils/bls", "eth2/utils/bls",
"eth2/utils/clap_utils",
"eth2/utils/compare_fields", "eth2/utils/compare_fields",
"eth2/utils/compare_fields_derive", "eth2/utils/compare_fields_derive",
"eth2/utils/deposit_contract", "eth2/utils/deposit_contract",

View File

@ -2,11 +2,14 @@
EF_TESTS = "tests/ef_tests" EF_TESTS = "tests/ef_tests"
# Builds the entire workspace in release (optimized). # Builds the Lighthouse binary in release (optimized).
# #
# Binaries will most likely be found in `./target/release` # Binaries will most likely be found in `./target/release`
install: install:
cargo install --path lighthouse --force --locked cargo install --path lighthouse --force --locked
# Builds the lcli binary in release (optimized).
install-lcli:
cargo install --path lcli --force --locked cargo install --path lcli --force --locked
# Runs the full workspace tests in **release**, without downloading any additional # Runs the full workspace tests in **release**, without downloading any additional

View File

@ -24,5 +24,6 @@ hex = "0.3"
validator_client = { path = "../validator_client" } validator_client = { path = "../validator_client" }
rayon = "1.2.0" rayon = "1.2.0"
eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" } eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" }
web3 = "0.8.0" web3 = "0.10.0"
futures = "0.1.25" futures = "0.1.25"
clap_utils = { path = "../eth2/utils/clap_utils" }

View File

@ -1,3 +1,4 @@
use crate::deposits;
use clap::{App, Arg, SubCommand}; use clap::{App, Arg, SubCommand};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> { pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
@ -7,6 +8,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.subcommand( .subcommand(
SubCommand::with_name("validator") SubCommand::with_name("validator")
.about("Generate or manage Etheruem 2.0 validators.") .about("Generate or manage Etheruem 2.0 validators.")
.subcommand(deposits::cli_app())
.subcommand( .subcommand(
SubCommand::with_name("new") SubCommand::with_name("new")
.about("Create a new Ethereum 2.0 validator.") .about("Create a new Ethereum 2.0 validator.")

View File

@ -0,0 +1,129 @@
use clap::{App, Arg, ArgMatches};
use clap_utils;
use environment::Environment;
use std::fs;
use std::path::PathBuf;
use types::EthSpec;
use validator_client::validator_directory::ValidatorDirectoryBuilder;
use web3::{transports::Ipc, types::Address, Web3};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new("deposited")
.about("Creates new Lighthouse validator keys and directories. Each newly-created validator
will have a deposit transaction formed and submitted to the deposit contract via
--eth1-ipc. Will only write each validator keys to disk if the deposit transaction returns
successfully from the eth1 node. The process exits immediately if any Eth1 tx fails. Does
not wait for Eth1 confirmation blocks, so there is no guarantee that a deposit will be
accepted in the Eth1 chain.")
.arg(
Arg::with_name("validator-dir")
.long("validator-dir")
.value_name("VALIDATOR_DIRECTORY")
.help("The path where the validator directories will be created. Defaults to ~/.lighthouse/validators")
.takes_value(true),
)
.arg(
Arg::with_name("eth1-ipc")
.long("eth1-ipc")
.value_name("ETH1_IPC_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("from-address")
.long("from-address")
.value_name("FROM_ETH1_ADDRESS")
.help("The address that will submit the eth1 deposit. Must be unlocked on the node
at --eth1-ipc.")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("deposit-gwei")
.long("deposit-gwei")
.value_name("DEPOSIT_GWEI")
.help("The GWEI value of the deposit amount. Defaults to the minimum amount
required for an active validator (MAX_EFFECTIVE_BALANCE.")
.takes_value(true),
)
.arg(
Arg::with_name("count")
.long("count")
.value_name("DEPOSIT_COUNT")
.help("The number of deposits to create, regardless of how many already exist")
.conflicts_with("limit")
.takes_value(true),
)
.arg(
Arg::with_name("at-least")
.long("at-least")
.value_name("VALIDATOR_COUNT")
.help("Observe the number of validators in --validator-dir, only creating enough to
ensure reach the given count. Never deletes an existing validator.")
.conflicts_with("count")
.takes_value(true),
)
}
pub fn cli_run<T: EthSpec>(matches: &ArgMatches, mut env: Environment<T>) -> Result<(), String> {
let spec = env.core_context().eth2_config.spec;
let validator_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
"validator_dir",
PathBuf::new().join(".lighthouse").join("validators"),
)?;
let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
let from_address: Address = clap_utils::parse_required(matches, "from-address")?;
let deposit_gwei = clap_utils::parse_optional(matches, "deposit-gwei")?
.unwrap_or_else(|| spec.max_effective_balance);
let count: Option<usize> = clap_utils::parse_optional(matches, "count")?;
let at_least: Option<usize> = clap_utils::parse_optional(matches, "at-least")?;
let n = match (count, at_least) {
(Some(_), Some(_)) => Err("Cannot supply --count and --at-least".to_string()),
(None, None) => Err("Must supply either --count or --at-least".to_string()),
(Some(count), None) => Ok(count),
(None, Some(at_least)) => fs::read_dir(&validator_dir)
.map(|iter| at_least.saturating_sub(iter.count()))
.map_err(|e| format!("Unable to read {:?}: {}", validator_dir, e)),
}?;
let deposit_contract = env
.testnet
.as_ref()
.ok_or_else(|| "Unable to run account manager without a testnet dir".to_string())?
.deposit_contract_address()
.map_err(|e| format!("Unable to parse deposit contract address: {}", e))?;
if deposit_contract == Address::zero() {
return Err("Refusing to deposit to the zero address. Check testnet configuration.".into());
}
let (_event_loop_handle, transport) =
Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
let web3 = Web3::new(transport);
for _ in 0..n {
let validator = env
.runtime()
.block_on(
ValidatorDirectoryBuilder::default()
.spec(spec.clone())
.custom_deposit_amount(deposit_gwei)
.thread_random_keypairs()
.submit_eth1_deposit(web3.clone(), from_address, deposit_contract),
)?
.create_directory(validator_dir.clone())?
.write_keypair_files()?
.write_eth1_data_file()?
.build()?;
if let Some(voting_keypair) = validator.voting_keypair {
println!("{:?}", voting_keypair.pk)
}
}
Ok(())
}

View File

@ -1,4 +1,5 @@
mod cli; mod cli;
mod deposits;
use clap::ArgMatches; use clap::ArgMatches;
use deposit_contract::DEPOSIT_GAS; use deposit_contract::DEPOSIT_GAS;
@ -6,7 +7,7 @@ use environment::{Environment, RuntimeContext};
use eth2_testnet_config::Eth2TestnetConfig; use eth2_testnet_config::Eth2TestnetConfig;
use futures::{future, Future, IntoFuture, Stream}; use futures::{future, Future, IntoFuture, Stream};
use rayon::prelude::*; use rayon::prelude::*;
use slog::{crit, error, info, Logger}; use slog::{error, info, Logger};
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@ -21,20 +22,8 @@ use web3::{
pub use cli::cli_app; pub use cli::cli_app;
/// Run the account manager, logging an error if the operation did not succeed.
pub fn run<T: EthSpec>(matches: &ArgMatches, mut env: Environment<T>) {
let log = env.core_context().log.clone();
match run_account_manager(matches, env) {
Ok(()) => (),
Err(e) => crit!(log, "Account manager failed"; "error" => e),
}
}
/// Run the account manager, returning an error if the operation did not succeed. /// Run the account manager, returning an error if the operation did not succeed.
fn run_account_manager<T: EthSpec>( pub fn run<T: EthSpec>(matches: &ArgMatches, mut env: Environment<T>) -> Result<(), String> {
matches: &ArgMatches,
mut env: Environment<T>,
) -> Result<(), String> {
let context = env.core_context(); let context = env.core_context();
let log = context.log.clone(); let log = context.log.clone();
@ -60,6 +49,7 @@ fn run_account_manager<T: EthSpec>(
match matches.subcommand() { match matches.subcommand() {
("validator", Some(matches)) => match matches.subcommand() { ("validator", Some(matches)) => match matches.subcommand() {
("deposited", Some(matches)) => deposits::cli_run(matches, env)?,
("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, env)?, ("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, env)?,
_ => { _ => {
return Err("Invalid 'validator new' command. See --help.".to_string()); return Err("Invalid 'validator new' command. See --help.".to_string());

View File

@ -8,7 +8,7 @@ edition = "2018"
eth1_test_rig = { path = "../../tests/eth1_test_rig" } eth1_test_rig = { path = "../../tests/eth1_test_rig" }
environment = { path = "../../lighthouse/environment" } environment = { path = "../../lighthouse/environment" }
toml = "^0.5" toml = "^0.5"
web3 = "0.8.0" web3 = "0.10.0"
[dependencies] [dependencies]
reqwest = "0.9" reqwest = "0.9"

View File

@ -1,10 +1,11 @@
//! Helper functions and an extension trait for Ethereum 2 ENRs. //! Helper functions and an extension trait for Ethereum 2 ENRs.
pub use libp2p::{core::identity::Keypair, discv5::enr::CombinedKey};
use super::ENR_FILENAME; use super::ENR_FILENAME;
use crate::types::{Enr, EnrBitfield}; use crate::types::{Enr, EnrBitfield};
use crate::NetworkConfig; use crate::NetworkConfig;
use libp2p::core::identity::Keypair; use libp2p::discv5::enr::EnrBuilder;
use libp2p::discv5::enr::{CombinedKey, EnrBuilder};
use slog::{debug, warn}; use slog::{debug, warn};
use ssz::{Decode, Encode}; use ssz::{Decode, Encode};
use ssz_types::BitVector; use ssz_types::BitVector;

View File

@ -2,13 +2,13 @@
pub(crate) mod enr; pub(crate) mod enr;
// Allow external use of the lighthouse ENR builder // Allow external use of the lighthouse ENR builder
pub use enr::build_enr; pub use enr::{build_enr, CombinedKey, Keypair};
use crate::metrics; use crate::metrics;
use crate::{error, Enr, NetworkConfig, NetworkGlobals}; use crate::{error, Enr, NetworkConfig, NetworkGlobals};
use enr::{Eth2Enr, BITFIELD_ENR_KEY, ETH2_ENR_KEY}; use enr::{Eth2Enr, BITFIELD_ENR_KEY, ETH2_ENR_KEY};
use futures::prelude::*; use futures::prelude::*;
use libp2p::core::{identity::Keypair, ConnectedPoint, Multiaddr, PeerId}; use libp2p::core::{ConnectedPoint, Multiaddr, PeerId};
use libp2p::discv5::enr::NodeId; use libp2p::discv5::enr::NodeId;
use libp2p::discv5::{Discv5, Discv5Event}; use libp2p::discv5::{Discv5, Discv5Event};
use libp2p::multiaddr::Protocol; use libp2p::multiaddr::Protocol;
@ -30,7 +30,7 @@ const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 120;
/// Initial delay between peer searches. /// Initial delay between peer searches.
const INITIAL_SEARCH_DELAY: u64 = 5; const INITIAL_SEARCH_DELAY: u64 = 5;
/// Local ENR storage filename. /// Local ENR storage filename.
const ENR_FILENAME: &str = "enr.dat"; pub const ENR_FILENAME: &str = "enr.dat";
/// Number of peers we'd like to have connected to a given long-lived subnet. /// Number of peers we'd like to have connected to a given long-lived subnet.
const TARGET_SUBNET_PEERS: u64 = 3; const TARGET_SUBNET_PEERS: u64 = 3;

View File

@ -22,4 +22,4 @@ pub use libp2p::{multiaddr, Multiaddr};
pub use libp2p::{PeerId, Swarm}; pub use libp2p::{PeerId, Swarm};
pub use peer_manager::{PeerDB, PeerInfo, PeerSyncStatus}; pub use peer_manager::{PeerDB, PeerInfo, PeerSyncStatus};
pub use rpc::RPCEvent; pub use rpc::RPCEvent;
pub use service::Service; pub use service::{Service, NETWORK_KEY_FILENAME};

View File

@ -27,7 +27,7 @@ use types::{EnrForkId, EthSpec};
type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>; type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>;
type Libp2pBehaviour<TSpec> = Behaviour<Substream<StreamMuxerBox>, TSpec>; type Libp2pBehaviour<TSpec> = Behaviour<Substream<StreamMuxerBox>, TSpec>;
const NETWORK_KEY_FILENAME: &str = "key"; pub const NETWORK_KEY_FILENAME: &str = "key";
/// The time in milliseconds to wait before banning a peer. This allows for any Goodbye messages to be /// The time in milliseconds to wait before banning a peer. This allows for any Goodbye messages to be
/// flushed and protocols to be negotiated. /// flushed and protocols to be negotiated.
const BAN_PEER_WAIT_TIMEOUT: u64 = 200; const BAN_PEER_WAIT_TIMEOUT: u64 = 200;

View File

@ -18,6 +18,7 @@ TL;DR isn't adequate.
## TL;DR ## TL;DR
```bash ```bash
make install-lcli
lcli new-testnet lcli new-testnet
lcli interop-genesis 128 lcli interop-genesis 128
lighthouse bn --testnet-dir ~/.lighthouse/testnet --dummy-eth1 --http --enr-match lighthouse bn --testnet-dir ~/.lighthouse/testnet --dummy-eth1 --http --enr-match
@ -27,7 +28,6 @@ lighthouse vc --testnet-dir ~/.lighthouse/testnet --allow-unsynced testnet insec
Optionally update the genesis time to now: Optionally update the genesis time to now:
```bash ```bash
<<<<<<< HEAD
lcli change-genesis-time ~/.lighthouse/testnet/genesis.ssz $(date +%s) lcli change-genesis-time ~/.lighthouse/testnet/genesis.ssz $(date +%s)
``` ```
@ -41,7 +41,7 @@ used for starting testnets and debugging.
Install `lcli` from the root directory of this repository with: Install `lcli` from the root directory of this repository with:
```bash ```bash
cargo install --path lcli --force make install-lcli
``` ```
### 1.2 Create a testnet directory ### 1.2 Create a testnet directory

View File

@ -0,0 +1,15 @@
[package]
name = "clap_utils"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "2.33.0"
hex = "0.3"
dirs = "2.0"
types = { path = "../../types" }
eth2_testnet_config = { path = "../eth2_testnet_config" }
eth2_ssz = { path = "../ssz" }

View File

@ -0,0 +1,116 @@
//! A helper library for parsing values from `clap::ArgMatches`.
use clap::ArgMatches;
use eth2_testnet_config::Eth2TestnetConfig;
use hex;
use ssz::Decode;
use std::path::PathBuf;
use std::str::FromStr;
use types::EthSpec;
/// Attempts to load the testnet dir at the path if `name` is in `matches`, returning an error if
/// the path cannot be found or the testnet dir is invalid.
///
/// If `name` is not in `matches`, attempts to return the "hard coded" testnet dir.
pub fn parse_testnet_dir_with_hardcoded_default<E: EthSpec>(
matches: &ArgMatches,
name: &'static str,
) -> Result<Eth2TestnetConfig<E>, String> {
parse_required::<PathBuf>(matches, name)
.and_then(|path| {
Eth2TestnetConfig::load(path.clone())
.map_err(|e| format!("Unable to open testnet dir at {:?}: {}", path, e))
})
.map(Result::Ok)
.unwrap_or_else(|_| {
Eth2TestnetConfig::hard_coded().map_err(|e| {
format!(
"The hard-coded testnet directory was invalid. \
This happens when Lighthouse is migrating between spec versions. \
Error : {}",
e
)
})
})
}
/// If `name` is in `matches`, parses the value as a path. Otherwise, attempts to find the user's
/// home directory and appends `default` to it.
pub fn parse_path_with_default_in_home_dir(
matches: &ArgMatches,
name: &'static str,
default: PathBuf,
) -> Result<PathBuf, String> {
matches
.value_of(name)
.map(|dir| {
dir.parse::<PathBuf>()
.map_err(|e| format!("Unable to parse {}: {}", name, e))
})
.unwrap_or_else(|| {
dirs::home_dir()
.map(|home| home.join(default))
.ok_or_else(|| format!("Unable to locate home directory. Try specifying {}", name))
})
}
/// Returns the value of `name` or an error if it is not in `matches` or does not parse
/// successfully using `std::string::FromStr`.
pub fn parse_required<T>(matches: &ArgMatches, name: &'static str) -> Result<T, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
parse_optional(matches, name)?.ok_or_else(|| format!("{} not specified", name))
}
/// Returns the value of `name` (if present) or an error if it does not parse successfully using
/// `std::string::FromStr`.
pub fn parse_optional<T>(matches: &ArgMatches, name: &'static str) -> Result<Option<T>, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
matches
.value_of(name)
.map(|val| {
val.parse()
.map_err(|e| format!("Unable to parse {}: {}", name, e))
})
.transpose()
}
/// Returns the value of `name` or an error if it is not in `matches` or does not parse
/// successfully using `ssz::Decode`.
///
/// Expects the value of `name` to be 0x-prefixed ASCII-hex.
pub fn parse_ssz_required<T: Decode>(
matches: &ArgMatches,
name: &'static str,
) -> Result<T, String> {
parse_ssz_optional(matches, name)?.ok_or_else(|| format!("{} not specified", name))
}
/// Returns the value of `name` (if present) or an error if it does not parse successfully using
/// `ssz::Decode`.
///
/// Expects the value of `name` (if any) to be 0x-prefixed ASCII-hex.
pub fn parse_ssz_optional<T: Decode>(
matches: &ArgMatches,
name: &'static str,
) -> Result<Option<T>, String> {
matches
.value_of(name)
.map(|val| {
if val.starts_with("0x") {
let vec = hex::decode(&val[2..])
.map_err(|e| format!("Unable to parse {} as hex: {:?}", name, e))?;
T::from_ssz_bytes(&vec)
.map_err(|e| format!("Unable to parse {} as SSZ: {:?}", name, e))
} else {
Err(format!("Unable to parse {}, must have 0x prefix", name))
}
})
.transpose()
}

View File

@ -22,7 +22,7 @@ impl From<ethabi::Error> for DecodeError {
} }
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000; pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 4_000_000; pub const DEPOSIT_GAS: usize = 400_000;
pub const ABI: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.json"); pub const ABI: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.json");
pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.bytecode"); pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.bytecode");
pub const DEPOSIT_DATA_LEN: usize = 420; // lol pub const DEPOSIT_DATA_LEN: usize = 420; // lol

View File

@ -27,3 +27,5 @@ dirs = "2.0"
genesis = { path = "../beacon_node/genesis" } genesis = { path = "../beacon_node/genesis" }
deposit_contract = { path = "../eth2/utils/deposit_contract" } deposit_contract = { path = "../eth2/utils/deposit_contract" }
tree_hash = { path = "../eth2/utils/tree_hash" } tree_hash = { path = "../eth2/utils/tree_hash" }
clap_utils = { path = "../eth2/utils/clap_utils" }
eth2-libp2p = { path = "../beacon_node/eth2-libp2p" }

View File

@ -1,12 +1,12 @@
use crate::helpers::{parse_hex_bytes, parse_u64};
use clap::ArgMatches; use clap::ArgMatches;
use clap_utils::{parse_required, parse_ssz_required};
use deposit_contract::{decode_eth1_tx_data, DEPOSIT_DATA_LEN}; use deposit_contract::{decode_eth1_tx_data, DEPOSIT_DATA_LEN};
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::EthSpec; use types::EthSpec;
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let rlp_bytes = parse_hex_bytes(matches, "deposit-data")?; let rlp_bytes = parse_ssz_required::<Vec<u8>>(matches, "deposit-data")?;
let amount = parse_u64(matches, "deposit-amount")?; let amount = parse_required(matches, "deposit-amount")?;
if rlp_bytes.len() != DEPOSIT_DATA_LEN { if rlp_bytes.len() != DEPOSIT_DATA_LEN {
return Err(format!( return Err(format!(

View File

@ -1,31 +1,35 @@
use clap::ArgMatches; use clap::ArgMatches;
use clap_utils;
use deposit_contract::{
testnet::{ABI, BYTECODE},
CONTRACT_DEPLOY_GAS,
};
use environment::Environment; use environment::Environment;
use eth1_test_rig::DepositContract; use futures::{Future, IntoFuture};
use std::fs::File; use std::path::PathBuf;
use std::io::Read;
use types::EthSpec; use types::EthSpec;
use web3::{transports::Http, Web3}; use web3::{
contract::{Contract, Options},
transports::Ipc,
types::{Address, U256},
Web3,
};
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let confirmations = matches let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
.value_of("confirmations") let from_address: Address = clap_utils::parse_required(matches, "from-address")?;
.ok_or_else(|| "Confirmations not specified")? let confirmations: usize = clap_utils::parse_required(matches, "confirmations")?;
.parse::<usize>()
.map_err(|e| format!("Failed to parse confirmations: {}", e))?;
let password = parse_password(matches)?; let (_event_loop_handle, transport) =
Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
let web3 = Web3::new(transport);
let endpoint = matches let bytecode = String::from_utf8(BYTECODE.to_vec()).map_err(|e| {
.value_of("eth1-endpoint")
.ok_or_else(|| "eth1-endpoint not specified")?;
let (_event_loop, transport) = Http::new(&endpoint).map_err(|e| {
format!( format!(
"Failed to start HTTP transport connected to ganache: {:?}", "Unable to parse deposit contract bytecode as utf-8: {:?}",
e e
) )
})?; })?;
let web3 = Web3::new(transport);
// It's unlikely that this will be the _actual_ deployment block, however it'll be close // It's unlikely that this will be the _actual_ deployment block, however it'll be close
// enough to serve our purposes. // enough to serve our purposes.
@ -37,54 +41,26 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
.block_on(web3.eth().block_number()) .block_on(web3.eth().block_number())
.map_err(|e| format!("Failed to get block number: {}", e))?; .map_err(|e| format!("Failed to get block number: {}", e))?;
info!("Present eth1 block number is {}", deploy_block); let address = env.runtime().block_on(
Contract::deploy(web3.eth(), &ABI)
.map_err(|e| format!("Unable to build contract deployer: {:?}", e))?
.confirmations(confirmations)
.options(Options {
gas: Some(U256::from(CONTRACT_DEPLOY_GAS)),
..Options::default()
})
.execute(bytecode, (), from_address)
.into_future()
.map_err(|e| format!("Unable to execute deployment: {:?}", e))
.and_then(|pending| {
pending.map_err(|e| format!("Unable to await pending contract: {:?}", e))
})
.map(|tx_receipt| tx_receipt.address())
.map_err(|e| format!("Failed to execute deployment: {:?}", e)),
)?;
info!("Deploying the bytecode at https://github.com/sigp/unsafe-eth2-deposit-contract",); println!("deposit_contract_address: {:?}", address);
println!("deposit_contract_deploy_block: {}", deploy_block);
info!(
"Submitting deployment transaction, waiting for {} confirmations",
confirmations
);
let deposit_contract = env
.runtime()
.block_on(DepositContract::deploy_testnet(
web3,
confirmations,
password,
))
.map_err(|e| format!("Failed to deploy contract: {}", e))?;
info!(
"Deposit contract deployed. address: {}, deploy_block: {}",
deposit_contract.address(),
deploy_block
);
Ok(()) Ok(())
} }
pub fn parse_password(matches: &ArgMatches) -> Result<Option<String>, String> {
if let Some(password_path) = matches.value_of("password") {
Ok(Some(
File::open(password_path)
.map_err(|e| format!("Unable to open password file: {:?}", e))
.and_then(|mut file| {
let mut password = String::new();
file.read_to_string(&mut password)
.map_err(|e| format!("Unable to read password file to string: {:?}", e))
.map(|_| password)
})
.map(|password| {
// Trim the linefeed from the end.
if password.ends_with('\n') {
password[0..password.len() - 1].to_string()
} else {
password
}
})?,
))
} else {
Ok(None)
}
}

View File

@ -0,0 +1,60 @@
use clap::ArgMatches;
use eth2_libp2p::{
discovery::{build_enr, CombinedKey, Keypair, ENR_FILENAME},
NetworkConfig, NETWORK_KEY_FILENAME,
};
use std::convert::TryInto;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::net::IpAddr;
use std::path::PathBuf;
use types::{EnrForkId, EthSpec};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let ip: IpAddr = clap_utils::parse_required(matches, "ip")?;
let udp_port: u16 = clap_utils::parse_required(matches, "udp-port")?;
let tcp_port: u16 = clap_utils::parse_required(matches, "tcp-port")?;
let output_dir: PathBuf = clap_utils::parse_required(matches, "output-dir")?;
if output_dir.exists() {
return Err(format!(
"{:?} already exists, will not override",
output_dir
));
}
let mut config = NetworkConfig::default();
config.enr_address = Some(ip);
config.enr_udp_port = Some(udp_port);
config.enr_tcp_port = Some(tcp_port);
let local_keypair = Keypair::generate_secp256k1();
let enr_key: CombinedKey = local_keypair
.clone()
.try_into()
.map_err(|e| format!("Unable to convert keypair: {:?}", e))?;
let enr = build_enr::<T>(&enr_key, &config, EnrForkId::default())
.map_err(|e| format!("Unable to create ENR: {:?}", e))?;
fs::create_dir_all(&output_dir).map_err(|e| format!("Unable to create output-dir: {:?}", e))?;
let mut enr_file = File::create(output_dir.join(ENR_FILENAME))
.map_err(|e| format!("Unable to create {}: {:?}", ENR_FILENAME, e))?;
enr_file
.write_all(&enr.to_base64().as_bytes())
.map_err(|e| format!("Unable to write ENR to {}: {:?}", ENR_FILENAME, e))?;
let secret_bytes = match local_keypair {
Keypair::Secp256k1(key) => key.secret().to_bytes(),
_ => return Err("Key is not a secp256k1 key".into()),
};
let mut key_file = File::create(output_dir.join(NETWORK_KEY_FILENAME))
.map_err(|e| format!("Unable to create {}: {:?}", NETWORK_KEY_FILENAME, e))?;
key_file
.write_all(&secret_bytes)
.map_err(|e| format!("Unable to write key to {}: {:?}", NETWORK_KEY_FILENAME, e))?;
Ok(())
}

View File

@ -5,7 +5,7 @@ mod change_genesis_time;
mod check_deposit_data; mod check_deposit_data;
mod deploy_deposit_contract; mod deploy_deposit_contract;
mod eth1_genesis; mod eth1_genesis;
mod helpers; mod generate_bootnode_enr;
mod interop_genesis; mod interop_genesis;
mod new_testnet; mod new_testnet;
mod parse_hex; mod parse_hex;
@ -18,6 +18,7 @@ use log::Level;
use parse_hex::run_parse_hex; use parse_hex::run_parse_hex;
use std::fs::File; use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use std::process;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use transition_blocks::run_transition_blocks; use transition_blocks::run_transition_blocks;
use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec}; use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec};
@ -27,8 +28,7 @@ fn main() {
let matches = App::new("Lighthouse CLI Tool") let matches = App::new("Lighthouse CLI Tool")
.about( .about(
"Performs various testing-related tasks, modelled after zcli. \ "Performs various testing-related tasks, including defining testnets.",
by @protolambda.",
) )
.arg( .arg(
Arg::with_name("spec") Arg::with_name("spec")
@ -40,6 +40,15 @@ fn main() {
.possible_values(&["minimal", "mainnet"]) .possible_values(&["minimal", "mainnet"])
.default_value("mainnet") .default_value("mainnet")
) )
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.global(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.subcommand( .subcommand(
SubCommand::with_name("genesis_yaml") SubCommand::with_name("genesis_yaml")
.about("Generates a genesis YAML file") .about("Generates a genesis YAML file")
@ -119,13 +128,22 @@ fn main() {
"Deploy a testing eth1 deposit contract.", "Deploy a testing eth1 deposit contract.",
) )
.arg( .arg(
Arg::with_name("eth1-endpoint") Arg::with_name("eth1-ipc")
.long("eth1-ipc")
.short("e") .short("e")
.long("eth1-endpoint") .value_name("ETH1_IPC_PATH")
.value_name("HTTP_SERVER") .help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true) .takes_value(true)
.default_value("http://localhost:8545") .required(true)
.help("The URL to the eth1 JSON-RPC http API."), )
.arg(
Arg::with_name("from-address")
.long("from-address")
.short("f")
.value_name("FROM_ETH1_ADDRESS")
.help("The address that will submit the contract creation. Must be unlocked.")
.takes_value(true)
.required(true)
) )
.arg( .arg(
Arg::with_name("confirmations") Arg::with_name("confirmations")
@ -135,13 +153,6 @@ fn main() {
.default_value("3") .default_value("3")
.help("The number of block confirmations before declaring the contract deployed."), .help("The number of block confirmations before declaring the contract deployed."),
) )
.arg(
Arg::with_name("password")
.long("password")
.value_name("FILE")
.takes_value(true)
.help("The password file to unlock the eth1 account (see --index)"),
)
) )
.subcommand( .subcommand(
SubCommand::with_name("refund-deposit-contract") SubCommand::with_name("refund-deposit-contract")
@ -149,37 +160,32 @@ fn main() {
"Calls the steal() function on a testnet eth1 contract.", "Calls the steal() function on a testnet eth1 contract.",
) )
.arg( .arg(
Arg::with_name("testnet-dir") Arg::with_name("eth1-ipc")
.short("d") .long("eth1-ipc")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg(
Arg::with_name("eth1-endpoint")
.short("e") .short("e")
.long("eth1-endpoint") .value_name("ETH1_IPC_PATH")
.value_name("HTTP_SERVER") .help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true) .takes_value(true)
.default_value("http://localhost:8545") .required(true)
.help("The URL to the eth1 JSON-RPC http API."),
) )
.arg( .arg(
Arg::with_name("password") Arg::with_name("from-address")
.long("password") .long("from-address")
.value_name("FILE") .short("f")
.value_name("FROM_ETH1_ADDRESS")
.help("The address that will submit the contract creation. Must be unlocked.")
.takes_value(true) .takes_value(true)
.help("The password file to unlock the eth1 account (see --index)"), .required(true)
) )
.arg( .arg(
Arg::with_name("account-index") Arg::with_name("contract-address")
.short("i") .long("contract-address")
.long("account-index") .short("c")
.value_name("INDEX") .value_name("CONTRACT_ETH1_ADDRESS")
.help("The address of the contract to be refunded. Its owner must match
--from-address.")
.takes_value(true) .takes_value(true)
.default_value("0") .required(true)
.help("The eth1 accounts[] index which will send the transaction"),
) )
) )
.subcommand( .subcommand(
@ -187,14 +193,6 @@ fn main() {
.about( .about(
"Listens to the eth1 chain and finds the genesis beacon state", "Listens to the eth1 chain and finds the genesis beacon state",
) )
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg( .arg(
Arg::with_name("eth1-endpoint") Arg::with_name("eth1-endpoint")
.short("e") .short("e")
@ -210,14 +208,6 @@ fn main() {
.about( .about(
"Produces an interop-compatible genesis state using deterministic keypairs", "Produces an interop-compatible genesis state using deterministic keypairs",
) )
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg( .arg(
Arg::with_name("validator-count") Arg::with_name("validator-count")
.long("validator-count") .long("validator-count")
@ -263,13 +253,6 @@ fn main() {
.about( .about(
"Produce a new testnet directory.", "Produce a new testnet directory.",
) )
.arg(
Arg::with_name("testnet-dir")
.long("testnet-dir")
.value_name("DIRECTORY")
.takes_value(true)
.help("The output path for the new testnet directory. Defaults to ~/.lighthouse/testnet"),
)
.arg( .arg(
Arg::with_name("min-genesis-time") Arg::with_name("min-genesis-time")
.long("min-genesis-time") .long("min-genesis-time")
@ -384,11 +367,55 @@ fn main() {
function signature."), function signature."),
) )
) )
.subcommand(
SubCommand::with_name("generate-bootnode-enr")
.about(
"Generates an ENR address to be used as a pre-genesis boot node..",
)
.arg(
Arg::with_name("ip")
.long("ip")
.value_name("IP_ADDRESS")
.takes_value(true)
.required(true)
.help("The IP address to be included in the ENR and used for discovery"),
)
.arg(
Arg::with_name("udp-port")
.long("udp-port")
.value_name("UDP_PORT")
.takes_value(true)
.required(true)
.help("The UDP port to be included in the ENR and used for discovery"),
)
.arg(
Arg::with_name("tcp-port")
.long("tcp-port")
.value_name("TCP_PORT")
.takes_value(true)
.required(true)
.help("The TCP port to be included in the ENR and used for application comms"),
)
.arg(
Arg::with_name("output-dir")
.long("output-dir")
.value_name("OUTPUT_DIRECTORY")
.takes_value(true)
.required(true)
.help("The directory in which to create the network dir"),
)
)
.get_matches(); .get_matches();
macro_rules! run_with_spec { macro_rules! run_with_spec {
($env_builder: expr) => { ($env_builder: expr) => {
run($env_builder, &matches) match run($env_builder, &matches) {
Ok(()) => process::exit(0),
Err(e) => {
println!("Failed to run lcli: {}", e);
process::exit(1)
}
}
}; };
} }
@ -403,14 +430,14 @@ fn main() {
} }
} }
fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) { fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) -> Result<(), String> {
let env = env_builder let env = env_builder
.multi_threaded_tokio_runtime() .multi_threaded_tokio_runtime()
.expect("should start tokio runtime") .map_err(|e| format!("should start tokio runtime: {:?}", e))?
.async_logger("trace", None) .async_logger("trace", None)
.expect("should start null logger") .map_err(|e| format!("should start null logger: {:?}", e))?
.build() .build()
.expect("should build env"); .map_err(|e| format!("should build env: {:?}", e))?;
match matches.subcommand() { match matches.subcommand() {
("genesis_yaml", Some(matches)) => { ("genesis_yaml", Some(matches)) => {
@ -449,30 +476,34 @@ fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
_ => unreachable!("guarded by slog possible_values"), _ => unreachable!("guarded by slog possible_values"),
}; };
info!("Genesis state YAML file created. Exiting successfully."); info!("Genesis state YAML file created. Exiting successfully.");
Ok(())
} }
("transition-blocks", Some(matches)) => run_transition_blocks::<T>(matches) ("transition-blocks", Some(matches)) => run_transition_blocks::<T>(matches)
.unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)), .map_err(|e| format!("Failed to transition blocks: {}", e)),
("pretty-hex", Some(matches)) => run_parse_hex::<T>(matches) ("pretty-hex", Some(matches)) => {
.unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e)), run_parse_hex::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
}
("deploy-deposit-contract", Some(matches)) => { ("deploy-deposit-contract", Some(matches)) => {
deploy_deposit_contract::run::<T>(env, matches) deploy_deposit_contract::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run deploy-deposit-contract command: {}", e)) .map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
} }
("refund-deposit-contract", Some(matches)) => { ("refund-deposit-contract", Some(matches)) => {
refund_deposit_contract::run::<T>(env, matches) refund_deposit_contract::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run refund-deposit-contract command: {}", e)) .map_err(|e| format!("Failed to run refund-deposit-contract command: {}", e))
} }
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, matches) ("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run eth1-genesis command: {}", e)), .map_err(|e| format!("Failed to run eth1-genesis command: {}", e)),
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(env, matches) ("interop-genesis", Some(matches)) => interop_genesis::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run interop-genesis command: {}", e)), .map_err(|e| format!("Failed to run interop-genesis command: {}", e)),
("change-genesis-time", Some(matches)) => change_genesis_time::run::<T>(matches) ("change-genesis-time", Some(matches)) => change_genesis_time::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run change-genesis-time command: {}", e)), .map_err(|e| format!("Failed to run change-genesis-time command: {}", e)),
("new-testnet", Some(matches)) => new_testnet::run::<T>(matches) ("new-testnet", Some(matches)) => new_testnet::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run new_testnet command: {}", e)), .map_err(|e| format!("Failed to run new_testnet command: {}", e)),
("check-deposit-data", Some(matches)) => check_deposit_data::run::<T>(matches) ("check-deposit-data", Some(matches)) => check_deposit_data::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run check-deposit-data command: {}", e)), .map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
(other, _) => error!("Unknown subcommand {}. See --help.", other), ("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches)
.map_err(|e| format!("Failed to run generate-bootnode-enr command: {}", e)),
(other, _) => Err(format!("Unknown subcommand {}. See --help.", other)),
} }
} }

View File

@ -1,8 +1,11 @@
use crate::helpers::*;
use clap::ArgMatches; use clap::ArgMatches;
use clap_utils::{
parse_optional, parse_path_with_default_in_home_dir, parse_required, parse_ssz_optional,
};
use eth2_testnet_config::Eth2TestnetConfig; use eth2_testnet_config::Eth2TestnetConfig;
use std::path::PathBuf; use std::path::PathBuf;
use types::{EthSpec, YamlConfig}; use std::time::{SystemTime, UNIX_EPOCH};
use types::{Address, EthSpec, YamlConfig};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let testnet_dir_path = parse_path_with_default_in_home_dir( let testnet_dir_path = parse_path_with_default_in_home_dir(
@ -10,18 +13,18 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
"testnet-dir", "testnet-dir",
PathBuf::from(".lighthouse/testnet"), PathBuf::from(".lighthouse/testnet"),
)?; )?;
let min_genesis_time = parse_u64_opt(matches, "min-genesis-time")?; let min_genesis_time = parse_optional(matches, "min-genesis-time")?;
let min_genesis_delay = parse_u64(matches, "min-genesis-delay")?; let min_genesis_delay = parse_required(matches, "min-genesis-delay")?;
let min_genesis_active_validator_count = let min_genesis_active_validator_count =
parse_u64(matches, "min-genesis-active-validator-count")?; parse_required(matches, "min-genesis-active-validator-count")?;
let min_deposit_amount = parse_u64(matches, "min-deposit-amount")?; let min_deposit_amount = parse_required(matches, "min-deposit-amount")?;
let max_effective_balance = parse_u64(matches, "max-effective-balance")?; let max_effective_balance = clap_utils::parse_required(matches, "max-effective-balance")?;
let effective_balance_increment = parse_u64(matches, "effective-balance-increment")?; let effective_balance_increment = parse_required(matches, "effective-balance-increment")?;
let ejection_balance = parse_u64(matches, "ejection-balance")?; let ejection_balance = parse_required(matches, "ejection-balance")?;
let eth1_follow_distance = parse_u64(matches, "eth1-follow-distance")?; let eth1_follow_distance = parse_required(matches, "eth1-follow-distance")?;
let deposit_contract_deploy_block = parse_u64(matches, "deposit-contract-deploy-block")?; let deposit_contract_deploy_block = parse_required(matches, "deposit-contract-deploy-block")?;
let genesis_fork_version = parse_fork_opt(matches, "genesis-fork-version")?; let genesis_fork_version = parse_ssz_optional::<[u8; 4]>(matches, "genesis-fork-version")?;
let deposit_contract_address = parse_address(matches, "deposit-contract-address")?; let deposit_contract_address: Address = parse_required(matches, "deposit-contract-address")?;
if testnet_dir_path.exists() { if testnet_dir_path.exists() {
return Err(format!( return Err(format!(
@ -57,3 +60,10 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
testnet.write_to_file(testnet_dir_path) testnet.write_to_file(testnet_dir_path)
} }
pub fn time_now() -> Result<u64, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.map_err(|e| format!("Unable to get time: {:?}", e))
}

View File

@ -1,12 +1,10 @@
use crate::deploy_deposit_contract::parse_password;
use clap::ArgMatches; use clap::ArgMatches;
use environment::Environment; use environment::Environment;
use eth2_testnet_config::Eth2TestnetConfig; use futures::Future;
use futures::{future, Future};
use std::path::PathBuf; use std::path::PathBuf;
use types::EthSpec; use types::EthSpec;
use web3::{ use web3::{
transports::Http, transports::Ipc,
types::{Address, TransactionRequest, U256}, types::{Address, TransactionRequest, U256},
Web3, Web3,
}; };
@ -15,102 +13,28 @@ use web3::{
pub const STEAL_FN_SIGNATURE: &[u8] = &[0xcf, 0x7a, 0x89, 0x65]; pub const STEAL_FN_SIGNATURE: &[u8] = &[0xcf, 0x7a, 0x89, 0x65];
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let endpoint = matches let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
.value_of("eth1-endpoint") let from: Address = clap_utils::parse_required(matches, "from-address")?;
.ok_or_else(|| "eth1-endpoint not specified")?; let contract_address: Address = clap_utils::parse_required(matches, "contract-address")?;
let account_index = matches let (_event_loop_handle, transport) =
.value_of("account-index") Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
.ok_or_else(|| "No account-index".to_string())? let web3 = Web3::new(transport);
.parse::<usize>()
.map_err(|e| format!("Unable to parse account-index: {}", e))?;
let password_opt = parse_password(matches)?; env.runtime().block_on(
web3.eth()
let testnet_dir = matches .send_transaction(TransactionRequest {
.value_of("testnet-dir")
.ok_or_else(|| ())
.and_then(|dir| dir.parse::<PathBuf>().map_err(|_| ()))
.unwrap_or_else(|_| {
dirs::home_dir()
.map(|home| home.join(".lighthouse").join("testnet"))
.expect("should locate home directory")
});
let eth2_testnet_config: Eth2TestnetConfig<T> = Eth2TestnetConfig::load(testnet_dir)?;
let (_event_loop, transport) = Http::new(&endpoint).map_err(|e| {
format!(
"Failed to start HTTP transport connected to ganache: {:?}",
e
)
})?;
let web3_1 = Web3::new(transport);
let web3_2 = web3_1.clone();
// Convert from `types::Address` to `web3::types::Address`.
let deposit_contract = Address::from_slice(
eth2_testnet_config
.deposit_contract_address()?
.as_fixed_bytes(),
);
let future = web3_1
.eth()
.accounts()
.map_err(|e| format!("Failed to get accounts: {:?}", e))
.and_then(move |accounts| {
accounts
.get(account_index)
.cloned()
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
})
.and_then(move |from_address| {
let future: Box<dyn Future<Item = Address, Error = String> + Send> =
if let Some(password) = password_opt {
// Unlock for only a single transaction.
let duration = None;
let future = web3_1
.personal()
.unlock_account(from_address, &password, duration)
.then(move |result| match result {
Ok(true) => Ok(from_address),
Ok(false) => Err("Eth1 node refused to unlock account".to_string()),
Err(e) => Err(format!("Eth1 unlock request failed: {:?}", e)),
});
Box::new(future)
} else {
Box::new(future::ok(from_address))
};
future
})
.and_then(move |from| {
let tx_request = TransactionRequest {
from, from,
to: Some(deposit_contract), to: Some(contract_address),
gas: Some(U256::from(400_000)), gas: Some(U256::from(400_000)),
gas_price: None, gas_price: None,
value: Some(U256::zero()), value: Some(U256::zero()),
data: Some(STEAL_FN_SIGNATURE.into()), data: Some(STEAL_FN_SIGNATURE.into()),
nonce: None, nonce: None,
condition: None, condition: None,
}; })
.map_err(|e| format!("Failed to call deposit fn: {:?}", e)),
web3_2 )?;
.eth()
.send_transaction(tx_request)
.map_err(|e| format!("Failed to call deposit fn: {:?}", e))
})
.map(move |tx| info!("Refund transaction submitted: eth1_tx_hash: {:?}", tx))
.map_err(move |e| error!("Unable to submit refund transaction: error: {}", e));
env.runtime()
.block_on(future)
.map_err(|()| "Failed to send transaction".to_string())?;
Ok(()) Ok(())
} }

View File

@ -19,3 +19,4 @@ environment = { path = "./environment" }
futures = "0.1.25" futures = "0.1.25"
validator_client = { "path" = "../validator_client" } validator_client = { "path" = "../validator_client" }
account_manager = { "path" = "../account_manager" } account_manager = { "path" = "../account_manager" }
clap_utils = { path = "../eth2/utils/clap_utils" }

View File

@ -28,6 +28,7 @@ pub struct EnvironmentBuilder<E: EthSpec> {
log: Option<Logger>, log: Option<Logger>,
eth_spec_instance: E, eth_spec_instance: E,
eth2_config: Eth2Config, eth2_config: Eth2Config,
testnet: Option<Eth2TestnetConfig<E>>,
} }
impl EnvironmentBuilder<MinimalEthSpec> { impl EnvironmentBuilder<MinimalEthSpec> {
@ -38,6 +39,7 @@ impl EnvironmentBuilder<MinimalEthSpec> {
log: None, log: None,
eth_spec_instance: MinimalEthSpec, eth_spec_instance: MinimalEthSpec,
eth2_config: Eth2Config::minimal(), eth2_config: Eth2Config::minimal(),
testnet: None,
} }
} }
} }
@ -50,6 +52,7 @@ impl EnvironmentBuilder<MainnetEthSpec> {
log: None, log: None,
eth_spec_instance: MainnetEthSpec, eth_spec_instance: MainnetEthSpec,
eth2_config: Eth2Config::mainnet(), eth2_config: Eth2Config::mainnet(),
testnet: None,
} }
} }
} }
@ -62,6 +65,7 @@ impl EnvironmentBuilder<InteropEthSpec> {
log: None, log: None,
eth_spec_instance: InteropEthSpec, eth_spec_instance: InteropEthSpec,
eth2_config: Eth2Config::interop(), eth2_config: Eth2Config::interop(),
testnet: None,
} }
} }
} }
@ -140,7 +144,7 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
/// Setups eth2 config using the CLI arguments. /// Setups eth2 config using the CLI arguments.
pub fn eth2_testnet_config( pub fn eth2_testnet_config(
mut self, mut self,
eth2_testnet_config: &Eth2TestnetConfig<E>, eth2_testnet_config: Eth2TestnetConfig<E>,
) -> Result<Self, String> { ) -> Result<Self, String> {
// Create a new chain spec from the default configuration. // Create a new chain spec from the default configuration.
self.eth2_config.spec = eth2_testnet_config self.eth2_config.spec = eth2_testnet_config
@ -155,6 +159,8 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
) )
})?; })?;
self.testnet = Some(eth2_testnet_config);
Ok(self) Ok(self)
} }
@ -169,6 +175,7 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
.ok_or_else(|| "Cannot build environment without log".to_string())?, .ok_or_else(|| "Cannot build environment without log".to_string())?,
eth_spec_instance: self.eth_spec_instance, eth_spec_instance: self.eth_spec_instance,
eth2_config: self.eth2_config, eth2_config: self.eth2_config,
testnet: self.testnet,
}) })
} }
} }
@ -211,6 +218,7 @@ pub struct Environment<E: EthSpec> {
log: Logger, log: Logger,
eth_spec_instance: E, eth_spec_instance: E,
pub eth2_config: Eth2Config, pub eth2_config: Eth2Config,
pub testnet: Option<Eth2TestnetConfig<E>>,
} }
impl<E: EthSpec> Environment<E> { impl<E: EthSpec> Environment<E> {

View File

@ -1,8 +1,9 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use beacon_node::{get_eth2_testnet_config, get_testnet_dir, ProductionBeaconNode}; use beacon_node::ProductionBeaconNode;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use clap_utils;
use env_logger::{Builder, Env}; use env_logger::{Builder, Env};
use environment::EnvironmentBuilder; use environment::EnvironmentBuilder;
use slog::{crit, info, warn}; use slog::{crit, info, warn};
@ -123,12 +124,13 @@ fn run<E: EthSpec>(
.ok_or_else(|| "Expected --debug-level flag".to_string())?; .ok_or_else(|| "Expected --debug-level flag".to_string())?;
let log_format = matches.value_of("log-format"); let log_format = matches.value_of("log-format");
let eth2_testnet_config = get_eth2_testnet_config(&get_testnet_dir(matches))?; let eth2_testnet_config =
clap_utils::parse_testnet_dir_with_hardcoded_default(matches, "testnet-dir")?;
let mut environment = environment_builder let mut environment = environment_builder
.async_logger(debug_level, log_format)? .async_logger(debug_level, log_format)?
.multi_threaded_tokio_runtime()? .multi_threaded_tokio_runtime()?
.eth2_testnet_config(&eth2_testnet_config)? .eth2_testnet_config(eth2_testnet_config)?
.build()?; .build()?;
let log = environment.core_context().log; let log = environment.core_context().log;
@ -164,7 +166,7 @@ fn run<E: EthSpec>(
if let Some(sub_matches) = matches.subcommand_matches("account_manager") { if let Some(sub_matches) = matches.subcommand_matches("account_manager") {
// Pass the entire `environment` to the account manager so it can run blocking operations. // Pass the entire `environment` to the account manager so it can run blocking operations.
account_manager::run(sub_matches, environment); account_manager::run(sub_matches, environment)?;
// Exit as soon as account manager returns control. // Exit as soon as account manager returns control.
return Ok(()); return Ok(());

View File

@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
web3 = "0.8.0" web3 = "0.10.0"
tokio = "0.1.22" tokio = "0.1.22"
futures = "0.1.25" futures = "0.1.25"
types = { path = "../../eth2/types"} types = { path = "../../eth2/types"}

View File

@ -41,3 +41,4 @@ bls = { path = "../eth2/utils/bls" }
remote_beacon_node = { path = "../eth2/utils/remote_beacon_node" } remote_beacon_node = { path = "../eth2/utils/remote_beacon_node" }
tempdir = "0.3" tempdir = "0.3"
rayon = "1.2.0" rayon = "1.2.0"
web3 = "0.10.0"

View File

@ -1,5 +1,6 @@
use bls::get_withdrawal_credentials; use bls::get_withdrawal_credentials;
use deposit_contract::encode_eth1_tx_data; use deposit_contract::{encode_eth1_tx_data, DEPOSIT_GAS};
use futures::{Future, IntoFuture};
use hex; use hex;
use ssz::{Decode, Encode}; use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
@ -12,6 +13,10 @@ use types::{
test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair, test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair,
PublicKey, SecretKey, Signature, PublicKey, SecretKey, Signature,
}; };
use web3::{
types::{Address, TransactionRequest, U256},
Transport, Web3,
};
const VOTING_KEY_PREFIX: &str = "voting"; const VOTING_KEY_PREFIX: &str = "voting";
const WITHDRAWAL_KEY_PREFIX: &str = "withdrawal"; const WITHDRAWAL_KEY_PREFIX: &str = "withdrawal";
@ -241,7 +246,7 @@ impl ValidatorDirectoryBuilder {
Ok(()) Ok(())
} }
pub fn write_eth1_data_file(mut self) -> Result<Self, String> { fn get_deposit_data(&self) -> Result<(Vec<u8>, u64), String> {
let voting_keypair = self let voting_keypair = self
.voting_keypair .voting_keypair
.as_ref() .as_ref()
@ -254,30 +259,35 @@ impl ValidatorDirectoryBuilder {
.amount .amount
.ok_or_else(|| "write_eth1_data_file requires an amount")?; .ok_or_else(|| "write_eth1_data_file requires an amount")?;
let spec = self.spec.as_ref().ok_or_else(|| "build requires a spec")?; let spec = self.spec.as_ref().ok_or_else(|| "build requires a spec")?;
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
&withdrawal_keypair.pk,
spec.bls_withdrawal_prefix_byte,
));
let mut deposit_data = DepositData {
pubkey: voting_keypair.pk.clone().into(),
withdrawal_credentials,
amount,
signature: Signature::empty_signature().into(),
};
deposit_data.signature = deposit_data.create_signature(&voting_keypair.sk, &spec);
let deposit_data = encode_eth1_tx_data(&deposit_data)
.map_err(|e| format!("Unable to encode eth1 deposit tx data: {:?}", e))?;
Ok((deposit_data, amount))
}
pub fn write_eth1_data_file(mut self) -> Result<Self, String> {
let path = self let path = self
.directory .directory
.as_ref() .as_ref()
.map(|directory| directory.join(ETH1_DEPOSIT_DATA_FILE)) .map(|directory| directory.join(ETH1_DEPOSIT_DATA_FILE))
.ok_or_else(|| "write_eth1_data_filer requires a directory")?; .ok_or_else(|| "write_eth1_data_filer requires a directory")?;
let deposit_data = { let (deposit_data, _) = self.get_deposit_data()?;
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
&withdrawal_keypair.pk,
spec.bls_withdrawal_prefix_byte,
));
let mut deposit_data = DepositData {
pubkey: voting_keypair.pk.clone().into(),
withdrawal_credentials,
amount,
signature: Signature::empty_signature().into(),
};
deposit_data.signature = deposit_data.create_signature(&voting_keypair.sk, &spec);
encode_eth1_tx_data(&deposit_data)
.map_err(|e| format!("Unable to encode eth1 deposit tx data: {:?}", e))?
};
if path.exists() { if path.exists() {
return Err(format!("Eth1 data file already exists at: {:?}", path)); return Err(format!("Eth1 data file already exists at: {:?}", path));
@ -293,6 +303,31 @@ impl ValidatorDirectoryBuilder {
Ok(self) Ok(self)
} }
pub fn submit_eth1_deposit<T: Transport>(
self,
web3: Web3<T>,
from: Address,
deposit_contract: Address,
) -> impl Future<Item = Self, Error = String> {
self.get_deposit_data()
.into_future()
.and_then(move |(deposit_data, deposit_amount)| {
web3.eth()
.send_transaction(TransactionRequest {
from,
to: Some(deposit_contract),
gas: Some(DEPOSIT_GAS.into()),
gas_price: None,
value: Some(from_gwei(deposit_amount)),
data: Some(deposit_data.into()),
nonce: None,
condition: None,
})
.map_err(|e| format!("Failed to send transaction: {:?}", e))
})
.map(|_tx| self)
}
pub fn build(self) -> Result<ValidatorDirectory, String> { pub fn build(self) -> Result<ValidatorDirectory, String> {
Ok(ValidatorDirectory { Ok(ValidatorDirectory {
directory: self.directory.ok_or_else(|| "build requires a directory")?, directory: self.directory.ok_or_else(|| "build requires a directory")?,
@ -303,6 +338,11 @@ impl ValidatorDirectoryBuilder {
} }
} }
/// Converts gwei to wei.
fn from_gwei(gwei: u64) -> U256 {
U256::from(gwei) * U256::exp10(9)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;