Validator on-boarding docs (#656)

* Add first draft of validator onboarding

* Update docs

* Add documentation link to main README

* Continue docs development

* Update book readme

* Update docs

* Allow vc to run without testnet subcommand

* Small change to onboarding docs

* Tidy CLI help messages

* Update docs

* Add check to val client see if beacon node is synced

* Add notifier service to validator client

* Re-order onboarding steps

* Update deposit contract address

* Update testnet dir

* Add note about public eth1 node

* Set default eth1 endpoint to sigp

* Fix broken test

* Try fix eth1 cache locking

* Be more specific about eth1 endpoint

* Increase gas limit for deposit

* Fix default deposit amount
This commit is contained in:
Paul Hauner 2019-12-09 22:42:36 +11:00 committed by GitHub
parent f1edca30ff
commit 3c6c06a505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 548 additions and 57 deletions

View File

@ -15,6 +15,8 @@ An open-source Ethereum 2.0 client, written in Rust and maintained by Sigma Prim
[Swagger Badge]: https://img.shields.io/badge/Open%20API-0.2.0-success [Swagger Badge]: https://img.shields.io/badge/Open%20API-0.2.0-success
[Swagger Link]: https://app.swaggerhub.com/apis-docs/spble/lighthouse_rest_api/0.2.0 [Swagger Link]: https://app.swaggerhub.com/apis-docs/spble/lighthouse_rest_api/0.2.0
[Documentation](http://lighthouse-book.sigmaprime.io/)
![terminalize](https://i.postimg.cc/kG11dpCW/lighthouse-cli-png.gif) ![terminalize](https://i.postimg.cc/kG11dpCW/lighthouse-cli-png.gif)
## Overview ## Overview

View File

@ -16,8 +16,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.long("deposit-value") .long("deposit-value")
.value_name("GWEI") .value_name("GWEI")
.takes_value(true) .takes_value(true)
.default_value("32000000000") .default_value("3200000000")
.help("The deposit amount in Gwei (not Wei). Default is 32 ETH."), .help("The deposit amount in Gwei (not Wei). Default is 3.2 ETH."),
) )
.arg( .arg(
Arg::with_name("send-deposits") Arg::with_name("send-deposits")

View File

@ -306,8 +306,7 @@ impl Service {
let log = self.log.clone(); let log = self.log.clone();
let update_interval = Duration::from_millis(self.config().auto_update_interval_millis); let update_interval = Duration::from_millis(self.config().auto_update_interval_millis);
loop_fn((), move |()| { let loop_future = loop_fn((), move |()| {
let exit = exit.clone();
let service = service.clone(); let service = service.clone();
let log_a = log.clone(); let log_a = log.clone();
let log_b = log.clone(); let log_b = log.clone();
@ -344,16 +343,11 @@ impl Service {
); );
} }
// Do not break the loop if there is an timer failure. // Do not break the loop if there is an timer failure.
Ok(()) Ok(Loop::Continue(()))
})
.map(move |_| {
if exit.is_live() {
Loop::Continue(())
} else {
Loop::Break(())
}
})
}) })
});
exit.until(loop_future).map(|_: Option<()>| ())
} }
/// Contacts the remote eth1 node and attempts to import deposit logs up to the configured /// Contacts the remote eth1 node and attempts to import deposit logs up to the configured

View File

