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:
parent
f1edca30ff
commit
3c6c06a505
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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`
|
||||||
|
@ -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)
|
||||||
|
179
book/src/become-a-validator.md
Normal file
179
book/src/become-a-validator.md
Normal 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
127
book/src/js/deposit.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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() {
|
||||||
|
@ -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
|
@ -1 +1 @@
|
|||||||
1743571
|
1773705
|
@ -1 +1 @@
|
|||||||
0xf382356688ae7dd3c2d6deb7e79c3ffe68816251
|
0x13e4d66c7215d7b63fec7b52fc65e6655093d906
|
Binary file not shown.
@ -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")
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
91
validator_client/src/notifier.rs
Normal file
91
validator_client/src/notifier.rs
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user