@ -6,7 +6,9 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.visible_aliases(&["b", "bn", "beacon"]) .visible_aliases(&["b", "bn", "beacon"])
.version(crate_version!()) .version(crate_version!())
.author("Sigma Prime <contact@sigmaprime.io>") .author("Sigma Prime <contact@sigmaprime.io>")
.about("Eth 2.0 Client") .about("The primary component which connects to the Ethereum 2.0 P2P network and \
downloads, verifies and stores blocks. Provides a HTTP API for querying \
the beacon chain and publishing messages to the network.")
/* /*
* Configuration directory locations. * Configuration directory locations.
*/ */
@ -187,7 +189,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.value_name("HTTP-ENDPOINT") .value_name("HTTP-ENDPOINT")
.help("Specifies the server for a web3 connection to the Eth1 chain.") .help("Specifies the server for a web3 connection to the Eth1 chain.")
.takes_value(true) .takes_value(true)
.default_value("http://localhost:8545") .default_value("https://goerli.public.sigp.io")
) )
.arg( .arg(
Arg::with_name("slots-per-restore-point") Arg::with_name("slots-per-restore-point")

View File

@ -3,7 +3,7 @@
Contains an [mdBook](https://github.com/rust-lang-nursery/mdBook) that serves Contains an [mdBook](https://github.com/rust-lang-nursery/mdBook) that serves
as the primary source of Lighthouse user documentation. as the primary source of Lighthouse user documentation.
The book is hosted at [lighthouse-book.sigmaprime.io](http://lighthouse-book.sigmaprime.io). The book is hosted at [lighthouse-book.sigmaprime.io](http://lighthouse-book.sigmaprime.io).i
## Usage ## Usage
@ -14,4 +14,4 @@ best source of information for building the book.
1. Install mdBook: `$ cargo install mdbook` 1. Install mdBook: `$ cargo install mdbook`
1. Build the book, open it in a browser and build after file changes: `$ mdbook 1. Build the book, open it in a browser and build after file changes: `$ mdbook
watch --open` serve --open`

View File

@ -1,7 +1,8 @@
# Summary # Summary
* [Introduction](./intro.md) * [Introduction](./intro.md)
* [Installation](./installation.md) * [Become a Validator](./become-a-validator.md)
* [Introduction](./intro.md)
* [Docker](./docker.md) * [Docker](./docker.md)
* [CLI](./cli.md) * [CLI](./cli.md)
* [Testnets](./testnets.md) * [Testnets](./testnets.md)

View File

@ -0,0 +1,179 @@
# Become an Ethereum 2.0 Validator*
_* Testnet validator_
Running Lighthouse validator is easy if you're familiar with the terminal. It
runs on Linux, MacOS and Windows.
Before you start, you'll need [Metamask](https://metamask.io/) and 3.2 gETH
(Goerli ETH). We recommend the [mudit.blog
faucet](https://faucet.goerli.mudit.blog/) for those familiar with Goerli, or
[goerli.net](https://goerli.net/) for an overview of the testnet.
### 1. Download and install Lighthouse
If you already have Rust installed, you can install Lighthouse with the
following three commands:
- `$ git clone https://github.com/sigp/lighthouse.git`
- `$ cd lighthouse`
- `$ make`
You've completed this step when you can run `$ lighthouse --help` and see the
help menu.
> - If you're not familiar with Rust or you'd like more detailed instructions, see
> the [Installation Guide](./installation.md).
> - The [Docker Guide](./docker.md) is great if you have Docker installed and would
> like to avoid installing Rust.
### 2. Start your Beacon Node
The beacon node is the core component of Eth2, it connects to other peers over
the Internet and maintains a view of the chain.
Start your beacon node with:
```bash
$ lighthouse beacon --eth1 --http
```
You're beacon node has started syncing when you see the following (truncated)
log:
```
Dec 09 12:57:18.026 INFO Syncing distance: 16837 slots (2 days 8 hrs), ...
```
It has finished syncing once you see the following (truncated) log:
```
Dec 09 12:27:06.010 INFO Synced slot: 16835, ...
```
> - The `--http` flag enables the HTTP API for the validator client.
> - The `--eth1` flag tells the beacon node that it should sync with an Ethereum
> 1 node (e.g., Geth). This is only required if you wish to run a validator.
> - We are hosting a public Goerli archive node and have set this as the
> default, but you can specify your own Eth1 node using the `--eth1-endpoint`
> flag. Presently we require the node to be a full archive node, but we're
> working to [fix](https://github.com/sigp/lighthouse/issues/637) this.
### 3. Generate your validator key
Generate new validator BLS keypairs using:
```shell
$ lighthouse account validator new random
```
You've completed this step when you see the equivalent line:
```
Dec 02 21:42:01.337 INFO Generated validator directories count: 1, base_path: "/home/karl/.lighthouse/validators"
```
> - This will generate a new _validator directory_ in the `.lighthouse/validators`
> directory. Your validator directory will be identified by it's public key,
> which looks something like `0xc483de...`. You'll need to find this directory
> for the next step.
> - These keys are good enough for the Lighthouse testnet, however they shouldn't
> be considered secure until we've undergone a security audit (planned Jan
> 2020).
### 4. Start your validator client
For security reasons, the validator client runs separately to the beacon node.
The validator client stores private keys and signs messages generated by the
beacon node.
You'll need both your beacon node _and_ validator client running if you want to
stake.
Start the validator client with:
```bash
$ lighthouse validator
```
The validator client is running and has found your validator keys from step 3
when you see the following log:
```
Dec 09 13:08:59.171 INFO Loaded validator keypair store voting_validators: 1
Dec 09 13:09:09.000 INFO Awaiting activation slot: 17787, ...
```
If your beacon node hasn't finished syncing yet, you'll see some `ERRO`
messages indicating that your node isn't synced yet. It is safest to wait for
your node to sync before moving onto the next step, otherwise your validator
may active before you're able to produce blocks and attestations. However, it
generally takes 4-8+ hours after deposit for a validator to become active. If
your `est_time` is less than 4 hours, you _should_ be fine to just move to the
next step. After all, this is a testnet and you're only risking Goerli ETH.
### 5. Submit your deposit
<div class="form-signin" id="uploadDiv">
<p>Upload the <code>eth1_deposit_data.rlp</code> file from your validator
directory (created in step 3) to submit your 3.2 Goerli-ETH
deposit using Metamask.</p>
<p>Hint: it's generally in the <code>$HOME/.lighthouse/validators/0x...</code> directory</p>
<input id="fileInput" type="file" style="display: none">
<button id="uploadButton" class="btn btn-lg btn-primary btn-block"
type="submit">Upload and Submit Deposit</button>
</div>
<div class="form-signin" id="waitingDiv" style="display: none">
<p>Your validator deposit was submitted and this step is complete.</p>
<p>See the transaction on <a id="txLink" target="_blank"
href="https://etherscan.io">Etherscan</a>
or <a href="">reload</a> to perform another deposit.</p>
</div>
<div class="form-signin" id="errorDiv" style="display: none">
<h4 class="h3 mb-3 font-weight-normal">Error</h4>
<p id="errorText">Unknown error.</p>
</div>
> This deposit is using gETH (Goerli ETH) which has no real value. Don't ever
> send _real_ ETH to our deposit contract!
## Next steps
Leave your beacon node and validator client running and you'll see logs as the
beacon node keeps synced with the network and the validator client produces
blocks and attestations.
It will take some time (minutes to hours) for the beacon chain to process and
activate your validator, however you'll know you're active when the validator
client starts successfully publishing attestations each slot:
```
Dec 03 08:49:40.053 INFO Successfully published attestation slot: 98, committee_index: 0, head_block: 0xa208…7fd5,
```
Although you'll produce an attestation each slot, it's less common to produce a
block. Watch for the block production logs too:
```
Dec 03 08:49:36.225 INFO Successfully published block slot: 98, attestations: 2, deposits: 0, service: block
```
If you see any `ERRO` (error) logs, please reach out on
[Discord](https://discord.gg/cyAszAh) or [create an
issue](https://github.com/sigp/lighthouse/issues/new).
Happy staking!
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script charset="utf-8"
src="https://cdn.ethers.io/scripts/ethers-v4.min.js"
type="text/javascript">
</script>
<script src="js/deposit.js"></script>

127
book/src/js/deposit.js Normal file
View File

@ -0,0 +1,127 @@
const NETWORK = "5";
const NETWORK_NAME = "Goerli Test Network";
const DEPOSIT_CONTRACT = "0x13e4d66c7215d7b63fec7b52fc65e6655093d906";
const DEPOSIT_AMOUNT_ETH = "3.2";
const GAS_LIMIT = "4000000";
const DEPOSIT_DATA_BYTES = 420;
let PREVIOUS_NON_ERROR_STATE = "";
$(document).ready(function(){
if (typeof window.ethereum !== 'undefined') {
ethereum.on('networkChanged', function (accounts) {
checkNetwork()
})
PREVIOUS_NON_ERROR_STATE = "upload";
checkNetwork()
} else {
console.error("No metamask detected!")
triggerError("Metamask is not installed.<br> <a href='https://metamask.io'>Get Metamask.</a>")
}
$("#fileInput").change(function() {
openFile(this.files[0])
});
$("#uploadButton").on("click", function() {
$("#fileInput").trigger("click");
});
});
function checkNetwork() {
if (window.ethereum.networkVersion === NETWORK) {
setUiState(PREVIOUS_NON_ERROR_STATE)
} else {
triggerError("Please set Metamask to use " + NETWORK_NAME + ".")
}
}
function doDeposit(deposit_data) {
const ethereum = window.ethereum;
const utils = ethers.utils;
let wei = utils.parseEther(DEPOSIT_AMOUNT_ETH);
let gasLimit = utils.bigNumberify(GAS_LIMIT);
ethereum.enable()
.then(function (accounts) {
let params = [{
"from": accounts[0],
"to": DEPOSIT_CONTRACT,
"gas": utils.hexlify(gasLimit),
"value": utils.hexlify(wei),
"data": deposit_data
}]
ethereum.sendAsync({
method: 'eth_sendTransaction',
params: params,
from: accounts[0], // Provide the user's account to use.
}, function (err, result) {
if (err !== null) {
triggerError("<p>" + err.message + "</p><p><a href=''>Reload</a> the window to try again.</p>")
} else {
let tx_hash = result.result;
$("#txLink").attr("href", "https://goerli.etherscan.io/tx/" + tx_hash);
setUiState("waiting");
}
})
})
.catch(function (error) {
triggerError("Unable to get Metamask accounts.<br>Reload page to try again.")
})
}
function openFile(file) {
var reader = new FileReader();
reader.onload = function () {
let data = reader.result;
if (data.startsWith("0x")) {
if (data.length === DEPOSIT_DATA_BYTES * 2 + 2) {
doDeposit(data)
} else {
triggerError("Invalid eth1_deposit_file. Bad length.")
}
} else {
triggerError("Invalid eth1_deposit_file. Did not start with 0x.")
}
}
reader.readAsBinaryString(file);
}
function triggerError(text) {
$("#errorText").html(text);
setUiState("error");
}
function setUiState(state) {
if (state === "upload") {
$('#uploadDiv').show();
$('#depositDiv').hide();
$('#waitingDiv').hide();
$('#errorDiv').hide();
} else if (state == "deposit") {
$('#uploadDiv').hide();
$('#depositDiv').show();
$('#waitingDiv').hide();
$('#errorDiv').hide();
} else if (state == "error") {
$('#uploadDiv').hide();
$('#depositDiv').hide();
$('#waitingDiv').hide();
$('#errorDiv').show();
} else if (state == "waiting") {
$('#uploadDiv').hide();
$('#depositDiv').hide();
$('#waitingDiv').show();
$('#errorDiv').hide();
}
if (state !== "error") {
PREVIOUS_NON_ERROR_STATE = state;
}
}

View File

@ -198,9 +198,9 @@ impl<E: EthSpec> Eth2TestnetConfig<E> {
mod tests { mod tests {
use super::*; use super::*;
use tempdir::TempDir; use tempdir::TempDir;
use types::{Eth1Data, Hash256, MinimalEthSpec, YamlConfig}; use types::{Eth1Data, Hash256, MainnetEthSpec, YamlConfig};
type E = MinimalEthSpec; type E = MainnetEthSpec;
#[test] #[test]
fn hard_coded_works() { fn hard_coded_works() {

View File

@ -1,13 +1,13 @@
FAR_FUTURE_EPOCH: 18446744073709551615 FAR_FUTURE_EPOCH: 18446744073709551615
BASE_REWARDS_PER_EPOCH: 4 BASE_REWARDS_PER_EPOCH: 4
DEPOSIT_CONTRACT_TREE_DEPTH: 32 DEPOSIT_CONTRACT_TREE_DEPTH: 32
SECONDS_PER_DAY: 480 SECONDS_PER_DAY: 2400
MAX_COMMITTEES_PER_SLOT: 4 MAX_COMMITTEES_PER_SLOT: 64
TARGET_COMMITTEE_SIZE: 4 TARGET_COMMITTEE_SIZE: 128
MIN_PER_EPOCH_CHURN_LIMIT: 4 MIN_PER_EPOCH_CHURN_LIMIT: 4
CHURN_LIMIT_QUOTIENT: 65536 CHURN_LIMIT_QUOTIENT: 65536
SHUFFLE_ROUND_COUNT: 10 SHUFFLE_ROUND_COUNT: 90
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64 MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
MIN_GENESIS_TIME: 0 MIN_GENESIS_TIME: 0
MIN_DEPOSIT_AMOUNT: 100 MIN_DEPOSIT_AMOUNT: 100
MAX_EFFECTIVE_BALANCE: 3200000000 MAX_EFFECTIVE_BALANCE: 3200000000
@ -35,11 +35,11 @@ DOMAIN_VOLUNTARY_EXIT: 0x04000000
JUSTIFICATION_BITS_LENGTH: 0x04000000 JUSTIFICATION_BITS_LENGTH: 0x04000000
MAX_VALIDATORS_PER_COMMITTEE: 2048 MAX_VALIDATORS_PER_COMMITTEE: 2048
GENESIS_EPOCH: 0 GENESIS_EPOCH: 0
SLOTS_PER_EPOCH: 8 SLOTS_PER_EPOCH: 32
SLOTS_PER_ETH1_VOTING_PERIOD: 16 SLOTS_PER_ETH1_VOTING_PERIOD: 1024
SLOTS_PER_HISTORICAL_ROOT: 64 SLOTS_PER_HISTORICAL_ROOT: 8192
EPOCHS_PER_HISTORICAL_VECTOR: 64 EPOCHS_PER_HISTORICAL_VECTOR: 65536
EPOCHS_PER_SLASHINGS_VECTOR: 64 EPOCHS_PER_SLASHINGS_VECTOR: 8192
HISTORICAL_ROOTS_LIMIT: 16777216 HISTORICAL_ROOTS_LIMIT: 16777216
VALIDATOR_REGISTRY_LIMIT: 1099511627776 VALIDATOR_REGISTRY_LIMIT: 1099511627776
MAX_PROPOSER_SLASHINGS: 16 MAX_PROPOSER_SLASHINGS: 16
@ -47,3 +47,4 @@ MAX_ATTESTER_SLASHINGS: 1
MAX_ATTESTATIONS: 128 MAX_ATTESTATIONS: 128
MAX_DEPOSITS: 16 MAX_DEPOSITS: 16
MAX_VOLUNTARY_EXITS: 16 MAX_VOLUNTARY_EXITS: 16
ETH1_FOLLOW_DISTANCE: 16

View File

@ -1 +1 @@
1743571 1773705

View File

@ -1 +1 @@
0xf382356688ae7dd3c2d6deb7e79c3ffe68816251 0x13e4d66c7215d7b63fec7b52fc65e6655093d906

View File

@ -23,7 +23,10 @@ fn main() {
let matches = App::new("Lighthouse") let matches = App::new("Lighthouse")
.version(crate_version!()) .version(crate_version!())
.author("Sigma Prime <contact@sigmaprime.io>") .author("Sigma Prime <contact@sigmaprime.io>")
.about("Eth 2.0 Client") .about(
"Ethereum 2.0 client by Sigma Prime. Provides a full-featured beacon \
node, a validator client and utilities for managing validator accounts.",
)
.arg( .arg(
Arg::with_name("spec") Arg::with_name("spec")
.short("s") .short("s")

View File

@ -4,7 +4,8 @@ use clap::{App, Arg, SubCommand};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> { pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new("validator_client") App::new("validator_client")
.visible_aliases(&["v", "vc", "validator"]) .visible_aliases(&["v", "vc", "validator"])
.about("Ethereum 2.0 Validator Client") .about("When connected to a beacon node, performs the duties of a staked \
validator (e.g., proposing blocks and attestations).")
.arg( .arg(
Arg::with_name("server") Arg::with_name("server")
.long("server") .long("server")

View File

@ -80,10 +80,13 @@ impl Config {
.into(), .into(),
); );
} }
process_testnet_subcommand(sub_cli_args, config) process_testnet_subcommand(sub_cli_args, config)?
} }
_ => return Err("You must use the testnet command. See '--help'.".into()), _ => {
}?; config.key_source = KeySource::Disk;
config
}
};
Ok(config) Ok(config)
} }

View File

@ -1,10 +1,10 @@
use crate::validator_store::ValidatorStore; use crate::validator_store::ValidatorStore;
use environment::RuntimeContext; use environment::RuntimeContext;
use exit_future::Signal; use exit_future::Signal;
use futures::{Future, IntoFuture, Stream}; use futures::{future, Future, IntoFuture, Stream};
use parking_lot::RwLock; use parking_lot::RwLock;
use remote_beacon_node::RemoteBeaconNode; use remote_beacon_node::RemoteBeaconNode;
use slog::{crit, error, info, trace, warn}; use slog::{crit, debug, error, info, trace, warn};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
@ -74,6 +74,34 @@ pub struct DutiesStore {
} }
impl DutiesStore { impl DutiesStore {
/// Returns the total number of validators that should propose in the given epoch.
fn proposer_count(&self, epoch: Epoch) -> usize {
self.store
.read()
.iter()
.filter(|(_validator_pubkey, validator_map)| {
validator_map
.get(&epoch)
.map(|duties| !duties.block_proposal_slots.is_empty())
.unwrap_or_else(|| false)
})
.count()
}
/// Returns the total number of validators that should attest in the given epoch.
fn attester_count(&self, epoch: Epoch) -> usize {
self.store
.read()
.iter()
.filter(|(_validator_pubkey, validator_map)| {
validator_map
.get(&epoch)
.map(|duties| duties.attestation_slot.is_some())
.unwrap_or_else(|| false)
})
.count()
}
fn block_producers(&self, slot: Slot, slots_per_epoch: u64) -> Vec<PublicKey> { fn block_producers(&self, slot: Slot, slots_per_epoch: u64) -> Vec<PublicKey> {
self.store self.store
.read() .read()
@ -219,7 +247,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
pub struct Inner<T, E: EthSpec> { pub struct Inner<T, E: EthSpec> {
store: Arc<DutiesStore>, store: Arc<DutiesStore>,
validator_store: ValidatorStore<T, E>, validator_store: ValidatorStore<T, E>,
slot_clock: T, pub(crate) slot_clock: T,
beacon_node: RemoteBeaconNode<E>, beacon_node: RemoteBeaconNode<E>,
context: RuntimeContext<E>, context: RuntimeContext<E>,
} }
@ -249,6 +277,21 @@ impl<T, E: EthSpec> Deref for DutiesService<T, E> {
} }
impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> { impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
/// Returns the total number of validators known to the duties service.
pub fn total_validator_count(&self) -> usize {
self.validator_store.num_voting_validators()
}
/// Returns the total number of validators that should propose in the given epoch.
pub fn proposer_count(&self, epoch: Epoch) -> usize {
self.store.proposer_count(epoch)
}
/// Returns the total number of validators that should attest in the given epoch.
pub fn attester_count(&self, epoch: Epoch) -> usize {
self.store.attester_count(epoch)
}
/// Returns the pubkeys of the validators which are assigned to propose in the given slot. /// Returns the pubkeys of the validators which are assigned to propose in the given slot.
/// ///
/// In normal cases, there should be 0 or 1 validators returned. In extreme cases (i.e., deep forking) /// In normal cases, there should be 0 or 1 validators returned. In extreme cases (i.e., deep forking)
@ -313,6 +356,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
let service_1 = self.clone(); let service_1 = self.clone();
let service_2 = self.clone(); let service_2 = self.clone();
let service_3 = self.clone(); let service_3 = self.clone();
let service_4 = self.clone();
let log_1 = self.context.log.clone(); let log_1 = self.context.log.clone();
let log_2 = self.context.log.clone(); let log_2 = self.context.log.clone();
@ -342,7 +386,36 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
}) })
.and_then(move |epoch| { .and_then(move |epoch| {
let log = service_2.context.log.clone(); let log = service_2.context.log.clone();
service_2.update_epoch(epoch).then(move |result| {
service_2
.beacon_node
.http
.beacon()
.get_head()
.map(move |head| (epoch, head.slot.epoch(E::slots_per_epoch())))
.map_err(move |e| {
error!(
log,
"Failed to contact beacon node";
"error" => format!("{:?}", e)
)
})
})
.and_then(move |(current_epoch, beacon_head_epoch)| {
let log = service_3.context.log.clone();
let future: Box<dyn Future<Item = (), Error = ()> + Send> =
if beacon_head_epoch + 1 < current_epoch {
error!(
log,
"Beacon node is not synced";
"node_head_epoch" => format!("{}", beacon_head_epoch),
"current_epoch" => format!("{}", current_epoch),
);
Box::new(future::ok(()))
} else {
Box::new(service_3.update_epoch(current_epoch).then(move |result| {
if let Err(e) = result { if let Err(e) = result {
error!( error!(
log, log,
@ -351,15 +424,18 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
); );
} }
let log = service_3.context.log.clone(); let log = service_4.context.log.clone();
service_3.update_epoch(epoch + 1).map_err(move |e| { service_4.update_epoch(current_epoch + 1).map_err(move |e| {
error!( error!(
log, log,
"Failed to get next epoch duties"; "Failed to get next epoch duties";
"http_error" => format!("{:?}", e) "http_error" => format!("{:?}", e)
); );
}) })
}) }))
};
future
}) })
.map(|_| ()) .map(|_| ())
} }
@ -394,7 +470,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
.insert(epoch, duties.clone(), E::slots_per_epoch()) .insert(epoch, duties.clone(), E::slots_per_epoch())
{ {
InsertOutcome::NewValidator => { InsertOutcome::NewValidator => {
info!( debug!(
log, log,
"First duty assignment for validator"; "First duty assignment for validator";
"proposal_slots" => format!("{:?}", &duties.block_proposal_slots), "proposal_slots" => format!("{:?}", &duties.block_proposal_slots),

View File

@ -4,6 +4,7 @@ mod cli;
mod config; mod config;
mod duties_service; mod duties_service;
mod fork_service; mod fork_service;
mod notifier;
mod validator_store; mod validator_store;
pub mod validator_directory; pub mod validator_directory;
@ -22,6 +23,7 @@ use futures::{
future::{self, loop_fn, Loop}, future::{self, loop_fn, Loop},
Future, IntoFuture, Future, IntoFuture,
}; };
use notifier::spawn_notifier;
use remote_beacon_node::RemoteBeaconNode; use remote_beacon_node::RemoteBeaconNode;
use slog::{error, info, Logger}; use slog::{error, info, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
@ -258,7 +260,16 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.start_update_service(&self.context.eth2_config.spec) .start_update_service(&self.context.eth2_config.spec)
.map_err(|e| format!("Unable to start attestation service: {}", e))?; .map_err(|e| format!("Unable to start attestation service: {}", e))?;
self.exit_signals = vec![duties_exit, fork_exit, block_exit, attestation_exit]; let notifier_exit =
spawn_notifier(self).map_err(|e| format!("Failed to start notifier: {}", e))?;
self.exit_signals = vec![
duties_exit,
fork_exit,
block_exit,
attestation_exit,
notifier_exit,
];
Ok(()) Ok(())
} }

View File

@ -0,0 +1,91 @@
use crate::ProductionValidatorClient;
use exit_future::Signal;
use futures::{Future, Stream};
use slog::{error, info};
use slot_clock::SlotClock;
use std::time::{Duration, Instant};
use tokio::timer::Interval;
use types::EthSpec;
/// Spawns a notifier service which periodically logs information about the node.
pub fn spawn_notifier<T: EthSpec>(client: &ProductionValidatorClient<T>) -> Result<Signal, String> {
let context = client.context.service_context("notifier".into());
let slot_duration = Duration::from_millis(context.eth2_config.spec.milliseconds_per_slot);
let duration_to_next_slot = client
.duties_service
.slot_clock
.duration_to_next_slot()
.ok_or_else(|| "slot_notifier unable to determine time to next slot")?;
// Run this half way through each slot.
let start_instant = Instant::now() + duration_to_next_slot + (slot_duration / 2);
// Run this each slot.
let interval_duration = slot_duration;
let duties_service = client.duties_service.clone();
let log_1 = context.log.clone();
let log_2 = context.log.clone();
let interval_future = Interval::new(start_instant, interval_duration)
.map_err(
move |e| error!(log_1, "Slot notifier timer failed"; "error" => format!("{:?}", e)),
)
.for_each(move |_| {
let log = log_2.clone();
if let Some(slot) = duties_service.slot_clock.now() {
let epoch = slot.epoch(T::slots_per_epoch());
let total_validators = duties_service.total_validator_count();
let proposing_validators = duties_service.proposer_count(epoch);
let attesting_validators = duties_service.attester_count(epoch);
if total_validators == 0 {
error!(log, "No validators present")
} else if total_validators == attesting_validators {
info!(
log_2,
"All validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else if attesting_validators > 0 {
info!(
log_2,
"Some validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else {
info!(
log_2,
"Awaiting activation";
"validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
}
} else {
error!(log, "Unable to read slot clock");
}
Ok(())
});
let (exit_signal, exit) = exit_future::signal();
let log = context.log.clone();
client.context.executor.spawn(
exit.until(interval_future)
.map(move |_| info!(log, "Shutdown complete")),
);
Ok(exit_signal)
}