Compare commits

...

875 Commits

Author SHA1 Message Date
Paul Hauner
98f74041a0 Use windows-2019 in release CI (#3090)
## Issue Addressed

NA

## Proposed Changes

Address a CI failure in the release suite.

Example: https://github.com/sigp/lighthouse/actions/runs/1984266187

## Additional Info

I believe we should merge this into `unstable` and `stable`. Then, move the `v2.1.4` commit to target the commit with the updated CI. It's sad that v2.1.4 has two commits, but they're functionally equivalent for users.
2022-03-15 03:21:11 +00:00
Paul Hauner
28aceaa213 v2.1.4 (#3076)
## Issue Addressed

NA

## Proposed Changes

- Bump version to `v2.1.4`
- Run `cargo update`

## Additional Info

I think this release should be published around the 15th of March.

Presently `blocked` for testing on our infrastructure.
2022-03-14 23:11:40 +00:00
Michael Sproul
c2e9354126 Gracefully handle missing sync committee duties (#3086)
## Issue Addressed

Closes https://github.com/sigp/lighthouse/issues/3085
Closes https://github.com/sigp/lighthouse/issues/2953

## Proposed Changes

Downgrade some of the warnings logged by the VC which were useful during development of the sync committee service but are creating trouble now that we avoid populating the `sync_duties` map with 0 active validators.
2022-03-14 06:16:49 +00:00
realbigsean
f5d8fdbb4e Proposer preparation data quoted validator index in API (#3080)
## Issue Addressed

#3077

## Proposed Changes

Quotes around validator index in `prepare_beacon_proposer` endpoint


Co-authored-by: realbigsean <sean@sigmaprime.io>
2022-03-13 21:57:05 +00:00
realbigsean
925e9241d1 Quotes around SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY in the API (#3074)
## Issue Addressed

#3073 

## Proposed Changes

Add around `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` in the API

Co-authored-by: realbigsean <sean@sigmaprime.io>
2022-03-13 21:57:04 +00:00
realbigsean
15b8811580 Update ttd in kiln config (#3081)
## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

Please list or describe the changes introduced by this PR.

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.


Co-authored-by: Pawan Dhananjay <pawandhananjay@gmail.com>
Co-authored-by: realbigsean <sean@sigmaprime.io>
2022-03-11 20:11:22 +00:00
Paul Hauner
e4fa7d906f Fix post-merge checkpoint sync (#3065)
## Issue Addressed

This address an issue which was preventing checkpoint-sync.

When the node starts from checkpoint sync, the head block and the finalized block are the same value. We did not respect this when sending a `forkchoiceUpdated` (fcU) call to the EL and were expecting fork choice to hold the *finalized ancestor of the head* and returning an error when it didn't.

This PR uses *only fork choice* for sending fcU updates. This is actually quite nice and avoids some atomicity issues between `chain.canonical_head` and `chain.fork_choice`. Now, whenever `chain.fork_choice.get_head` returns a value we also cache the values required for the next fcU call.

## TODO

- [x] ~~Blocked on #3043~~
- [x] Ensure there isn't a warn message at startup.
2022-03-10 06:05:24 +00:00
Paul Hauner
6d4af4c9ca Kiln (#3067)
## Issue Addressed

Adds the [Kiln](https://github.com/eth-clients/merge-testnets/tree/main/kiln) configs, so we can use `--network kiln`. 

## Additional Notes

- Also includes the fix from #3066.
2022-03-10 02:34:17 +00:00
Paul Hauner
c475499dfe Fix UnableToReadSlot at startup (#3066)
## Issue Addressed

Don't send an fcU message at startup if it's pre-genesis. The startup fcU message is not critical, not required by the spec, so it's fine to avoid it for networks that start post-Bellatrix fork.
2022-03-09 23:04:19 +00:00
Michael Sproul
65eaf01942 VC: avoid sending fee recipients until just before merge (#3064)
## Issue Addressed

Presently if the VC is configured with a fee recipient it will error out when sending fee-recipient preparations to a beacon node that doesn't yet support the API:

```
Mar 08 22:23:36.236 ERRO Unable to publish proposer preparation  error: All endpoints failed https://eth2-beacon-prater.infura.io/ => RequestFailed(StatusCode(404)), service: preparation
```

This doesn't affect other VC duties, but could be a source of anxiety for users trying to do the right thing and configure their fee recipients in advance.

## Proposed Changes

Change the preparation service to only send preparations if the current slot is later than 2 epochs before the Bellatrix hard fork epoch.

## Additional Info

I've tagged this v2.1.4 as I think it's a small change that's worth having for the next release
2022-03-09 06:36:38 +00:00
Paul Hauner
267d8babc8 Prepare proposer (#3043)
## Issue Addressed

Resolves #2936

## Proposed Changes

Adds functionality for calling [`validator/prepare_beacon_proposer`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/prepareBeaconProposer) in advance.

There is a `BeaconChain::prepare_beacon_proposer` method which, which called, computes the proposer for the next slot. If that proposer has been registered via the `validator/prepare_beacon_proposer` API method, then the `beacon_chain.execution_layer` will be provided the `PayloadAttributes` for us in all future forkchoiceUpdated calls. An artificial forkchoiceUpdated call will be created 4s before each slot, when the head updates and when a validator updates their information.

Additionally, I added strict ordering for calls from the `BeaconChain` to the `ExecutionLayer`. I'm not certain the `ExecutionLayer` will always maintain this ordering, but it's a good start to have consistency from the `BeaconChain`. There are some deadlock opportunities introduced, they are documented in the code.

## Additional Info

- ~~Blocked on #2837~~

Co-authored-by: realbigsean <seananderson33@GMAIL.com>
2022-03-09 00:42:05 +00:00
Divma
527dfa4893 cargo audit updates (#3063)
## Issue Addressed
Closes #3008 and updates `regex` to solve https://rustsec.org/advisories/RUSTSEC-2022-0013
2022-03-08 19:48:12 +00:00
Pawan Dhananjay
381d0ece3c auth for engine api (#3046)
## Issue Addressed

Resolves #3015 

## Proposed Changes

Add JWT token based authentication to engine api requests. The jwt secret key is read from the provided file and is used to sign tokens that are used for authenticated communication with the EL node.

- [x] Interop with geth (synced `merge-devnet-4` with the `merge-kiln-v2` branch on geth)
- [x] Interop with other EL clients (nethermind on `merge-devnet-4`)
- [x] ~Implement `zeroize` for jwt secrets~
- [x] Add auth server tests with `mock_execution_layer`
- [x] Get auth working with the `execution_engine_integration` tests






Co-authored-by: Paul Hauner <paul@paulhauner.com>
2022-03-08 06:46:24 +00:00
Paul Hauner
3b4865c3ae Poll the engine_exchangeTransitionConfigurationV1 endpoint (#3047)
## Issue Addressed

There has been an [`engine_exchangetransitionconfigurationv1`](https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_exchangetransitionconfigurationv1) method added to the execution API specs.

The `engine_exchangetransitionconfigurationv1` will be polled every 60s as per this PR: https://github.com/ethereum/execution-apis/pull/189. If that PR is merged as-is, then we will be matching the spec. If that PR *is not* merged, we are still fully compatible with the spec, but just doing more than we are required.

## Additional Info

- [x] ~~Blocked on #2837~~
- [x] Add method to EE integration tests
2022-03-08 04:40:42 +00:00
Akihito Nakano
4186d117af Replace OpenOptions::new with File::options to be readable (#3059)
## Issue Addressed

Closes #3049 

This PR updates widely but this replace is safe as `File::options()` is equivelent to `OpenOptions::new()`.
ref: https://doc.rust-lang.org/stable/src/std/fs.rs.html#378-380
2022-03-07 06:30:18 +00:00
tim gretler
cbda0a2f0a Add log debounce to work processor (#3045)
## Issue Addressed

#3010 

## Proposed Changes

- move log debounce time latch to `./common/logging`
- add timelatch to limit logging for `attestations_delay_queue` and `queued_block_roots`

## Additional Info

- Is a separate crate for the time latch preferred? 
- `elapsed()` could take `LOG_DEBOUNCE_INTERVAL ` as an argument to allow for different granularity.
2022-03-07 06:30:17 +00:00
Michael Sproul
1829250ee4 Ignore attestations to finalized blocks (don't reject) (#3052)
## Issue Addressed

Addresses spec changes from v1.1.0:

- https://github.com/ethereum/consensus-specs/pull/2830
- https://github.com/ethereum/consensus-specs/pull/2846

## Proposed Changes

* Downgrade the REJECT for `HeadBlockFinalized` to an IGNORE. This applies to both unaggregated and aggregated attestations.

## Additional Info

I thought about also changing the penalty for `UnknownTargetRoot` but I don't think it's reachable in practice.
2022-03-04 00:41:22 +00:00
Paul Hauner
09d2187198 Lower debug! logs to trace! (#3053)
## Issue Addressed

These logs were very loud during sync.
2022-03-03 22:37:42 +00:00
Paul Hauner
aea43b626b Rename random to prev_randao (#3040)
## Issue Addressed

As discussed on last-night's consensus call, the testnets next week will target the [Kiln Spec v2](https://hackmd.io/@n0ble/kiln-spec).

Presently, we support Kiln V1. V2 is backwards compatible, except for renaming `random` to `prev_randao` in:

- https://github.com/ethereum/execution-apis/pull/180
- https://github.com/ethereum/consensus-specs/pull/2835

With this PR we'll no longer be compatible with the existing Kintsugi and Kiln testnets, however we'll be ready for the testnets next week. I raised this breaking change in the call last night, we are all keen to move forward and break things.

We now target the [`merge-kiln-v2`](https://github.com/MariusVanDerWijden/go-ethereum/tree/merge-kiln-v2) branch for interop with Geth. This required adding the `--http.aauthport` to the tester to avoid a port conflict at startup.

### Changes to exec integration tests

There's some change in the `merge-kiln-v2` version of Geth that means it can't compile on a vanilla Github runner. Bumping the `go` version on the runner solved this issue.

Whilst addressing this, I refactored the `testing/execution_integration` crate to be a *binary* rather than a *library* with tests. This means that we don't need to run the `build.rs` and build Geth whenever someone runs `make lint` or `make test-release`. This is nice for everyday users, but it's also nice for CI so that we can have a specific runner for these tests and we don't need to ensure *all* runners support everything required to build all execution clients.

## More Info

- [x] ~~EF tests are failing since the rename has broken some tests that reference the old field name. I have been told there will be new tests released in the coming days (25/02/22 or 26/02/22).~~
2022-03-03 02:10:57 +00:00
Divma
4bf1af4e85 Custom RPC request management for sync (#3029)
## Proposed Changes
Make `lighthouse_network` generic over request ids, now usable by sync
2022-03-02 22:07:17 +00:00
Age Manning
e88b18be09 Update libp2p (#3039)
Update libp2p. 

This corrects some gossipsub metrics.
2022-03-02 05:09:52 +00:00
Age Manning
f3c1dde898 Filter non global ips from discovery (#3023)
## Issue Addressed

#3006 

## Proposed Changes

This PR changes the default behaviour of lighthouse to ignore discovered IPs that are not globally routable. It adds a CLI flag, --enable-local-discovery to permit the non-global IPs in discovery.

NOTE: We should take care in merging this as I will break current set-ups that rely on local IP discovery. I made this the non-default behaviour because we dont really want to be wasting resources attempting to connect to non-routable addresses and we dont want to propagate these to others (on the chance we can connect to one of these local nodes), improving discoveries efficiency.
2022-03-02 03:14:27 +00:00
Akihito Nakano
668115a4b8 Rename Eth1/Eth2 in documents (#3021)
## Issue Addressed

Resolves https://github.com/sigp/lighthouse/issues/3019

## Proposed Changes

- Eth2 Eth2.0 Ethereum 2.0 -> Ethereum consensus
- Eth2 network -> consensus layer
- Ethereum 2.0 specification -> Ethereum proof-of-stake consensus specification
- Eth2 deposit contract -> Staking deposit contract
- Eth1 -> execution client

## Additional Info

The description needs to be updated by someone who has permission to do. 📝 

<img width="487" alt="image" src="https://user-images.githubusercontent.com/1885716/153995211-816d9561-751e-4810-abb9-83d979379783.png">
2022-03-02 01:05:08 +00:00
Age Manning
e34524be75 Increase default target-peer count to 80 (#3005)
Increase the default peer count from 50 to 80
2022-03-02 01:05:07 +00:00
Paul Hauner
b6493d5e24 Enforce Optimistic Sync Conditions & CLI Tests (v2) (#3050)
## Description

This PR adds a single, trivial commit (f5d2b27d78) atop #2986 to resolve a tests compile error. The original author (@ethDreamer) is AFK so I'm getting this one merged ☺️ 

Please see #2986 for more information about the other, significant changes in this PR.


Co-authored-by: Mark Mackey <mark@sigmaprime.io>
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
2022-03-01 22:56:47 +00:00
Age Manning
a1b730c043 Cleanup small issues (#3027)
Downgrades some excessive networking logs and corrects some metrics.
2022-03-01 01:49:22 +00:00
Paul Hauner
27e83b888c Retrospective invalidation of exec. payloads for opt. sync (#2837)
## Issue Addressed

NA

## Proposed Changes

Adds the functionality to allow blocks to be validated/invalidated after their import as per the [optimistic sync spec](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#how-to-optimistically-import-blocks). This means:

- Updating `ProtoArray` to allow flipping the `execution_status` of ancestors/descendants based on payload validity updates.
- Creating separation between `execution_layer` and the `beacon_chain` by creating a `PayloadStatus` struct.
- Refactoring how the `execution_layer` selects a `PayloadStatus` from the multiple statuses returned from multiple EEs.
- Adding testing framework for optimistic imports.
- Add `ExecutionBlockHash(Hash256)` new-type struct to avoid confusion between *beacon block roots* and *execution payload hashes*.
- Add `merge` to [`FORKS`](c3a793fd73/Makefile (L17)) in the `Makefile` to ensure we test the beacon chain with merge settings.
    - Fix some tests here that were failing due to a missing execution layer.

## TODO

- [ ] Balance tests

Co-authored-by: Mark Mackey <mark@sigmaprime.io>
2022-02-28 22:07:48 +00:00
Michael Sproul
5e1f8a8480 Update to Rust 1.59 and 2021 edition (#3038)
## Proposed Changes

Lots of lint updates related to `flat_map`, `unwrap_or_else` and string patterns. I did a little more creative refactoring in the op pool, but otherwise followed Clippy's suggestions.

## Additional Info

We need this PR to unblock CI.
2022-02-25 00:10:17 +00:00
Mac L
c1df5d29cb Ensure logfile respects the validators-dir CLI flag (#3003)
## Issue Addressed

Closes #2990 

## Proposed Changes

Add a check to see if the `--validators-dir` CLI flag is set and if so store validator logs into it.
Ensure that if the log directory cannot be created, emit a `WARN` and disable file logging rather than panicking. 

## Additional Info

Panics associated with logfiles can still occur in these scenarios:
1. The `$datadir/validators/logs` directory already exists with the wrong permissions (or was changed after creation).
1. The logfile already exists with the wrong permissions (or was changed after creation).
> These panics are cosmetic only since only the logfile thread panics. Following the panics, LH will continue to function as normal. 

I believe this is due to the use of [`slog::Fuse`](https://docs.rs/slog/latest/slog/struct.Fuse.html) when initializing the logger.
I'm not sure if there a better way of handling logfile errors?
I think ideally, rather than panicking, we would emit a `WARN` to the stdout logger with the panic reason, then exit the logfile thread gracefully.
2022-02-24 00:31:35 +00:00
Mac L
696de58141 Add aliases for validator-dir flags (#3034)
## Issue Addressed

#3020

## Proposed Changes

- Alias the `validators-dir` arg to `validator-dir` in the `validator_client` subcommand.
- Alias the `validator-dir` arg to `validators-dir` in the `account_manager validator` subcommand.
- Add test for the validator_client alias.
2022-02-22 03:09:02 +00:00
Paul Hauner
5a0b049049 Avoid hogging the fallback status lock in the VC (#3022)
## Issue Addressed

Addresses https://github.com/sigp/lighthouse/issues/2926

## Proposed Changes

Appropriated from https://github.com/sigp/lighthouse/issues/2926#issuecomment-1039676768:

When a node returns *any* error we call [`CandidateBeaconNode::set_offline`](c3a793fd73/validator_client/src/beacon_node_fallback.rs (L424)) which sets it's `status` to `CandidateError::Offline`. That node will then be ignored until the routine [`fallback_updater_service`](c3a793fd73/validator_client/src/beacon_node_fallback.rs (L44)) manages to reconnect to it.

However, I believe there was an issue in the [`CanidateBeaconNode::refesh_status`](c3a793fd73/validator_client/src/beacon_node_fallback.rs (L157-L178)) method, which is used by the updater service to see if the node has come good again. It was holding a [write lock on the `status` field](c3a793fd73/validator_client/src/beacon_node_fallback.rs (L165)) whilst it polled the node status. This means a long timeout would hog the write lock and starve other processes.

When a VC is trying to access a beacon node for whatever purpose (getting duties, posting blocks, etc), it performs [three passes](c3a793fd73/validator_client/src/beacon_node_fallback.rs (L432-L482)) through the lists of nodes, trying to run some generic `function` (closure, lambda, etc) on each node:

- 1st pass: only try running `function` on all nodes which are both synced and online.
- 2nd pass: try running `function` on all nodes that are online, but not necessarily synced.
- 3rd pass: for each offline node, try refreshing its status and then running `function` on it.

So, it turns out that if the `CanidateBeaconNode::refesh_status` function from the routine update service is hogging the write-lock, the 1st pass gets blocked whilst trying to read the status of the first node. So, nodes that should be left until the 3rd pass are blocking the process of the 1st and 2nd passes, hence the behaviour described in #2926.

## Additional Info

NA
2022-02-22 03:09:00 +00:00
Michael Sproul
b37d5db8df Increase Bors timeout, refine target-branch-check (#3035)
## Issue Addressed

Timeouts due to Windows builds running for 2h 20m.

## Proposed Changes

* Increase Bors timeout to 3h
* Refine the target branch check so that it will pass when we make PRs to feature branches. This is just an extra change I've been meaning to sneak in for a while.

## Additional Info

* I think it would also be cool to try caching for CI again, but that's a separate issue and we'll still need the long timeout on a cache miss.
2022-02-21 23:21:03 +00:00
Mac L
104e3104f9 Add API to compute block packing efficiency data (#2879)
## Issue Addressed
N/A

## Proposed Changes
Add a HTTP API which can be used to compute the block packing data for all blocks over a discrete range of epochs.

## Usage
### Request
```
curl "http:localhost:5052/lighthouse/analysis/block_packing_efficiency?start_epoch=57730&end_epoch=57732"
```
### Response
```
[
  {
    "slot": "1847360",
    "block_hash": "0xa7dc230659802df2f99ea3798faede2e75942bb5735d56e6bfdc2df335dcd61f",
    "proposer_info": {
      "validator_index": 1686,
      "graffiti": ""
    },
    "available_attestations": 7096,
    "included_attestations": 6459,
    "prior_skip_slots": 0
  },
  ...
]
```
## Additional Info

This is notably different to the existing lcli code:
- Uses `BlockReplayer` #2863 and as such runs significantly faster than the previous method.
- Corrects the off-by-one #2878
- Removes the `offline` validators component. This was only a "best guess" and simply was used as a way to determine an estimate of the "true" packing efficiency and was generally not helpful in terms of direct comparisons between different packing methods. As such it has been removed from the API and any future estimates of "offline" validators would be better suited in a separate/more targeted API or as part of 'beacon watch': #2873 
- Includes `prior_skip_slots`.
2022-02-21 23:21:02 +00:00
eklm
56b2ec6b29 Allow proposer duties request for the next epoch (#2963)
## Issue Addressed

Closes #2880 

## Proposed Changes

Support requests to the next epoch in proposer_duties api.

## Additional Info

Implemented with skipping proposer cache for this case because the cache for the future epoch will be missed every new slot as dependent_root is changed and we don't want to "wash it out" by saving additional values.
2022-02-18 05:32:00 +00:00
tim gretler
c8019caba6 Fix sync committee polling for 0 validators (#2999)
## Issue Addressed

#2953

## Proposed Changes

Adds empty local validator check. 

## Additional Info

Two other options: 
- add check inside `local_index` collection. Instead of after collection.
- Move `local_index` collection to the beginning of the `poll_sync_committee_duties` function and combine sync committee with altair fork check.
2022-02-18 02:36:44 +00:00
Age Manning
3ebb8b0244 Improved peer management (#2993)
## Issue Addressed

I noticed in some logs some excess and unecessary discovery queries. What was happening was we were pruning our peers down to our outbound target and having some disconnect. When we are below this threshold we try to find more peers (even if we are at our peer limit). The request becomes futile because we have no more peer slots. 

This PR corrects this issue and advances the pruning mechanism to favour subnet peers. 

An overview the new logic added is:
- We prune peers down to a target outbound peer count which is higher than the minimum outbound peer count.
- We only search for more peers if there is room to do so, and we are below the minimum outbound peer count not the target. So this gives us some buffer for peers to disconnect. The buffer is currently 10%

The modified pruning logic is documented in the code but for reference it should do the following:
- Prune peers with bad scores first
- If we need to prune more peers, then prune peers that are subscribed to a long-lived subnet
- If we still need to prune peers, the prune peers that we have a higher density of on any given subnet which should drive for uniform peers across all subnets.

This will need a bit of testing as it modifies some significant peer management behaviours in lighthouse.
2022-02-18 02:36:43 +00:00
Michael Sproul
da4ca024f1 Use SmallVec in Bitfield (#3025)
## Issue Addressed

Alternative to #2935

## Proposed Changes

Replace the `Vec<u8>` inside `Bitfield` with a `SmallVec<[u8; 32>`. This eliminates heap allocations for attestation bitfields until we reach 500K validators, at which point we can consider increasing `SMALLVEC_LEN` to 40 or 48.

While running Lighthouse under `heaptrack` I found that SSZ encoding and decoding of bitfields corresponded to 22% of all allocations by count. I've confirmed that with this change applied those allocations disappear entirely.

## Additional Info

We can win another 8 bytes of space by using `smallvec`'s [`union` feature](https://docs.rs/smallvec/1.8.0/smallvec/#union), although I might leave that for a future PR because I don't know how experimental that feature is and whether it uses some spicy `unsafe` blocks.
2022-02-17 23:55:04 +00:00
Paul Hauner
0a6a8ea3b0 Engine API v1.0.0.alpha.6 + interop tests (#3024)
## Issue Addressed

NA

## Proposed Changes

This PR extends #3018 to address my review comments there and add automated integration tests with Geth (and other implementations, in the future).

I've also de-duplicated the "unused port" logic by creating an  `common/unused_port` crate.

## Additional Info

I'm not sure if we want to merge this PR, or update #3018 and merge that. I don't mind, I'm primarily opening this PR to make sure CI works.


Co-authored-by: Mark Mackey <mark@sigmaprime.io>
2022-02-17 21:47:06 +00:00
Paul Hauner
2f8531dc60 Update to consensus-specs v1.1.9 (#3016)
## Issue Addressed

Closes #3014

## Proposed Changes

- Rename `receipt_root` to `receipts_root`
- Rename `execute_payload` to `notify_new_payload`
   - This is slightly weird since we modify everything except the actual HTTP call to the engine API. That change is expected to be implemented in #2985 (cc @ethDreamer)
- Enable "random" tests for Bellatrix.

## Notes

This will break *partially* compatibility with Kintusgi testnets in order to gain compatibility with [Kiln](https://hackmd.io/@n0ble/kiln-spec) testnets. I think it will only break the BN APIs due to the `receipts_root` change, however it might have some other effects too.

Co-authored-by: Michael Sproul <micsproul@gmail.com>
2022-02-14 23:57:23 +00:00
Michael Sproul
886afd684a Update block reward API docs (#3013)
## Proposed Changes

Fix the URLs and source code link in the docs for the block rewards API.
2022-02-11 11:02:09 +00:00
Paul Hauner
c3a793fd73 v2.1.3 (#3017)
## Issue Addressed

NA

## Proposed Changes

Bump versions

## Additional Info

NA
2022-02-11 01:54:33 +00:00
Zachinquarantine
b5921e4248 Remove Pyrmont testnet (#2543)
## Issue Addressed

N/A

## Proposed Changes

Removes all configurations and hard-coded rules related to the deprecated Pyrmont testnet.

## Additional Info

Pyrmont is deprecated/will be shut down after being used for scenario testing, this PR removes configurations related to it.

Co-authored-by: Zachinquarantine <zachinquarantine@yahoo.com>
2022-02-10 06:02:55 +00:00
Divma
1306b2db96 libp2p upgrade + gossipsub interval fix (#3012)
## Issue Addressed
Lighthouse gossiping late messages

## Proposed Changes
Point LH to our fork using tokio interval, which 1) works as expected 2) is more performant than the previous version that actually worked as expected
Upgrade libp2p 

## Additional Info
https://github.com/libp2p/rust-libp2p/issues/2497
2022-02-10 04:12:03 +00:00
Paul Hauner
7e38d203ce Add "update priority" (#2988)
## Issue Addressed

NA

## Proposed Changes

Add the "Update Priority" section which has featured in many of our previous releases (e.g., [Poñeta](https://github.com/sigp/lighthouse/releases/v2.1.1)).

Previously this section has been copied in manually.

## Additional Info

NA
2022-02-09 07:44:42 +00:00
Philipp K
5388183884 Allow per validator fee recipient via flag or file in validator client (similar to graffiti / graffiti-file) (#2924)
## Issue Addressed

#2883 

## Proposed Changes

* Added `suggested-fee-recipient` & `suggested-fee-recipient-file` flags to validator client (similar to graffiti / graffiti-file implementation).
* Added proposer preparation service to VC, which sends the fee-recipient of all known validators to the BN via [/eth/v1/validator/prepare_beacon_proposer](https://github.com/ethereum/beacon-APIs/pull/178) api once per slot
* Added [/eth/v1/validator/prepare_beacon_proposer](https://github.com/ethereum/beacon-APIs/pull/178) api endpoint and preparation data caching
* Added cleanup routine to remove cached proposer preparations when not updated for 2 epochs

## Additional Info

Changed the Implementation following the discussion in #2883.



Co-authored-by: pk910 <philipp@pk910.de>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
Co-authored-by: Philipp K <philipp@pk910.de>
2022-02-08 19:52:20 +00:00
Paul Hauner
d172c0b9fc Bump crossbeam-utils to fix cargo-audit CI failure (#3004)
## Issue Addressed

Bump `crossbeam-utils` to `0.8.7` since `0.8.6` was yanked and that made `cargo audit` fail.
2022-02-07 23:25:09 +00:00
ladidan
1fd883d79a Fix Docker run -p for both TCP and UDP (#2998)
## Issue Addressed

[Docker run] ... "-p 9000:9000" defaults to expose TCP only.

## Proposed Changes

Add "-p 9000:9000/udp" for UDP peer discovery.
2022-02-07 23:25:08 +00:00
Divma
36fc887a40 Gossip cache timeout adjustments (#2997)
## Proposed Changes

- Do not retry to publish sync committee messages.
- Give a more lenient timeout to slashings and exits
2022-02-07 23:25:06 +00:00
Age Manning
675c7b7e26 Correct a dial race condition (#2992)
## Issue Addressed

On a network with few nodes, it is possible that the same node can be found from a subnet discovery and a normal peer discovery at the same time.

The network behaviour loads these peers into events and processes them when it has the chance. It can happen that the same peer can enter the event queue more than once and then attempt to be dialed twice. 

This PR shifts the registration of nodes in the peerdb as being dialed before they enter the NetworkBehaviour queue, preventing multiple attempts of the same peer being entered into the queue and avoiding the race condition.
2022-02-07 23:25:05 +00:00
Divma
48b7c8685b upgrade libp2p (#2933)
## Issue Addressed

Upgrades libp2p to v.0.42.0 pre release (https://github.com/libp2p/rust-libp2p/pull/2440)
2022-02-07 23:25:03 +00:00
Divma
615695776e Retry gossipsub messages when insufficient peers (#2964)
## Issue Addressed
#2947 

## Proposed Changes

Store messages that fail to be published due to insufficient peers for retry later. Messages expire after half an epoch and are retried if gossipsub informs us that an useful peer has connected. Currently running in Atlanta

## Additional Info
If on retry sending the messages fails they will not be tried again
2022-02-03 01:12:30 +00:00
Paul Hauner
0177b9286e v2.1.2 (#2980)
## Issue Addressed

NA

## Proposed Changes

- Bump version to `v2.1.2`
- Run `cargo update`

## Additional Info

NA
2022-02-01 23:53:53 +00:00
Paul Hauner
fc37d51e10 Add checks to prevent fwding old messages (#2978)
## Issue Addressed

NA

## Proposed Changes

Checks to see if attestations or sync messages are still valid before "accepting" them for propagation.

## Additional Info

NA
2022-02-01 01:04:24 +00:00
Paul Hauner
a6da87066b Add strict penalties const bool (#2976)
## Issue Addressed

NA

## Proposed Changes

Adds `STRICT_LATE_MESSAGE_PENALTIES: bool` which allows for toggling penalties for late sync/attn messages.

`STRICT_LATE_MESSAGE_PENALTIES` is set to `false`, since we're seeing a lot of late messages on the network which are causing peer drops. We can toggle the bool during testing to try and figure out what/who is the cause of these late messages.

In effect, this PR *relaxes* peer downscoring for late attns and sync committee messages.

## Additional Info

- ~~Blocked on #2974~~
2022-02-01 01:04:22 +00:00
Mac L
286996b090 Fix small typo in error log (#2975)
## Proposed Changes

Fixes a small typo I came across.
2022-01-31 22:55:07 +00:00
Akihito Nakano
5d26050e97 local testnet: Fix an error on startup (#2973)
## Issue Addressed

Resolves https://github.com/sigp/lighthouse/issues/2763#issuecomment-1024858187

## Proposed Changes

- Skip if the line is blank. 👌
2022-01-31 22:55:06 +00:00
Akihito Nakano
736457b562 Run setup.sh foreground in order to avoid timing issues (#2970)
## Issue Addressed

Resolves https://github.com/sigp/lighthouse/pull/2919#issuecomment-1022892369

## Proposed Changes

- Run setup.sh foreground in order to avoid timing issues
2022-01-31 22:55:04 +00:00
Michael Sproul
139b44342f Optimized Docker images (#2966)
## Issue Addressed

Closes #2938

## Proposed Changes

* Build and publish images with a `-modern` suffix which enable CPU optimizations for modern hardware.
* Add docs for the plethora of available images!
* Unify all the Docker workflows in `docker.yml` (including for tagged releases).

## Additional Info

The `Dockerfile` is no longer used by our Docker Hub builds, as we use `cross` and a generic approach for ARM and x86. There's a new CI job `docker-build-from-source` which tests the `Dockerfile` without publishing anything.
2022-01-31 22:55:03 +00:00
Age Manning
bdd70d7aef Reduce gossip history (#2969)
The gossipsub history was increased to a good portion of a slot from 2.1 seconds in the last release.

Although it shouldn't cause too much issue, it could be related to recieving later messages than usual and interacting with our scoring system penalizing peers. For consistency, this PR reduces the time we gossip messages back to the same values of the previous release.

It also adjusts the gossipsub heartbeat time for testing purposes with a developer flag but this should not effect end users.
2022-01-31 07:29:41 +00:00
Pawan Dhananjay
9ff2160198 Parse uint256 as decimal string (#2957)
## Issue Addressed

N/A

## Proposed Changes

https://github.com/sigp/lighthouse/pull/2940 introduced a bug where we parsed the uint256 terminal total difficulty as a hex string instead of a decimal string. Fixes the bug and adds tests.
2022-01-30 23:22:05 +00:00
Michael Sproul
e961ff60b4 Implement standard keystore API (#2736)
## Issue Addressed

Implements the standard key manager API from https://ethereum.github.io/keymanager-APIs/, formerly https://github.com/ethereum/beacon-APIs/pull/151
Related to https://github.com/sigp/lighthouse/issues/2557

## Proposed Changes

- [x] Add all of the new endpoints from the standard API: GET, POST and DELETE.
- [x] Add a `validators.enabled` column to the slashing protection database to support atomic disable + export.
- [x] Add tests for all the common sequential accesses of the API
- [x] Add tests for interactions with remote signer validators
- [x] Add end-to-end tests for migration of validators from one VC to another
- [x] Implement the authentication scheme from the standard (token bearer auth)

## Additional Info

The `enabled` column in the validators SQL database is necessary to prevent a race condition when exporting slashing protection data. Without the slashing protection database having a way of knowing that a key has been disabled, a concurrent request to sign a message could insert a new record into the database. The `delete_concurrent_with_signing` test exercises this code path, and was indeed failing before the `enabled` column was added.

The validator client authentication has been modified from basic auth to bearer auth, with basic auth preserved for backwards compatibility.
2022-01-30 23:22:04 +00:00
Kirill Fedoseev
ee000d5219 Native support for Gnosis Beacon Chain network (#2931)
## Proposed Changes

Add a new hardcoded spec for the Gnosis Beacon Chain.
Ideally, official Lighthouse executables will be able to connect to the gnosis beacon chain from now on, using `--network gnosis` CLI option.
2022-01-27 22:58:33 +00:00
Michael Sproul
99d2c33387 Avoid looking up pre-finalization blocks (#2909)
## Issue Addressed

This PR fixes the unnecessary `WARN Single block lookup failed` messages described here:

https://github.com/sigp/lighthouse/pull/2866#issuecomment-1008442640

## Proposed Changes

Add a new cache to the `BeaconChain` that tracks the block roots of blocks from before finalization. These could be blocks from the canonical chain (which might need to be read from disk), or old pre-finalization blocks that have been forked out.

The cache also stores a set of block roots for in-progress single block lookups, which duplicates some of the information from sync's `single_block_lookups` hashmap:

a836e180f9/beacon_node/network/src/sync/manager.rs (L192-L196)

On a live node you can confirm that the cache is working by grepping logs for the message: `Rejected attestation to finalized block`.
2022-01-27 22:58:32 +00:00
Mac L
e05142b798 Add API to compute discrete validator attestation performance (#2874)
## Issue Addressed

N/A

## Proposed Changes

Add a HTTP API which can be used to compute the attestation performances of a validator (or all validators) over a discrete range of epochs.
Performances can be computed for a single validator, or for the global validator set. 

## Usage
### Request
The API can be used as follows:
```
curl "http://localhost:5052/lighthouse/analysis/attestation_performance/{validator_index}?start_epoch=57730&end_epoch=57732"
```
Alternatively, to compute performances for the global validator set:
```
curl "http://localhost:5052/lighthouse/analysis/attestation_performance/global?start_epoch=57730&end_epoch=57732"
```

### Response
The response is JSON formatted as follows:
```
[
  {
    "index": 72,
    "epochs": {
      "57730": {
        "active": true,
        "head": false,
        "target": false,
        "source": false
      },
      "57731": {
        "active": true,
        "head": true,
        "target": true,
        "source": true,
        "delay": 1
      },
      "57732": {
        "active": true,
        "head": true,
        "target": true,
        "source": true,
        "delay": 1
      },
    }
  }
]
```
> Note that the `"epochs"` are not guaranteed to be in ascending order. 

## Additional Info

- This API is intended to be used in our upcoming validator analysis tooling (#2873) and will likely not be very useful for regular users. Some advanced users or block explorers may find this API useful however.
- The request range is limited to 100 epochs (since the range is inclusive and it also computes the `end_epoch` it's actually 101 epochs) to prevent Lighthouse using exceptionally large amounts of memory.
2022-01-27 22:58:31 +00:00
tim gretler
782abdcab5 Outaded flag in lighthouse book (#2965)
## Proposed Changes

Outdated flag. Need to use `--wallet-name` instead.
2022-01-27 01:06:04 +00:00
Michael Sproul
e70daaa3b6 Implement API for block rewards (#2628)
## Proposed Changes

Add an API endpoint for retrieving detailed information about block rewards.

For information on usage see [the docs](https://github.com/sigp/lighthouse/blob/block-rewards-api/book/src/api-lighthouse.md#lighthouseblock_rewards), and the source.
2022-01-27 01:06:02 +00:00
Mac L
013a3cc3e0 Add flag to disable confirmation when performing voluntary exits (#2955)
## Issue Addressed

Currently performing a voluntary exit prompts for manual confirmation. This prevents automation of exits.

## Proposed Changes

Add the flag `--no-confirmation` to the account manager when performing voluntary exits to bypass this manual confirmation.
2022-01-26 23:14:25 +00:00
Michael Sproul
85d73d5443 Set mmap threshold to 128KB in malloc utils (#2937)
## Issue Addressed

Closes https://github.com/sigp/lighthouse/issues/2857

## Proposed Changes

Explicitly set GNU malloc's MMAP_THRESHOLD to 128KB, disabling dynamic adjustments. For rationale see the linked issue.
2022-01-26 23:14:24 +00:00
Divma
f2b1e096b2 Code quality improvents to the network service (#2932)
Checking how to priorize the polling of the network I moved most of the service code to functions. This change I think it's worth on it's own for code quality since inside the `tokio::select` many tools don't work (cargo fmt, sometimes clippy, and sometimes even the compiler's errors get wack). This is functionally equivalent to the previous code, just better organized
2022-01-26 23:14:23 +00:00
Divma
9964f5afe5 Document why we hash downloaded blocks for both sync algs (#2927)
## Proposed Changes
Initially the idea was to remove hashing of blocks in backfill sync. After considering it more, we conclude that we need to do it in both (forward and backfill) anyway. But since we forgot why we were doing it in the first place, this PR documents this logic. 

Future us should find it useful 


Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com>
2022-01-26 23:14:22 +00:00
Akihito Nakano
150931950d Fix errors from local testnet scripts on MacOS (#2919)
## Issue Addressed

Resolves https://github.com/sigp/lighthouse/issues/2763

## Proposed Changes

- Add a workflow which tests that local testnet starts successfully
  - Added `set` option into the scripts in order to fail fast so that we can notice errors during starting local testnet.
- Fix errors on MacOS
  - The redirect `&>>` is supported since bash v4 but the version bundled in macOS(11.6.1) is v3. a54f119c9b
2022-01-26 23:14:20 +00:00
Paul Hauner
5f628a71d4 v2.1.1 (#2951)
## Issue Addressed

NA

## Proposed Changes

- Bump Lighthouse version to v2.1.1
- Update `thread_local` from v1.1.3 to v1.1.4 to address https://rustsec.org/advisories/RUSTSEC-2022-0006

## Additional Info

- ~~Blocked on #2950~~
- ~~Blocked on #2952~~
2022-01-25 00:46:24 +00:00
Michael Sproul
69288f6164 VC: don't warn if BN config doesn't match exactly (#2952)
## Proposed Changes

Remove the check for exact equality on the beacon node spec when polling `/config/spec` from the VC. This check was always overzealous, and mostly served to check that the BN was configured for upcoming forks. I've replaced it by explicit checks of the `altair_fork_epoch` and `bellatrix_fork_epoch` instead.

## Additional Info

We should come back to this and clean it up so that we can retain compatibility while removing the field `default`s we installed.
2022-01-24 22:33:04 +00:00
Pawan Dhananjay
b9b3ea70de Fix metric name for monitoring (#2950)
## Issue Addressed

Resolves #2949 

## Proposed Changes

Fix metric naming for libp2p peer count.
2022-01-24 22:33:02 +00:00
Age Manning
ca29b580a2 Increase target subnet peers (#2948)
In the latest release we decreased the target number of subnet peers. 

It appears this could be causing issues in some cases and so reverting it back to the previous number it wise. A larger PR that follows this will address some other related discovery issues and peer management around subnet peer discovery.
2022-01-24 12:08:00 +00:00
Michael Sproul
f35a33716b Quote validator indices when posting duties (#2943)
## Proposed Changes

This PR establishes compatibility between Lighthouse's VC and Nimbus's BN.

Lighthouse was previously `POST`ing unquoted lists of validator indices to the attester and sync duties endpoints which were (correctly) not accepted by Nimbus. These lists had slipped through the cracks because they didn't have an explicit wrapper type to add `serde` annotations to. I've added the `ValidatorIndexDataRef` newtype in order to implement the modified serialisation behaviour.

## Testing

Combined with https://github.com/sigp/lighthouse/pull/2940, I've confirmed that this PR allows my Lighthouse VC on Prater to validate with the public Nimbus BN listed here: https://github.com/status-im/nimbus-eth2#quickly-test-your-tooling-against-nimbus. I haven't had a block proposal yet, but attestations and sync committee messages are working.

## Additional Info

This may also provide compatibility with Prysm BNs but I haven't had a chance to test that yet.
2022-01-21 06:07:20 +00:00
Pawan Dhananjay
799aedd631 Add default config options for transition constants (#2940)
## Issue Addressed

Continuation to #2934 

## Proposed Changes

Currently, we have the transition fields in the config (`TERMINAL_TOTAL_DIFFICULTY`, `TERMINAL_BLOCK_HASH` and `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH`) as mandatory fields. 

This is causing compatibility issues with other client BN's (nimbus and teku v22.1.0) which don't return these fields on a `eth/v1/config/spec` api call. Since we don't use this values until the merge, I think it's okay to have default values set for these fields as well to ensure compatibility.
2022-01-20 23:05:42 +00:00
Rishi Kumar Ray
f0f327af0c Removed all disable_forks (#2925)
#2923 

Which issue # does this PR address?
There's a redundant field on the BeaconChain called disabled_forks that was once part of our fork-aware networking (#953) but which is no longer used and could be deleted. so Removed all references to disabled_forks so that the code compiles and git grep disabled_forks returns no results.

## Proposed Changes

Please list or describe the changes introduced by this PR.
Removed all references of disabled_forks


Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com>
2022-01-20 09:14:26 +00:00
Age Manning
fc7a1a7dc7 Allow disconnected states to introduce new peers without warning (#2922)
## Issue Addressed

We emit a warning to verify that all peer connection state information is consistent. A warning is given under one edge case;
We try to dial a peer with peer-id X and multiaddr Y. The peer responds to multiaddr Y with a different peer-id, Z. The dialing to the peer fails, but libp2p injects the failed attempt as peer-id Z. 

In this instance, our PeerDB tries to add a new peer in the disconnected state under a previously unknown peer-id. This is harmless and so this PR permits this behaviour without logging a warning.
2022-01-20 09:14:25 +00:00
Akihito Nakano
a8ae9c8418 Add linkcheck workflow (#2918)
## Issue Addressed

Resolves #2889

## Additional Info

I have checked that the `linkcheck` workflow runs and detects broken links as expected, in https://github.com/ackintosh/lighthouse/pull/1.
2022-01-20 09:14:23 +00:00
eklm
0116c8d464 Change type of extra fields in ConfigAndPreset (#2913)
## Issue Addressed

#2900

## Proposed Changes

Change type of extra_fields in ConfigAndPreset so it can contain non string values (inside serde_json::Value)
2022-01-20 09:14:21 +00:00
Mac L
d06f87486a Support duplicate keys in HTTP API query strings (#2908)
## Issues Addressed

Closes #2739
Closes #2812

## Proposed Changes

Support the deserialization of query strings containing duplicate keys into their corresponding types.
As `warp` does not support this feature natively (as discussed in #2739), it relies on the external library [`serde_array_query`](https://github.com/sigp/serde_array_query) (written by @michaelsproul)

This is backwards compatible meaning that both of the following requests will produce the same output:
```
curl "http://localhost:5052/eth/v1/events?topics=head,block"
```
```
curl "http://localhost:5052/eth/v1/events?topics=head&topics=block"
```

## Additional Info

Certain error messages have changed slightly.  This only affects endpoints which accept multiple values.
For example:
```
{"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]}
```
is now
```
{"code":400,"message":"BAD_REQUEST: unable to parse query","stacktraces":[]}
```


The serve order of the endpoints `get_beacon_state_validators` and `get_beacon_state_validators_id` have flipped:
```rust
.or(get_beacon_state_validators_id.boxed())
.or(get_beacon_state_validators.boxed())
``` 
This is to ensure proper error messages when filter fallback occurs due to the use of the `and_then` filter.

## Future Work
- Cleanup / remove filter fallback behaviour by substituting `and_then` with `then` where appropriate.
- Add regression tests for HTTP API error messages.

## Credits
- @mooori for doing the ground work of investigating possible solutions within the existing Rust ecosystem.
- @michaelsproul for writing [`serde_array_query`](https://github.com/sigp/serde_array_query) and for helping debug the behaviour of the `warp` filter fallback leading to incorrect error messages.
2022-01-20 09:14:19 +00:00
Paul Hauner
79db2d4deb v2.1.0 (#2928)
## Issue Addressed

NA

## Proposed Changes

Bump to `v2.1.0`.

## Additional Info

NA
2022-01-20 03:39:41 +00:00
Michael Sproul
95b3183cb4 Make /config/spec backwards compat for VC (#2934)
## Proposed Changes

Restore compatibility with beacon nodes using the `MERGE` naming by:

1. Adding defaults for the Bellatrix `Config` fields
2. Not attempting to read (or serve) the Bellatrix preset on `/config/spec`.

I've confirmed that this works with Infura, and just logs a warning:

```
Jan 20 10:51:31.078 INFO Connected to beacon node                endpoint: https://eth2-beacon-mainnet.infura.io/, version: teku/v22.1.0/linux-x86_64/-eclipseadoptium-openjdk64bitservervm-java-17
Jan 20 10:51:31.344 WARN Beacon node config does not match exactly, advice: check that the BN is updated and configured for any upcoming forks, endpoint: https://eth2-beacon-mainnet.infura.io/
Jan 20 10:51:31.344 INFO Initialized beacon node connections     available: 1, total: 1
```
2022-01-20 01:31:53 +00:00
Michael Sproul
ef7351ddfe Update to spec v1.1.8 (#2893)
## Proposed Changes

Change the canonical fork name for the merge to Bellatrix. Keep other merge naming the same to avoid churn.

I've also fixed and enabled the `fork` and `transition` tests for Bellatrix, and the v1.1.7 fork choice tests.

Additionally, the `BellatrixPreset` has been added with tests. It gets served via the `/config/spec` API endpoint along with the other presets.
2022-01-19 00:24:19 +00:00
Paul Hauner
9ed92d6e78 Use "release candidate" in book (#2920)
## Issue Addressed

NA

## Proposed Changes

Since we use the `rc` (release candidate) tag in our version strings, it seems consistent if we also use "release candidate" in the book rather than "pre-release".

Notably, Github adds a "pre-release" tag to release when we request. I think it's OK that Github uses that term whilst we consistently use "release candidate". Our docs indicate that the terms are interchangeable. 

## Additional Info

I hope to use the new docs link in the `v2.1.0` release, so it would be nice if we can merge this soon 🙏
2022-01-17 06:09:58 +00:00
Michael Sproul
a836e180f9 Release v2.1.0-rc.1 (#2921)
## Proposed Changes

New release candidate to address Windows build failure for rc.0
2022-01-17 03:25:30 +00:00
Paul Hauner
a26b8802da Release v2.1.0-rc.0 (#2905)
## Issue Addressed

NA

## Proposed Changes

Bump version tags to `v2.1.0-rc.0`.

## Additional Info

NA
2022-01-16 23:25:25 +00:00
Paul Hauner
c11253a82f Remove grandparents from snapshot cache (#2917)
## Issue Addressed

NA

## Proposed Changes

In https://github.com/sigp/lighthouse/pull/2832 we made some changes to the `SnapshotCache` to help deal with the one-block reorgs seen on mainnet (and testnets).

I believe the change in #2832 is good and we should keep it, but I think that in its present form it is causing the `SnapshotCache` to hold onto states that it doesn't need anymore. For example, a skip slot will result in one more `BeaconSnapshot` being stored in the cache.

This PR adds a new type of pruning that happens after a block is inserted to the cache. We will remove any snapshot from the cache that is a *grandparent* of the block being imported. Since we know the grandparent has two valid blocks built atop it, it is not at risk from a one-block re-org. 

## Additional Info

NA
2022-01-14 07:20:55 +00:00
Michael Sproul
ceeab02e3a Lazy hashing for SignedBeaconBlock in sync (#2916)
## Proposed Changes

Allocate less memory in sync by hashing the `SignedBeaconBlock`s in a batch directly, rather than going via SSZ bytes.

Credit to @paulhauner for finding this source of temporary allocations.
2022-01-14 07:20:54 +00:00
Age Manning
1c667ad3ca PeerDB Status unknown bug fix (#2907)
## Issue Addressed

The PeerDB was getting out of sync with the number of disconnected peers compared to the actual count. As this value determines how many we store in our cache, over time the cache was depleting and we were removing peers immediately resulting in errors that manifest as unknown peers for some operations.

The error occurs when dialing a peer fails, we were not correctly updating the peerdb counter because the increment to the counter was placed in the wrong order and was therefore not incrementing the count. 

This PR corrects this.
2022-01-14 05:42:48 +00:00
Age Manning
6f4102aab6 Network performance tuning (#2608)
There is a pretty significant tradeoff between bandwidth and speed of gossipsub messages. 

We can reduce our bandwidth usage considerably at the cost of minimally delaying gossipsub messages. The impact of delaying messages has not been analyzed thoroughly yet, however this PR in conjunction with some gossipsub updates show considerable bandwidth reduction. 

This PR allows the user to set a CLI value (`network-load`) which is an integer in the range of 1 of 5 depending on their bandwidth appetite. 1 represents the least bandwidth but slowest message recieving and 5 represents the most bandwidth and fastest received message time. 

For low-bandwidth users it is likely to be more efficient to use a lower value. The default is set to 3, which currently represents a reduced bandwidth usage compared to previous version of this PR. The previous lighthouse versions are equivalent to setting the `network-load` CLI to 4.

This PR is awaiting a few gossipsub updates before we can get it into lighthouse.
2022-01-14 05:42:47 +00:00
Age Manning
db95255aeb Remove gitter from readme (#2914)
We dont check gitter, so shouldn't refer users to it.
2022-01-14 04:07:17 +00:00
Akihito Nakano
6883e1bfb6 Fix broken links in book (#2912)
## Issue Addressed

https://github.com/sigp/lighthouse/issues/2889

## Additional Info

I have checked that linkcheck has succeeded on the book built locally. 👌 

```shell
$ cd book
$ mdbook serve --open
...
2022-01-14 01:13:40 [INFO] (mdbook::book): Book building has started
2022-01-14 01:13:40 [INFO] (mdbook::book): Running the html backend

$ linkcheck http://localhost:3000
Perfect. Checked 4495 links, 80 destination URLs (76 ignored).
```

Also I'll tackle running linkcheck on CI in another pull request.
2022-01-14 00:38:04 +00:00
Michael Sproul
e8887ffea0 Rust 1.58 lints (#2906)
## Issue Addressed

Closes #2616

## Proposed Changes

* Fixes for new Rust 1.58.0 lints
* Enable the `fn_to_numeric_cast_any` (#2616)
2022-01-13 22:39:58 +00:00
Paul Hauner
2ce2ec9b62 Remove penalty for attesting to unknown head (#2903)
## Issue Addressed

- Resolves https://github.com/sigp/lighthouse/issues/2902

## Proposed Changes

As documented in https://github.com/sigp/lighthouse/issues/2902, there are some cases where we will score peers very harshly for sending attestations to an unknown head.

This PR removes the penalty when an attestation for an unknown head is received, queued for block look-up, then popped from the queue without the head block being known. This prevents peers from being penalized for an unknown block when that peer was never actually asked for the block.

Peer penalties should still be applied to the peers who *do* get the request for the block and fail to respond with a valid block. As such, peers who send us attestations to non-existent heads should eventually be booted.

## Additional Info

- [ ] Need to confirm that a timeout for a bbroot request will incur a penalty.
2022-01-13 03:08:38 +00:00
realbigsean
f13e9c3d10 Antithesis docker workflow (#2877)
## Issue Addressed

Automates a build and push to antithesis servers on merges to unstable. They run tests against lighthouse daily and have requested more frequent pushes. Currently we are just manually pushing stable images when we have a new release.

## Proposed Changes

- Add a `Dockerfile.libvoidstar`
- Add the `libvoidstar.so` binary
- Add a new workflow to autmatically build and push on merges to unstable

## Additional Info
Requires adding the following secrets
-`ANTITHESIS_USERNAME`
-`ANTITHESIS_PASSWORD`
-`ANTITHESIS_REPOSITORY`
-`ANTITHESIS_SERVER`

Tested here: https://github.com/realbigsean/lighthouse/actions/runs/1612821446

Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: realbigsean <sean@sigmaprime.io>
2022-01-12 20:58:41 +00:00
Paul Hauner
aaa5344eab Add peer score adjustment msgs (#2901)
## Issue Addressed

N/A

## Proposed Changes

This PR adds the `msg` field to `Peer score adjusted` log messages. These `msg` fields help identify *why* a peer was banned.

Example:

```
Jan 11 04:18:48.096 DEBG Peer score adjusted                     score: -100.00, peer_id: 16Uiu2HAmQskxKWWGYfginwZ51n5uDbhvjHYnvASK7PZ5gBdLmzWj, msg: attn_unknown_head, service: libp2p
Jan 11 04:18:48.096 DEBG Peer score adjusted                     score: -27.86, peer_id: 16Uiu2HAmA7cCb3MemVDbK3MHZoSb7VN3cFUG3vuSZgnGesuVhPDE, msg: sync_past_slot, service: libp2p
Jan 11 04:18:48.096 DEBG Peer score adjusted                     score: -100.00, peer_id: 16Uiu2HAmQskxKWWGYfginwZ51n5uDbhvjHYnvASK7PZ5gBdLmzWj, msg: attn_unknown_head, service: libp2p
Jan 11 04:18:48.096 DEBG Peer score adjusted                     score: -28.86, peer_id: 16Uiu2HAmA7cCb3MemVDbK3MHZoSb7VN3cFUG3vuSZgnGesuVhPDE, msg: sync_past_slot, service: libp2p
Jan 11 04:18:48.096 DEBG Peer score adjusted                     score: -29.86, peer_id: 16Uiu2HAmA7cCb3MemVDbK3MHZoSb7VN3cFUG3vuSZgnGesuVhPDE, msg: sync_past_slot, service: libp2p
```

There is also a `libp2p_report_peer_msgs_total` metrics which allows us to see count of reports per `msg` tag. 

## Additional Info

NA
2022-01-12 05:32:14 +00:00
Paul Hauner
61f60bdf03 Avoid penalizing peers for delays during processing (#2894)
## Issue Addressed

NA

## Proposed Changes

We have observed occasions were under-resourced nodes will receive messages that were valid *at the time*, but later become invalidated due to long waits for a `BeaconProcessor` worker.

In this PR, we will check to see if the message was valid *at the time of receipt*. If it was initially valid but invalid now, we just ignore the message without penalizing the peer.

## Additional Info

NA
2022-01-12 02:36:24 +00:00
Michael Sproul
b656007963 Skip serializing proposer boost if null (#2899)
## Issue Addressed

Restore compatibility between Lighthouse v2.0.1 VC and `unstable` BN in preparation for the next release.

## Proposed Changes

* Don't serialize the `PROPOSER_SCORE_BOOST` as `null` because it breaks the `extra_fields: HashMap<String, String>` used by the v2.0.1 VC.
2022-01-11 05:33:29 +00:00
Paul Hauner
4848e53155 Avoid peer penalties on internal errors for batch block import (#2898)
## Issue Addressed

NA

## Proposed Changes

I've observed some Prater nodes (and potentially some mainnet nodes) banning peers due to validator pubkey cache lock timeouts. For the `BeaconChainError`-type of errors, they're caused by internal faults and we can't necessarily tell if the peer is bad or not. I think this is causing us to ban peers unnecessarily when running on under-resourced machines.

## Additional Info

NA
2022-01-11 05:33:28 +00:00
Michael Sproul
6976796162 Update dependencies including sha2 (#2896)
## Proposed Changes

Although the [security advisory](https://rustsec.org/advisories/RUSTSEC-2021-0100.html) only lists `sha2` 0.9.7 as vulnerable, the [changelog](https://github.com/RustCrypto/hashes/blob/master/sha2/CHANGELOG.md#099-2022-01-06) states that 0.9.8 is also vulnerable, and has been yanked.
2022-01-11 01:35:56 +00:00
Paul Hauner
02e2fd2fb8 Add early attester cache (#2872)
## Issue Addressed

NA

## Proposed Changes

Introduces a cache to attestation to produce atop blocks which will become the head, but are not fully imported (e.g., not inserted into the database).

Whilst attesting to a block before it's imported is rather easy, if we're going to produce that attestation then we also need to be able to:

1. Verify that attestation.
1. Respond to RPC requests for the `beacon_block_root`.

Attestation verification (1) is *partially* covered. Since we prime the shuffling cache before we insert the block into the early attester cache, we should be fine for all typical use-cases. However, it is possible that the cache is washed out before we've managed to insert the state into the database and then attestation verification will fail with a "missing beacon state"-type error.

Providing the block via RPC (2) is also partially covered, since we'll check the database *and* the early attester cache when responding a blocks-by-root request. However, we'll still omit the block from blocks-by-range requests (until the block lands in the DB). I *think* this is fine, since there's no guarantee that we return all blocks for those responses.

Another important consideration is whether or not the *parent* of the early attester block is available in the databse. If it were not, we might fail to respond to blocks-by-root request that are iterating backwards to collect a chain of blocks. I argue that *we will always have the parent of the early attester block in the database.* This is because we are holding the fork-choice write-lock when inserting the block into the early attester cache and we do not drop that until the block is in the database.
2022-01-11 01:35:55 +00:00
Richard Patel
65b1374b58 Document Homebrew package (#2885)
## Issue Addressed

Resolves #2329

## Proposed Changes

Documents the recently added `lighthouse` Homebrew formula.

## Additional Info

NA


Co-authored-by: Richard Patel <richard@blockdaemon.com>
Co-authored-by: Michael Sproul <micsproul@gmail.com>
2022-01-08 01:15:07 +00:00
Fredrik Svantes
ccdc10c288 Adjusting ARCHIVE_URL (#2892)
Was renamed from eth2-clients to eth-clients
2022-01-07 05:32:34 +00:00
Akihito Nakano
daa3da3758 Add tests for flags enable-enr-auto-update and disable-packet-filter (#2887)
Resolves https://github.com/sigp/lighthouse/issues/2602

## Issue Addressed

https://github.com/sigp/lighthouse/pull/2749#issue-1037552417
> ## Open TODO
> Add tests for boot_node flags `enable-enr-auto-update` and `disable-packet-filter`. They end up in [Discv5Config](9ed2cba6bc/boot_node/src/config.rs (L29)), which doesn't support serde (de)serialization.

## Proposed Changes

- Added tests for flags `enable-enr-auto-update` and `disable-packet-filter`
- Instead of (de)serialize Discv5Config, added the two fields copied from Discv5Config to BootNodeConfigSerialization.
2022-01-07 05:32:33 +00:00
Mac L
20941bc0f7 Fix off-by-one in block packing lcli (#2878)
## Issue Addressed

The current `lcli` block packing code has an off-by-one where it would include an extra slot (the oldest slot) of attestations as "available" (this means there would be 33 slots of "available" attestations instead of 32).
There is typically only single-digit attestations remaining from that slot and as such does not cause a significant change to the results although every efficiency will have been very slightly under-reported.

## Proposed Changes

Prune the `available_attestation_set` before writing out the data instead of after.

## Additional Info

This `lcli` code will soon be deprecated by a Lighthouse API (#2879)  which will run significantly faster and will be used to hook into our upcoming monitoring platform #2873.
2022-01-07 05:32:29 +00:00
Philipp K
668477872e Allow value for beacon_node fee-recipient argument (#2884)
## Issue Addressed

The fee-recipient argument of the beacon node does not allow a value to be specified:

> $ lighthouse beacon_node --merge --fee-recipient "0x332E43696A505EF45b9319973785F837ce5267b9"
> error: Found argument '0x332E43696A505EF45b9319973785F837ce5267b9' which wasn't expected, or isn't valid in this context
> 
> USAGE:
>    lighthouse beacon_node --fee-recipient --merge
>
> For more information try --help

## Proposed Changes

Allow specifying a value for the fee-recipient argument in beacon_node/src/cli.rs

## Additional Info

I've added .takes_value(true) and successfully proposed a block in the kintsugi testnet with my own fee-recipient address instead of the hardcoded default. I think that was just missed as the argument does not make sense without a value :)


Co-authored-by: pk910 <philipp@pk910.de>
Co-authored-by: Michael Sproul <micsproul@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2022-01-07 01:21:42 +00:00
Paul Hauner
f6b5b1a8be Use ? debug formatting for block roots in beacon_chain.rs (#2890)
## Issue Addressed

NA

## Proposed Changes

Ensures full roots are printed, rather than shortened versions like `0x935b…d376`.

For example, it would be nice if we could do API queries based upon the roots shown in the `Beacon chain re-org` event:

```
Jan 05 12:36:52.224 WARN Beacon chain re-org                     reorg_distance: 2, new_slot: 2073184, new_head: 0x8a97…2dec, new_head_parent: 0xa985…7688, previous_slot: 2073183, previous_head: 0x935b…d376, service: beacon
Jan 05 13:35:05.832 WARN Beacon chain re-org                     reorg_distance: 1, new_slot: 2073475, new_head: 0x9207…c6b9, new_head_parent: 0xb2ce…839b, previous_slot: 2073474, previous_head: 0x8066…92f7, service: beacon
```

## Additional Info

We should eventually fix this project-wide, however this is a short-term patch.
2022-01-06 05:16:50 +00:00
Michael Sproul
fac117667b Update to superstruct v0.4.1 (#2886)
## Proposed Changes

Update `superstruct` to bring in @realbigsean's fixes necessary for MEV-compatible private beacon block types (a la #2795).

The refactoring is due to another change in superstruct that allows partial getters to be auto-generated.
2022-01-06 03:14:58 +00:00
Michael Sproul
0b54ff17f2 Fix assert in slashing protection import (#2881)
## Issue Addressed

There was an overeager assert in the import of slashing protection data here:

fff01b24dd/validator_client/slashing_protection/src/slashing_database.rs (L939)

We were asserting that if the import contained any blocks for a validator, then the database should contain only a single block for that validator due to pruning/consolidation. However, we would only prune if the import contained _relevant blocks_ (that would actually change the maximum slot):

fff01b24dd/validator_client/slashing_protection/src/slashing_database.rs (L629-L633)

This lead to spurious failures (in the form of `ConsistencyError`s) when importing an interchange containing no new blocks for any of the validators. This wasn't hard to trigger, e.g. export and then immediately re-import the same file.

## Proposed Changes

This PR fixes the issue by simplifying the import so that it's more like the import for attestations. I.e. we make the assert true by always pruning when the imported file contains blocks.

In practice this doesn't have any downsides: if we import a new block then the behaviour is as before, except that we drop the `signing_root`. If we import an existing block or an old block then we prune the database to a single block. The only time this would be relevant is during extreme clock drift locally _plus_ import of a non-drifted interchange, which should occur infrequently.

## Additional Info

I've also added `Arbitrary` implementations to the slashing protection types so that we can fuzz them. I have a fuzzer sitting in a separate directory which I may or may not commit in a subsequent PR.

There's a new test in the standard interchange tests v5.2.1 that checks for this issue: https://github.com/eth-clients/slashing-protection-interchange-tests/pull/12
2022-01-04 20:46:44 +00:00
realbigsean
dfc8968201 Update rust version in lcli Dockerfile (#2876)
The `lcli` docker build was no longer working on the old rust version

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-12-22 18:55:42 +00:00
Pawan Dhananjay
a0c5701e36 Only import blocks with valid execution payloads (#2869)
## Issue Addressed

N/A

## Proposed Changes

We are currently treating errors from the EL on `engine_executePayload` as `PayloadVerificationStatus::NotVerified`. This adds the block as a candidate head block in fork choice even if the EL explicitly rejected the block as invalid. 

`PayloadVerificationStatus::NotVerified` should be only returned when the EL explicitly returns "syncing" imo. This PR propagates an error instead of returning `NotVerified` on EL all EL errors.
2021-12-22 08:15:37 +00:00
Age Manning
81c667b58e Additional networking metrics (#2549)
Adds additional metrics for network monitoring and evaluation.


Co-authored-by: Mark Mackey <mark@sigmaprime.io>
2021-12-22 06:17:14 +00:00
eklm
60d917d9e9 Allow to set validator password via reimport (#2868)
## Issue Addressed

Resolves #2854 

## Proposed Changes

If validator was imported first without entering password and then imported again with valid password update the password in validator_definitions.yml

## Additional Info

There can be other cases for updating existing validator during import. They are not covered here.

Co-authored-by: Michael Sproul <micsproul@gmail.com>
2021-12-21 08:23:18 +00:00
Michael Sproul
3b61ac9cbf Optimise slasher DB layout and switch to MDBX (#2776)
## Issue Addressed

Closes #2286
Closes #2538
Closes #2342

## Proposed Changes

Part II of major slasher optimisations after #2767

These changes will be backwards-incompatible due to the move to MDBX (and the schema change) 😱 

* [x] Shrink attester keys from 16 bytes to 7 bytes.
* [x] Shrink attester records from 64 bytes to 6 bytes.
* [x] Separate `DiskConfig` from regular `Config`.
* [x] Add configuration for the LRU cache size.
* [x] Add a "migration" that deletes any legacy LMDB database.
2021-12-21 08:23:17 +00:00
Michael Sproul
a290a3c537 Add configurable block replayer (#2863)
## Issue Addressed

Successor to #2431

## Proposed Changes

* Add a `BlockReplayer` struct to abstract over the intricacies of calling `per_slot_processing` and `per_block_processing` while avoiding unnecessary tree hashing.
* Add a variant of the forwards state root iterator that does not require an `end_state`.
* Use the `BlockReplayer` when reconstructing states in the database. Use the efficient forwards iterator for frozen states.
* Refactor the iterators to remove `Arc<HotColdDB>` (this seems to be neater than making _everything_ an `Arc<HotColdDB>` as I did in #2431).

Supplying the state roots allow us to avoid building a tree hash cache at all when reconstructing historic states, which saves around 1 second flat (regardless of `slots-per-restore-point`). This is a small percentage of worst-case state load times with 200K validators and SPRP=2048 (~15s vs ~16s) but a significant speed-up for more frequent restore points: state loads with SPRP=32 should be now consistently <500ms instead of 1.5s (a ~3x speedup).

## Additional Info

Required by https://github.com/sigp/lighthouse/pull/2628
2021-12-21 06:30:52 +00:00
Divma
56d596ee42 Unban peers at the swarm level when purged (#2855)
## Issue Addressed
#2840
2021-12-20 23:45:21 +00:00
Globallager
f721f8d0a0 Minor Edit on Port Forward Reference (#2867)
## Issue Addressed

N/A

## Proposed Changes

Additional link to the corresponding page describing port forwarding.

## Additional Info

N/A
2021-12-19 22:18:50 +00:00
eklm
9be3d4ecac Downgrade AttestationStateIsFinalized error to debug (#2866)
## Issue Addressed

#2834 

## Proposed Changes

Change log message severity from error to debug in attestation verification when attestation state is finalized.
2021-12-17 07:59:46 +00:00
Michael Sproul
10dac51c6f Enable mallinfo2 behind feature flag (#2864)
## Proposed Changes

Add `mallinfo2` behind a feature flag so that we can get accurate memory metrics during debugging. It can be enabled when building Lighthouse like so (so long as the platform supports it):

```
cargo install --path lighthouse --features "malloc_utils/mallinfo2"
```
2021-12-15 20:39:50 +00:00
Paul Hauner
4dcb262c2d Update docker images to Ubuntu latest (#2862)
## Issue Addressed

- Resolves #2778

## Proposed Changes

Updates docker images from Buster (10) to Bullseye (11), since Bullseye is [listed](https://www.debian.org/releases/) as the "current stable release".

## Additional Info

NA
2021-12-15 07:44:44 +00:00
Divma
eee0260a68 do not count dialing peers in the connection limit (#2856)
## Issue Addressed
#2841 

## Proposed Changes
Not counting dialing peers while deciding if we have reached the target peers in case of outbound peers.

## Additional Info
Checked this running in nodes and bandwidth looks normal, peer count looks normal too
2021-12-15 05:48:45 +00:00
Michael Sproul
52c69c4eee Update OpenSSL (#2865)
## Proposed Changes

Bump OpenSSL for a new security advisory: https://rustsec.org/advisories/RUSTSEC-2021-0129
2021-12-15 03:56:14 +00:00
Michael Sproul
a43d5e161f Optimise balances cache in case of skipped slots (#2849)
## Proposed Changes

Remove the `is_first_block_in_epoch` logic from the balances cache update logic, as it was incorrect in the case of skipped slots. The updated code is simpler because regardless of whether the block is the first in the epoch we can check if an entry for the epoch boundary root already exists in the cache, and update the cache accordingly.

Additionally, to assist with flip-flopping justified epochs, move to cloning the balance cache rather than moving it. This should still be very fast in practice because the balances cache is a ~1.6MB `Vec`, and this operation is expected to only occur infrequently.
2021-12-13 23:35:57 +00:00
realbigsean
b22ac95d7f v1.1.6 Fork Choice changes (#2822)
## Issue Addressed

Resolves: https://github.com/sigp/lighthouse/issues/2741
Includes: https://github.com/sigp/lighthouse/pull/2853 so that we can get ssz static tests passing here on v1.1.6. If we want to merge that first, we can make this diff slightly smaller

## Proposed Changes

- Changes the `justified_epoch` and `finalized_epoch` in the `ProtoArrayNode` each to an `Option<Checkpoint>`. The `Option` is necessary only for the migration, so not ideal. But does allow us to add a default logic to `None` on these fields during the database migration.
- Adds a database migration from a legacy fork choice struct to the new one, search for all necessary block roots in fork choice by iterating through blocks in the db.
- updates related to https://github.com/ethereum/consensus-specs/pull/2727
  -  We will have to update the persisted forkchoice to make sure the justified checkpoint stored is correct according to the updated fork choice logic. This boils down to setting the forkchoice store's justified checkpoint to the justified checkpoint of the block that advanced the finalized checkpoint to the current one. 
  - AFAICT there's no migration steps necessary for the update to allow applying attestations from prior blocks, but would appreciate confirmation on that
- I updated the consensus spec tests to v1.1.6 here, but they will fail until we also implement the proposer score boost updates. I confirmed that the previously failing scenario `new_finalized_slot_is_justified_checkpoint_ancestor` will now pass after the boost updates, but haven't confirmed _all_ tests will pass because I just quickly stubbed out the proposer boost test scenario formatting.
- This PR now also includes proposer boosting https://github.com/ethereum/consensus-specs/pull/2730

## Additional Info
I realized checking justified and finalized roots in fork choice makes it more likely that we trigger this bug: https://github.com/ethereum/consensus-specs/pull/2727

It's possible the combination of justified checkpoint and finalized checkpoint in the forkchoice store is different from in any block in fork choice. So when trying to startup our store's justified checkpoint seems invalid to the rest of fork choice (but it should be valid). When this happens we get an `InvalidBestNode` error and fail to start up. So I'm including that bugfix in this branch.

Todo:

- [x] Fix fork choice tests
- [x] Self review
- [x] Add fix for https://github.com/ethereum/consensus-specs/pull/2727
- [x] Rebase onto Kintusgi 
- [x] Fix `num_active_validators` calculation as @michaelsproul pointed out
- [x] Clean up db migrations

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-12-13 20:43:22 +00:00
Pawan Dhananjay
e391b32858 Merge devnet 3 (#2859)
## Issue Addressed

N/A

## Proposed Changes

Changes required for the `merge-devnet-3`. Added some more non substantive renames on top of @realbigsean 's commit. 
Note: this doesn't include the proposer boosting changes in kintsugi v3.

This devnet isn't running with the proposer boosting fork choice changes so if we are looking to merge https://github.com/sigp/lighthouse/pull/2822 into `unstable`, then I think we should just maintain this branch for the devnet temporarily. 


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-12-12 09:04:21 +00:00
Michael Sproul
62d11e886e Update rusqlite from yanked version (#2861)
## Issue Addressed

The version of `rusqlite` that we were depending on has been yanked due to a vulnerability. The vulnerability only affects `update_hook`, which we don't use in Lighthouse.

There is no need to push a release -- users are safe to ignore this warning.

## Additional Info

Incoming advisory: https://github.com/rustsec/advisory-db/pull/1117
2021-12-08 23:12:08 +00:00
Lion - dapplion
2984f4b474 Remove wrong duplicated comment (#2751)
## Issue Addressed

Remove wrong duplicated comment. Comment was copied from ban_peer() but doesn't apply to unban_peer()
2021-12-06 05:34:15 +00:00
Mac L
a7a7edb6cf Optimise snapshot cache for late blocks (#2832)
## Proposed Changes

In the event of a late block, keep the block in the snapshot cache by cloning it. This helps us process new blocks quickly in the event the late block was re-org'd.


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-12-06 03:41:31 +00:00
realbigsean
b5f2764bae fix cache miss justified balances calculation (#2852)
## Issue Addressed

We were calculating justified balances incorrectly on cache misses in `set_justified_checkpoint`

## Proposed Changes

Use the `get_effective_balances` method as opposed to `state.balances`, which returns exact balances


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-12-03 16:58:10 +00:00
realbigsean
a80ccc3a33 1.57.0 lints (#2850)
## Issue Addressed

New rust lints

## Proposed Changes

- Boxing some enum variants
- removing some unused fields (is the validator lockfile unused? seemed so to me)

## Additional Info

- some error fields were marked as dead code but are logged out in areas
- left some dead fields in our ef test code because I assume they are useful for debugging?

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-12-03 04:44:30 +00:00
Pawan Dhananjay
f3c237cfa0 Restrict network limits based on merge fork epoch (#2839) 2021-12-02 14:32:31 +11:00
Paul Hauner
144978f8f8 Remove duplicate slot_clock method (#2842) 2021-12-02 14:29:59 +11:00
Paul Hauner
94385fe17b Support legacy data directories (#2846) 2021-12-02 14:29:59 +11:00
Paul Hauner
ab86b42874 Kintsugi Diva comments (#2836)
* Remove TODOs

* Fix typo
2021-12-02 14:29:59 +11:00
ethDreamer
c2f2813385 Cleanup Comments & Fix get_pow_block_hash_at_ttd() (#2835) 2021-12-02 14:29:59 +11:00
Paul Hauner
1b56ebf85e Kintsugi review comments (#2831)
* Fix makefile

* Return on invalid finalized block

* Fix todo in gossip scoring

* Require --merge for --fee-recipient

* Bump eth2_serde_utils

* Change schema versions

* Swap hash/uint256 test_random impls

* Use default for ExecutionPayload::empty

* Check for DBs before removing

* Remove kintsugi docker image

* Fix CLI default value
2021-12-02 14:29:59 +11:00
Paul Hauner
82a81524e3 Bump crate versions (#2829) 2021-12-02 14:29:57 +11:00
ethDreamer
f6748537db Removed PowBlock struct that never got used (#2813) 2021-12-02 14:29:20 +11:00
Paul Hauner
5f0fef2d1e Kintsugi on_merge_block tests (#2811)
* Start v1.1.5 updates

* Implement new payload creation logic

* Tidy, add comments

* Remove unused error enums

* Add validate payload for gossip

* Refactor validate_merge_block

* Split payload verification in per block processing

* Add execute_payload

* Tidy

* Tidy

* Start working on new fork choice tests

* Fix failing merge block test

* Skip block_lookup_failed test

* Fix failing terminal block test

* Fixes from self-review

* Address review comments
2021-12-02 14:29:20 +11:00
pawan
44a7b37ce3 Increase network limits (#2796)
Fix max packet sizes

Fix max_payload_size function

Add merge block test

Fix max size calculation; fix up test

Clear comments

Add a payload_size_function

Use safe arith for payload calculation

Return an error if block too big in block production

Separate test to check if block is over limit
2021-12-02 14:29:20 +11:00
Paul Hauner
afe59afacd Ensure difficulty/hash/epoch overrides change the ChainSpec (#2798)
* Unify loading of eth2_network_config

* Apply overrides at lighthouse binary level

* Remove duplicate override values

* Add merge values to existing net configs

* Make override flags global

* Add merge fields to testing config

* Add one to TTD

* Fix failing engine tests

* Fix test compile error

* Remove TTD flags

* Move get_eth2_network_config

* Fix warn

* Address review comments
2021-12-02 14:29:18 +11:00
Paul Hauner
47db682d7e Implement engine API v1.0.0-alpha.4 (#2810)
* Added ForkchoiceUpdatedV1 & GetPayloadV1

* Added ExecutePayloadV1

* Added new geth test vectors

* Separated Json Object/Serialization Code into file

* Deleted code/tests for Requests Removed from spec

* Finally fixed serialization of null '0x'

* Made Naming of JSON Structs Consistent

* Fix clippy lints

* Remove u64 payload id

* Remove unused serde impls

* Swap to [u8; 8] for payload id

* Tidy

* Adjust some block gen return vals

* Tidy

* Add fallback when payload id is unknown

* Remove comment

Co-authored-by: Mark Mackey <mark@sigmaprime.io>
2021-12-02 14:26:55 +11:00
Paul Hauner
cdfd1304a5 Skip memory intensive engine test (#2809)
* Allocate less memory (3GB) in engine tests

* Run cargo format

* Remove tx too large test

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-12-02 14:26:55 +11:00
Paul Hauner
cbd2201164 Fixes after rebasing Kintsugi onto unstable (#2799)
* Fix fork choice after rebase

* Remove paulhauner warp dep

* Fix fork choice test compile errors

* Assume fork choice payloads are valid

* Add comment

* Ignore new tests

* Fix error in test skipping
2021-12-02 14:26:55 +11:00
Paul Hauner
eb35c64afd Remove old uses of testnet 2021-12-02 14:26:55 +11:00
Pawan Dhananjay
24966c059d Fix Uint256 deserialization (#2786)
* Change base_fee_per_gas to Uint256

* Add custom (de)serialization to ExecutionPayload

* Fix errors

* Add a quoted_u256 module

* Remove unused function

* lint

* Add test

* Remove extra line

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-12-02 14:26:55 +11:00
realbigsean
de49c7ddaa 1.1.5 merge spec tests (#2781)
* Fix arbitrary check kintsugi

* Add merge chain spec fields, and a function to determine which constant to use based on the state variant

* increment spec test version

* Remove `Transaction` enum wrapper

* Remove Transaction new-type

* Remove gas validations

* Add `--terminal-block-hash-epoch-override` flag

* Increment spec tests version to 1.1.5

* Remove extraneous gossip verification https://github.com/ethereum/consensus-specs/pull/2687

* - Remove unused Error variants
- Require both "terminal-block-hash-epoch-override" and "terminal-block-hash-override" when either flag is used

* - Remove a couple more unused Error variants

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-12-02 14:26:55 +11:00
realbigsean
cdbe603adf Fix arbitrary check kintsugi (#2777) 2021-12-02 14:26:54 +11:00
Paul Hauner
cee18ca842 Move merge-f2f docker to kintsugi (#2774) 2021-12-02 14:26:54 +11:00
Paul Hauner
86e0c56a38 Kintsugi rebase patches (#2769)
* Freshen Cargo.lock

* Fix gossip worker

* Update map_fork_name_with
2021-12-02 14:26:54 +11:00
Paul Hauner
6b4cc63b57 Accept TTD override as decimal (#2676) 2021-12-02 14:26:54 +11:00
realbigsean
d8eec16c5e v1.1.1 spec updates (#2684)
* update initializing from eth1 for merge genesis

* read execution payload header from file lcli

* add `create-payload-header` command to `lcli`

* fix base fee parsing

* Apply suggestions from code review

* default `execution_payload_header` bool to false when deserializing `meta.yml` in EF tests

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-12-02 14:26:54 +11:00
Paul Hauner
6dde12f311 [Merge] Optimistic Sync: Stage 1 (#2686)
* Add payload verification status to fork choice

* Pass payload verification status to import_block

* Add valid back-propagation

* Add head safety status latch to API

* Remove ExecutionLayerStatus

* Add execution info to client notifier

* Update notifier logs

* Change use of "hash" to refer to beacon block

* Shutdown on invalid finalized block

* Tidy, add comments

* Fix failing FC tests

* Allow blocks with unsafe head

* Fix forkchoiceUpdate call on startup
2021-12-02 14:26:54 +11:00
Pawan Dhananjay
aa1d57aa55 Fix db paths when datadir is relative (#2682) 2021-12-02 14:26:53 +11:00
Paul Hauner
67a6f91df6 [Merge] Optimistic EL verification (#2683)
* Ignore payload errors

* Only return payload handle on valid response

* Push some engine logs down to debug

* Push ee fork choice log to debug

* Push engine call failure to debug

* Push some more errors to debug

* Fix panic at startup
2021-12-02 14:26:53 +11:00
Paul Hauner
35350dff75 [Merge] Block validator duties when EL is not ready (#2672)
* Reject some HTTP endpoints when EL is not ready

* Restrict more endpoints

* Add watchdog task

* Change scheduling

* Update to new schedule

* Add "syncing" concept

* Remove RequireSynced

* Add is_merge_complete to head_info

* Cache latest_head in Engines

* Call consensus_forkchoiceUpdate on startup
2021-12-02 14:26:53 +11:00
Paul Hauner
d6fda44620 Disable notifier logging from dummy eth1 backend (#2680) 2021-12-02 14:26:53 +11:00
Paul Hauner
7975ceff97 Update lcli pubkey replace command (#2677) 2021-12-02 14:26:53 +11:00
ethDreamer
52e5083502 Fixed bugs for m3 readiness (#2669)
* Fixed bugs for m3 readiness

* woops

* cargo fmt..
2021-12-02 14:26:53 +11:00
Paul Hauner
b162b067de Misc changes for merge testnets (#2667)
* Thread eth1_block_hash into interop genesis state

* Add merge-fork-epoch flag

* Build LH with minimal spec by default

* Add verbose logs to execution_layer

* Add --http-allow-sync-stalled flag

* Update lcli new-testnet to create genesis state

* Fix http test

* Fix compile errors in tests
2021-12-02 14:26:52 +11:00
Paul Hauner
a1033a9247 Add BeaconChainHarness tests for The Merge (#2661)
* Start adding merge tests

* Expose MockExecutionLayer

* Add mock_execution_layer to BeaconChainHarness

* Progress with merge test

* Return more detailed errors with gas limit issues

* Use a better gas limit in block gen

* Ensure TTD is met in block gen

* Fix basic_merge tests

* Start geth testing

* Fix conflicts after rebase

* Remove geth tests

* Improve merge test

* Address clippy lints

* Make pow block gen a pure function

* Add working new test, breaking existing test

* Fix test names

* Add should_panic

* Don't run merge tests in debug

* Detect a tokio runtime when starting MockServer

* Fix clippy lint, include merge tests
2021-12-02 14:26:52 +11:00
Paul Hauner
801f6f7425 Disable autotests for beacon_chain (#2658) 2021-12-02 14:26:52 +11:00
Pawan Dhananjay
471cf10392 Add merge fork_epoch and fork_version to Config (#2663) 2021-12-02 14:26:52 +11:00
realbigsean
5efb7aeab4 add automated docker build for merge-f2f branch (#2654) 2021-12-02 14:26:52 +11:00
Paul Hauner
01031931d9 [Merge] Add execution API test vectors from Geth (#2651)
* Add geth request vectors

* Add geth response vectors

* Fix clippy lints
2021-12-02 14:26:52 +11:00
Paul Hauner
20ca7a56ed [Merge] Add serde impls for Transactions type (#2649)
* Start implemented serde for transactions

* Revise serde impl

* Add tests for transaction decoding
2021-12-02 14:26:51 +11:00
Paul Hauner
d8623cfc4f [Merge] Implement execution_layer (#2635)
* Checkout serde_utils from rayonism

* Make eth1::http functions pub

* Add bones of execution_layer

* Modify decoding

* Expose Transaction, cargo fmt

* Add executePayload

* Add all minimal spec endpoints

* Start adding json rpc wrapper

* Finish custom JSON response handler

* Switch to new rpc sending method

* Add first test

* Fix camelCase

* Finish adding tests

* Begin threading execution layer into BeaconChain

* Fix clippy lints

* Fix clippy lints

* Thread execution layer into ClientBuilder

* Add CLI flags

* Add block processing methods to ExecutionLayer

* Add block_on to execution_layer

* Integrate execute_payload

* Add extra_data field

* Begin implementing payload handle

* Send consensus valid/invalid messages

* Fix minor type in task_executor

* Call forkchoiceUpdated

* Add search for TTD block

* Thread TTD into execution layer

* Allow producing block with execution payload

* Add LRU cache for execution blocks

* Remove duplicate 0x on ssz_types serialization

* Add tests for block getter methods

* Add basic block generator impl

* Add is_valid_terminal_block to EL

* Verify merge block in block_verification

* Partially implement --terminal-block-hash-override

* Add terminal_block_hash to ChainSpec

* Remove Option from terminal_block_hash in EL

* Revert merge changes to consensus/fork_choice

* Remove commented-out code

* Add bones for handling RPC methods on test server

* Add first ExecutionLayer tests

* Add testing for finding terminal block

* Prevent infinite loops

* Add insert_merge_block to block gen

* Add block gen test for pos blocks

* Start adding payloads to block gen

* Fix clippy lints

* Add execution payload to block gen

* Add execute_payload to block_gen

* Refactor block gen

* Add all routes to mock server

* Use Uint256 for base_fee_per_gas

* Add working execution chain build

* Remove unused var

* Revert "Use Uint256 for base_fee_per_gas"

This reverts commit 6c88f19ac4.

* Fix base_fee_for_gas Uint256

* Update execute payload handle

* Improve testing, fix bugs

* Fix default fee-recipient

* Fix fee-recipient address (again)

* Add check for terminal block, add comments, tidy

* Apply suggestions from code review

Co-authored-by: realbigsean <seananderson33@GMAIL.com>

* Fix is_none on handle Drop

* Remove commented-out tests

Co-authored-by: realbigsean <seananderson33@GMAIL.com>
2021-12-02 14:26:51 +11:00
ethDreamer
1563bce905 Finished Gossip Block Validation Conditions (#2640)
* Gossip Block Validation is Much More Efficient

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-12-02 14:26:51 +11:00
ethDreamer
fe945bc84a Fork boundary fix (#2646)
* Fixed Gossip Topics on Fork Boundary
2021-12-02 14:26:51 +11:00
realbigsean
aa534f8989 Store execution block hash in fork choice (#2643)
* - Update the fork choice `ProtoNode` to include `is_merge_complete`
- Add database migration for the persisted fork choice

* update tests

* Small cleanup

* lints

* store execution block hash in fork choice rather than bool
2021-12-02 14:26:51 +11:00
Michael Sproul
fd828199f5 Update test vectors to v1.1.0 (#2642) 2021-12-02 14:26:51 +11:00
Paul Hauner
18eee2dc82 Handle merge fork in web3signer (#2631) 2021-12-02 14:26:51 +11:00
Michael Sproul
10b263fed4 Update merge consensus to v1.1.0-beta.5 (#2630) 2021-12-02 14:26:50 +11:00
Paul Hauner
c10e8ce955 Fix clippy lints on merge-f2f (#2626)
* Remove unchecked arith from ssz_derive

* Address clippy lints in block_verfication

* Use safe math for is_valid_gas_limit
2021-12-02 14:26:50 +11:00
Michael Sproul
cce855f9ea Fix consensus, SSZ, tree hash & run merge EF tests (#2622)
* Update to v1.1.0-beta.4 (squash of #2548)

* SSZ, cached tree hash, EF tests
2021-12-02 14:26:50 +11:00
Mark Mackey
5687c56d51 Initial merge changes
Added Execution Payload from Rayonism Fork

Updated new Containers to match Merge Spec

Updated BeaconBlockBody for Merge Spec

Completed updating BeaconState and BeaconBlockBody

Modified ExecutionPayload<T> to use Transaction<T>

Mostly Finished Changes for beacon-chain.md

Added some things for fork-choice.md

Update to match new fork-choice.md/fork.md changes

ran cargo fmt

Added Missing Pieces in eth2_libp2p for Merge

fix ef test

Various Changes to Conform Closer to Merge Spec
2021-12-02 14:26:50 +11:00
Mac L
fe75a0a9a1 Add background file logging (#2762)
## Issue Addressed

Closes #1996 

## Proposed Changes

Run a second `Logger` via `sloggers` which logs to a file in the background with:
- separate `debug-level` for background and terminal logging
- the ability to limit log size
- rotation through a customizable number of log files
- an option to compress old log files (`.gz` format)

Add the following new CLI flags:
- `--logfile-debug-level`: The debug level of the log files
- `--logfile-max-size`: The maximum size of each log file
- `--logfile-max-number`: The number of old log files to store
- `--logfile-compress`: Whether to compress old log files

By default background logging uses the `debug` log level and saves logfiles to:
- Beacon Node:  `$HOME/.lighthouse/$network/beacon/logs/beacon.log`
- Validator Client:  `$HOME/.lighthouse/$network/validators/logs/validator.log`

Or, when using the `--datadir` flag:
`$datadir/beacon/logs/beacon.log` and `$datadir/validators/logs/validator.log`

Once rotated, old logs are stored like so: `beacon.log.1`, `beacon.log.2` etc. 
> Note: `beacon.log.1` is always newer than `beacon.log.2`.

## Additional Info

Currently the default value of `--logfile-max-size` is 200 (MB) and `--logfile-max-number` is 5.
This means that the maximum storage space that the logs will take up by default is 1.2GB. 
(200MB x 5 from old log files + <200MB the current logfile being written to)
Happy to adjust these default values to whatever people think is appropriate. 

It's also worth noting that when logging to a file, we lose our custom `slog` formatting. This means the logfile logs look like this:
```
Oct 27 16:02:50.305 INFO Lighthouse started, version: Lighthouse/v2.0.1-8edd9d4+, module: lighthouse:413
Oct 27 16:02:50.305 INFO Configured for network, name: prater, module: lighthouse:414
```
2021-11-30 03:25:32 +00:00
Age Manning
6625aa4afe Status'd Peer Not Found (#2761)
## Issue Addressed

Users are experiencing `Status'd peer not found` errors

## Proposed Changes

Although I cannot reproduce this error, this is only one connection state change that is not addressed in the peer manager (that I could see). The error occurs because the number of disconnected peers in the peerdb becomes out of sync with the actual number of disconnected peers. From what I can tell almost all possible connection state changes are handled, except for the case when a disconnected peer changes to be disconnecting. This can potentially happen at the peer connection limit, where a previously connected peer switches to disconnecting. 

This PR decrements the disconnected counter when this event occurs and from what I can tell, covers all possible disconnection state changes in the peer manager.
2021-11-28 22:46:17 +00:00
Divma
413b0b5b2b Correctly update range status when outdated chains are removed (#2827)
We were batch removing chains when purging, and then updating the status of the collection for each of those. This makes the range status be out of sync with the real status. This represented no harm to the global sync status, but I've changed it to comply with a correct debug assertion that I got triggered while doing some testing.
Also added tests and improved code quality as per @paulhauner 's suggestions.
2021-11-26 01:13:49 +00:00
Pawan Dhananjay
9eedb6b888 Allow additional subnet peers (#2823)
## Issue Addressed

N/A

## Proposed Changes

1. Don't disconnect peer from dht on connection limit errors
2. Bump up `PRIORITY_PEER_EXCESS` to allow for dialing upto 60 peers by default.



Co-authored-by: Diva M <divma@protonmail.com>
2021-11-25 21:27:08 +00:00
Michael Sproul
2c07a72980 Revert peer DB changes from #2724 (#2828)
## Proposed Changes

This reverts commit 53562010ec from PR #2724

Hopefully this will restore the reliability of the sync simulator.
2021-11-25 03:45:52 +00:00
Michael Sproul
3fb8162dcc Use published ssz/tree_hash (#2825)
## Proposed Changes

Switch over to the latest published versions of the crates in the SSZ/`tree_hash` family.

## Additional Info

The crates were published at the current head of `unstable`: 0b319d4926. All 5 crates listed in this PR were published via tags, e.g. https://github.com/sigp/lighthouse/releases/tag/tree-hash-v0.4.0
2021-11-24 00:28:30 +00:00
Mac L
c61fbf71a4 Ensure consistent log formatting (#2819)
## Issue Addressed

N/A

## Proposed Changes

Filter out certain ascii characters when logging to ensure proper log formatting.
2021-11-23 07:06:22 +00:00
Age Manning
0b319d4926 Inform dialing via the behaviour (#2814)
I had this change but it seems to have been lost in chaos of network upgrades.

The swarm dialing event seems to miss some cases where we dial via the behaviour. This causes an error to be logged as the peer manager doesn't know about some dialing events. 

This shifts the logic to the behaviour to inform the peer manager.
2021-11-19 04:42:33 +00:00
Divma
53562010ec Move peer db writes to eth2 libp2p (#2724)
## Issue Addressed
Part of a bigger effort to make the network globals read only. This moves all writes to the `PeerDB` to the `eth2_libp2p` crate. Limiting writes to the peer manager is a slightly more complicated issue for a next PR, to keep things reviewable.

## Proposed Changes
- Make the peers field in the globals a private field.
- Allow mutable access to the peers field to `eth2_libp2p` for now.
- Add a new network message to update the sync state.

Co-authored-by: Age Manning <Age@AgeManning.com>
2021-11-19 04:42:31 +00:00
Divma
31386277c3 Sync wrong dbg assertion (#2821)
## Issue Addressed

Running a beacon node I triggered a sync debug panic. And so finally the time to create tests for sync arrived. Fortunately, te bug was not in the sync algorithm itself but a wrong assertion

## Proposed Changes

- Split Range's impl from the BeaconChain via a trait. This is needed for testing. The TestingRig/Harness is way bigger than needed and does not provide the modification functionalities that are needed to test sync. I find this simpler, tho some could disagree.
- Add a regression test for sync that fails before the changes.
- Fix the wrong assertion.
2021-11-19 02:38:25 +00:00
Age Manning
e519af9012 Update Lighthouse Dependencies (#2818)
## Issue Addressed

Updates lighthouse dependencies to resolve audit issues in out-dated deps.
2021-11-18 05:08:42 +00:00
Pawan Dhananjay
e32c09bfda Fix decoding max length (#2816)
## Issue Addressed

N/A

## Proposed Changes

Fix encoder max length to the correct value (`MAX_RPC_SIZE`).
2021-11-16 22:23:39 +00:00
Age Manning
a43a2448b7 Investigate and correct RPC Response Timeouts (#2804)
RPC Responses are for some reason not removing their timeout when they are completing. 

As an example:

```
Nov 09 01:18:20.256 DEBG Received BlocksByRange Request          step: 1, start_slot: 728465, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:20.263 DEBG Received BlocksByRange Request          step: 1, start_slot: 728593, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:20.483 DEBG BlocksByRange Response sent             returned: 63, requested: 64, current_slot: 2466389, start_slot: 728465, msg: Failed to return all requested blocks, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:20.500 DEBG BlocksByRange Response sent             returned: 64, requested: 64, current_slot: 2466389, start_slot: 728593, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:21.068 DEBG Received BlocksByRange Request          step: 1, start_slot: 728529, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:21.272 DEBG BlocksByRange Response sent             returned: 63, requested: 64, current_slot: 2466389, start_slot: 728529, msg: Failed to return all requested blocks, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:23.434 DEBG Received BlocksByRange Request          step: 1, start_slot: 728657, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:23.665 DEBG BlocksByRange Response sent             returned: 64, requested: 64, current_slot: 2466390, start_slot: 728657, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:25.851 DEBG Received BlocksByRange Request          step: 1, start_slot: 728337, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:25.851 DEBG Received BlocksByRange Request          step: 1, start_slot: 728401, count: 64, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:26.094 DEBG BlocksByRange Response sent             returned: 62, requested: 64, current_slot: 2466390, start_slot: 728401, msg: Failed to return all requested blocks, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:26.100 DEBG BlocksByRange Response sent             returned: 63, requested: 64, current_slot: 2466390, start_slot: 728337, msg: Failed to return all requested blocks, peer: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw
Nov 09 01:18:31.070 DEBG RPC Error                               direction: Incoming, score: 0, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw, client: Prysm: version: a80b1c252a9b4773493b41999769bf3134ac373f, os_version: unknown, err: Stream Timeout, protocol: beacon_blocks_by_range, service: libp2p
Nov 09 01:18:31.070 WARN Timed out to a peer's request. Likely insufficient resources, reduce peer count, service: libp2p
Nov 09 01:18:31.085 DEBG RPC Error                               direction: Incoming, score: 0, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw, client: Prysm: version: a80b1c252a9b4773493b41999769bf3134ac373f, os_version: unknown, err: Stream Timeout, protocol: beacon_blocks_by_range, service: libp2p
Nov 09 01:18:31.085 WARN Timed out to a peer's request. Likely insufficient resources, reduce peer count, service: libp2p
Nov 09 01:18:31.459 DEBG RPC Error                               direction: Incoming, score: 0, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw, client: Prysm: version: a80b1c252a9b4773493b41999769bf3134ac373f, os_version: unknown, err: Stream Timeout, protocol: beacon_blocks_by_range, service: libp2p
Nov 09 01:18:31.459 WARN Timed out to a peer's request. Likely insufficient resources, reduce peer count, service: libp2p
Nov 09 01:18:34.129 DEBG RPC Error                               direction: Incoming, score: 0, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw, client: Prysm: version: a80b1c252a9b4773493b41999769bf3134ac373f, os_version: unknown, err: Stream Timeout, protocol: beacon_blocks_by_range, service: libp2p
Nov 09 01:18:34.130 WARN Timed out to a peer's request. Likely insufficient resources, reduce peer count, service: libp2p
Nov 09 01:18:35.686 DEBG Peer Manager disconnecting peer         reason: Too many peers, peer_id: 16Uiu2HAmEmBURejquBUMgKAqxViNoPnSptTWLA2CfgSPnnKENBNw, service: libp2p
```

This PR is to investigate and correct the issue. 

~~My current thoughts are that for some reason we are not closing the streams correctly, or fast enough, or the executor is not registering the closes and waking up.~~ - Pretty sure this is not the case, see message below for a more accurate reason.

~~I've currently added a timeout to stream closures in an attempt to force streams to close and the future to always complete.~~ I removed this
2021-11-16 03:42:25 +00:00
Pawan Dhananjay
58b04acf28 Fix simulator issues (#2802)
## Issue Addressed

Resolves #2775 

## Proposed Changes

1. Reduces the target_peers to stop recursive discovery requests.
2. Changes the eth1 `auto_update_interval` config parameter to be equal to the eth1 block time in the simulator. Without this, the eth1 `latest_cached_block` wouldn't be updated for some slots around `eth1_voting_period` boundaries which would lead to the eth1 cache falling out of sync. This is what caused the `Syncing eth1 block cache` and `No valid eth1_data votes` logs.
2021-11-15 06:38:29 +00:00
Pawan Dhananjay
3919046731 Add a section on using infura as the checkpoint sync provider (#2797)
## Issue Addressed

N/A

## Proposed Changes

From discord, it seems like users are a bit unclear on how to run checkpoint sync if they don't have an existing synced beacon node. Adds a note on how to use infura for the checkpoint sync feature.
2021-11-10 23:43:12 +00:00
Paul Hauner
931daa40d7 Add fork choice EF tests (#2737)
## Issue Addressed

Resolves #2545

## Proposed Changes

Adds the long-overdue EF tests for fork choice. Although we had pretty good coverage via other implementations that closely followed our approach, it is nonetheless important for us to implement these tests too.

During testing I found that we were using a hard-coded `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` value rather than one from the `ChainSpec`. This caused a failure during a minimal preset test. This doesn't represent a risk to mainnet or testnets, since the hard-coded value matched the mainnet preset.

## Failing Cases

There is one failing case which is presently marked as `SkippedKnownFailure`:

```
case 4 ("new_finalized_slot_is_justified_checkpoint_ancestor") from /home/paul/development/lighthouse/testing/ef_tests/consensus-spec-tests/tests/minimal/phase0/fork_choice/on_block/pyspec_tests/new_finalized_slot_is_justified_checkpoint_ancestor failed with NotEqual:
head check failed: Got Head { slot: Slot(40), root: 0x9183dbaed4191a862bd307d476e687277fc08469fc38618699863333487703e7 } | Expected Head { slot: Slot(24), root: 0x105b49b51bf7103c182aa58860b039550a89c05a4675992e2af703bd02c84570 }
```

This failure is due to #2741. It's not a particularly high priority issue at the moment, so we fix it after merging this PR.
2021-11-08 07:29:04 +00:00
mooori
d01fe02824 Add regression tests for boot_node (#2749)
## Issue Addressed
Resolves #2602

## Proposed Changes

*Note: For a review it might help to look at the individual commits.*

### `boot_node`
Add support for the flags `dump-config` and `immediate-shutdown`. For `immediate-shutdown` the actual behavior could be described as `dump-config-and-exit`.

Both flags are handled in `boot_node::main`, which appears to be the simplest approach.

### `boot_node` regression tests
Added in `lighthouse/tests/boot_node.rs`.

### `CommandLineTestExec`
Factors out boilerplate related to CLI tests. It's used in the regression tests for `boot_node`, `beacon_node` and `validator_client`.

## Open TODO
Add tests for `boot_node` flags `enable-enr-auto-update` and `disable-packet-filter`. They end up in [`Discv5Config`](9ed2cba6bc/boot_node/src/config.rs (L29)), which doesn't support serde (de)serialization.

I haven't found a workaround - guidance would be appreciated.
2021-11-08 01:37:58 +00:00
Divma
fbafe416d1 Move the peer manager to be a behaviour (#2773)
This simply moves some functions that were "swarm notifications" to a network behaviour implementation.

Notes
------
- We could disconnect from the peer manager but we would lose the rpc shutdown message
- We still notify from the swarm since this is the most reliable way to get some events. Ugly but best for now
- Events need to be pushed with "add event" to wake the waker

Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com>
2021-11-08 00:01:10 +00:00
Michael Sproul
df02639b71 De-duplicate attestations in the slasher (#2767)
## Issue Addressed

Closes https://github.com/sigp/lighthouse/issues/2112
Closes https://github.com/sigp/lighthouse/issues/1861

## Proposed Changes

Collect attestations by validator index in the slasher, and use the magic of reference counting to automatically discard redundant attestations. This results in us storing only 1-2% of the attestations observed when subscribed to all subnets, which carries over to a 50-100x reduction in data stored 🎉 

## Additional Info

There's some nuance to the configuration of the `slot-offset`. It has a profound effect on the effictiveness of de-duplication, see the docs added to the book for an explanation: 5442e695e5/book/src/slasher.md (slot-offset)
2021-11-08 00:01:09 +00:00
Rémy Roy
fadb8b2b2b Add minimum supported version for checkpoint sync in book (#2779)
## Issue Addressed

No specific issue. Just some improvement in the documentation provided by the book.

## Proposed Changes

Add minimum supported version for checkpoint sync in book to make sure users who want to use this feature know they need to be using at least version 2.0.0.
2021-11-05 06:35:52 +00:00
Pascal Bach
2ed6775dcf Add cargo vendor test (#2076)
## Issue Addressed

This is related to #1926 and #1712.

## Proposed Changes

This PR adds a test that make sure that the used dependencies can be vendored.

Being able to vendor the dependencies is important for archival and repdroducibility purpose.
It's also required to package lighthouse for some Linux distributions. Specifically [NixOS](https://nixos.org/) and [Yocto](https://www.yoctoproject.org/).

## Additional Info

This PR only adds the test, it doesn't clean up the dependencies yet. That's why it is in draft.
2021-11-05 04:42:12 +00:00
Divma
a683e0296a Peer manager cfg (#2766)
## Issue Addressed
I've done this change in a couple of WIPs already so I might as well submit it on its own. This changes no functionality but reduces coupling in a 0.0001%. It also helps new people who need to work in the peer manager to better understand what it actually needs from the outside

## Proposed Changes

Add a config to the peer manager
2021-11-03 23:44:44 +00:00
Divma
7502970a7d Do not compute metrics in the network service if the cli flag is not set (#2765)
## Issue Addressed

The computation of metrics in the network service can be expensive. This disables the computation unless the cli flag `metrics` is set.

## Additional Info
Metrics in other parts of the network are still updated, since most are simple metrics and checking if metrics are enabled each time each metric is updated doesn't seem like a gain.
2021-11-03 00:06:03 +00:00
realbigsean
c4ad0e3fb3 Ensure dependent root consistency in head events (#2753)
## Issue Addressed

@paulhauner noticed that when we send head events, we use the block root from `new_head` in `fork_choice_internal`, but calculate `dependent_root` and `previous_dependent_root` using the `canonical_head`. This is normally fine because `new_head` updates the `canonical_head` in `fork_choice_internal`, but it's possible we have a reorg updating `canonical_head` before our head events are sent. So this PR ensures `dependent_root` and `previous_dependent_root` are always derived from the state associated with `new_head`.



Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-11-02 02:26:32 +00:00
Mac L
80627b428b Fix linting error on Windows (#2759)
While testing some code on Windows, I ran into a failure when using `clippy` via (`make lint`):
```
error: this expression borrows a reference (`&str`) that is immediately dereferenced by the compiler
   --> common/filesystem/src/lib.rs:105:43
    |
105 |         let mut acl = ACL::from_file_path(&path_str, false).map_err(Error::UnableToRetrieveACL)?;
    |                                           ^^^^^^^^^ help: change this to: `path_str`
    |
    = note: `-D clippy::needless-borrow` implied by `-D warnings`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
    
error: could not compile `filesystem` due to previous error
```

## Proposed Changes

Remove the unnecessary borrow as suggested.

## Additional Info

Since we are only running `clippy` in CI on Ubuntu, I believe we don't have any way (in CI) to detect these Windows specific lint errors (either from new code, or from linting changes from new Rust versions. 
This is because code marked as `#[cfg(windows)]` is not checked on `unix` systems and vice versa.

I'm conscious that our CI runs are already taking a long time, and that adding a new Windows `clippy` run would add a non-negligible amount of time to the runs (not sure if this topic has already been discussed), but it something to be aware of.

## Extra Note
I don't think this is the case, but it might be worth someone else running `make lint` on their Windows machines to eliminate the possibility that this is an error specific to my setup.
2021-11-01 07:44:42 +00:00
Pawan Dhananjay
4499adc7fd Check proposer index during block production (#2740)
## Issue Addressed

Resolves #2612 

## Proposed Changes

Implements both the checks mentioned in the original issue. 
1. Verifies the `randao_reveal` in the beacon node
2. Cross checks the proposer index after getting back the block from the beacon node.

## Additional info
The block production time increases by ~10x because of the signature verification on the beacon node (based on the `beacon_block_production_process_seconds` metric) when running on a local testnet.
2021-11-01 07:44:40 +00:00
Michael Sproul
ffb04e1a9e Add op pool metrics for attestations (#2758)
## Proposed Changes

Add several metrics for the number of attestations in the op pool. These give us a way to observe the number of valid, non-trivial attestations during block packing rather than just the size of the entire op pool.
2021-11-01 05:52:31 +00:00
Michael Sproul
e8a557fdd8 Update prometheus to v0.13.0 (#2757)
## Issue Addressed

Closes #2485
2021-11-01 05:52:30 +00:00
Divma
e2c0650d16 Relax late sync committee penalty (#2752)
## Issue Addressed

Getting too many peers kicked due to slightly late sync committee messages as tested on.. under-performant hardware.

## Proposed Changes

Only penalize if the message is more than one slot late. Still ignore the message-

Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com>
2021-10-31 22:30:19 +00:00
Age Manning
1790010260 Upgrade to latest libp2p (#2605)
This is a pre-cursor to the next libp2p upgrade. 

It is currently being used for staging a number of PR upgrades which are contingent on the latest libp2p.
2021-10-29 01:59:29 +00:00
ethDreamer
2c4413454a Fixed Gossip Topics on Fork Boundary (#2619)
## Issue Addressed

The [p2p-interface section of the `altair` spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/p2p-interface.md#transitioning-the-gossip) says you should subscribe to the topics for a fork "In advance of the fork" and unsubscribe from old topics `2 Epochs` after the new fork is activated. We've chosen to subscribe to new fork topics `2 slots` before the fork is initiated.

This function is supposed to return the required fork digests at any given time but as it was currently written, it doesn't return the fork digest for a previous fork if you've switched to the current fork less than 2 epoch's ago. Also this function required modification for every new fork we add.

## Proposed Changes

Make this function fork-agnostic and correctly handle the previous fork topic digests when you've only just switched to the new fork.
2021-10-29 00:05:27 +00:00
Pawan Dhananjay
88063398f6 Prevent double import of blocks (#2647)
## Issue Addressed

Resolves #2611 

## Proposed Changes

Adds a duplicate block root cache to the `BeaconProcessor`. Adds the block root to the cache before calling `process_gossip_block` and `process_rpc_block`. Since `process_rpc_block` is called only for single block lookups, we don't have to worry about batched block imports.

The block is imported from the source(gossip/rpc) that arrives first. The block that arrives second is not imported to avoid the db access issue.
There are 2 cases:
1. Block that arrives second is from rpc: In this case, we return an optimistic `BlockError::BlockIsAlreadyKnown` to sync.
2. Block that arrives second is from gossip: In this case, we only do gossip verification and forwarding but don't import the block into the the beacon chain.

## Additional info
Splits up `process_gossip_block` function to `process_gossip_unverified_block` and `process_gossip_verified_block`.
2021-10-28 03:36:14 +00:00
Michael Sproul
2dc6163043 Add API version headers and map_fork_name! (#2745)
## Proposed Changes

* Add the `Eth-Consensus-Version` header to the HTTP API for the block and state endpoints. This is part of the v2.1.0 API that was recently released: https://github.com/ethereum/beacon-APIs/pull/170
* Add tests for the above. I refactored the `eth2` crate's helper functions to make this more straight-forward, and introduced some new mixin traits that I think greatly improve readability and flexibility.
* Add a new `map_with_fork!` macro which is useful for decoding a superstruct type without naming all its variants. It is now used for SSZ-decoding `BeaconBlock` and `BeaconState`, and for JSON-decoding `SignedBeaconBlock` in the API.

## Additional Info

The `map_with_fork!` changes will conflict with the Merge changes, but when resolving the conflict the changes from this branch should be preferred (it is no longer necessary to enumerate every fork). The merge fork _will_  need to be added to `map_fork_name_with`.
2021-10-28 01:18:04 +00:00
Mac L
8edd9d45ab Fix purge-db edge case (#2747)
## Issue Addressed

Currently, if you launch the beacon node with the `--purge-db` flag and the `beacon` directory exists, but one (or both) of the `chain_db` or `freezer-db` directories are missing, it will error unnecessarily with: 
```
Failed to remove chain_db: No such file or directory (os error 2)
```

This is an edge case which can occur in cases of manual intervention (a user deleted the directory) or if you had previously run with the `--purge-db` flag and Lighthouse errored before it could initialize the db directories.

## Proposed Changes

Check if the `chain_db`/`freezer_db` exists before attempting to remove them. This prevents unnecessary errors.
2021-10-25 22:11:28 +00:00
Mac L
39c0d1219c Add note about java dependency (#2746)
## Issue Addressed
Currently, running the Web3Signer tests locally without having a java runtime environment installed and available on your PATH will result in the tests failing. 

## Proposed Changes
Add a note regarding the Web3Signer tests being dependent on java (similar to what we have for `ganache-cli`)
2021-10-25 22:11:26 +00:00
Age Manning
3a51f829d5 Add metrics for individual async tasks (#2735)
I have been in the process of debugging libp2p tasks as there is something locking our executor. 

This addition adds a metric allowing us to track all tasks within lighthouse allowing us to identify various sections of Lighthouse code that may be taking longer than normal to process.
2021-10-25 05:18:49 +00:00
Michael Sproul
bf1667a904 Fix test warnings on Rust 1.56.0 (#2743)
## Issue Addressed

Continuation of #2728, fix the fork choice tests for Rust 1.56.0 so that `unstable` is free of warnings.

CI will be broken until this PR merges, because we strictly enforce the absence of warnings (even for tests)
2021-10-22 04:49:51 +00:00
Divma
d4819bfd42 Add a waker to the RPC handler (#2721)
## Issue Addressed

Attempts to fix #2701 but I doubt this is the reason behind that.

## Proposed Changes

maintain a waker in the rpc handler and call it if an event is received
2021-10-21 06:14:36 +00:00
Pawan Dhananjay
de34001e78 Update next_fork_subscriptions correctly (#2688)
## Issue Addressed

N/A

## Proposed Changes

Update the `next_fork_subscriptions` timer only after a fork happens.
2021-10-21 04:38:44 +00:00
Divma
2f7b5f8cb0 Update mainnet altair types test (#2738)
## Issue Addressed
e895074ba updated the altair fork and now that we are a week away this test no longer panics.

## Proposed Changes
Remove the expected panic and explanatory note.
2021-10-20 23:12:12 +00:00
divma
99f7a7db58 remove double backfill sync state (#2733)
## Issue Addressed
In the backfill sync the state was maintained twice, once locally and also in the globals. This makes it so that it's maintained only once.

The only behavioral change is that when backfill sync in paused, the global backfill state is updated. I asked @AgeManning about this and he deemed it a bug, so this solves it.
2021-10-19 22:32:25 +00:00
Michael Sproul
aad397f00a Resolve Rust 1.56 lints and warnings (#2728)
## Issue Addressed

When compiling with Rust 1.56.0 the compiler generates 3 instances of this warning:

```
warning: trailing semicolon in macro used in expression position
   --> common/eth2_network_config/src/lib.rs:181:24
    |
181 |                     })?;
    |                        ^
...
195 |         let deposit_contract_deploy_block = load_from_file!(DEPLOY_BLOCK_FILE);
    |                                             ---------------------------------- in this macro invocation
    |
    = note: `#[warn(semicolon_in_expressions_from_macros)]` on by default
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813>
    = note: this warning originates in the macro `load_from_file` (in Nightly builds, run with -Z macro-backtrace for more info)
```

This warning is completely harmless, but will be visible to users compiling Lighthouse v2.0.1 (or earlier) with Rust 1.56.0 (to be released October 21st). It is **completely safe** to ignore this warning, it's just a superficial change to Rust's syntax.

## Proposed Changes

This PR removes the semi-colon as recommended, and fixes the new Clippy lints from 1.56.0
2021-10-19 00:30:42 +00:00
Akihito Nakano
efec60ee90 Tiny fix: wrong log level (#2720)
## Proposed Changes

If the `RemoveChain` is critical log level should be crit. 🙂
2021-10-19 00:30:41 +00:00
Michael Sproul
d2e3d4c6f1 Add flag to disable lock timeouts (#2714)
## Issue Addressed

Mitigates #1096

## Proposed Changes

Add a flag to the beacon node called `--disable-lock-timeouts` which allows opting out of lock timeouts.

The lock timeouts serve a dual purpose:

1. They prevent any single operation from hogging the lock for too long. When a timeout occurs it logs a nasty error which indicates that there's suboptimal lock use occurring, which we can then act on.
2. They allow deadlock detection. We're fairly sure there are no deadlocks left in Lighthouse anymore but the timeout locks offer a safeguard against that.

However, timeouts on locks are not without downsides:

They allow for the possibility of livelock, particularly on slower hardware. If lock timeouts keep failing spuriously the node can be prevented from making any progress, even if it would be able to make progress slowly without the timeout. One particularly concerning scenario which could occur would be if a DoS attack succeeded in slowing block signature verification times across the network, and all Lighthouse nodes got livelocked because they timed out repeatedly. This could also occur on just a subset of nodes (e.g. dual core VPSs or Raspberri Pis).

By making the behaviour runtime configurable this PR allows us to choose the behaviour we want depending on circumstance. I suspect that long term we could make the timeout-free approach the default (#2381 moves in this direction) and just enable the timeouts on our testnet nodes for debugging purposes. This PR conservatively leaves the default as-is so we can gain some more experience before switching the default.
2021-10-19 00:30:40 +00:00
Age Manning
df40700ddd Rename eth2_libp2p to lighthouse_network (#2702)
## Description

The `eth2_libp2p` crate was originally named and designed to incorporate a simple libp2p integration into lighthouse. Since its origins the crates purpose has expanded dramatically. It now houses a lot more sophistication that is specific to lighthouse and no longer just a libp2p integration. 

As of this writing it currently houses the following high-level lighthouse-specific logic:
- Lighthouse's implementation of the eth2 RPC protocol and specific encodings/decodings
- Integration and handling of ENRs with respect to libp2p and eth2
- Lighthouse's discovery logic, its integration with discv5 and logic about searching and handling peers. 
- Lighthouse's peer manager - This is a large module handling various aspects of Lighthouse's network, such as peer scoring, handling pings and metadata, connection maintenance and recording, etc.
- Lighthouse's peer database - This is a collection of information stored for each individual peer which is specific to lighthouse. We store connection state, sync state, last seen ips and scores etc. The data stored for each peer is designed for various elements of the lighthouse code base such as syncing and the http api.
- Gossipsub scoring - This stores a collection of gossipsub 1.1 scoring mechanisms that are continuously analyssed and updated based on the ethereum 2 networks and how Lighthouse performs on these networks.
- Lighthouse specific types for managing gossipsub topics, sync status and ENR fields
- Lighthouse's network HTTP API metrics - A collection of metrics for lighthouse network monitoring
- Lighthouse's custom configuration of all networking protocols, RPC, gossipsub, discovery, identify and libp2p. 

Therefore it makes sense to rename the crate to be more akin to its current purposes, simply that it manages the majority of Lighthouse's network stack. This PR renames this crate to `lighthouse_network`

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-10-19 00:30:39 +00:00
Michael Sproul
06e310c4eb Export slashing protection per validator (#2674)
## Issue Addressed

Part of https://github.com/sigp/lighthouse/issues/2557

## Proposed Changes

Refactor the slashing protection export so that it can export data for a subset of validators.

This is the last remaining building block required for supporting the standard validator API (which I'll start to build atop this branch)

## Additional Info

Built on and requires #2598
2021-10-19 00:30:38 +00:00
divma
e75ce534f6 Ignore cargo audit advisory (#2730)
## Issue Addressed
Related to #2727 

Ignores the audit failure for the same reasons in #2727
2021-10-18 21:59:27 +00:00
Paul Hauner
fff01b24dd Release v2.0.1 (#2726)
## Issue Addressed

NA

## Proposed Changes

- Update versions to `v2.0.1` in anticipation for a release early next week.
- Add `--ignore` to `cargo audit`. See #2727.

## Additional Info

NA
2021-10-18 03:08:32 +00:00
Age Manning
180c90bf6d Correct peer connection transition logic (#2725)
## Description

This PR updates the peer connection transition logic. It is acceptable for a peer to immediately transition from a disconnected state to a disconnecting state. This can occur when we are at our peer limit and a new peer's dial us.
2021-10-17 04:04:36 +00:00
Paul Hauner
a7b675460d Add Altair tests to op pool (#2723)
## Issue Addressed

NA

## Proposed Changes

Adds some more testing for Altair to the op pool. Credits to @michaelsproul for some appropriated efforts here.

## Additional Info

NA


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-10-16 05:07:23 +00:00
Paul Hauner
cfafe7ba3a Update to consensus-spec-tests v1.1.3 (#2722)
## Issue Addressed

NA

## Proposed Changes

Updates to `testing/ef_tests` to use https://github.com/ethereum/consensus-spec-tests/releases/tag/v1.1.3.

Also updates `initialize_beacon_state_from_eth1` to set the `state.fork.previous_version` to the Altair fork version when starting a new Altair chain from genesis. This will not have an effect on mainnet or any long-lived testnets. This was introduced in https://github.com/ethereum/consensus-specs/releases/tag/v1.1.1.

## Additional Info

NA
2021-10-16 05:07:21 +00:00
Michael Sproul
5cde3fc4da Reduce lock contention in backfill sync (#2716)
## Proposed Changes

Clone the proposer pubkeys during backfill signature verification to reduce the time that the pubkey cache lock is held for. Cloning such a small number of pubkeys has negligible impact on the total running time, but greatly reduces lock contention.

On a Ryzen 5950X, the setup step seems to take around 180us regardless of whether the key is cloned or not, while the verification takes 7ms. When Lighthouse is limited to 10% of one core using `sudo cpulimit --pid <pid> --limit 10` the total time jumps up to 800ms, but the setup step remains only 250us. This means that under heavy load this PR could cut the time the lock is held for from 800ms to 250us, which is a huge saving of 99.97%!
2021-10-15 03:28:03 +00:00
Paul Hauner
9c5a8ab7f2 Change "too many resources" to "insufficient resources" in eth2_libp2p (#2713)
## Issue Addressed

NA

## Proposed Changes

Fixes what I assume is a typo in a log message. See the diff for details.

## Additional Info

NA
2021-10-15 00:07:12 +00:00
Mac L
7c23e2142a Allow custom certificates when connecting to BN (#2703)
## Issue Addressed

Resolves #2262 

## Proposed Changes

Add a new CLI flag `--beacon-nodes-tls-certs` which allows the user to specify a path to a certificate file (or a list of files, separated by commas). The VC will then use these certificates (in addition to the existing certificates in the OS trust store) when connecting to a beacon node over HTTPS.

## Additional Info

This only supports certificates in PEM format.
2021-10-15 00:07:11 +00:00
Age Manning
05040e68ec Update discovery (#2711)
## Issue Addressed

#2695 

## Proposed Changes

This updates discovery to the latest version which has patched a panic that occurred due to a race condition in the bucket logic.
2021-10-14 22:09:38 +00:00
Paul Hauner
ef49524ff8 Quoted altair fields (2.0) (#2712)
## Issue Addressed

Resolves #2689

## Proposed Changes

Copy of #2709 so I can appease CI and merge without waiting for @realbigsean to come online. See #2709 for more information.

## Additional Info

NA


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-10-14 02:58:12 +00:00
Paul Hauner
18340d1fb6 Get arbitrary check passing (2.0) (#2710)
## Issue Addressed

NA

## Proposed Changes

This is a wholesale rip-off of #2708, see that PR for more of a description.

I've made this PR since @realbigsean is offline and I can't merge his PR due to Github's frustrating `target-branch-check` bug. I also changed the branch to `unstable`, since I'm trying to minimize the diff between `merge-f2f`/`unstable`. I'll just rebase `merge-f2f` onto `unstable` after this PR merges.

When running `make lint` I noticed the following warning:

```
warning: patch for `fixed-hash` uses the features mechanism. default-features and features will not take effect because the patch dependency does not support this mechanism
```

So, I removed the `features` section from the patch.

## Additional Info

NA


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-10-14 02:58:11 +00:00
Paul Hauner
e2d09bb8ac Add BeaconChainHarness::builder (#2707)
## Issue Addressed

NA

## Proposed Changes

This PR is near-identical to https://github.com/sigp/lighthouse/pull/2652, however it is to be merged into `unstable` instead of `merge-f2f`. Please see that PR for reasoning.

I'm making this duplicate PR to merge to `unstable` in an effort to shrink the diff between `unstable` and `merge-f2f` by doing smaller, lead-up PRs.

## Additional Info

NA
2021-10-14 02:58:10 +00:00
Michael Sproul
0a77d783a4 Make slashing protection import more resilient (#2598)
## Issue Addressed

Closes #2419

## Proposed Changes

Address a long-standing issue with the import of slashing protection data where the import would fail due to the data appearing slashable w.r.t the existing database. Importing is now idempotent, and will have no issues importing data that has been handed back and forth between different validator clients, or different implementations.

The implementation works by updating the high and low watermarks if they need updating, and not attempting to check if the input is slashable w.r.t itself or the database. This is a strengthening of the minification that we started to do by default since #2380, and what Teku has been doing since the beginning.

## Additional Info

The only feature we lose by doing this is the ability to do non-minified imports of clock drifted messages (cf. Prysm on Medalla). In theory, with the previous implementation we could import all the messages in case of clock drift and be aware of the "gap" between the real present time and the messages signed in the far future. _However_ for attestations this is close to useless, as the source epoch will advance as soon as justification occurs, which will require us to make slashable attestations with respect to our bogus attestation(s). E.g. if I sign an attestation 100=>200 when the current epoch is 101, then I won't be able to vote in any epochs prior to 101 becoming justified because 101=>102, 101=>103, etc are all surrounded by 100=>200. Seeing as signing attestations gets blocked almost immediately in this case regardless of our import behaviour, there's no point trying to handle it. For blocks the situation is more hopeful due to the lack of surrounds, but losing block proposals from validators who by definition can't attest doesn't seem like an issue (the other block proposers can pick up the slack).
2021-10-13 01:49:51 +00:00
Pawan Dhananjay
34d22b5920 Reduce validator monitor logging verbosity (#2606)
## Issue Addressed

Resolves #2541

## Proposed Changes

Reduces verbosity of validator monitor per epoch logging by batching info logs for multiple validators.

Instead of a log for every validator managed by the validator monitor, we now batch logs for attestation records for previous epoch.

Before:
```log
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 1, epoch: 65875, matched_head: true, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 2, epoch: 65875, matched_head: true, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 3, epoch: 65875, matched_head: true, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 4, epoch: 65875, matched_head: true, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 5, epoch: 65875, matched_head: false, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 WARN Attestation failed to match head        validator: 5, epoch: 65875, service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 6, epoch: 65875, matched_head: false, matched_target: true, inclusion_lag: 0 slot(s), service: val_mon
Sep 20 06:53:08.239 WARN Attestation failed to match head        validator: 6, epoch: 65875, service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 7, epoch: 65875, matched_head: true, matched_target: false, inclusion_lag: 1 slot(s), service: val_mon
Sep 20 06:53:08.239 WARN Attestation failed to match target      validator: 7, epoch: 65875, service: val_mon
Sep 20 06:53:08.239 WARN Sub-optimal inclusion delay             validator: 7, epoch: 65875, optimal: 1, delay: 2, service: val_mon
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validator: 8, epoch: 65875, matched_head: true, matched_target: false, inclusion_lag: 1 slot(s), service: val_mon
Sep 20 06:53:08.239 WARN Attestation failed to match target      validator: 8, epoch: 65875, service: val_mon
Sep 20 06:53:08.239 WARN Sub-optimal inclusion delay             validator: 8, epoch: 65875, optimal: 1, delay: 2, service: val_mon
Sep 20 06:53:08.239 ERRO Previous epoch attestation missing      validator: 9, epoch: 65875, service: val_mon
Sep 20 06:53:08.239 ERRO Previous epoch attestation missing      validator: 10, epoch: 65875, service: val_mon
```

after
```
Sep 20 06:53:08.239 INFO Previous epoch attestation success      validators: [1,2,3,4,5,6,7,8,9] , epoch: 65875, service: val_mon
Sep 20 06:53:08.239 WARN Previous epoch attestation failed to match head, validators: [5,6], epoch: 65875, service: val_mon
Sep 20 06:53:08.239 WARN Previous epoch attestation failed to match target, validators: [7,8], epoch: 65875, service: val_mon
Sep 20 06:53:08.239 WARN Previous epoch attestations had sub-optimal inclusion delay, validators: [7,8], epoch: 65875, service: val_mon
Sep 20 06:53:08.239 ERRO Previous epoch attestation missing      validators: [9,10], epoch: 65875, service: val_mon
```

The detailed individual logs are downgraded to debug logs.
2021-10-12 05:06:48 +00:00
Mac L
a73d698e30 Add TLS capability to the beacon node HTTP API (#2668)
Currently, the beacon node has no ability to serve the HTTP API over TLS.
Adding this functionality would be helpful for certain use cases, such as when you need a validator client to connect to a backup beacon node which is outside your local network, and the use of an SSH tunnel or reverse proxy would be inappropriate.

## Proposed Changes

- Add three new CLI flags to the beacon node
  - `--http-enable-tls`: enables TLS
  - `--http-tls-cert`: to specify the path to the certificate file
  - `--http-tls-key`: to specify the path to the key file
- Update the HTTP API to optionally use `warp`'s [`TlsServer`](https://docs.rs/warp/0.3.1/warp/struct.TlsServer.html) depending on the presence of the `--http-enable-tls` flag
- Update tests and docs
- Use a custom branch for `warp` to ensure proper error handling

## Additional Info

Serving the API over TLS should currently be considered experimental. The reason for this is that it uses code from an [unmerged PR](https://github.com/seanmonstar/warp/pull/717). This commit provides the `try_bind_with_graceful_shutdown` method to `warp`, which is helpful for controlling error flow when the TLS configuration is invalid (cert/key files don't exist, incorrect permissions, etc). 
I've implemented the same code in my [branch here](https://github.com/macladson/warp/tree/tls).

Once the code has been reviewed and merged upstream into `warp`, we can remove the dependency on my branch and the feature can be considered more stable.

Currently, the private key file must not be password-protected in order to be read into Lighthouse.
2021-10-12 03:35:49 +00:00
Age Manning
0aee7ec873 Refactor Peerdb and PeerManager (#2660)
## Proposed Changes

This is a refactor of the PeerDB and PeerManager. A number of bugs have been surfacing around the connection state of peers and their interaction with the score state. 

This refactor tightens the mutability properties of peers such that only specific modules are able to modify the state of peer information preventing inadvertant state changes that can lead to our local peer manager db being out of sync with libp2p. 

Further, the logic around connection and scoring was quite convoluted and the distinction between the PeerManager and Peerdb was not well defined. Although these issues are not fully resolved, this PR is step to cleaning up this logic. The peerdb solely manages most mutability operations of peers leaving high-order logic to the peer manager. 

A single `update_connection_state()` function has been added to the peer-db making it solely responsible for modifying the peer's connection state. The way the peer's scores can be modified have been reduced to three simple functions (`update_scores()`, `update_gossipsub_scores()` and `report_peer()`). This prevents any add-hoc modifications of scores and only natural processes of score modification is allowed which simplifies the reasoning of score and state changes.
2021-10-11 02:45:06 +00:00
Michael Sproul
708557a473 Fix cargo audit warns for nix, psutil, time (#2699)
## Issue Addressed

Fix `cargo audit` failures on `unstable`

Closes #2698

## Proposed Changes

The main culprit is `nix`, which is vulnerable for versions below v0.23.0. We can't get by with a straight-forward `cargo update` because `psutil` depends on an old version of `nix` (cf. https://github.com/rust-psutil/rust-psutil/pull/93). Hence I've temporarily forked `psutil` under the `sigp` org, where I've included the update to `nix` v0.23.0.

Additionally, I took the chance to update the `time` dependency to v0.3, which removed a bunch of stale deps including `stdweb` which is no longer maintained. Lighthouse only uses the `time` crate in the notifier to do some pretty printing, and so wasn't affected by any of the breaking changes in v0.3 ([changelog here](https://github.com/time-rs/time/blob/main/CHANGELOG.md#030-2021-07-30)).
2021-10-11 00:10:35 +00:00
Michael Sproul
229542cd6c Avoid negative values in malloc_utils metrics (#2692)
## Proposed Changes

While investigating memory usage I noticed that the malloc metrics were going negative once they passed 2GiB. This is because the underlying `mallinfo` function returns a `i32`, and we were casting it straight to an `i64`, preserving the sign.

The long-term fix will be to move to `mallinfo2`, but it's still not yet widely available.
2021-10-11 00:10:34 +00:00
Pawan Dhananjay
7c7ba770de Update broken api links (#2665)
## Issue Addressed

Resolves #2563 
Replacement for #2653 as I'm not able to reopen that PR after force pushing.

## Proposed Changes

Fixes all broken api links. Cherry picked changes in #2590 and updated a few more links.

Co-authored-by: Mason Stallmo <masonstallmo@gmail.com>
2021-10-06 00:46:09 +00:00
Pawan Dhananjay
73ec29c267 Don't log errors on resubscription of gossip topics (#2613)
## Issue Addressed

Resolves #2555

## Proposed Changes

Don't log errors on resubscribing to topics. Also don't log errors if we are setting already set attnet/syncnet bits.
2021-10-06 00:46:08 +00:00
Wink Saville
58870fc6d3 Add test_logger as feature to logging (#2586)
## Issue Addressed

Fix #2585

## Proposed Changes

Provide a canonical version of test_logger that can be used
throughout lighthouse.

## Additional Info

This allows tests to conditionally emit logging data by adding
test_logger as the default logger. And then when executing
`cargo test --features logging/test_logger` log output
will be visible:

  wink@3900x:~/lighthouse/common/logging/tests/test-feature-test_logger (Add-test_logger-as-feature-to-logging)
  $ cargo test --features logging/test_logger
      Finished test [unoptimized + debuginfo] target(s) in 0.02s
       Running unittests (target/debug/deps/test_logger-e20115db6a5e3714)

  running 1 test
  Sep 10 12:53:45.212 INFO hi, module: test_logger:8
  test tests::test_fn_with_logging ... ok

  test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Doc-tests test-logger

  running 0 tests

  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Or, in normal scenarios where logging isn't needed, executing
`cargo test` the log output will not be visible:

  wink@3900x:~/lighthouse/common/logging/tests/test-feature-test_logger (Add-test_logger-as-feature-to-logging)
  $ cargo test
      Finished test [unoptimized + debuginfo] target(s) in 0.02s
       Running unittests (target/debug/deps/test_logger-02e02f8d41e8cf8a)

  running 1 test
  test tests::test_fn_with_logging ... ok

  test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Doc-tests test-logger

  running 0 tests

  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2021-10-06 00:46:07 +00:00
realbigsean
02a646a27d Fix name collision in script (#2678)
## Issue Addressed

N/A

## Proposed Changes

We set a `$TAG` env variable in the github actions workflow, and then re-use this name in the `publish.sh` script. It makes this check `if [[ -z "$TAG" ]]` return true, when it should return false on the first time it's hit.

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-10-05 22:54:11 +00:00
Michael Sproul
7c88f582d9 Release v2.0.0 (#2673)
## Proposed Changes

* Bump version to v2.0.0
* Update dependencies (obsoletes #2670). `tokio-macros` v1.4.0 had been yanked due to a bug.
2021-10-05 03:53:18 +00:00
Michael Sproul
ed1fc7cca6 Fix I/O atomicity issues with checkpoint sync (#2671)
## Issue Addressed

This PR addresses an issue found by @YorickDowne during testing of v2.0.0-rc.0.

Due to a lack of atomic database writes on checkpoint sync start-up, it was possible for the database to get into an inconsistent state from which it couldn't recover without `--purge-db`. The core of the issue was that the store's anchor info was being stored _before_ the `PersistedBeaconChain`. If a crash occured so that anchor info was stored but _not_ the `PersistedBeaconChain`, then on restart Lighthouse would think the database was unitialized and attempt to compare-and-swap a `None` value, but would actually find the stale info from the previous run.

## Proposed Changes

The issue is fixed by writing the anchor info, the split point, and the `PersistedBeaconChain` atomically on start-up. Some type-hinting ugliness was required, which could possibly be cleaned up in future refactors.
2021-10-05 03:53:17 +00:00
Kane Wallmann
28b79084cd Fix chain_id value in config/deposit_contract RPC method (#2659)
## Issue Addressed

This PR addresses issue #2657

## Proposed Changes

Changes `/eth/v1/config/deposit_contract` endpoint to return the chain ID from the loaded chain spec instead of eth1::DEFAULT_NETWORK_ID which is the Goerli chain ID of 5.

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-10-01 06:32:38 +00:00
Wink Saville
21d1af435a Create a local testnet (#2614)
The testnet will be on the local computer and have 1 eth1 node,
4 beacon nodes, 1 validator with 20 vc's.
2021-10-01 06:32:37 +00:00
Michael Sproul
ea78315749 Release v2.0.0-rc.0 (#2634)
## Proposed Changes

Cut the first release candidate for v2.0.0, in preparation for testing and release this week

## Additional Info

Builds on #2632, which should either be merged first or in the same batch
2021-10-01 01:23:55 +00:00
Age Manning
29a8865d07 Consistent tracking of disconnected peers (#2650)
## Issue Addressed

N/A

## Proposed Changes

When peers switching to a disconnecting state, decrement the disconnected peers counter. This also downgrades some crit logs to errors. 

I've also added a re-sync point when peers get unbanned the disconnected peer count will match back to the number of disconnected peers if it has gone out of sync previously.
2021-09-30 04:31:43 +00:00
Squirrel
db4d72c4f1 Remove unused deps (#2592)
Found some deps you're possibly not using.

Please shout if you think they are indeed still needed.
2021-09-30 04:31:42 +00:00
Mac L
4c510f8f6b Add BlockTimesCache to allow additional block delay metrics (#2546)
## Issue Addressed

Closes #2528

## Proposed Changes

- Add `BlockTimesCache` to provide block timing information to `BeaconChain`. This allows additional metrics to be calculated for blocks that are set as head too late.
- Thread the `seen_timestamp` of blocks received from RPC responses (except blocks from syncing) through to the sync manager, similar to what is done for blocks from gossip.

## Additional Info

This provides the following additional metrics:
- `BEACON_BLOCK_OBSERVED_SLOT_START_DELAY_TIME`
  - The delay between the start of the slot and when the block was first observed.
- `BEACON_BLOCK_IMPORTED_OBSERVED_DELAY_TIME`
   - The delay between when the block was first observed and when the block was imported.
- `BEACON_BLOCK_HEAD_IMPORTED_DELAY_TIME`
  - The delay between when the block was imported and when the block was set as head.

The metric `BEACON_BLOCK_IMPORTED_SLOT_START_DELAY_TIME` was removed.

A log is produced when a block is set as head too late, e.g.:
```
Aug 27 03:46:39.006 DEBG Delayed head block                      set_as_head_delay: Some(21.731066ms), imported_delay: Some(119.929934ms), observed_delay: Some(3.864596988s), block_delay: 4.006257988s, slot: 1931331, proposer_index: 24294, block_root: 0x937602c89d3143afa89088a44bdf4b4d0d760dad082abacb229495c048648a9e, service: beacon
```
2021-09-30 04:31:41 +00:00
Pawan Dhananjay
70441aa554 Improve valmon inclusion delay calculation (#2618)
## Issue Addressed

Resolves #2552 

## Proposed Changes

Offers some improvement in inclusion distance calculation in the validator monitor. 

When registering an attestation from a block, instead of doing `block.slot() - attesstation.data.slot()` to get the inclusion distance, we now pass the parent block slot from the beacon chain and do `parent_slot.saturating_sub(attestation.data.slot())`. This allows us to give best effort inclusion distance in scenarios where the attestation was included right after a skip slot. Note that this does not give accurate results in scenarios where the attestation was included few blocks after the skip slot.

In this case, if the attestation slot was `b1` and was included in block `b2` with a skip slot in between, we would get the inclusion delay as 0  (by ignoring the skip slot) which is the best effort inclusion delay.
```
b1 <- missed <- b2
``` 

Here, if the attestation slot was `b1` and was included in block `b3` with a skip slot and valid block `b2` in between, then we would get the inclusion delay as 2 instead of 1 (by ignoring the skip slot).
```
b1 <- missed <- b2 <- b3 
```
A solution for the scenario 2 would be to count number of slots between included slot and attestation slot ignoring the skip slots in the beacon chain and pass the value to the validator monitor. But I'm concerned that it could potentially lead to db accesses for older blocks in extreme cases.


This PR also uses the validator monitor data for logging per epoch inclusion distance. This is useful as we won't get inclusion data in post-altair summaries.


Co-authored-by: Michael Sproul <micsproul@gmail.com>
2021-09-30 01:22:43 +00:00
realbigsean
7d13e57d9f Add interop metrics (#2645)
## Issue Addressed

Resolves: #2644

## Proposed Changes

- Adds mandatory metrics mentioned here: https://github.com/ethereum/beacon-metrics/blob/master/metrics.md#interop-metrics

## Additional Info

Couldn't figure out how to alias metrics, so I created them all as new gauges/counters.

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-09-29 23:44:24 +00:00
Michael Sproul
c0122e1a52 Refine VC->BN config check (#2636)
## Proposed Changes

Instead of checking for strict equality between a BN's spec and the VC's local spec, just check the genesis fork version. This prevents us from failing eagerly for minor differences, while still protecting the VC from connecting to a completely incompatible BN.

A warning is retained for the previous case where the specs are not exactly equal, which is to be expected if e.g. running against Infura before Infura configures the mainnet Altair fork epoch.
2021-09-27 04:22:07 +00:00
Michael Sproul
e895074ba9 Activate Altair on mainnet at epoch 74240 (#2632)
## Proposed Changes

Schedule Altair on mainnet for epoch 74240 as per https://github.com/ethereum/consensus-specs/pull/2625

This puts the date for Altair as Wed Oct 27 2021 10:56:23 GMT+0000
2021-09-27 04:22:06 +00:00
realbigsean
113ef74ef6 Add contribution and proof event (#2527)
## Issue Addressed

N/A

## Proposed Changes

Add the new ContributionAndProof event: https://github.com/ethereum/beacon-APIs/pull/158

## Additional Info

N/A

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-09-25 07:53:58 +00:00
Mac L
440badd973 Provide lcli tooling for attestation packing analysis (#2480)
## Proposed Changes

Add tooling to lcli to provide a way to measure the attestation packing efficiency of historical blocks by querying a beacon node API endpoint.

## Additional Info

Since block rewards are proportional to the number of unique attestations included in the block, a measure of efficiency can be calculated by comparing the number of unique attestations that could have been included into a block vs the number of unique attestations that were actually included.

This lcli tool provides the following data per block:
- Slot Number
- Proposer Index and Grafitti (if any)
- Available Unique Attestations
- Included Unique Attestations
- Best-effort estimate of the number of offline validators for the epoch. This means we can normalize the calculated efficiency, removing offline validators from the available attestation set.

The data is outputted as a csv file.

## Usage
Install lcli:
```
make install-lcli
```
Alternatively install with the `fake_crypto` feature to skip signature verification which improves performance:
```
cargo install --path lcli --features=fake_crypto --force --locked
```

Ensure a Lighthouse beacon node is running and synced. A non-default API endpoint can be passed with the `--endpoint` flag.

Run:
```
lcli etl-block-efficiency --output /path/to/output.csv --start-epoch 40 --end-epoch 80
```
2021-09-25 07:53:56 +00:00
Paul Hauner
924a1345b1 Update zeroize_derive (#2625)
## Issue Addressed

NA

## Proposed Changes

As `cargo audit` astutely pointed out, the version of `zeroize_derive` were were using had a vulnerability:

```
Crate:         zeroize_derive
Version:       1.1.0
Title:         `#[zeroize(drop)]` doesn't implement `Drop` for `enum`s
Date:          2021-09-24
ID:            RUSTSEC-2021-0115
URL:           https://rustsec.org/advisories/RUSTSEC-2021-0115
Solution:      Upgrade to >=1.2.0
```

This PR updates `zeroize` and `zeroize_derive` to appease `cargo audit`.

`tiny-bip39` was also updated to allow compile.

## Additional Info

I don't believe this vulnerability actually affected the Lighthouse code-base directly. However, `tiny-bip39` may have been affected which may have resulted in some uncleaned memory in Lighthouse. Whilst this is not ideal, it's not a major issue. Zeroization is a nice-to-have since it only protects from sophisticated attacks or attackers that already have a high level of access already.
2021-09-25 05:58:37 +00:00
Paul Hauner
fe52322088 Implement SSZ union type (#2579)
## Issue Addressed

NA

## Proposed Changes

Implements the "union" type from the SSZ spec for `ssz`, `ssz_derive`, `tree_hash` and `tree_hash_derive` so it may be derived for `enums`:

https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.3/ssz/simple-serialize.md#union

The union type is required for the merge, since the `Transaction` type is defined as a single-variant union `Union[OpaqueTransaction]`.

### Crate Updates

This PR will (hopefully) cause CI to publish new versions for the following crates:

- `eth2_ssz_derive`: `0.2.1` -> `0.3.0`
- `eth2_ssz`: `0.3.0` -> `0.4.0`
- `eth2_ssz_types`: `0.2.0` -> `0.2.1`
- `tree_hash`: `0.3.0` -> `0.4.0`
- `tree_hash_derive`: `0.3.0` -> `0.4.0`

These these crates depend on each other, I've had to add a workspace-level `[patch]` for these crates. A follow-up PR will need to remove this patch, ones the new versions are published.

### Union Behaviors

We already had SSZ `Encode` and `TreeHash` derive for enums, however it just did a "transparent" pass-through of the inner value. Since the "union" decoding from the spec is in conflict with the transparent method, I've required that all `enum` have exactly one of the following enum-level attributes:

#### SSZ

-  `#[ssz(enum_behaviour = "union")]`
    - matches the spec used for the merge
-  `#[ssz(enum_behaviour = "transparent")]`
    - maintains existing functionality
    - not supported for `Decode` (never was)
    
#### TreeHash

-  `#[tree_hash(enum_behaviour = "union")]`
    - matches the spec used for the merge
-  `#[tree_hash(enum_behaviour = "transparent")]`
    - maintains existing functionality

This means that we can maintain the existing transparent behaviour, but all existing users will get a compile-time error until they explicitly opt-in to being transparent.

### Legacy Option Encoding

Before this PR, we already had a union-esque encoding for `Option<T>`. However, this was with the *old* SSZ spec where the union selector was 4 bytes. During merge specification, the spec was changed to use 1 byte for the selector.

Whilst the 4-byte `Option` encoding was never used in the spec, we used it in our database. Writing a migrate script for all occurrences of `Option` in the database would be painful, especially since it's used in the `CommitteeCache`. To avoid the migrate script, I added a serde-esque `#[ssz(with = "module")]` field-level attribute to `ssz_derive` so that we can opt into the 4-byte encoding on a field-by-field basis.

The `ssz::legacy::four_byte_impl!` macro allows a one-liner to define the module required for the `#[ssz(with = "module")]` for some `Option<T> where T: Encode + Decode`.

Notably, **I have removed `Encode` and `Decode` impls for `Option`**. I've done this to force a break on downstream users. Like I mentioned, `Option` isn't used in the spec so I don't think it'll be *that* annoying. I think it's nicer than quietly having two different union implementations or quietly breaking the existing `Option` impl.

### Crate Publish Ordering

I've modified the order in which CI publishes crates to ensure that we don't publish a crate without ensuring we already published a crate that it depends upon.

## TODO

- [ ] Queue a follow-up `[patch]`-removing PR.
2021-09-25 05:58:36 +00:00
Michael Sproul
a844ce5ba9 Update spec tests to v1.1.0-beta.4 (#2548)
## Proposed Changes

Bump the spec tests to beta.4, including the new randomised tests (which all pass 🎉)
2021-09-25 05:58:35 +00:00
Age Manning
00a7ef0036 Correct bug in sync (#2615)
A bug that causes failed batches to continually download in a loop is corrected.
2021-09-23 01:32:04 +00:00
Paul Hauner
be11437c27 Batch BLS verification for attestations (#2399)
## Issue Addressed

NA

## Proposed Changes

Adds the ability to verify batches of aggregated/unaggregated attestations from the network.

When the `BeaconProcessor` finds there are messages in the aggregated or unaggregated attestation queues, it will first check the length of the queue:

- `== 1` verify the attestation individually.
- `>= 2` take up to 64 of those attestations and verify them in a batch.

Notably, we only perform batch verification if the queue has a backlog. We don't apply any artificial delays to attestations to try and force them into batches. 

### Batching Details

To assist with implementing batches we modify `beacon_chain::attestation_verification` to have two distinct categories for attestations:

- *Indexed* attestations: those which have passed initial validation and were valid enough for us to derive an `IndexedAttestation`.
- *Verified* attestations: those attestations which were indexed *and also* passed signature verification. These are well-formed, interesting messages which were signed by validators.

The batching functions accept `n` attestations and then return `n` attestation verification `Result`s, where those `Result`s can be any combination of `Ok` or `Err`. In other words, we attempt to verify as many attestations as possible and return specific per-attestation results so peer scores can be updated, if required.

When we batch verify attestations, we first try to map all those attestations to *indexed* attestations. If any of those attestations were able to be indexed, we then perform batch BLS verification on those indexed attestations. If the batch verification succeeds, we convert them into *verified* attestations, disabling individual signature checking. If the batch fails, we convert to verified attestations with individual signature checking enabled.

Ultimately, we optimistically try to do a batch verification of attestation signatures and fall-back to individual verification if it fails. This opens an attach vector for "poisoning" the attestations and causing us to waste a batch verification. I argue that peer scoring should do a good-enough job of defending against this and the typical-case gains massively outweigh the worst-case losses.

## Additional Info

Before this PR, attestation verification took the attestations by value (instead of by reference). It turns out that this was unnecessary and, in my opinion, resulted in some undesirable ergonomics (e.g., we had to pass the attestation back in the `Err` variant to avoid clones). In this PR I've modified attestation verification so that it now takes a reference.

I refactored the `beacon_chain/tests/attestation_verification.rs` tests so they use a builder-esque "tester" struct instead of a weird macro. It made it easier for me to test individual/batch with the same set of tests and I think it was a nice tidy-up. Notably, I did this last to try and make sure my new refactors to *actual* production code would pass under the existing test suite.
2021-09-22 08:49:41 +00:00
Michael Sproul
9667dc2f03 Implement checkpoint sync (#2244)
## Issue Addressed

Closes #1891
Closes #1784

## Proposed Changes

Implement checkpoint sync for Lighthouse, enabling it to start from a weak subjectivity checkpoint.

## Additional Info

- [x] Return unavailable status for out-of-range blocks requested by peers (#2561)
- [x] Implement sync daemon for fetching historical blocks (#2561)
- [x] Verify chain hashes (either in `historical_blocks.rs` or the calling module)
- [x] Consistency check for initial block + state
- [x] Fetch the initial state and block from a beacon node HTTP endpoint
- [x] Don't crash fetching beacon states by slot from the API
- [x] Background service for state reconstruction, triggered by CLI flag or API call.

Considered out of scope for this PR:

- Drop the requirement to provide the `--checkpoint-block` (this would require some pretty heavy refactoring of block verification)


Co-authored-by: Diva M <divma@protonmail.com>
2021-09-22 00:37:28 +00:00
Age Manning
280e4fe23d Increase connection limits and allow priority connections (#2604)
In previous network updates we have made our libp2p connections more lean by limiting the maximum number of connections a lighthouse node will accept before libp2p rejects new connections. 

However, we still maintain the logic that at maximum connections, we try to dial extra peers if they are needed by a validator client to publish messages on a specific subnet. The dials typically result in failures due the libp2p connection limits. 

This PR adds an extra factor, `PRIORITY_PEER_EXCESS` which sets aside a new allocation of peers we are able to dial in case we need these peers for the VC client.  This allocation sits along side the excess peer (which allows extra incoming peers on top of our target peer limit). 

The drawback here, is that libp2p now allows extra peers to connect to us (beyond the standard peer limit) which the peer manager should subsequently reject.
2021-09-21 07:45:13 +00:00
realbigsean
fb1df2c926 Add a note about doppelganger protection interoperability to the docs (#2607)
## Issue Addressed

N/A

## Proposed Changes

Add a note to the Doppelganger Protection docs about how it is not interoperable until an endpoint facilitating it is standardized (https://github.com/ethereum/beacon-APIs/pull/131).

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-09-20 22:28:37 +00:00
Age Manning
a73dcb7b6d Improved handling of IP Banning (#2530)
This PR in general improves the handling around peer banning. 

Specifically there were issues when multiple peers under a single IP connected to us after we banned the IP for poor behaviour.

This PR should now handle these peers gracefully as well as make some improvements around how we previously disconnected and banned peers. 

The logic now goes as follows:
- Once a peer gets banned, its gets registered with its known IP addresses
- Once enough banned peers exist under a single IP that IP is banned
- We retain connections with existing peers under this IP
- Any new connections under this IP are rejected
2021-09-17 04:02:31 +00:00
Pawan Dhananjay
64ad2af100 Subscribe to altair gossip topics 2 slots before fork (#2532)
## Issue Addressed

N/A

## Proposed Changes

Add a fork_digest to `ForkContext` only if it is set in the config.
Reject gossip messages on post fork topics before the fork happens.

Edit: Instead of rejecting gossip messages on post fork topics, we now subscribe to post fork topics 2 slots before the fork.

Co-authored-by: Age Manning <Age@AgeManning.com>
2021-09-17 01:11:16 +00:00
Age Manning
acdcea9663 Update mainnet bootnodes (#2594)
Sigma Prime is transitioning our mainnet bootnodes and this PR represents the transition of our bootnodes. 

After a few releases, old boot-nodes will be deprecated.
2021-09-16 04:45:07 +00:00
Age Manning
56e0615df8 Experimental discovery (#2577)
# Description

A few changes have been made to discovery. In particular a custom re-write of an LRU cache which previously was read/write O(N) for all our sessions ~5k, to a more reasonable hashmap-style O(1). 

Further there has been reported issues in the current discv5, so added error handling to help identify the issue has been added.
2021-09-16 04:45:05 +00:00
Paul Hauner
c5c7476518 Web3Signer support for VC (#2522)
[EIP-3030]: https://eips.ethereum.org/EIPS/eip-3030
[Web3Signer]: https://consensys.github.io/web3signer/web3signer-eth2.html

## Issue Addressed

Resolves #2498

## Proposed Changes

Allows the VC to call out to a [Web3Signer] remote signer to obtain signatures.


## Additional Info

### Making Signing Functions `async`

To allow remote signing, I needed to make all the signing functions `async`. This caused a bit of noise where I had to convert iterators into `for` loops.

In `duties_service.rs` there was a particularly tricky case where we couldn't hold a write-lock across an `await`, so I had to first take a read-lock, then grab a write-lock.

### Move Signing from Core Executor

Whilst implementing this feature, I noticed that we signing was happening on the core tokio executor. I suspect this was causing the executor to temporarily lock and occasionally trigger some HTTP timeouts (and potentially SQL pool timeouts, but I can't verify this). Since moving all signing into blocking tokio tasks, I noticed a distinct drop in the "atttestations_http_get" metric on a Prater node:

![http_get_times](https://user-images.githubusercontent.com/6660660/132143737-82fd3836-2e7e-445b-a143-cb347783baad.png)

I think this graph indicates that freeing the core executor allows the VC to operate more smoothly.

### Refactor TaskExecutor

I noticed that the `TaskExecutor::spawn_blocking_handle` function would fail to spawn tasks if it were unable to obtain handles to some metrics (this can happen if the same metric is defined twice). It seemed that a more sensible approach would be to keep spawning tasks, but without metrics. To that end, I refactored the function so that it would still function without metrics. There are no other changes made.

## TODO

- [x] Restructure to support multiple signing methods.
- [x] Add calls to remote signer from VC.
- [x] Documentation
- [x] Test all endpoints
- [x] Test HTTPS certificate
- [x] Allow adding remote signer validators via the API
- [x] Add Altair support via [21.8.1-rc1](https://github.com/ConsenSys/web3signer/releases/tag/21.8.1-rc1)
- [x] Create issue to start using latest version of web3signer. (See #2570)

## Notes

- ~~Web3Signer doesn't yet support the Altair fork for Prater. See https://github.com/ConsenSys/web3signer/issues/423.~~
- ~~There is not yet a release of Web3Signer which supports Altair blocks. See https://github.com/ConsenSys/web3signer/issues/391.~~
2021-09-16 03:26:33 +00:00
Michael Sproul
58012f85e1 Shutdown gracefully on panic (#2596)
## Proposed Changes

* Modify the `TaskExecutor` so that it spawns a "monitor" future for each future spawned by `spawn` or `spawn_blocking`. This monitor future joins the handle of the child future and shuts down the executor if it detects a panic.
* Enable backtraces by default by setting the environment variable `RUST_BACKTRACE`.
* Spawn the `ProductionBeaconNode` on the `TaskExecutor` so that if a panic occurs during start-up it will take down the whole process. Previously we were using a raw Tokio `spawn`, but I can't see any reason not to use the executor (perhaps someone else can).

## Additional Info

I considered using [`std::panic::set_hook`](https://doc.rust-lang.org/std/panic/fn.set_hook.html) to instantiate a custom panic handler, however this doesn't allow us to send a shutdown signal because `Fn` functions can't move variables (i.e. the shutdown sender) out of their environment. This also prevents it from receiving a `Logger`.  Hence I decided to leave the panic handler untouched, but with backtraces turned on by default.

I did a run through the code base with all the raw Tokio spawn functions disallowed by Clippy, and found only two instances where we bypass the `TaskExecutor`: the HTTP API and `InitializedValidators` in the VC. In both places we use `spawn_blocking` and handle the return value, so I figured that was OK for now.

In terms of performance I think the overhead should be minimal. The monitor tasks will just get parked by the executor until their child resolves.

I've checked that this covers Discv5, as the `TaskExecutor` gets injected into Discv5 here: f9bba92db3/beacon_node/src/lib.rs (L125-L126)
2021-09-15 00:01:18 +00:00
Age Manning
95b17137a8 Reduce network debug noise (#2593)
The identify network debug logs can get quite noisy and are unnecessary to print on every request/response. 

This PR reduces debug noise by only printing messages for identify messages that offer some new information.
2021-09-14 08:28:35 +00:00
Wink Saville
4755d4b236 Update sloggers to v2.0.2 (#2588)
fixes #2584
2021-09-14 06:48:26 +00:00
Paul Hauner
f9bba92db3 v1.5.2 (#2595)
## Issue Addressed

NA

## Proposed Changes

Version bump

## Additional Info

Please do not `bors` without my approval, I am still testing.
2021-09-13 23:01:19 +00:00
Squirrel
e4ed42a9d8 Fix nightly bump num bigint (#2591)
## Issue Addressed

Builds again on latest nightly

## Proposed Changes

Break was caused by: https://github.com/rust-lang/rust/issues/88581
2021-09-12 23:55:20 +00:00
Mason Stallmo
f7dd24c998 Add quoted u64/u64_vec to SyncCommitteeSubscription (#2589)
## Issue Addressed

Resolves #2582 

## Proposed Changes

Use `quoted_u64` and `quoted_u64_vec` custom serde deserializers from `eth2_serde_utils` to support the proper Eth2.0 API spec for `/eth/v1/validator/sync_committee_subscriptions`

## Additional Info

N/A
2021-09-11 23:56:16 +00:00
Mason Stallmo
46cd67d519 Case insensitive match for ForkName (#2587)
## Issue Addressed

#2583

## Proposed Changes

Case insensitive match on `fork_name` when calling `ForkName::from_str`

## Additional Info

N/A
2021-09-11 06:07:20 +00:00
Wink Saville
8d2650fffd Make local-testnet setup more discoverable (#2572)
Move the contents of book/src/local-testnets.md into book/src/setup.md
to make it more discoverable.

Also, the link to scripts/local_testnet was missing `/local_testnet`.
2021-09-07 23:56:59 +00:00
Wink Saville
3b0f3247c6 Fix typo of vars.env (#2574) 2021-09-07 03:14:03 +00:00
Fredrik Svantes
1eefd6d413 Add how users should report security vulnerabilities for this repository (#2562)
## Proposed Changes

Suggestion to add a notice on how to report security vulnerabilities. This is visible at https://github.com/sigp/lighthouse/security
2021-09-07 01:54:05 +00:00
Paul Hauner
ddbd4e6965 v1.5.2-rc.0 (#2565)
## Issue Addressed

NA

## Proposed Changes

- Bump version
- Tidy some comments mangled by the version change regex.

## Additional Info

NA
2021-09-03 23:28:21 +00:00
Michael Sproul
9c785a9b33 Optimize process_attestation with active balance cache (#2560)
## Proposed Changes

Cache the total active balance for the current epoch in the `BeaconState`. Computing this value takes around 1ms, and this was negatively impacting block processing times on Prater, particularly when reconstructing states.

With a large number of attestations in each block, I saw the `process_attestations` function taking 150ms, which means that reconstructing hot states can take up to 4.65s (31 * 150ms), and reconstructing freezer states can take up to 307s (2047 * 150ms).

I opted to add the cache to the beacon state rather than computing the total active balance at the start of state processing and threading it through. Although this would be simpler in a way, it would waste time, particularly during block replay, as the total active balance doesn't change for the duration of an epoch. So we save ~32ms for hot states, and up to 8.1s for freezer states (using `--slots-per-restore-point 8192`).
2021-09-03 07:50:43 +00:00
Michael Sproul
f4aa1d8aea Archive remote_signer code (#2559)
## Proposed Changes

This PR deletes all `remote_signer` code from Lighthouse, for the following reasons:

* The `remote_signer` code is unused, and we have no plans to use it now that we're moving to supporting the Web3Signer APIs: #2522
* It represents a significant maintenance burden. The HTTP API tests have been prone to platform-specific failures, and breakages due to dependency upgrades, e.g. #2400.

Although the code is deleted it remains in the Git history should we ever want to recover it. For ease of reference:

- The last commit containing remote signer code: 5a3bcd2904
- The last Lighthouse version: v1.5.1
2021-09-03 06:09:18 +00:00
Michael Sproul
d9910f96c5 Remove SIGPIPE handler (#2558)
## Proposed Changes

Remove the SIGPIPE handler added in #2486.

We saw some of the testnet nodes running under `systemd` being stopped due to `journald` restarts. The systemd docs state:

> If systemd-journald.service is stopped, the stream connections associated with all services are terminated. Further writes to those streams by the service will result in EPIPE errors. In order to react gracefully in this case it is recommended that programs logging to standard output/error ignore such errors. If the SIGPIPE UNIX signal handler is not blocked or turned off, such write attempts will also result in such process signals being generated, see signal(7).

From https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html

## Additional Info

It turned out that the issue described in #2114 was due to `tee`'s behaviour rather than Lighthouse's, so the SIGPIPE handler isn't required for any current use case. An alternative to disabling it all together would be to exit with a non-zero code so that systemd knows to restart the process, but it seems more desirable to tolerate journald glitches than to restart frequently.
2021-09-03 04:26:06 +00:00
Pawan Dhananjay
ac274221c5 Run altair specific checks with the simulator (#2556)
## Issue Addressed

N/A

## Proposed Changes

Set a valid fork epoch in the simulator and run checks on 
1. If all nodes transitioned at the fork
2. If all altair block sync aggregates are full
2021-09-03 02:41:11 +00:00
Pawan Dhananjay
6f18f95893 Update file permissions (#2499)
## Issue Addressed

Resolves #2438 
Resolves #2437 

## Proposed Changes

Changes the permissions for validator client http server api token file and secret key to 600 from 644. Also changes the permission for logfiles generated using the `--logfile` cli option to 600.

Logs the path to the api token instead of the actual api token. Updates docs to reflect the change.
2021-09-03 02:41:10 +00:00
realbigsean
50321c6671 Updates to make crates publishable (#2472)
## Issue Addressed

Related to: #2259

Made an attempt at all the necessary updates here to publish the crates to crates.io. I incremented the minor versions on all the crates that have been previously published. We still might run into some issues as we try to publish because I'm not able to test this out but I think it's a good starting point.

## Proposed Changes

- Add description and license to `ssz_types` and `serde_util`
- rename `serde_util` to `eth2_serde_util`
- increment minor versions
- remove path dependencies
- remove patch dependencies 

## Additional Info
Crates published: 

- [x] `tree_hash` -- need to publish `tree_hash_derive` and `eth2_hashing` first
- [x] `eth2_ssz_types` -- need to publish `eth2_serde_util` first
- [x] `tree_hash_derive`
- [x] `eth2_ssz`
- [x] `eth2_ssz_derive`
- [x] `eth2_serde_util`
- [x] `eth2_hashing`


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-09-03 01:10:25 +00:00
Pawan Dhananjay
5a3bcd2904 Validator monitor support for sync committees (#2476)
## Issue Addressed

N/A

## Proposed Changes

Add functionality in the validator monitor to provide sync committee related metrics for monitored validators.


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-08-31 23:31:36 +00:00
Paul Hauner
44fa54004c Persist to DB after setting canonical head (#2547)
## Issue Addressed

NA

## Proposed Changes

Missed head votes on attestations is a well-known issue. The primary cause is a block getting set as the head *after* the attestation deadline.

This PR aims to shorten the overall time between "block received" and "block set as head" by:

1. Persisting the head and fork choice *after* setting the canonical head
    - Informal measurements show this takes ~200ms
 1. Pruning the op pool *after* setting the canonical head.
 1. No longer persisting the op pool to disk during `BeaconChain::fork_choice`
     - Informal measurements show this can take up to 1.2s.
     
I also add some metrics to help measure the effect of these changes.
     
Persistence changes like this run the risk of breaking assumptions downstream. However, I have considered these risks and I think we're fine here. I will describe my reasoning for each change.

## Reasoning

### Change 1:  Persisting the head and fork choice *after* setting the canonical head

For (1), although the function is called `persist_head_and_fork_choice`, it only persists:

- Fork choice
- Head tracker
- Genesis block root

Since `BeaconChain::fork_choice_internal` does not modify these values between the original time we were persisting it and the current time, I assert that the change I've made is non-substantial in terms of what ends up on-disk. There's the possibility that some *other* thread has modified fork choice in the extra time we've given it, but that's totally fine.

Since the only time we *read* those values from disk is during startup, I assert that this has no impact during runtime. 

### Change 2: Pruning the op pool after setting the canonical head

Similar to the argument above, we don't modify the op pool during `BeaconChain::fork_choice_internal` so it shouldn't matter when we prune. This change should be non-substantial.

### Change 3: No longer persisting the op pool to disk during `BeaconChain::fork_choice`

This change *is* substantial. With the proposed changes, we'll only be persisting the op pool to disk when we shut down cleanly (i.e., the `BeaconChain` gets dropped). This means we'll save disk IO and time during usual operation, but a `kill -9` or similar "crash" will probably result in an out-of-date op pool when we reboot. An out-of-date op pool can only have an impact when producing blocks or aggregate attestations/sync committees.

I think it's pretty reasonable that a crash might result in an out-of-date op pool, since:

- Crashes are fairly rare. Practically the only time I see LH suffer a full crash is when the OOM killer shows up, and that's a very serious event.
- It's generally quite rare to produce a block/aggregate immediately after a reboot. Just a few slots of runtime is probably enough to have a decent-enough op pool again.

## Additional Info

Credits to @macladson for the timings referenced here.
2021-08-31 04:48:21 +00:00
Paul Hauner
1031f79aca Improve ergonomics of adding a new network config (#2489)
## Issue Addressed

NA

## Proposed Changes

This PR adds some more fancy macro magic to make it easier to add a new built-in (aka "baked-in") testnet config to the `lighthouse` binary.

Previously, a user needed to modify several files and repeat themselves several times. Now, they only need to add a single definition in the `eth2_config` crate. No repetition 🎉
2021-08-30 23:27:28 +00:00
Pawan Dhananjay
b4dd98b3c6 Shutdown after sync (#2519)
## Issue Addressed

Resolves #2033 

## Proposed Changes

Adds a flag to enable shutting down beacon node right after sync is completed.

## Additional Info

Will need modification after weak subjectivity sync is enabled to change definition of a fully synced node.
2021-08-30 13:46:13 +00:00
Michael Sproul
10945e0619 Revert bad blocks on missed fork (#2529)
## Issue Addressed

Closes #2526

## Proposed Changes

If the head block fails to decode on start up, do two things:

1. Revert all blocks between the head and the most recent hard fork (to `fork_slot - 1`).
2. Reset fork choice so that it contains the new head, and all blocks back to the new head's finalized checkpoint.

## Additional Info

I tweaked some of the beacon chain test harness stuff in order to make it generic enough to test with a non-zero slot clock on start-up. In the process I consolidated all the various `new_` methods into a single generic one which will hopefully serve all future uses 🤞
2021-08-30 06:41:31 +00:00
ethDreamer
6b65b6f3bd removed verify_exit_time_independent_only function (#2509)
## Issue Addressed

Closes #1676 

## Proposed Changes

Removed unneeded functions `verify_exit_parametric` and `verify_exit_time_independent_only`
2021-08-30 06:41:30 +00:00
Mason Stallmo
bc14d1d73d Add more unix signal handlers (#2486)
## Issue Addressed

Resolves #2114 

Swapped out the ctrlc crate for tokio signals to hook register handlers for SIGPIPE and SIGHUP along with SIGTERM and SIGINT.

## Proposed Changes

- Swap out the ctrlc crate for tokio signals for unix signal handing
- Register signals for SIGPIPE and SHIGUP that trigger the same shutdown procedure as SIGTERM and SIGINT

## Additional Info

I tested these changes against the examples in the original issue and noticed some interesting behavior on my machine. When running `lighthouse bn --network pyrmont |& tee -a pyrmont_bn.log` or `lighthouse bn --network pyrmont 2>&1 | tee -a pyrmont_bn.log` none of the above signals are sent to the lighthouse program in a way I was able to observe. 

The only time it seems that the signal gets sent to the lighthouse program is if there is no redirection of stderr to stdout. I'm not as familiar with the details of how unix signals work in linux with a redirect like that so I'm not sure if this is a bug in the program or expected behavior.

Signals are correctly received without the redirection and if the above signals are sent directly to the program with something like `kill`.
2021-08-30 05:19:34 +00:00
Pawan Dhananjay
99737c551a Improve eth1 fallback logging (#2490)
## Issue Addressed

Resolves #2487 

## Proposed Changes

Logs a message once in every invocation of `Eth1Service::update` method if the primary endpoint is unavailable for some reason. 

e.g.
```log
Aug 03 00:09:53.517 WARN Error connecting to eth1 node endpoint  action: trying fallbacks, endpoint: http://localhost:8545/, service: eth1_rpc
Aug 03 00:09:56.959 INFO Fetched data from fallback              fallback_number: 1, service: eth1_rpc
```

The main aim of this PR is to have an accompanying message to the "action: trying fallbacks" error message that is returned when checking the endpoint for liveness. This is mainly to indicate to the user that the fallback was live and reachable. 

## Additional info
This PR is not meant to be a catch all for all cases where the primary endpoint failed. For instance, this won't log anything if the primary node was working fine during endpoint liveness checking and failed during deposit/block fetching. This is done intentionally to reduce number of logs while initial deposit/block sync and to avoid more complicated logic.
2021-08-30 00:51:26 +00:00
ladidan
beab306e07 Fix log output for INFO Found no doppelganger (#2551)
## Issue Addressed

log output "INFO Found no doppelganger validator_index: 11111, epoch: 11111, further_checks_remaining: 0, service: doppelganger"
whereby validator_index = epoch

## Proposed Changes

epoch = current epoch
2021-08-29 23:29:47 +00:00
Paul Hauner
b0ac3464ca v1.5.1 (#2544)
## Issue Addressed

NA

## Proposed Changes

- Bump version

## Additional Info

NA
2021-08-27 01:58:19 +00:00
Paul Hauner
4405425726 Expand gossip duplicate cache time (#2542)
## Issue Addressed

NA

## Proposed Changes

This PR expands the time that entries exist in the gossip-sub duplicate cache. Recent investigations found that this cache is one slot (12s) shorter than the period for which an attestation is permitted to propagate on the gossip network.

Before #2540, this was causing peers to be unnecessarily down-scored for sending old attestations. Although that issue has been fixed, the duplicate cache time is increased here to avoid such messages from getting any further up the networking stack then required.

## Additional Info

NA
2021-08-26 23:25:50 +00:00
Michael Sproul
9fb94fbebe Add Altair fork epoch for Prater (#2537)
## Issue Addressed

https://github.com/eth2-clients/eth2-networks/pull/58

## Proposed Changes

Add the fork epoch for Altair on Prater: 36660

## Additional Info

This `config.yaml` is copied exactly from upstream. Large parts already matched due to our preemptive move to the new config style.
2021-08-26 09:42:23 +00:00
Paul Hauner
3fdad38eba Remove penality for duplicate attestation from same validator (#2540)
## Issue Addressed

NA

## Proposed Changes

A Discord user presented logs which indicated a drop in their peer count caused by a variety of peers sending attestations where we'd already seen an attestation for that validator. It's presently unclear how this case came about, but during our investigation I noticed that we are down-voting peers for sending such attestations.

There are three scenarios where we may receive duplicate unagg. attestations from the same validator:

1. The validator is committing a slashable offense.
2. The gossipsub message-deduping functionality is not working as expected.
3. We received the message via the HTTP prior to seeing it via gossip.

Scenario (1) would be so costly for an attacker that I don't think we need to add DoS protection for it.

Scenario (2) seems feasible. Our "seen message" caches in gossipsub might fill up/expire and let through these duplicates. There are also cases involving message ID mismatches with the other peers. In both these cases, I don't think we should be doing 1 attestation == -1 point down-voting.

Scenario (3) is not necessarily a fault of the peer and we shouldn't down-score them for it.

## Additional Info

NA
2021-08-26 08:00:50 +00:00
Age Manning
09545fe668 Increase maximum gossipsub subscriptions (#2531)
Due to the altair fork, in principle we can now subscribe to up to 148 topics. This bypasses our original limit and we can end up rejecting subscriptions. 

This PR increases the limit to account for the fork.
2021-08-26 02:01:10 +00:00
Pawan Dhananjay
d3b4cbed53 Packet filter cli option (#2523)
## Issue Addressed

N/A

## Proposed Changes

Adds a cli option to disable packet filter in `lighthouse bootnode`. This is useful in running local testnets as the bootnode bans requests from the same ip(localhost) if the packet filter is enabled.
2021-08-26 00:29:39 +00:00
Michael Sproul
aca49fc45e Security update for openssl (#2539)
## Proposed Changes

Appease `cargo audit` by updating `openssl-src` to avoid these two vulns:

- https://rustsec.org/advisories/RUSTSEC-2021-0097
- https://rustsec.org/advisories/RUSTSEC-2021-0098
2021-08-25 03:13:41 +00:00
realbigsean
5b8436e33f Fork schedule api (#2525)
## Issue Addressed

Resolves #2524

## Proposed Changes

- Return all known forks in the `/config/fork_schedule`, previously returned only the head of the chain's fork.
- Deleted the `StateId::head` method because it was only previously used in this endpoint.


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-08-24 01:36:27 +00:00
realbigsean
bc0599246f Windows binaries (#2492)
## Issue Addressed

Resolves #2406

## Proposed Changes

Add windows release binaries to our CI

## Additional Info


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-08-24 01:36:26 +00:00
Pawan Dhananjay
02fd54bea7 Refactor discovery queries (#2518)
## Issue Addressed

N/A

## Proposed Changes

Refactor discovery queries such that only `QueryType::Subnet` queries are queued for grouping. `QueryType::FindPeers` is always made regardless of the number of active `Subnet` queries (max = 2). This prevents `FindPeers` queries from getting starved if `Subnet` queries start queuing up. 

Also removes `GroupedQueryType` struct and use `QueryType` for all queuing and processing of discovery requests.

## Additional Info

Currently, no distinction is made between subnet discovery queries for attestation and sync committee subnets when grouping queries. Potentially we could prioritise attestation queries over sync committee queries in the future.
2021-08-24 00:12:13 +00:00
Paul Hauner
90d5ab1566 v1.5.0 (#2535)
## Issue Addressed

NA

## Proposed Changes

- Version bump
- Increase queue sizes for aggregated attestations and re-queued attestations. 

## Additional Info

NA
2021-08-23 04:27:36 +00:00
Paul Hauner
f2a8c6229c Metrics and DEBG log for late gossip blocks (#2533)
## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

- Add a counter metric to log when a block is received late from gossip.
- Also push a `DEBG` log for the above condition.
- Use Debug (`?`) instead of Display (`%`) for a bunch of logs in the beacon processor, so we don't have to deal with concatenated block roots.
- Add new ERRO and CRIT to HTTP API to alert users when they're publishing late blocks.

## Additional Info

NA
2021-08-23 00:59:14 +00:00
Paul Hauner
12fe72bd37 Always require auth header in VC (#2517)
## Issue Addressed

- Resolves #2512 

## Proposed Changes

Enforces that all routes require an auth token for the VC.

## TODO

- [x] Tests
2021-08-18 01:31:28 +00:00
Paul Hauner
c7379836a5 v1.5.0-rc.1 (#2516)
## Issue Addressed

NA

## Proposed Changes

- Bump version

## Additional Info

NA
2021-08-17 05:34:31 +00:00
Michael Sproul
bc6a848ded Quote integers in sync committee contributions (#2515)
## Issue Addressed

Closes #2514

## Additional Info

I've verified that the API now behaves correctly on altair-devnet-3
2021-08-17 02:35:23 +00:00
Michael Sproul
c0a2f501d9 Upgrade dependencies (#2513)
## Proposed Changes

* Consolidate Tokio versions: everything now uses the latest v1.10.0, no more `tokio-compat`.
* Many semver-compatible changes via `cargo update`. Notably this upgrades from the yanked v0.8.0 version of crossbeam-deque which is present in v1.5.0-rc.0
* Many semver incompatible upgrades via `cargo upgrades` and `cargo upgrade --workspace pkg_name`. Notable ommissions:
    - Prometheus, to be handled separately: https://github.com/sigp/lighthouse/issues/2485
    - `rand`, `rand_xorshift`: the libsecp256k1 package requires 0.7.x, so we'll stick with that for now
    - `ethereum-types` is pinned at 0.11.0 because that's what `web3` is using and it seems nice to have just a single version
    
## Additional Info

We still have two versions of `libp2p-core` due to `discv5` depending on the v0.29.0 release rather than `master`. AFAIK it should be OK to release in this state (cc @AgeManning )
2021-08-17 01:00:24 +00:00
Pawan Dhananjay
d17350c0fa Lower penalty for past/future slot errors (#2510)
## Issue Addressed

N/A

## Proposed Changes

Reduce the penalties with past/future slot errors for sync committee messages.
2021-08-16 23:30:18 +00:00
Paul Hauner
4c4ebfbaa1 v1.5.0 rc.0 (#2506)
## Issue Addressed

NA

## Proposed Changes

- Bump to `v1.5.0-rc.0`.
- Increase attestation reprocessing queue size (I saw this filling up on Prater).
- Reduce error log for full attn reprocessing queue to warn.

## TODO

- [x] Manual testing
- [x] Resolve https://github.com/sigp/lighthouse/pull/2493
- [x] Include https://github.com/sigp/lighthouse/pull/2501
2021-08-12 04:02:46 +00:00
Paul Hauner
4af6fcfafd Bump libp2p to address inconsistency in mesh peer tracking (#2493)
## Issue Addressed

- Resolves #2457
- Resolves #2443

## Proposed Changes

Target the (presently unreleased) head of `libp2p/rust-libp2p:master` in order to obtain the fix from https://github.com/libp2p/rust-libp2p/pull/2175.

Additionally:

- `libsecp256k1` needed to be upgraded to satisfy the new version of `libp2p`.
- There were also a handful of minor changes to `eth2_libp2p` to suit some interface changes.
- Two `cargo audit --ignore` flags were remove due to libp2p upgrades.

## Additional Info
 
 NA
2021-08-12 01:59:20 +00:00
Paul Hauner
33ff51a096 Add Altair fork schedule for Pyrmont (#2501)
## Issue Addressed

NA

## Proposed Changes

Adds the Altair fork schedule for Pyrmont, as per https://github.com/eth2-clients/eth2-networks/pull/56 (credits to @ajsutton).

## Additional Info

- I've marked this as `do-not-merge` until the upstream PR is merged.
- I've tagged this for `v1.5.0` because I expect the upstream PR to be merged soon, and I think it would be great if v1.5.0 shipped fully ready for the Pyrmont fork.
2021-08-11 06:17:25 +00:00
Paul Hauner
54f92cc263 Use THC for state.inactivity_scores (#2504)
## Issue Addressed

- Resolves #2502

## Proposed Changes

Adds tree-hash caching (THC 🍁) for `state.inactivity_scores`, as per #2502.

Since the `inactivity_scores` field is introduced during Altair, the cache must be optional (i.e., not present pre-Altair). The mechanism for optional caches was already implemented via the `ParticipationTreeHashCache`, albeit not quite generically enough. To this end, I made the `ParticipationTreeHashCache` more generic and renamed it to `OptionalTreeHashCache`. This made the code a little more verbose around the previous/current epoch participation fields, but overall less verbose when the needs of `inactivity_scores` are considered.

All changes to `ParticipationTreeHashCache` should be *non-substantial*.

## Additional Info

NA
2021-08-09 04:58:17 +00:00
Paul Hauner
7b46c4bb7a Add docs for Pre-Releases (#2505)
## Issue Addressed

NA

## Proposed Changes

When we released [`v1.4.0-rc.0`](https://github.com/sigp/lighthouse/releases/tag/v1.4.0-rc.0), we added a bunch of text about pre-releases. That information was useful, but somewhat hard to reference in future pre-releases.

This PR adds some docs to the book so whenever we do a pre-release we can point users to these docs for more info.

## Additional Info

NA
2021-08-09 02:43:04 +00:00
Paul Hauner
ceda27371d Ensure doppelganger detects attestations in blocks (#2495)
## Issue Addressed

NA

## Proposed Changes

When testing our (not-yet-released) Doppelganger implementation, I noticed that we aren't detecting attestations included in blocks (only those on the gossip network).

This is because during [block processing](e8c0d1f19b/beacon_node/beacon_chain/src/beacon_chain.rs (L2168)) we only update the `observed_attestations` cache with each attestation, but not the `observed_attesters` cache. This is the correct behaviour when we consider the [p2p spec](https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md):

> [IGNORE] There has been no other valid attestation seen on an attestation subnet that has an identical attestation.data.target.epoch and participating validator index.

We're doing the right thing here and still allowing attestations on gossip that we've seen in a block. However, this doesn't work so nicely for Doppelganger.

To resolve this, I've taken the following steps:

- Add a `observed_block_attesters` cache.
- Rename `observed_attesters` to `observed_gossip_attesters`.

## TODO

- [x] Add a test to ensure a validator that's been seen in a block attestation (but not a gossip attestation) returns `true` for `BeaconChain::validator_seen_at_epoch`.
- [x] Add a test to ensure `observed_block_attesters` isn't polluted via gossip attestations and vice versa. 


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-08-09 02:43:03 +00:00
Paul Hauner
ff85b05249 Add docs for doppelganger protection (#2496)
## Issue Addressed

NA

## Proposed Changes

- Adds docs for Doppelganger Protection
- Shortens a log message since it was a bit longer than our usual formatting.

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2021-08-06 02:13:16 +00:00
Paul Hauner
71ab16e404 Register vals with doppelganger earlier (#2494)
## Issue Addressed

NA

## Proposed Changes

Registers validators with the doppelganger service at the earliest possible point.

This avoids the following (non-harmful, but scary) log when pruning the slashing DB on startup:

```
CRIT Validator unknown to doppelganger service, pubkey: 0xabc..., msg: preventing validator from performing duties, service: doppelganger
```

## Additional Info

NA
2021-08-06 02:13:15 +00:00
Michael Sproul
17a2c778e3 Altair validator client and HTTP API (#2404)
## Proposed Changes

* Implement the validator client and HTTP API changes necessary to support Altair


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-08-06 00:47:31 +00:00
Pawan Dhananjay
350b6f19de Print only unique doppelgangers (#2500)
## Issue Addressed

N/A

## Proposed Changes

This is just a cosmetic change to print only the unique list of violaters. We could repeat violaters in the list if an attestation and aggregation both were detected from the same validator.
2021-08-05 22:27:40 +00:00
Pawan Dhananjay
fc8969302c Update mdbook version in github actions (#2497)
## Issue Addressed

Resolves #2477 

## Proposed Changes

Always use the latest mdbook version in github actions. [Source](https://github.com/peaceiris/actions-mdbook#%EF%B8%8F-use-the-latest-version-of-mdbook)
2021-08-05 00:58:23 +00:00
Pawan Dhananjay
e8c0d1f19b Altair networking (#2300)
## Issue Addressed

Resolves #2278 

## Proposed Changes

Implements the networking components for the Altair hard fork https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/p2p-interface.md

## Additional Info

This PR acts as the base branch for networking changes and tracks https://github.com/sigp/lighthouse/pull/2279 . Changes to gossip, rpc and discovery can be separate PRs to be merged here for ease of review.

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-08-04 01:44:57 +00:00
Paul Hauner
6a620a31da Fix starting-epoch check in doppelganger (#2491)
## Issue Addressed

NA

## Proposed Changes

Fixes a bug in Doppelganger Protection which would cause false-positives when a VC is restarted in the same epoch where it has already produced a signed message.

It could also cause a false-negative in the scenario where time skips forward (perhaps due to host suspend/wake). The new `time_skips_forward_with_doppelgangers` test covers this case.

This was a simple (and embarrassing, on my behalf) `>=` instead of `<=` bug that was missed by my tests but detected during manual testing by @michaelsproul (🙏). Regression tests have been added.

## Additional Info

NA

## TODO

- [x] Add test for doppelganger in epoch > next_check_epoch
2021-08-04 00:03:47 +00:00
Michael Sproul
187425cdc1 Bump discv5 to v0.1.0-beta.9 (#2479)
Bump discv5 to fix the issues with IP filters and removing nodes.

~~Blocked on an upstream release, and more testnet data.~~
2021-08-03 01:05:06 +00:00
realbigsean
c5786a8821 Doppelganger detection (#2230)
## Issue Addressed

Resolves #2069 

## Proposed Changes

- Adds a `--doppelganger-detection` flag
- Adds a `lighthouse/seen_validators` endpoint, which will make it so the lighthouse VC is not interopable with other client beacon nodes if the `--doppelganger-detection` flag is used, but hopefully this will become standardized. Relevant Eth2 API repo issue: https://github.com/ethereum/eth2.0-APIs/issues/64
- If the `--doppelganger-detection` flag is used, the VC will wait until the beacon node is synced, and then wait an additional 2 epochs. The reason for this is to make sure the beacon node is able to subscribe to the subnets our validators should be attesting on. I think an alternative would be to have the beacon node subscribe to all subnets for 2+ epochs on startup by default.

## Additional Info

I'd like to add tests and would appreciate feedback. 

TODO:  handle validators started via the API, potentially make this default behavior

Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-07-31 03:50:52 +00:00
SaNNNNNNNN
834ee98bc2 Fix flag in redundancy docs (#2482)
Replace all --process-all-attestations with --import-all-attestations

## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

Please list or describe the changes introduced by this PR.

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2021-07-31 02:24:09 +00:00
realbigsean
303deb9969 Rust 1.54.0 lints (#2483)
## Issue Addressed

N/A

## Proposed Changes

- Removing a bunch of unnecessary references
- Updated `Error::VariantError` to `Error::Variant`
- There were additional enum variant lints that I ignored, because I thought our variant names were fine
- removed `MonitoredValidator`'s `pubkey` field, because I couldn't find it used anywhere. It looks like we just use the string version of the pubkey (the `id` field) if there is no index

## Additional Info



Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-07-30 01:11:47 +00:00
Paul Hauner
8efd9fc324 Add AttesterCache for attestation production (#2478)
## Issue Addressed

- Resolves #2169

## Proposed Changes

Adds the `AttesterCache` to allow validators to produce attestations for older slots. Presently, some arbitrary restrictions can force validators to receive an error when attesting to a slot earlier than the present one. This can cause attestation misses when there is excessive load on the validator client or time sync issues between the VC and BN.

## Additional Info

NA
2021-07-29 04:38:26 +00:00
Michael Sproul
1d4f90e2eb Bump tests to v1.1.0-beta.2 (#2481)
## Proposed Changes

Bump spec tests to v1.1.0-beta.2, for conformance with the latest spec release: https://github.com/ethereum/eth2.0-specs/releases/tag/v1.1.0-beta.2

## Additional Info

We already happen to be compatible with the latest spec change that requires sync contributions to have at least one bit set. I'm gonna call it foresight on @realbigsean's part 😎

6e3ca48cb9/beacon_node/beacon_chain/src/sync_committee_verification.rs (L285-L288)
2021-07-29 02:16:54 +00:00
Michael Sproul
923486f34c Use bulk verification for sync_aggregate signature (#2415)
## Proposed Changes

Add the `sync_aggregate` from `BeaconBlock` to the bulk signature verifier for blocks. This necessitates a new signature set constructor for the sync aggregate, which is different from the others due to the use of [`eth2_fast_aggregate_verify`](https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/bls.md#eth2_fast_aggregate_verify) for sync aggregates, per [`process_sync_aggregate`](https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/beacon-chain.md#sync-aggregate-processing). I made the choice to return an optional signature set, with `None` representing the case where the signature is valid on account of being the point at infinity (requires no further checking).

To "dogfood" the changes and prevent duplication, the consensus logic now uses the signature set approach as well whenever it is required to verify signatures (which should only be in testing AFAIK). The EF tests pass with the code as it exists currently, but failed before I adapted the `eth2_fast_aggregate_verify` changes (which is good).

As a result of this change Altair block processing should be a little faster, and importantly, we will no longer accidentally verify signatures when replaying blocks, e.g. when replaying blocks from the database.
2021-07-28 05:40:21 +00:00
Paul Hauner
6e3ca48cb9 Cache participating indices for Altair epoch processing (#2416)
## Issue Addressed

NA

## Proposed Changes

This PR addresses two things:

1. Allows the `ValidatorMonitor` to work with Altair states.
1. Optimizes `altair::process_epoch` (see [code](https://github.com/paulhauner/lighthouse/blob/participation-cache/consensus/state_processing/src/per_epoch_processing/altair/participation_cache.rs) for description)

## Breaking Changes

The breaking changes in this PR revolve around one premise:

*After the Altair fork, it's not longer possible (given only a `BeaconState`) to identify if a validator had *any* attestation included during some epoch. The best we can do is see if that validator made the "timely" source/target/head flags.*

Whilst this seems annoying, it's not actually too bad. Finalization is based upon "timely target" attestations, so that's really the most important thing. Although there's *some* value in knowing if a validator had *any* attestation included, it's far more important to know about "timely target" participation, since this is what affects finality and justification.

For simplicity and consistency, I've also removed the ability to determine if *any* attestation was included from metrics and API endpoints. Now, all Altair and non-Altair states will simply report on the head/target attestations.

The following section details where we've removed fields and provides replacement values.

### Breaking Changes: Prometheus Metrics

Some participation metrics have been removed and replaced. Some were removed since they are no longer relevant to Altair (e.g., total attesting balance) and others replaced with gwei values instead of pre-computed values. This provides more flexibility at display-time (e.g., Grafana).

The following metrics were added as replacements:

- `beacon_participation_prev_epoch_head_attesting_gwei_total`
- `beacon_participation_prev_epoch_target_attesting_gwei_total`
- `beacon_participation_prev_epoch_source_attesting_gwei_total`
- `beacon_participation_prev_epoch_active_gwei_total`

The following metrics were removed:

- `beacon_participation_prev_epoch_attester`
   - instead use `beacon_participation_prev_epoch_source_attesting_gwei_total / beacon_participation_prev_epoch_active_gwei_total`.
- `beacon_participation_prev_epoch_target_attester`
   - instead use `beacon_participation_prev_epoch_target_attesting_gwei_total / beacon_participation_prev_epoch_active_gwei_total`.
- `beacon_participation_prev_epoch_head_attester`
   - instead use `beacon_participation_prev_epoch_head_attesting_gwei_total / beacon_participation_prev_epoch_active_gwei_total`.

The `beacon_participation_prev_epoch_attester` endpoint has been removed. Users should instead use the pre-existing `beacon_participation_prev_epoch_target_attester`. 

### Breaking Changes: HTTP API

The `/lighthouse/validator_inclusion/{epoch}/{validator_id}` endpoint loses the following fields:

- `current_epoch_attesting_gwei` (use `current_epoch_target_attesting_gwei` instead)
- `previous_epoch_attesting_gwei` (use `previous_epoch_target_attesting_gwei` instead)

The `/lighthouse/validator_inclusion/{epoch}/{validator_id}` endpoint lose the following fields:

- `is_current_epoch_attester` (use `is_current_epoch_target_attester` instead)
- `is_previous_epoch_attester` (use `is_previous_epoch_target_attester` instead)
- `is_active_in_current_epoch` becomes `is_active_unslashed_in_current_epoch`.
- `is_active_in_previous_epoch` becomes `is_active_unslashed_in_previous_epoch`.

## Additional Info

NA

## TODO

- [x] Deal with total balances
- [x] Update validator_inclusion API
- [ ] Ensure `beacon_participation_prev_epoch_target_attester` and `beacon_participation_prev_epoch_head_attester` work before Altair

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-07-27 07:01:01 +00:00
Michael Sproul
f5bdca09ff Update to spec v1.1.0-beta.1 (#2460)
## Proposed Changes

Update to the latest version of the Altair spec, which includes new tests and a tweak to the target sync aggregators.

## Additional Info

This change is _not_ required for the imminent Altair devnet, and is waiting on the merge of #2321 to unstable.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-07-27 05:43:35 +00:00
Michael Sproul
84e6d71950 Tree hash caching and optimisations for Altair (#2459)
## Proposed Changes

Remove the remaining Altair `FIXME`s from consensus land.

1. Implement tree hash caching for the participation lists. This required some light type manipulation, including removing the `TreeHash` bound from `CachedTreeHash` which was purely descriptive.
2. Plumb the proposer index through Altair attestation processing, to avoid calculating it for _every_ attestation (potentially 128ms on large networks). This duplicates some work from #2431, but with the aim of getting it in sooner, particularly for the Altair devnets.
3. Removes two FIXMEs related to `superstruct` and cloning, which are unlikely to be particularly detrimental and will be tracked here instead: https://github.com/sigp/superstruct/issues/5
2021-07-23 00:23:53 +00:00
Michael Sproul
74aa99c409 Document BN API security considerations (#2470)
## Issue Addressed

Closes #2468

## Proposed Changes

Document security considerations for the beacon node API, with strong recommendations against exposing it to the internet.
2021-07-22 01:37:01 +00:00
Michael Sproul
63923eaa29 Bump discv5 to v0.1.0-beta.8 (#2471)
## Proposed Changes

Update discv5 to fix bugs seen on `altair-devnet-1`
2021-07-21 07:10:52 +00:00
Mac L
17b6d7ce86 Add http-address flag to VC (#2467)
## Issue Addressed

#2454 

## Proposed Changes

Adds the `--http-address` flag to allow the user to use custom HTTP addresses. This can be helpful for certain Docker setups.

Since using custom HTTP addresses is unsafe due to the server being unencrypted,  `--unencrypted-http-transport` was also added as a safety flag and must be used in tandem with `--http-address`. This is to ensure the user is aware of the risks associated with using non-local HTTP addresses.
2021-07-21 07:10:51 +00:00
realbigsean
bcf8ba6318 Add lcli Dockerfile and auto-build to CI (#2469)
## Issue Addressed

Resolves: #2087

## Proposed Changes

- Add a `Dockerfile` to the `lcli` directory
- Add a github actions job to build and push and `lcli` docker image on pushes to `unstable` and `stable`

## Additional Info

It's a little awkward but `lcli` requires the full project scope so must be built: 
- from the `lighthouse` dir with: `docker build -f ./lcli/Dockerflie .`
- from the `lcli` dir with: `docker build -f ./Dockerfile ../`

Didn't include `libssl-dev` or `ca-certificates`, `lcli` doesn't need these right?

Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Michael Sproul <micsproul@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-07-21 03:24:23 +00:00
Age Manning
9a8320beaa Merge pull request #2389 from sigp/network-1.5
Network Updates for 1.5
2021-07-15 18:15:07 +10:00
Age Manning
08fedbfcba Libp2p Connection Limit (#2455)
* Get libp2p to handle connection limits

* fmt
2021-07-15 16:43:18 +10:00
Age Manning
6818a94171 Discovery update (#2458) 2021-07-15 16:43:18 +10:00
Age Manning
381befbf82 Ensure disconnecting peers are added to the peerdb (#2451) 2021-07-15 16:43:18 +10:00
Age Manning
059d9ec1b1 Gossipsub scoring improvements (#2391)
* Tweak gossipsub parameters for improved scoring

* Modify gossip history

* Update settings

* Make mesh window constant

* Decrease the mesh message deliveries weight

* Fmt
2021-07-15 16:43:18 +10:00
Age Manning
c62810b408 Update to Libp2p to 39.1 (#2448)
* Adjust beacon node timeouts for validator client HTTP requests (#2352)

Resolves #2313

Provide `BeaconNodeHttpClient` with a dedicated `Timeouts` struct.
This will allow granular adjustment of the timeout duration for different calls made from the VC to the BN. These can either be a constant value, or as a ratio of the slot duration.

Improve timeout performance by using these adjusted timeout duration's only whenever a fallback endpoint is available.

Add a CLI flag called `use-long-timeouts` to revert to the old behavior.

Additionally set the default `BeaconNodeHttpClient` timeouts to the be the slot duration of the network, rather than a constant 12 seconds. This will allow it to adjust to different network specifications.

Co-authored-by: Paul Hauner <paul@paulhauner.com>

* Use read_recursive locks in database (#2417)

Closes #2245

Replace all calls to `RwLock::read` in the `store` crate with `RwLock::read_recursive`.

* Unfortunately we can't run the deadlock detector on CI because it's pinned to an old Rust 1.51.0 nightly which cannot compile Lighthouse (one of our deps uses `ptr::addr_of!` which is too new). A fun side-project at some point might be to update the deadlock detector.
* The reason I think we haven't seen this deadlock (at all?) in practice is that _writes_ to the database's split point are quite infrequent, and a concurrent write is required to trigger the deadlock. The split point is only written when finalization advances, which is once per epoch (every ~6 minutes), and state reads are also quite sporadic. Perhaps we've just been incredibly lucky, or there's something about the timing of state reads vs database migration that protects us.
* I wrote a few small programs to demo the deadlock, and the effectiveness of the `read_recursive` fix: https://github.com/michaelsproul/relock_deadlock_mvp
* [The docs for `read_recursive`](https://docs.rs/lock_api/0.4.2/lock_api/struct.RwLock.html#method.read_recursive) warn of starvation for writers. I think in order for starvation to occur the database would have to be spammed with so many state reads that it's unable to ever clear them all and find time for a write, in which case migration of states to the freezer would cease. If an attack could be performed to trigger this starvation then it would likely trigger a deadlock in the current code, and I think ceasing migration is preferable to deadlocking in this extreme situation. In practice neither should occur due to protection from spammy peers at the network layer. Nevertheless, it would be prudent to run this change on the testnet nodes to check that it doesn't cause accidental starvation.

* Return more detail when invalid data is found in the DB during startup (#2445)

- Resolves #2444

Adds some more detail to the error message returned when the `BeaconChainBuilder` is unable to access or decode block/state objects during startup.

NA

* Use hardware acceleration for SHA256 (#2426)

Modify the SHA256 implementation in `eth2_hashing` so that it switches between `ring` and `sha2` to take advantage of [x86_64 SHA extensions](https://en.wikipedia.org/wiki/Intel_SHA_extensions). The extensions are available on modern Intel and AMD CPUs, and seem to provide a considerable speed-up: on my Ryzen 5950X it dropped state tree hashing times by about 30% from 35ms to 25ms (on Prater).

The extensions became available in the `sha2` crate [last year](https://www.reddit.com/r/rust/comments/hf2vcx/ann_rustcryptos_sha1_and_sha2_now_support/), and are not available in Ring, which uses a [pure Rust implementation of sha2](https://github.com/briansmith/ring/blob/main/src/digest/sha2.rs). Ring is faster on CPUs that lack the extensions so I've implemented a runtime switch to use `sha2` only when the extensions are available. The runtime switching seems to impose a miniscule penalty (see the benchmarks linked below).

* Start a release checklist (#2270)

NA

Add a checklist to the release draft created by CI. I know @michaelsproul was also working on this and I suspect @realbigsean also might have useful input.

NA

* Serious banning

* fmt

Co-authored-by: Mac L <mjladson@pm.me>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-07-15 16:43:18 +10:00
Age Manning
3c0d3227ab Global Network Behaviour Refactor (#2442)
* Network upgrades (#2345)

* Discovery patch (#2382)

* Upgrade libp2p and unstable gossip

* Network protocol upgrades

* Correct dependencies, reduce incoming bucket limit

* Clean up dirty DHT entries before repopulating

* Update cargo lock

* Update lockfile

* Update ENR dep

* Update deps to specific versions

* Update test dependencies

* Update docker rust, and remote signer tests

* More remote signer test fixes

* Temp commit

* Update discovery

* Remove cached enrs after dialing

* Increase the session capacity, for improved efficiency

* Bleeding edge discovery (#2435)

* Update discovery banning logic and tokio

* Update to latest discovery

* Shift to latest discovery

* Fmt

* Initial re-factor of the behaviour

* More progress

* Missed changes

* First draft

* Discovery as a behaviour

* Adding back event waker (not convinced its neccessary, but have made this many changes already)

* Corrections

* Speed up discovery

* Remove double log

* Fmt

* After disconnect inform swarm about ban

* More fmt

* Appease clippy

* Improve ban handling

* Update tests

* Update cargo.lock

* Correct tests

* Downgrade log
2021-07-15 16:43:17 +10:00
Pawan Dhananjay
64226321b3 Relax requirement for enr fork digest predicate (#2433) 2021-07-15 16:43:17 +10:00
Age Manning
c1d2e35c9e Bleeding edge discovery (#2435)
* Update discovery banning logic and tokio

* Update to latest discovery

* Shift to latest discovery

* Fmt
2021-07-15 16:43:17 +10:00
Age Manning
f4bc9db16d Change the window mode of yamux (#2390) 2021-07-15 16:43:17 +10:00
Age Manning
6fb48b45fa Discovery patch (#2382)
* Upgrade libp2p and unstable gossip

* Network protocol upgrades

* Correct dependencies, reduce incoming bucket limit

* Clean up dirty DHT entries before repopulating

* Update cargo lock

* Update lockfile

* Update ENR dep

* Update deps to specific versions

* Update test dependencies

* Update docker rust, and remote signer tests

* More remote signer test fixes

* Temp commit

* Update discovery

* Remove cached enrs after dialing

* Increase the session capacity, for improved efficiency
2021-07-15 16:43:17 +10:00
Age Manning
4aa06c9555 Network upgrades (#2345) 2021-07-15 16:43:10 +10:00
Paul Hauner
b0f5c4c776 Clarify eth1 error message (#2461)
## Issue Addressed

- Closes #2452

## Proposed Changes

Addresses: https://github.com/sigp/lighthouse/issues/2452#issuecomment-879873511

## Additional Info

NA
2021-07-15 04:22:06 +00:00
realbigsean
a3a7f39b0d [Altair] Sync committee pools (#2321)
Add pools supporting sync committees:
- naive sync aggregation pool
- observed sync contributions pool
- observed sync contributors pool
- observed sync aggregators pool

Add SSZ types and tests related to sync committee signatures.

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-07-15 00:52:02 +00:00
Michael Sproul
8fa6e463ca Update direct libsecp256k1 dependencies (#2456)
## Proposed Changes

* Remove direct dependencies on vulnerable `libsecp256k1 0.3.5`
* Ignore the RUSTSEC issue until it is resolved in #2389
2021-07-14 05:24:10 +00:00
Paul Hauner
fc4c611476 Remove msg about longer sync with remote eth1 nodes (#2453)
## Issue Addressed

- Resolves #2452

## Proposed Changes

I've seen a few people confused by this and I don't think the message is really worth it.

## Additional Info

NA
2021-07-14 05:24:09 +00:00
divma
304fb05e44 Maintain attestations that reference unknown blocks (#2319)
## Issue Addressed

#635 

## Proposed Changes
- Keep attestations that reference a block we have not seen for 30secs before being re processed
- If we do import the block before that time elapses, it is reprocessed in that moment
- The first time it fails, do nothing wrt to gossipsub propagation or peer downscoring. If after being re processed it fails, downscore with a `LowToleranceError` and ignore the message.
2021-07-14 05:24:08 +00:00
Paul Hauner
9656ffee7c Metrics for sync aggregate fullness (#2439)
## Issue Addressed

NA

## Proposed Changes

Adds a metric to see how many set bits are in the sync aggregate for each beacon block being imported.

## Additional Info

NA
2021-07-13 02:22:55 +00:00
Paul Hauner
27aec1962c Add more detail to "Prior attestation known" log (#2447)
## Issue Addressed

NA

## Proposed Changes

Adds more detail to the log when an attestation is ignored due to a prior one being known. This will help identify which validators are causing the issue.

## Additional Info

NA
2021-07-13 01:02:03 +00:00
Paul Hauner
20fce117f3 Start a release checklist (#2270)
## Issue Addressed

NA

## Proposed Changes

Add a checklist to the release draft created by CI. I know @michaelsproul was also working on this and I suspect @realbigsean also might have useful input.

## Additional Info

NA
2021-07-12 10:02:25 +00:00
Michael Sproul
2c691af95b Use hardware acceleration for SHA256 (#2426)
## Proposed Changes

Modify the SHA256 implementation in `eth2_hashing` so that it switches between `ring` and `sha2` to take advantage of [x86_64 SHA extensions](https://en.wikipedia.org/wiki/Intel_SHA_extensions). The extensions are available on modern Intel and AMD CPUs, and seem to provide a considerable speed-up: on my Ryzen 5950X it dropped state tree hashing times by about 30% from 35ms to 25ms (on Prater).

## Additional Info

The extensions became available in the `sha2` crate [last year](https://www.reddit.com/r/rust/comments/hf2vcx/ann_rustcryptos_sha1_and_sha2_now_support/), and are not available in Ring, which uses a [pure Rust implementation of sha2](https://github.com/briansmith/ring/blob/main/src/digest/sha2.rs). Ring is faster on CPUs that lack the extensions so I've implemented a runtime switch to use `sha2` only when the extensions are available. The runtime switching seems to impose a miniscule penalty (see the benchmarks linked below).
2021-07-12 08:47:01 +00:00
Paul Hauner
a7b7134abb Return more detail when invalid data is found in the DB during startup (#2445)
## Issue Addressed

- Resolves #2444

## Proposed Changes

Adds some more detail to the error message returned when the `BeaconChainBuilder` is unable to access or decode block/state objects during startup.

## Additional Info

NA
2021-07-12 07:31:27 +00:00
Michael Sproul
371c216ac3 Use read_recursive locks in database (#2417)
## Issue Addressed

Closes #2245

## Proposed Changes

Replace all calls to `RwLock::read` in the `store` crate with `RwLock::read_recursive`.

## Additional Info

* Unfortunately we can't run the deadlock detector on CI because it's pinned to an old Rust 1.51.0 nightly which cannot compile Lighthouse (one of our deps uses `ptr::addr_of!` which is too new). A fun side-project at some point might be to update the deadlock detector.
* The reason I think we haven't seen this deadlock (at all?) in practice is that _writes_ to the database's split point are quite infrequent, and a concurrent write is required to trigger the deadlock. The split point is only written when finalization advances, which is once per epoch (every ~6 minutes), and state reads are also quite sporadic. Perhaps we've just been incredibly lucky, or there's something about the timing of state reads vs database migration that protects us.
* I wrote a few small programs to demo the deadlock, and the effectiveness of the `read_recursive` fix: https://github.com/michaelsproul/relock_deadlock_mvp
* [The docs for `read_recursive`](https://docs.rs/lock_api/0.4.2/lock_api/struct.RwLock.html#method.read_recursive) warn of starvation for writers. I think in order for starvation to occur the database would have to be spammed with so many state reads that it's unable to ever clear them all and find time for a write, in which case migration of states to the freezer would cease. If an attack could be performed to trigger this starvation then it would likely trigger a deadlock in the current code, and I think ceasing migration is preferable to deadlocking in this extreme situation. In practice neither should occur due to protection from spammy peers at the network layer. Nevertheless, it would be prudent to run this change on the testnet nodes to check that it doesn't cause accidental starvation.
2021-07-12 07:31:26 +00:00
Mac L
b3c7e59a5b Adjust beacon node timeouts for validator client HTTP requests (#2352)
## Issue Addressed

Resolves #2313 

## Proposed Changes

Provide `BeaconNodeHttpClient` with a dedicated `Timeouts` struct.
This will allow granular adjustment of the timeout duration for different calls made from the VC to the BN. These can either be a constant value, or as a ratio of the slot duration.

Improve timeout performance by using these adjusted timeout duration's only whenever a fallback endpoint is available.

Add a CLI flag called `use-long-timeouts` to revert to the old behavior.

## Additional Info

Additionally set the default `BeaconNodeHttpClient` timeouts to the be the slot duration of the network, rather than a constant 12 seconds. This will allow it to adjust to different network specifications.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-07-12 01:47:48 +00:00
Michael Sproul
b4689e20c6 Altair consensus changes and refactors (#2279)
## Proposed Changes

Implement the consensus changes necessary for the upcoming Altair hard fork.

## Additional Info

This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`.


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-07-09 06:15:32 +00:00
zhangzheng
89361573d4 Update testnet-validator.md (#2432)
add testnet prater
2021-07-09 04:44:56 +00:00
Paul Hauner
78e5c0c157 Capture a missed VC error (#2436)
## Issue Addressed

Related to #2430, #2394

## Proposed Changes

As per https://github.com/sigp/lighthouse/issues/2430#issuecomment-875323615, ensure that the `ProductionValidatorClient::new` error raises a log and shuts down the VC. Also, I implemened `spawn_ignoring_error`, as per @michaelsproul's suggestion in https://github.com/sigp/lighthouse/pull/2436#issuecomment-876084419.

I got unlucky and CI picked up a [new rustsec vuln](https://rustsec.org/advisories/RUSTSEC-2021-0072). To fix this, I had to update the following crates:

- `tokio`
- `web3`
- `tokio-compat-02`

## Additional Info

NA
2021-07-09 03:20:24 +00:00
Mac L
406e3921d9 Use forwards iterator for state root lookups (#2422)
## Issue Addressed

#2377 

## Proposed Changes

Implement the same code used for block root lookups (from #2376) to state root lookups in order to improve performance and reduce associated memory spikes (e.g. from certain HTTP API requests).

## Additional Changes

- Tests using `rev_iter_state_roots` and `rev_iter_block_roots` have been refactored to use their `forwards` versions instead.
- The `rev_iter_state_roots` and `rev_iter_block_roots` functions are now unused and have been removed.
- The `state_at_slot` function has been changed to use the `forwards` iterator.

## Additional Info

- Some tests still need to be refactored to use their `forwards_iter` versions. These tests start their iteration from a specific beacon state and thus use the `rev_iter_state_roots_from` and `rev_iter_block_roots_from` functions. If they can be refactored, those functions can also be removed.
2021-07-06 02:38:53 +00:00
Age Manning
73d002ef92 Update outdated dependencies (#2425)
This updates some older dependencies to address a few cargo audit warnings.

The majority of warnings come from network dependencies which will be addressed in #2389. 

This PR contains some minor dep updates that are not network related.

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-07-05 00:54:17 +00:00
Mac L
206486006c Add Lighthouse version and commit hash to Prometheus metrics (#2427)
## Issue Addressed

#2225 

## Proposed Changes

Exposes the version given from the `lighthouse_version` crate to the Prometheus metrics server.

## Additional Info

- This metric appears in both the Beacon Node and Validator Client metrics servers.
- This is the simplest solution. It might be better to include the version and commit hash as separate labels rather than combined, however this would be more involved. Happy to do it that way if this is too cumbersome to use.
- The metric appears as:
```
# HELP lighthouse_info The build of Lighthouse running on the server
# TYPE lighthouse_info gauge
lighthouse_info{version="Lighthouse/v1.4.0-379664a+"} 1
```
2021-07-02 01:33:30 +00:00
Michael Sproul
379664a648 Improve compilation error on 32-bit (#2424)
## Issue Addressed

Closes #1661

## Proposed Changes

Add a dummy package called `target_check` which gets compiled early in the build and fails if the target is 32-bit

## Additional Info

You can test the efficacy of this check with:

```
cross build --release --manifest-path lighthouse/Cargo.toml --target i686-unknown-linux-gnu
```

In which case this compilation error is shown:

```
error: Lighthouse requires a 64-bit CPU and operating system
  --> common/target_check/src/lib.rs:8:1
   |
8  | / assert_cfg!(
9  | |     target_pointer_width = "64",
10 | |     "Lighthouse requires a 64-bit CPU and operating system",
11 | | );
   | |__^
```
2021-06-30 04:56:22 +00:00
Fredrik Svantes
9461ac2d50 Run apt-get upgrade to get latest security updates (#2418)
## Issue Addressed

None.

## Proposed Changes

Run apt-get upgrade to install latest security updates.

## Additional Info

Images often take a long time to get the latest security updates, while running apt-get upgrade will pull the latest updates.


Co-authored-by: Age Manning <Age@AgeManning.com>
2021-06-30 02:06:49 +00:00
platschi
2357a41868 Fix typo in advanced networking docs (#2412)
## Proposed Changes

Fixing a typo in the advanced networking docs which mentions ``--target-peer`` instead of the correct ``--target-peers`` flag
2021-06-22 00:17:06 +00:00
Michael Sproul
6583ce325b Minify slashing protection interchange data (#2380)
## Issue Addressed

Closes #2354

## Proposed Changes

Add a `minify` method to `slashing_protection::Interchange` that keeps only the maximum-epoch attestation and maximum-slot block for each validator. Specifically, `minify` constructs "synthetic" attestations (with no `signing_root`) containing the maximum source epoch _and_ the maximum target epoch from the input. This is equivalent to the `minify_synth` algorithm that I've formally verified in this repository:

https://github.com/michaelsproul/slashing-proofs

## Additional Info

Includes the JSON loading optimisation from #2347
2021-06-21 05:46:36 +00:00
realbigsean
b84ff9f793 rust 1.53.0 updates (#2411)
## Issue Addressed

`make lint` failing on rust 1.53.0.

## Proposed Changes

1.53.0 updates

## Additional Info

I haven't figure out why yet, we were now hitting the recursion limit in a few crates. So I had to add `#![recursion_limit = "256"]` in a few places


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-06-18 05:58:01 +00:00
Michael Sproul
3dc1eb5eb6 Ignore inactive validators in validator monitor (#2396)
## Proposed Changes

A user on Discord (`@ChewsMacRibs`) reported that the validator monitor was logging `WARN Attested to an incorrect head` for their validator while it was awaiting activation.

This PR modifies the monitor so that it ignores inactive validators, by the logic that they are either awaiting activation, or have already exited. Either way, there's no way for an inactive validator to have their attestations included on chain, so no need for the monitor to report on them.

## Additional Info

To reproduce the bug requires registering validator keys manually with `--validator-monitor-pubkeys`. I don't think the bug will present itself with `--validator-monitor-auto`.
2021-06-17 02:10:48 +00:00
Jack
98ab00cc52 Handle Geth pre-EIP-155 block sync error condition (#2304)
## Issue Addressed

#2293 

## Proposed Changes

 - Modify the handler for the `eth_chainId` RPC (i.e., `get_chain_id`) to explicitly match against the Geth error string returned for pre-EIP-155 synced Geth nodes
 - ~~Add a new helper function, `rpc_error_msg`, to aid in the above point~~
 - Refactor `response_result` into `response_result_or_error` and patch reliant RPC handlers accordingly (thanks to @pawanjay176)

## Additional Info

Geth, as of Pangaea Expanse (v1.10.0), returns an explicit error when it is not synced past the EIP-155 block (2675000). Previously, Geth simply returned a chain ID of 0 (which was obviously much easier to handle on Lighthouse's part).


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-06-17 02:10:47 +00:00
realbigsean
b1657a60e9 Reorg events (#2090)
## Issue Addressed

Resolves #2088

## Proposed Changes

Add the `chain_reorg` SSE event topic

## Additional Info


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-06-17 02:10:46 +00:00
divma
3261eff0bf split outbound and inbound codecs encoded types (#2410)
Splits the inbound and outbound requests, for maintainability.
2021-06-17 00:40:16 +00:00
Clifton King
a526145b4a Fix remote signer test (#2400)
## Proposed Changes

Unescape text for json comparison in:

3a24ca5f14/remote_signer/tests/sign.rs (L282-L285)

Which causes this error:

```
---- sign::invalid_field_fork stdout ----
thread 'sign::invalid_field_fork' panicked at 'assertion failed: `(left == right)`
  left: `"Unable to parse body message from JSON: Error(\"invalid hex (InvalidHexCharacter { c: 'I', index: 0 })\", line: 1, column: 237097)"`,
 right: `"Unable to parse body message from JSON: Error(\"invalid hex (InvalidHexCharacter { c: \\'I\\', index: 0 })\", line: 1, column: 237097)"`', testing/remote_signer_test/src/consumer.rs:144:5
```

This is my first contribution and happy to receive feedback if you have any. Thanks
2021-06-16 10:42:55 +00:00
Pawan Dhananjay
dffe31c312 Add an account command to enable/disable validators (#2386)
## Issue Addressed

Resolves #2322 

## Proposed Changes

Adds a `modify` command to `lighthouse account validator` with subcommands to enable and disable specific or all pubkeys.
2021-06-16 09:16:51 +00:00
Paul Hauner
3b600acdc5 v1.4.0 (#2402)
## Issue Addressed

NA

## Proposed Changes

- Bump versions and update `Cargo.lock`

## Additional Info

NA

## TODO

- [x] Ensure #2398 gets merged succesfully
2021-06-10 01:44:49 +00:00
Paul Hauner
b383836418 Modify Malloc Tuning (#2398)
## Issue Addressed

NA

## Proposed Changes

I've noticed some of the SigP Prater nodes struggling on v1.4.0-rc.0. I suspect this is due to the changes in #2296. Specifically, the trade-off which lowered the memory footprint whilst increasing runtime on some functions.

Presently, this PR is documenting my testing on Prater.

## Additional Info

NA
2021-06-09 02:30:06 +00:00
Paul Hauner
4a6f2fac81 Only perform malloc tuning for beacon node (#2397)
## Issue Addressed

NA

## Proposed Changes

Only run `configure_memory_alllocator` for the BN process.

I noticed that VC memory usage increases significantly with the new malloc tuning parameters. This was also raised by a user on [r/ethstaker](https://www.reddit.com/r/ethstaker/comments/nr8998/lighthouse_prerelease_v140rc0/h0fnt9l?utm_source=share&utm_medium=web2x&context=3).

There wasn't any issue with memory usage by the VC before we implemented #2296, so I think we were a bit overzealous when we allowed these changes to affect it. This PR allows things that weren't broken to remain unfixed.

## Additional Info

NA
2021-06-07 02:34:10 +00:00
Paul Hauner
93100f221f Make less logs for attn with unknown head (#2395)
## Issue Addressed

NA

## Proposed Changes

I am starting to see a lot of slog-async overflows (i.e., too many logs) on Prater whenever we see attestations for an unknown block. Since these logs are identical (except for peer id) and we expose volume/count of these errors via `metrics::GOSSIP_ATTESTATION_ERRORS_PER_TYPE`, I took the following actions to remove them from `DEBUG` logs:

- Push the "Attestation for unknown block" log to trace.
- Add a debug log in `search_for_block`. In effect, this should serve as a de-duped version of the previous, downgraded log.

## Additional Info

TBC
2021-06-07 02:34:09 +00:00
Pawan Dhananjay
502402c6b9 Fix options for --eth1-endpoints flag (#2392)
## Issue Addressed

N/A

## Proposed Changes

Set `config.sync_eth1_chain` to true when using just the  `--eth1-endpoints` flag (without `--eth1`).
2021-06-04 00:10:59 +00:00
Paul Hauner
f6280aa663 v1.4.0-rc.0 (#2379)
## Issue Addressed

NA

## Proposed Changes

Bump versions.

## Additional Info

This is not exactly the v1.4.0 release described in [Lighthouse Update #36](https://lighthouse.sigmaprime.io/update-36.html).

Whilst it contains:

- Beta Windows support
- A reduction in Eth1 queries
- A reduction in memory footprint

It does not contain:

- Altair
- Doppelganger Protection
- The remote signer

We have decided to release some features early. This is primarily due to the desire to allow users to benefit from the memory saving improvements as soon as possible.

## TODO

- [x] Wait for #2340, #2356 and #2376 to merge and then rebase on `unstable`. 
- [x] Ensure discovery issues are fixed (see #2388)
- [x] Ensure https://github.com/sigp/lighthouse/pull/2382 is merged/removed.
- [x] Ensure https://github.com/sigp/lighthouse/pull/2383 is merged/removed.
- [x] Ensure https://github.com/sigp/lighthouse/pull/2384 is merged/removed.
- [ ] Double-check eth1 cache is carried between boots
2021-06-03 00:13:02 +00:00
Paul Hauner
90ea075c62 Revert "Network protocol upgrades (#2345)" (#2388)
## Issue Addressed

NA

## Proposed Changes

Reverts #2345 in the interests of getting v1.4.0 out this week. Once we have released that, we can go back to testing this again.

## Additional Info

NA
2021-06-02 01:07:28 +00:00
Paul Hauner
d34f922c1d Add early check for RPC block relevancy (#2289)
## Issue Addressed

NA

## Proposed Changes

When observing `jemallocator` heap profiles and Grafana, it became clear that Lighthouse is spending significant RAM/CPU on processing blocks from the RPC. On investigation, it seems that we are loading the parent of the block *before* we check to see if the block is already known. This is a big waste of resources.

This PR adds an additional `check_block_relevancy` call as the first thing we do when we try to process a `SignedBeaconBlock` via the RPC (or other similar methods). Ultimately, `check_block_relevancy` will be called again later in the block processing flow. It's a very light function and I don't think trying to optimize it out is worth the risk of a bad block slipping through. 

Also adds a `New RPC block received` info log when we process a new RPC block. This seems like interesting and infrequent info.

## Additional Info

NA
2021-06-02 01:07:27 +00:00
Paul Hauner
bf4e02e2cc Return a specific error for frozen attn states (#2384)
## Issue Addressed

NA

## Proposed Changes

Return a very specific error when at attestation reads shuffling from a frozen `BeaconState`. Previously, this was returning `MissingBeaconState` which indicates a much more serious issue.

## Additional Info

Since `get_inconsistent_state_for_attestation_verification_only` is only called once in `BeaconChain::with_committee_cache`, it is quite easy to reason about the impact of this change.
2021-06-01 06:59:43 +00:00
Paul Hauner
ba9c4c5eea Return more detail in Eth1 HTTP errors (#2383)
## Issue Addressed

NA

## Proposed Changes

Whilst investigating #2372, I [learned](https://github.com/sigp/lighthouse/issues/2372#issuecomment-851725049) that the error message returned from some failed Eth1 requests are always `NotReachable`. This makes debugging quite painful.

This PR adds more detail to these errors. For example:

- Bad infura key: `ERRO Failed to update eth1 cache             error: Failed to update Eth1 service: "All fallback errored: https://mainnet.infura.io/ => EndpointError(RequestFailed(\"Response HTTP status was not 200 OK:  401 Unauthorized.\"))", retry_millis: 60000, service: eth1_rpc`
- Unreachable server: `ERRO Failed to update eth1 cache             error: Failed to update Eth1 service: "All fallback errored: http://127.0.0.1:8545/ => EndpointError(RequestFailed(\"Request failed: reqwest::Error { kind: Request, url: Url { scheme: \\\"http\\\", cannot_be_a_base: false, username: \\\"\\\", password: None, host: Some(Ipv4(127.0.0.1)), port: Some(8545), path: \\\"/\\\", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError(\\\"tcp connect error\\\", Os { code: 111, kind: ConnectionRefused, message: \\\"Connection refused\\\" })) }\"))", retry_millis: 60000, service: eth1_rpc`
- Bad server: `ERRO Failed to update eth1 cache             error: Failed to update Eth1 service: "All fallback errored: http://127.0.0.1:8545/ => EndpointError(RequestFailed(\"Response HTTP status was not 200 OK:  501 Not Implemented.\"))", retry_millis: 60000, service: eth1_rpc`

## Additional Info

NA
2021-06-01 06:59:41 +00:00
Paul Hauner
4c7bb4984c Use the forwards iterator more often (#2376)
## Issue Addressed

NA

## Primary Change

When investigating memory usage, I noticed that retrieving a block from an early slot (e.g., slot 900) would cause a sharp increase in the memory footprint (from 400mb to 800mb+) which seemed to be ever-lasting.

After some investigation, I found that the reverse iteration from the head back to that slot was the likely culprit. To counter this, I've switched the `BeaconChain::block_root_at_slot` to use the forwards iterator, instead of the reverse one.

I also noticed that the networking stack is using `BeaconChain::root_at_slot` to check if a peer is relevant (`check_peer_relevance`). Perhaps the steep, seemingly-random-but-consistent increases in memory usage are caused by the use of this function.

Using the forwards iterator with the HTTP API alleviated the sharp increases in memory usage. It also made the response much faster (before it felt like to took 1-2s, now it feels instant).

## Additional Changes

In the process I also noticed that we have two functions for getting block roots:

- `BeaconChain::block_root_at_slot`: returns `None` for a skip slot.
- `BeaconChain::root_at_slot`: returns the previous root for a skip slot.

I unified these two functions into `block_root_at_slot` and added the `WhenSlotSkipped` enum. Now, the caller must be explicit about the skip-slot behaviour when requesting a root. 

Additionally, I replaced `vec![]` with `Vec::with_capacity` in `store::chunked_vector::range_query`. I stumbled across this whilst debugging and made this modification to see what effect it would have (not much). It seems like a decent change to keep around, but I'm not concerned either way.

Also, `BeaconChain::get_ancestor_block_root` is unused, so I got rid of it 🗑️.

## Additional Info

I haven't also done the same for state roots here. Whilst it's possible and a good idea, it's more work since the fwds iterators are presently block-roots-specific.

Whilst there's a few places a reverse iteration of state roots could be triggered (e.g., attestation production, HTTP API), they're no where near as common as the `check_peer_relevance` call. As such, I think we should get this PR merged first, then come back for the state root iters. I made an issue here https://github.com/sigp/lighthouse/issues/2377.
2021-05-31 04:18:20 +00:00
Kevin Lu
320a683e72 Minimum Outbound-Only Peers Requirement (#2356)
## Issue Addressed

#2325 

## Proposed Changes

This pull request changes the behavior of the Peer Manager by including a minimum outbound-only peers requirement. The peer manager will continue querying for peers if this outbound-only target number hasn't been met. Additionally, when peers are being removed, an outbound-only peer will not be disconnected if doing so brings us below the minimum. 

## Additional Info

Unit test for heartbeat function tests that disconnection behavior is correct. Continual querying for peers if outbound-only hasn't been met is not directly tested, but indirectly through unit testing of the helper function that counts the number of outbound-only peers.

EDIT: Am concerned about the behavior of ```update_peer_scores```. If we have connected to a peer with a score below the disconnection threshold (-20), then its connection status will remain connected, while its score state will change to disconnected. 

```rust
let previous_state = info.score_state();            
// Update scores            
info.score_update();
Self::handle_score_transitions(                
               previous_state,
                peer_id,
                info, 
               &mut to_ban_peers,
               &mut to_unban_peers,
               &mut self.events,
               &self.log,
);
```

```previous_state``` will be set to Disconnected, and then because ```handle_score_transitions``` only changes connection status for a peer if the state changed, the peer remains connected. Then in the heartbeat code, because we only disconnect healthy peers if we have too many peers, these peers don't get disconnected. I'm not sure realistically how often this scenario would occur, but it might be better to adjust the logic to account for scenarios where the score state implies a connection status different from the current connection status. 

Co-authored-by: Kevin Lu <kevlu93@gmail.com>
2021-05-31 04:18:19 +00:00
Mac L
0847986936 Reduce outbound requests to eth1 endpoints (#2340)
## Issue Addressed

#2282 

## Proposed Changes

Reduce the outbound requests made to eth1 endpoints by caching the results from `eth_chainId` and `net_version`.
Further reduce the overall request count by increasing `auto_update_interval_millis` from `7_000` (7 seconds) to `60_000` (1 minute). 
This will result in a reduction from ~2000 requests per hour to 360 requests per hour (during normal operation). A reduction of 82%.

## Additional Info

If an endpoint fails, its state is dropped from the cache and the `eth_chainId` and `net_version` calls will be made for that endpoint again during the regular update cycle (once per minute) until it is back online.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-05-31 04:18:18 +00:00
Age Manning
ec5cceba50 Correct issue with dialing peers (#2375)
The ordering of adding new peers to the peerdb and deciding when to dial them was not considered in a previous update.

This adds the condition that if a peer is not in the peer-db then it is an acceptable peer to dial.

This makes #2374 obsolete.
2021-05-29 07:25:06 +00:00
Age Manning
d12e746b50 Network protocol upgrades (#2345)
This provides a number of upgrades to gossipsub and discovery. 

The updates are extensive and this needs thorough testing.
2021-05-28 22:02:10 +00:00
Paul Hauner
456b313665 Tune GNU malloc (#2299)
## Issue Addressed

NA

## Proposed Changes

Modify the configuration of [GNU malloc](https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html) to reduce memory footprint.

- Set `M_ARENA_MAX` to 4.
    - This reduces memory fragmentation at the cost of contention between threads.
- Set `M_MMAP_THRESHOLD` to 2mb
    - This means that any allocation >= 2mb is allocated via an anonymous mmap, instead of on the heap/arena. This reduces memory fragmentation since we don't need to keep growing the heap to find big contiguous slabs of free memory.
- ~~Run `malloc_trim` every 60 seconds.~~
    - ~~This shaves unused memory from the top of the heap, preventing the heap from constantly growing.~~
    - Removed, see: https://github.com/sigp/lighthouse/pull/2299#issuecomment-825322646

*Note: this only provides memory savings on the Linux (glibc) platform.*
    
## Additional Info

I'm going to close #2288 in favor of this for the following reasons:

- I've managed to get the memory footprint *smaller* here than with jemalloc.
- This PR seems to be less of a dramatic change than bringing in the jemalloc dep.
- The changes in this PR are strictly runtime changes, so we can create CLI flags which disable them completely. Since this change is wide-reaching and complex, it's nice to have an easy "escape hatch" if there are undesired consequences.

## TODO

- [x] Allow configuration via CLI flags
- [x] Test on Mac
- [x] Test on RasPi.
- [x] Determine if GNU malloc is present?
    - I'm not quite sure how to detect for glibc.. This issue suggests we can't really: https://github.com/rust-lang/rust/issues/33244
- [x] Make a clear argument regarding the affect of this on CPU utilization.
- [x] Test with higher `M_ARENA_MAX` values.
- [x] Test with longer trim intervals
- [x] Add some stats about memory savings
- [x] Remove `malloc_trim` calls & code
2021-05-28 05:59:45 +00:00
Pawan Dhananjay
fdaeec631b Monitoring service api (#2251)
## Issue Addressed

N/A

## Proposed Changes

Adds a client side api for collecting system and process metrics and pushing it to a monitoring service.
2021-05-26 05:58:41 +00:00
Age Manning
55aada006f More stringent dialing (#2363)
* More stringent dialing

* Cover cached enr dialing
2021-05-26 14:21:44 +10:00
Michael Sproul
5d9a1bc558 Add Windows to Bors config (#2358)
We accidentally omitted the new Windows tests (#2333) from the Bors config, meaning that PRs will merge before the tests pass. This PR corrects that.
2021-05-20 00:23:08 +00:00
ethDreamer
ba55e140ae Enable Compatibility with Windows (#2333)
## Issue Addressed

Windows incompatibility.

## Proposed Changes

On windows, lighthouse needs to default to STDIN as tty doesn't exist. Also Windows uses ACLs for file permissions. So to mirror chmod 600, we will remove every entry in a file's ACL and add only a single SID that is an alias for the file owner.

Beyond that, there were several changes made to different unit tests because windows has slightly different error messages as well as frustrating nuances around killing a process :/

## Additional Info

Tested on my Windows VM and it appears to work, also compiled & tested on Linux with these changes. Permissions look correct on both platforms now. Just waiting for my validator to activate on Prater so I can test running full validator client on windows.

Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
Co-authored-by: Michael Sproul <micsproul@gmail.com>
2021-05-19 23:05:16 +00:00
Michael Sproul
58e52f8f40 Write validator definitions atomically (#2338)
## Issue Addressed

Closes https://github.com/sigp/lighthouse/issues/2159

## Proposed Changes

Rather than trying to write the validator definitions to disk directly, use a temporary file called `.validator_defintions.yml.tmp` and then atomically rename it to `validator_definitions.yml`. This avoids truncating the primary file, which can cause permanent damage when the disk is full.

The same treatment is also applied to the validator key cache, although the situation is less dire if it becomes corrupted because it can just be deleted without the user having to reimport keys or resupply passwords.

## Additional Info

* `File::create` truncates upon opening: https://doc.rust-lang.org/std/fs/struct.File.html#method.create
* `fs::rename` uses `rename` on UNIX and `MoveFileEx` on Windows: https://doc.rust-lang.org/std/fs/fn.rename.html
* UNIX `rename` call is atomic: https://unix.stackexchange.com/questions/322038/is-mv-atomic-on-my-fs
* Windows `MoveFileEx` is _not_ atomic in general, and Windows lacks any clear API for atomic file renames :(
   https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows

## Further Work

* Consider whether we want to try a different Windows syscall as part of #2333. The `rust-atomicwrites` crate seems promising, but actually uses the same syscall under the hood presently: https://github.com/untitaker/rust-atomicwrites/issues/27.
2021-05-12 02:04:44 +00:00
Mário Havel
480b247828 Add NETWORK_ID variable (#2330)
Same variable BOOTNODE_PORT was used for p2p port of bootnode and testnet Chain and Network ID. Adding variable NETWORK_ID to make scripts less confusing and create option to choose arbitrary ID. 

Co-authored-by: Mário Havel <61149543+taxmeifyoucan@users.noreply.github.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-05-12 00:51:20 +00:00
ethDreamer
cb47388ad7 Updated to comply with new clippy formatting rules (#2336)
## Issue Addressed

The latest version of Rust has new clippy rules & the codebase isn't up to date with them.

## Proposed Changes

Small formatting changes that clippy tells me are functionally equivalent
2021-05-10 00:53:09 +00:00
Mac L
bacc38c3da Add testing for beacon node and validator client CLI flags (#2311)
## Issue Addressed

N/A

## Proposed Changes

Add unit tests for the various CLI flags associated with the beacon node and validator client. These changes require the addition of two new flags: `dump-config` and `immediate-shutdown`.

## Additional Info

Both `dump-config` and `immediate-shutdown` are marked as hidden since they should only be used in testing and other advanced use cases.
**Note:** This requires changing `main.rs` so that the flags can adjust the program behavior as necessary.

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-05-06 00:36:22 +00:00
Mac L
4cc613d644 Add SensitiveUrl to redact user secrets from endpoints (#2326)
## Issue Addressed

#2276 

## Proposed Changes

Add the `SensitiveUrl` struct which wraps `Url` and implements custom `Display` and `Debug` traits to redact user secrets from being logged in eth1 endpoints, beacon node endpoints and metrics.

## Additional Info

This also includes a small rewrite of the eth1 crate to make requests using `Url` instead of `&str`. 
Some error messages have also been changed to remove `Url` data.
2021-05-04 01:59:51 +00:00
realbigsean
2ccb358d87 cargo audit fix (#2331)
## Issue Addressed

`cargo audit` failing due to a vuln in `openssl`

## Proposed Changes

Updates to the `Cargo.lock` made as a result of running `cargo audit fix`

## Additional Info




Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-05-04 00:44:05 +00:00
ethDreamer
0aa8509525 Filter Disconnected Peers from Discv5 DHT (#2219)
## Issue Addressed
#2107

## Proposed Change
The peer manager will mark peers as disconnected in the discv5 DHT when they disconnect or dial fails

## Additional Info
Rationale for this particular change is explained in my comment on #2107
2021-04-28 04:07:37 +00:00
Michael Sproul
0754ba3be7 Fix typos in redundancy docs (#2320)
## Proposed Changes

Fix a long-standing typo in the redundancy docs that uses `lighthouse bn` instead of `lighthouse vc`.
2021-04-25 23:55:59 +00:00
realbigsean
2c2c443718 404's on API requests for slots that have been skipped or orphaned (#2272)
## Issue Addressed

Resolves #2186

## Proposed Changes

404 for any block-related information on a slot that was skipped or orphaned

Affected endpoints:
- `/eth/v1/beacon/blocks/{block_id}`
- `/eth/v1/beacon/blocks/{block_id}/root`
- `/eth/v1/beacon/blocks/{block_id}/attestations`
- `/eth/v1/beacon/headers/{block_id}`

## Additional Info



Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-04-25 03:59:59 +00:00
Pascal Bach
c646d2f7a3 Allow specifying alternative url for deposit_contract (#2295)
## Issue Addressed

None

## Proposed Changes

Adds support for downloading the deposit contract from a different location
by setting the environement variables `LIGHTHOUSE_DEPOSIT_CONTRACT_SPEC_URL`
and `LIGHTHOUSE_DEPOSIT_CONTRACT_TESTNET_URL`.

It also adds support to fetch the content from a local file:// URL.

This allows pre fetching to build in an environment without network access.

## Additional Info

Being able to build without network access is required to package the application for https://nixos.org/. But I imagine it might be useful for other distributions too.
2021-04-16 06:47:34 +00:00
Pawan Dhananjay
2992ca66cd Add a no-wait flag for voluntary exits (#2292)
## Issue Addressed

N/A

## Proposed Changes

Adds a `no-wait` flag to the validator exit command which exits right after publishing the voluntary exit to the beacon chain. It does not wait for confirmation that the exit has been included in the beacon chain. By default, the flag is false.

cc @stefa2k
2021-04-16 05:26:53 +00:00
Cayman
125915e632 Fix readme typo (#2312)
## Proposed Changes

Fix small typo in the readme
2021-04-14 02:30:54 +00:00
Paul Hauner
3a24ca5f14 v1.3.0 (#2310)
## Issue Addressed

NA

## Proposed Changes

Bump versions.

## Additional Info

This is a minor release (not patch) due to the very slight change introduced by #2291.
2021-04-13 22:46:34 +00:00
Michael Sproul
3b901dc5ec Pack attestations into blocks in parallel (#2307)
## Proposed Changes

Use two instances of max cover when packing attestations into blocks: one for the previous epoch, and one for the current epoch. This reduces the amount of computation done by roughly half due to the `O(n^2)` running time of max cover (`2 * (n/2)^2 = n^2/2`). This should help alleviate some load on block proposal, particularly on Prater.
2021-04-13 05:27:42 +00:00
Paul Hauner
c1203f5e52 Add specific log and metric for delayed blocks (#2308)
## Issue Addressed

NA

## Proposed Changes

- Adds a specific log and metric for when a block is enshrined as head with a delay that will caused bad attestations
    - We *technically* already expose this information, but it's a little tricky to determine during debugging. This makes it nice and explicit.
- Fixes a minor reporting bug with the validator monitor where it was expecting agg. attestations too early (at half-slot rather than two-thirds-slot).

## Additional Info

NA
2021-04-13 02:16:59 +00:00
Paul Hauner
0df7be1814 Add check for aggregate target (#2306)
## Issue Addressed
NA

## Proposed Changes

- Ensure that the [target consistency check](b356f52c5c) is always performed on aggregates.
- Add a regression test.

## Additional Info

NA
2021-04-13 00:24:39 +00:00
Age Manning
aaa14073ff Clean up warnings (#2240)
This is a small PR that cleans up compiler warnings. 

The most controversial change is removing the `data_dir` field from the `BeaconChainBuilder`. 

It was removed because it was never read.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
Co-authored-by: Herman Junge <hermanjunge@protonmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-04-12 00:57:43 +00:00
Mac L
f6f64cf0f5 Correcting disable-enr-auto-update flag definition (#2303)
## Issue Addressed

N/A

## Proposed Changes

Correct the `disable-enr-auto-update` boolean flag so that it no longer requires a value.
Previously it would require a value which was never used.

## Additional Info

Flag is read here: https://github.com/sigp/lighthouse/blob/unstable/beacon_node/src/config.rs#L585-L587
2021-04-11 23:52:29 +00:00
Paul Hauner
e7e5878953 Avoid BeaconState clone during metrics scrape (#2298)
## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

Avoids cloning the `BeaconState` each time Prometheus scrapes our metrics (generally every 5s 😱).

I think the original motivation behind this was *"don't hold the lock on the head whilst we do computation on it"*, however I think is flawed since our computation here is so small that it'll be quicker than the clone.

The primary motivation here is to maintain a small memory footprint by holding less in memory (i.e., the cloned `BeaconState`) and to avoid the fragmentation-creep that occurs when cloning the big contiguous slabs of memory in the `BeaconState`.

I also collapsed the active/slashed/withdrawn counters into a single loop to increase efficiency.

## Additional Info

NA
2021-04-07 01:02:56 +00:00
stefa2k
66590d043c Correcting command example validator exit (#2291)
## Issue Addressed

None

## Proposed Changes

Using correct flag in example for exiting validator.

## Additional Info

None
2021-04-03 00:38:51 +00:00
Paul Hauner
52995ab5f5 Use generic BLS object instead of BLST (#2290)
## Issue Addressed

NA

## Proposed Changes

Fixes a compile error when using the `milagro` feature. I can't see any need to use the specific BLST object here. @pawanjay176 can you please confirm?

## Additional Info

NA
2021-04-02 23:34:17 +00:00
Pawan Dhananjay
95a362213d Fix local testnet scripts (#2229)
## Issue Addressed

Resolves #2094 

## Proposed Changes

Fixes scripts for creating local testnets. Adds an option in `lighthouse boot_node` to run with a previously generated enr.
2021-03-30 05:17:58 +00:00
Paul Hauner
9eb1945136 v1.2.2 (#2287)
## Issue Addressed

NA

## Proposed Changes

- Bump versions

## Additional Info

NA
2021-03-30 04:07:03 +00:00
Paul Hauner
3d239b85ac Allow for a clock disparity on the duties endpoints (#2283)
## Issue Addressed

Resolves #2280

## Proposed Changes

Allows for API consumers to call the proposer/attester duties endpoints [`MAXIMUM_GOSSIP_CLOCK_DISPARITY`](b34a79dc0b/beacon_node/beacon_chain/src/beacon_chain.rs (L99-L102)) earlier than the current epoch. For additional reasoning, see https://github.com/sigp/lighthouse/issues/2280#issuecomment-805358897.

## Additional Info

NA
2021-03-29 23:42:35 +00:00
Paul Hauner
03cefd0065 Expand observed attestations capacity (#2266)
## Issue Addressed

NA

## Proposed Changes

I noticed the following error on one of our nodes:

```
Mar 18 00:03:35 ip-xxxx lighthouse-bn[333503]: Mar 18 00:03:35.103 ERRO Unable to validate aggregate            error: ObservedAttestersError(EpochTooLow { epoch: Epoch(23961), lowest_permissible_epoch: Epoch(23962) }), peer_id: 16Uiu2HAm5GL5KzPLhvfg9MBBFSpBqTVGRFSiTg285oezzWcZzwEv
```

The slot during this log was 766,815 (the last slot of the epoch). I believe this is due to an off-by-one error in `observed_attesters` where we were failing to provide enough capacity to store observations from the previous, current and next epochs. See code comments for further reasoning.

Here's a link to the spec: https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#beacon_aggregate_and_proof

## Additional Info

NA
2021-03-29 23:42:34 +00:00
stefa2k
39928d5c69 correcting name of flag (#2285)
## Issue Addressed

None, just a very small fix of documentation

## Proposed Changes

Fixing naming of paramter listed in documentation.

## Additional Info

No changes to code, just fixing documentation
2021-03-29 06:39:15 +00:00
Håvard Anda Estensen
d3d8c22edf Add to docs that macOS binaries are available (#2281)
## Proposed Changes

macOS binaries are available and should be listed in the Lighthouse Book
2021-03-26 04:53:58 +00:00
Michael Sproul
f9d60f5436 VC: accept unknown fields in chain spec (#2277)
## Issue Addressed

Closes #2274

## Proposed Changes

* Modify the `YamlConfig` to collect unknown fields into an `extra_fields` map, instead of failing hard.
* Log a debug message if there are extra fields returned to the VC from one of its BNs.

This restores Lighthouse's compatibility with Teku beacon nodes (and therefore Infura)
2021-03-26 04:53:57 +00:00
Paul Hauner
9a71a7e486 Fix default implementation on FixedVector (#2264)
## Issue Addressed

NA

## Proposed Changes

Whilst hacking on something I noticed that the default implementation of `FixedVector` can violate the length constraint!

E.g., `let v: FixedVector<u8; U4> = <_>::default()` would create a fixed vector with length 0, even though it promises to *always* have length 4! This causes SSZ deserialization to fail and probably other things too.

This isn't a security risk as it can't be triggered externally, however it's a foot gun for LH devs.

## Additional Info

NA
2021-03-22 00:54:09 +00:00
Paul Hauner
d18bba588b Update mailing list link (#2269)
## Issue Addressed

NA

## Proposed Changes

Updates the signup link to point to the new list.

## Additional Info

NA
2021-03-18 07:27:03 +00:00
Paul Hauner
b34a79dc0b v1.2.1 (#2263)
## Issue Addressed

NA

## Proposed Changes

- Bump version.
- Add some new ENR for Prater
    - Afri: https://github.com/eth2-clients/eth2-testnets/pull/42
    - Prysm: https://github.com/eth2-clients/eth2-testnets/pull/43
- Apply the fixes from #2181 to the no-eth1-sim to try fix CI issues. 

## Additional Info

NA
2021-03-18 04:20:46 +00:00
Pawan Dhananjay
aae8bbd130 Fix voluntary exit to work with latest beacon api (#2257)
## Issue Addressed

Also fixes #1932 

## Proposed Changes

Use `ValidatorStatus::ActiveOngoing` instead of `ValidatorStatus::Active` to filter active validators.
Prints extra information regarding successful voluntary exit.
2021-03-17 05:09:58 +00:00
Paul Hauner
015ab7d0a7 Optimize validator duties (#2243)
## Issue Addressed

Closes #2052

## Proposed Changes

- Refactor the attester/proposer duties endpoints in the BN
    - Performance improvements
    - Fixes some potential inconsistencies with the dependent root fields.
    - Removes `http_api::beacon_proposer_cache` and just uses the one on the `BeaconChain` instead.
    - Move the code for the proposer/attester duties endpoints into separate files, for readability.
- Refactor the `DutiesService` in the VC
    - Required to reduce the delay on broadcasting new blocks.
    - Gets rid of the `ValidatorDuty` shim struct that came about when we adopted the standard API.
    - Separate block/attestation duty tasks so that they don't block each other when one is slow.
- In the VC, use `PublicKeyBytes` to represent validators instead of `PublicKey`. `PublicKey` is a legit crypto object whilst `PublicKeyBytes` is just a byte-array, it's much faster to clone/hash `PublicKeyBytes` and this change has had a significant impact on runtimes.
    - Unfortunately this has created lots of dust changes.
 - In the BN, store `PublicKeyBytes` in the `beacon_proposer_cache` and allow access to them. The HTTP API always sends `PublicKeyBytes` over the wire and the conversion from `PublicKey` -> `PublickeyBytes` is non-trivial, especially when queries have 100s/1000s of validators (like Pyrmont).
 - Add the `state_processing::state_advance` mod which dedups a lot of the "apply `n` skip slots to the state" code.
    - This also fixes a bug with some functions which were failing to include a state root as per [this comment](072695284f/consensus/state_processing/src/state_advance.rs (L69-L74)). I couldn't find any instance of this bug that resulted in anything more severe than keying a shuffling cache by the wrong block root.
 - Swap the VC block service to use `mpsc` from `tokio` instead of `futures`. This is consistent with the rest of the code base.
    
~~This PR *reduces* the size of the codebase 🎉~~ It *used* to reduce the size of the code base before I added more comments. 

## Observations on Prymont

- Proposer duties times down from peaks of 450ms to consistent <1ms.
- Current epoch attester duties times down from >1s peaks to a consistent 20-30ms.
- Block production down from +600ms to 100-200ms.

## Additional Info

- ~~Blocked on #2241~~
- ~~Blocked on #2234~~

## TODO

- [x] ~~Refactor this into some smaller PRs?~~ Leaving this as-is for now.
- [x] Address `per_slot_processing` roots.
- [x] Investigate slow next epoch times. Not getting added to cache on block processing?
- [x] Consider [this](072695284f/beacon_node/store/src/hot_cold_store.rs (L811-L812)) in the scenario of replacing the state roots


Co-authored-by: pawan <pawandhananjay@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-03-17 05:09:57 +00:00
realbigsean
6a69b20be1 Validator import password flag (#2228)
## Issue Addressed

#2224

## Proposed Changes

Add a `--password-file` option to the `lighthouse account validator import` command. The flag requires `--reuse-password` and will copy the password over to the `validator_definitions.yml` file. I used #2070 as a guide for validating the password as UTF-8 and stripping newlines.

## Additional Info



Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-03-17 05:09:56 +00:00
Pawan Dhananjay
87825b2bd2 Add prater testnet config (#2260)
## Issue Addressed

Resolves #2258 

## Proposed Changes

Add support for prater testnet.
2021-03-17 00:47:06 +00:00
Kirk Baird
0c52a2ac1d Version bump milagro_bls to v1.4.2 (#2261)
## Issue Addressed

n/a

## Proposed Changes

Bump the version of `milagro_bls` to version v1.4.2

## Additional Info

No breaking changes.
2021-03-16 03:11:51 +00:00
Michael Sproul
3919737978 Release v1.2.0 (#2249)
## Proposed Changes

Release v1.2.0 unchanged from the release candidate.
2021-03-10 01:28:32 +00:00
Michael Sproul
770a2ca030 Fix proposer cache priming upon state advance (#2252)
## Proposed Changes

While investigating an incorrect head + target vote for the epoch boundary block 708544, I noticed that the state advance failed to prime the proposer cache, as per these logs:

```
Mar 09 21:42:47.448 DEBG Subscribing to subnet                   target_slot: 708544, subnet: Y, service: attestation_service
Mar 09 21:49:08.063 DEBG Advanced head state one slot            current_slot: 708543, state_slot: 708544, head_root: 0xaf5e69de09f384ee3b4fb501458b7000c53bb6758a48817894ec3d2b030e3e6f, service: state_advance
Mar 09 21:49:08.063 DEBG Completed state advance                 initial_slot: 708543, advanced_slot: 708544, head_root: 0xaf5e69de09f384ee3b4fb501458b7000c53bb6758a48817894ec3d2b030e3e6f, service: state_advance
Mar 09 21:49:14.787 DEBG Proposer shuffling cache miss           block_slot: 708544, block_root: 0x9b14bf68667ab1d9c35e6fd2c95ff5d609aa9e8cf08e0071988ae4aa00b9f9fe, parent_slot: 708543, parent_root: 0xaf5e69de09f384ee3b4fb501458b7000c53bb6758a48817894ec3d2b030e3e6f, service: beacon
Mar 09 21:49:14.800 DEBG Successfully processed gossip block     root: 0x9b14bf68667ab1d9c35e6fd2c95ff5d609aa9e8cf08e0071988ae4aa00b9f9fe, slot: 708544, graffiti: , service: beacon
Mar 09 21:49:14.800 INFO New block received                      hash: 0x9b14…f9fe, slot: 708544
Mar 09 21:49:14.984 DEBG Head beacon block                       slot: 708544, root: 0x9b14…f9fe, finalized_epoch: 22140, finalized_root: 0x28ec…29a7, justified_epoch: 22141, justified_root: 0x59db…e451, service: beacon
Mar 09 21:49:15.055 INFO Unaggregated attestation                validator: XXXXX, src: api, slot: 708544, epoch: 22142, delay_ms: 53, index: Y, head: 0xaf5e69de09f384ee3b4fb501458b7000c53bb6758a48817894ec3d2b030e3e6f, service: val_mon
Mar 09 21:49:17.001 DEBG Slot timer                              sync_state: Synced, current_slot: 708544, head_slot: 708544, head_block: 0x9b14…f9fe, finalized_epoch: 22140, finalized_root: 0x28ec…29a7, peers: 55, service: slot_notifier
```

The reason for this is that the condition was backwards, so that whole block of code was unreachable.

Looking at the attestations for the block included in the block after, we can see that lots of validators missed it. Some of them may be Lighthouse v1.1.1-v1.2.0-rc.0, but it's probable that they would have missed even with the proposer cache primed, given how late the block 708544 arrived (the cache miss occurred 3.787s after the slot start): https://beaconcha.in/block/708545#attestations
2021-03-10 00:20:50 +00:00
Michael Sproul
786e25ea08 Release candidate v1.2.0-rc.0 (#2248)
Prepare for v1.2.0 with this release candidate.

To be merged after #2247 and #2246

Co-authored-by: Age Manning <Age@AgeManning.com>
2021-03-08 06:27:50 +00:00
Age Manning
babd153352 Prevent adding and dialing bootnodes when discovery is disabled (#2247)
This is a small PR which prevents unwanted bootnodes from being added to the DHT and being dialed when the `--disable-discovery` flag is set. 

The main reason one would want to disable discovery is to connect to a fix set of peers. Currently, regardless of what the user does, Lighthouse will populate its DHT with previously known peers and also fill it with the spec's bootnodes. It will then dial the bootnodes that are capable of being dialed. This prevents testing with a fixed peer list.

This PR prevents these excess nodes from being added and dialed if the user has set `--disable-discovery`.
2021-03-08 06:27:49 +00:00
Michael Sproul
8faab89f09 Update blst to 0.3.3 (#2246)
## Proposed Changes

Supranational just made a `blst` 0.3.3 release, so I figure we may as well include it for v1.2.0
2021-03-08 05:19:30 +00:00
Paul Hauner
e4eb0eb168 Use advanced state for block production (#2241)
## Issue Addressed

NA

## Proposed Changes

- Use the pre-states from #2174 during block production.
    - Running this on Pyrmont shows block production times dropping from ~550ms to ~150ms.
- Create `crit` and `warn` logs when a block is published to the API later than we expect.
    - On mainnet we are issuing a warn if the block is published more than 1s later than the slot start and a crit for more than 3s.
- Rename some methods on the `SnapshotCache` for clarity.
- Add the ability to pass the state root to `BeaconChain::produce_block_on_state` to avoid computing a state root. This is a very common LH optimization.
- Add a metric that tracks how late we broadcast blocks received from the HTTP API. This is *technically* a duplicate of a `ValidatorMonitor` log, but I wanted to have it for the case where we aren't monitoring validators too.
2021-03-04 04:43:31 +00:00
Michael Sproul
363f15f362 Use the database to persist the pubkey cache (#2234)
## Issue Addressed

Closes #1787

## Proposed Changes

* Abstract the `ValidatorPubkeyCache` over a "backing" which is either a file (legacy), or the database.
* Implement a migration from schema v2 to schema v3, whereby the contents of the cache file are copied to the DB, and then the file is deleted. The next release to include this change must be a minor version bump, and we will need to warn users of the inability to downgrade (this is our first DB schema change since mainnet genesis).
* Move the schema migration code from the `store` crate into the `beacon_chain` crate so that it can access the datadir and the `ValidatorPubkeyCache`, etc. It gets injected back into the `store` via a closure (similar to what we do in fork choice).
2021-03-04 01:25:12 +00:00
Michael Sproul
c370100719 Resolve RUSTSEC-2020-0146 (#2242)
## Issue Addressed

Closes #2237

## Proposed Changes

Bump `generic-array` to patched version `0.12.4`
2021-03-04 00:00:51 +00:00
Paul Hauner
b30ff6affc Expose the startup timestamp to Prometheus (#2233)
## Issue Addressed

Resolves #1788

## Proposed Changes

As per #1788, expose the time at which the process started via the `process_start_time_seconds` Prometheus metric. This will help users track uptime.

## Additional Info

NA

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-03-02 22:35:47 +00:00
Pawan Dhananjay
da8791abd7 Set graffiti per validator (#2044)
## Issue Addressed

Resolves #1944 

## Proposed Changes

Adds a "graffiti" key to the `validator_definitions.yml`. Setting the key will override anything passed through the validator `--graffiti` flag. 
Returns an error if the value for the graffiti key is > 32 bytes instead of silently truncating.
2021-03-02 22:35:46 +00:00
Age Manning
1c507c588e Update to the latest libp2p (#2239)
Updates to the latest libp2p and ignores RUSTSEC-2020-0146 from cargo-audit


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-03-02 05:59:49 +00:00
Michael Sproul
5c285f652a Bump spec tests to v1.0.1 (#2235)
## Issue Addressed

Closes #2232
2021-03-02 05:59:48 +00:00
Paul Hauner
cc6f7998fd Improve error when user doesn't have a wallet (#2231)
## Issue Addressed

NA

## Proposed Changes

I was doing some testing and noticed that this error could be a bit nicer. It helps users understand that they need to create a wallet before a validator.

## Additional Info

NA
2021-03-02 05:59:47 +00:00
realbigsean
ed9b245de0 update tokio-stream to 0.1.3 and use BroadcastStream (#2212)
## Issue Addressed

Resolves #2189 

## Proposed Changes

use tokio's `BroadcastStream`

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-03-01 01:58:05 +00:00
Michael Sproul
baef1db40f Lint for sum and product in consensus code (#2226)
## Issue Addressed

Closes #1621

## Proposed Changes

Use the `disallowed_method` lint to ban uses of `Iterator::{sum,product}` from `types` and `state_processing`.

## Additional Info

The lint is turned off in the tree hash caching code, as it is performance sensitive and overflowy arithmetic is already allowed there.
2021-03-01 00:57:13 +00:00
Michael Sproul
0b2ccecbcf Make lighthouse_version compatible with old Git (#2223)
## Proposed Changes

When building the release binaries with Cross, Ubuntu 16.04 is used, which uses an old verison of Git lacking support for `--exclude`. This PR changes `lighthouse_version` to use `--match` instead.
2021-02-24 23:51:05 +00:00
Michael Sproul
afd4786c59 Prune slashing protection DB (#2194)
## Proposed Changes

Prune the slashing protection database so that it doesn't exhibit unbounded growth. Prune by dropping attestations and blocks from more than 512 epochs ago, relying on the guards that prevent signing messages with slots or epochs less than the minimum recorded in the DB.

The pruning process is potentially time consuming, so it's scheduled to run only every 512 epochs, in the last 2/3rds of a slot. This gives it at least 4 seconds to run without impacting other signing, which I think should be sufficient. I've seen it run for several minutes (yikes!) on our Pyrmont nodes, but I suspect that 1) this will only occur on the first run when the database is still huge 2) no other production users will be impacted because they don't have enough validators per node.

Pruning also happens at start-up, as I figured this is a fairly infrequent event, and if a user is experiencing problems with the VC related to pruning, it's nice to be able to trigger it with a quick restart. Users are also conditioned to not mind missing a few attestations during a restart.

We need to include a note in the release notes that users may see the message `timed out waiting for connection` the first time they prune a huge database, but that this is totally fine and to be expected (the VC will miss those attestations in the meantime).

I'm also open to making this opt-in for now, although the sooner we get users doing it, the less painful it will be: prune early, prune often!
2021-02-24 23:51:04 +00:00
Michael Sproul
2f077b11fe Allow HTTP API to return SSZ blocks (#2209)
## Issue Addressed

Implements https://github.com/ethereum/eth2.0-APIs/pull/125

## Proposed Changes

Optionally return SSZ bytes from the `beacon/blocks` endpoint.
2021-02-24 04:15:14 +00:00
realbigsean
5bc93869c8 Update ValidatorStatus to match the v1 API (#2149)
## Issue Addressed

N/A

## Proposed Changes

We are currently a bit off of the standard API spec because we have [this](https://hackmd.io/bQxMDRt1RbS1TLno8K4NPg?view) proposal implemented for validator status.  Based on discussion [here](https://github.com/ethereum/eth2.0-APIs/pull/94), it looks like this won't be added to the spec until v2, so this PR implements [this](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) validator status logic instead

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-02-24 04:15:13 +00:00
Paul Hauner
a764c3b247 Handle early blocks (#2155)
## Issue Addressed

NA

## Problem this PR addresses

There's an issue where Lighthouse is banning a lot of peers due to the following sequence of events:

1. Gossip block 0xabc arrives ~200ms early
    - It is propagated across the network, with respect to [`MAXIMUM_GOSSIP_CLOCK_DISPARITY`](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/p2p-interface.md#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets).
    - However, it is not imported to our database since the block is early.
2. Attestations for 0xabc arrive, but the block was not imported.
    - The peer that sent the attestation is down-voted.
        - Each unknown-block attestation causes a score loss of 1, the peer is banned at -100.
        - When the peer is on an attestation subnet there can be hundreds of attestations, so the peer is banned quickly (before the missed block can be obtained via rpc).

## Potential solutions

I can think of three solutions to this:

1. Wait for attestation-queuing (#635) to arrive and solve this.
    - Easy
    - Not immediate fix.
    - Whilst this would work, I don't think it's a perfect solution for this particular issue, rather (3) is better.
1. Allow importing blocks with a tolerance of `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
    - Easy
    - ~~I have implemented this, for now.~~
1. If a block is verified for gossip propagation (i.e., signature verified) and it's within `MAXIMUM_GOSSIP_CLOCK_DISPARITY`, then queue it to be processed at the start of the appropriate slot.
    - More difficult
    - Feels like the best solution, I will try to implement this.
    
    
**This PR takes approach (3).**

## Changes included

- Implement the `block_delay_queue`, based upon a [`DelayQueue`](https://docs.rs/tokio-util/0.6.3/tokio_util/time/delay_queue/struct.DelayQueue.html) which can store blocks until it's time to import them.
- Add a new `DelayedImportBlock` variant to the `beacon_processor::WorkEvent` enum to handle this new event.
- In the `BeaconProcessor`, refactor a `tokio::select!` to a struct with an explicit `Stream` implementation. I experienced some issues with `tokio::select!` in the block delay queue and I also found it hard to debug. I think this explicit implementation is nicer and functionally equivalent (apart from the fact that `tokio::select!` randomly chooses futures to poll, whereas now we're deterministic).
- Add a testing framework to the `beacon_processor` module that tests this new block delay logic. I also tested a handful of other operations in the beacon processor (attns, slashings, exits) since it was super easy to copy-pasta the code from the `http_api` tester.
    - To implement these tests I added the concept of an optional `work_journal_tx` to the `BeaconProcessor` which will spit out a log of events. I used this in the tests to ensure that things were happening as I expect.
    - The tests are a little racey, but it's hard to avoid that when testing timing-based code. If we see CI failures I can revise. I haven't observed *any* failures due to races on my machine or on CI yet.
    - To assist with testing I allowed for directly setting the time on the `ManualSlotClock`.
- I gave the `beacon_processor::Worker` a `Toolbox` for two reasons; (a) it avoids changing tons of function sigs when you want to pass a new object to the worker and (b) it seemed cute.
2021-02-24 03:08:52 +00:00
Michael Sproul
399d073ab4 Fix lighthouse_version (#2221)
## Proposed Changes

Somehow since Lighthouse v1.1.3 the behaviour of `git-describe` has changed so that it includes the version tag, the number of commits since that tag, _and_ the commit. According to the docs this is how it should always have behaved?? Weird!

https://git-scm.com/docs/git-describe/2.30.1

Anyway, this lead to `lighthouse_version` producing this monstrosity of a version string when building #2194:

```
Lighthouse/v1.1.3-v1.1.3-5-gac07
```

Observe it in the wild here: https://pyrmont.beaconcha.in/block/694880

Adding `--exclude="*"` prevents `git-describe` from trying to include the tag, and on that troublesome commit from #2194 it now produces the correct version string.
2021-02-23 23:31:37 +00:00
Paul Hauner
46920a84e8 v1.1.3 (#2217)
## Issue Addressed

NA

## Proposed Changes

Bump versions

## Additional Info

NA
2021-02-22 06:21:38 +00:00
Paul Hauner
4362ea4f98 Fix false positive "State advance too slow" logs (#2218)
## Issue Addressed

- Resolves #2214

## Proposed Changes

Fix the false positive warning log described in #2214.

## Additional Info

NA
2021-02-21 23:47:53 +00:00
Paul Hauner
8949ae7c4e Address ENR update loop (#2216)
## Issue Addressed

- Resolves #2215

## Proposed Changes

Addresses a potential loop when the majority of peers indicate that we are contactable via an IPv6 address.

See https://github.com/sigp/discv5/pull/62 for further rationale.

## Additional Info

The alternative to this PR is to use `--disable-enr-auto-update` and then manually supply an `--enr-address` and `--enr-upd-port`. However, that requires the user to know their IP addresses in order for discovery to work properly. This might not be practical/achievable for some users, hence this hotfix.
2021-02-21 23:47:52 +00:00
Paul Hauner
8c6537e71d v1.1.2 (#2213)
## Issue Addressed

NA

## Proposed Changes

Bump versions

## Additional Info

NA
2021-02-19 00:49:32 +00:00
Paul Hauner
f8cc82f2b1 Switch back to warp with cors wildcard support (#2211)
## Issue Addressed

- Resolves #2204
- Resolves #2205

## Proposed Changes

Switches to my fork of `warp` which contains support for cors wildcards: https://github.com/paulhauner/warp/tree/cors-wildcard

I have a PR open on the `warp` repo but it hasn't had any interest from the maintainers as of yet: https://github.com/seanmonstar/warp/pull/726. I think running from a fork is the best we can do for now.

## Additional Info

NA
2021-02-18 22:33:12 +00:00
Lion - dapplion
613382f304 Add slot offset computing to be downloaded slot (#2198)
The current implementation assumes the range offset of slots downloaded on a batch to equal zero. This conflicts with the condition to consider this chain as sync. For finalized sync, it results in one extra batch being downloaded which can't be processed.

CC @wemeetagain
2021-02-18 08:24:46 +00:00
Paul Hauner
1ab495738d Fix short sha in github actions (#2210)
## Issue Addressed

NA

## Proposed Changes

Swap to a full sha as per: https://github.com/sigp/lighthouse/runs/1922773402
2021-02-18 06:18:47 +00:00
Paul Hauner
3ffee365e2 Update rustup in release action (#2206)
## Issue Addressed

NA

## Proposed Changes

Ensure the arm64 binary is build with the latest version of Rust.
2021-02-17 23:04:09 +00:00
Paul Hauner
f819ba5414 v1.1.1 (#2202)
## Issue Addressed

NA

## Proposed Changes

Bump versions
2021-02-16 00:09:02 +00:00
Pawan Dhananjay
4a357c9947 Upgrade rand_core (#2201)
## Issue Addressed

N/A

## Proposed Changes

Upgrade `rand_core` to latest version to fix https://rustsec.org/advisories/RUSTSEC-2021-0023
2021-02-15 20:34:49 +00:00
Paul Hauner
88cc222204 Advance state to next slot after importing block (#2174)
## Issue Addressed

NA

## Proposed Changes

Add an optimization to perform `per_slot_processing` from the *leading-edge* of block processing to the *trailing-edge*. Ultimately, this allows us to import the block at slot `n` faster because we used the tail-end of slot `n - 1` to perform `per_slot_processing`.

Additionally, add a "block proposer cache" which allows us to cache the block proposer for some epoch. Since we're now doing trailing-edge `per_slot_processing`, we can prime this cache with the values for the next epoch before those blocks arrive (assuming those blocks don't have some weird forking).

There were several ancillary changes required to achieve this: 

- Remove the `state_root` field  of `BeaconSnapshot`, since there's no need to know it on a `pre_state` and in all other cases we can just read it from `block.state_root()`.
    - This caused some "dust" changes of `snapshot.beacon_state_root` to `snapshot.beacon_state_root()`, where the `BeaconSnapshot::beacon_state_root()` func just reads the state root from the block.
- Rename `types::ShuffingId` to `AttestationShufflingId`. I originally did this because I added a `ProposerShufflingId` struct which turned out to be not so useful. I thought this new name was more descriptive so I kept it.
- Address https://github.com/ethereum/eth2.0-specs/pull/2196
- Add a debug log when we get a block with an unknown parent. There was previously no logging around this case.
- Add a function to `BeaconState` to compute all proposers for an epoch without re-computing the active indices for each slot.

## Additional Info

- ~~Blocked on #2173~~
- ~~Blocked on #2179~~ That PR was wrapped into this PR.
- There's potentially some places where we could avoid computing the proposer indices in `per_block_processing` but I haven't done this here. These would be an optimization beyond the issue at hand (improving block propagation times) and I think this PR is already doing enough. We can come back for that later.

## TODO

- [x] Tidy, improve comments.
- [x] ~~Try avoid computing proposer index in `per_block_processing`?~~
2021-02-15 07:17:52 +00:00
Paul Hauner
3000f3e5da Dht persistence on drop (v2) (#2200)
## Issue Addressed

NA

## Proposed Changes

This is simply #2177 with a merge conflict fixed.

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-02-15 06:09:55 +00:00
Michael Sproul
1a748699d9 Rust 1.50 fixes (#2199)
## Issue Addressed

https://rustsec.org/advisories/RUSTSEC-2021-0021.html

## Proposed Changes

* Bump `nb-connect` to avoid the above advisory (and migrate from the yanked crate)
* Add a `rustup update stable` to the Docker build to fix this issue: https://github.com/sigp/lighthouse/actions/runs/567075245
2021-02-15 06:09:54 +00:00
realbigsean
ff1813e618 Automated release Github upload fix (#2192)
## Issue Addressed

Our v1.1.0 automated release failed to upload to Github. The `hub` command failed with a `403`, which seems like this issue: https://github.com/github/hub/issues/2149
 
## Proposed Changes

The suggested fix in that issue is to set the `$GITHUB_USER` environment variable. I can't really test this because this hasn't been failing on my fork, but seems low risk



Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-02-15 06:09:53 +00:00
Age Manning
9ae92aa256 Update bootnode ENRs (#2191)
Updates the mainnet boot-node ENRs to the current version

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2021-02-15 06:09:52 +00:00
Akihito Nakano
fee24539ac Add a GitHub Action to cancel previous runs (#2180)
## Issue Addressed

It takes over 20 minutes to run the GitHub Workflow for lighthouse. It would be time-saving to cancel previous runs. 

## Proposed Changes

Added [styfle/cancel-workflow-action](https://github.com/styfle/cancel-workflow-action) to our workflow. I've configured the action according to [Advanced settings](https://github.com/styfle/cancel-workflow-action#advanced).
2021-02-15 06:09:51 +00:00
Pawan Dhananjay
6e6e9104f5 Prevent adding duplicate validators to validator_definitions.yml (#2166)
## Issue Addressed

N/A

## Proposed Changes

This is mostly a UX improvement.

Currently, when recursively finding keystores, we only ignore keystores with same path.This leads to potential issues while copying datadirs (e.g. copying datadir to a new ssd with more storage). After copying new datadir and starting the vc, we will  discover the copied keystores as new keystores and add it to the definitions file leading to duplicate entries.

This PR avoids duplicate keystores being discovered as new keystore by checking for duplicate pubkeys as well.
2021-02-15 06:09:51 +00:00
Paul Hauner
8e5c20b6d1 Update for clippy 1.50 (#2193)
## Issue Addressed

NA

## Proposed Changes

Rust 1.50 has landed 🎉

The shiny new `clippy` peers down upon us mere mortals with disgust. Brutish peasants wrapping our `usize`s in superfluous `Option`s... tsk tsk.

I've performed the goat sacrifice and corrected our evil ways in this PR. Tonight we shall pray that Github Actions bestows the almighty green tick upon us.

## Additional Info

NA


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2021-02-15 00:09:12 +00:00
Michael Sproul
e2ff9c66a1 Remove links to old master branch (#2190)
## Proposed Changes

In preparation for deleting the `master` branch, remove all links to it from the book/README.
2021-02-11 06:06:54 +00:00
realbigsean
e20f64b21a Update to tokio 1.1 (#2172)
## Issue Addressed

resolves #2129
resolves #2099 
addresses some of #1712
unblocks #2076
unblocks #2153 

## Proposed Changes

- Updates all the dependencies mentioned in #2129, except for web3. They haven't merged their tokio 1.0 update because they are waiting on some dependencies of their own. Since we only use web3 in tests, I think updating it in a separate issue is fine. If they are able to merge soon though, I can update in this PR. 

- Updates `tokio_util` to 0.6.2 and `bytes` to 1.0.1.

- We haven't made a discv5 release since merging tokio 1.0 updates so I'm using a commit rather than release atm. **Edit:** I think we should merge an update of `tokio_util` to 0.6.2 into discv5 before this release because it has panic fixes in `DelayQueue`  --> PR in discv5:  https://github.com/sigp/discv5/pull/58

## Additional Info

tokio 1.0 changes that required some changes in lighthouse:

- `interval.next().await.is_some()` -> `interval.tick().await`
- `sleep` future is now `!Unpin` -> https://github.com/tokio-rs/tokio/issues/3028
- `try_recv` has been temporarily removed from `mpsc` -> https://github.com/tokio-rs/tokio/issues/3350
- stream features have moved to `tokio-stream` and `broadcast::Receiver::into_stream()` has been temporarily removed -> `https://github.com/tokio-rs/tokio/issues/2870
- I've copied over the `BroadcastStream` wrapper from this PR, but can update to use `tokio-stream` once it's merged https://github.com/tokio-rs/tokio/pull/3384

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-02-10 23:29:49 +00:00
Michael Sproul
6f4da9a5d2 Check that pull requests target unstable (#2187)
Attempt to prevent accidental merges to `stable` due to GitHub's default behaviour of opening PRs against it.

I've intentionally opened this PR against `stable` to test the functionality ;)
2021-02-09 02:00:53 +00:00
Paul Hauner
7c059117f4 Avoid resizing attn signature sets vec (#2184)
## Issue Addressed

NA

## Proposed Changes

Reduces allocations by initializing the `pubkeys` vec to its final size. I doubt this will make a substantial difference, but it's nice to do it this way.

Seeing as `indexed_attestation.attesting_indices` has a [fixed length](e4b62139d7/consensus/types/src/indexed_attestation.rs (L22)), there's no real risk of a memory blow-up by pre-allocating the size of the `Vec`.

## Additional Info

NA
2021-02-09 02:00:51 +00:00
Paul Hauner
194609d210 Ignore vulnerability in hyper (#2188)
## Issue Addressed

NA

## Proposed Changes

Ignores a [hyper vuln](https://rustsec.org/advisories/RUSTSEC-2021-0020) that will be fixed in #2172.

I am comfortable with ignoring this because we have a fix in the works and the impact of the vuln is low to negligible.   

## Additional Info

NA
2021-02-08 23:41:22 +00:00
Paul Hauner
e383ef3e91 Avoid temp allocations with slog (#2183)
## Issue Addressed

Which issue # does this PR address?

## Proposed Changes

Replaces use of `format!` in `slog` logging with it's special no-allocation `?` and `%` shortcuts. According to a `heaptrack` analysis today over about a period of an hour, this will reduce temporary allocations by at least 4%.

## Additional Info

NA
2021-02-04 07:31:47 +00:00
Paul Hauner
ff35fbb121 Add metrics for beacon block propagation (#2173)
## Issue Addressed

NA

## Proposed Changes

Adds some metrics to track delays regarding:

- LH processing of blocks
- delays receiving blocks from other nodes.

## Additional Info

NA
2021-02-04 05:33:56 +00:00
Guillaume Ballet
de193c95d3 fix a couple typos in comments in merkle_hasher (#2171)
Found what I believe to be a couple typos in the comments as I was going through the merkleization code.
2021-02-03 04:52:22 +00:00
Pawan Dhananjay
420c2d28f8 Fix simulator failed runs (#2181)
## Issue Addressed

N/A

## Proposed Changes

Another attempt at fixing simulator issues for `eth1-sim`. 
The `LocalValidatorClient` here blocks till genesis has occurred. 
e4b62139d7/testing/simulator/src/local_network.rs (L145-L150)

Due to this, only the first validator(validator_0) starts before genesis. The remaining 3 vc's in the simulation start only after genesis. This was probably causing issues with missing the duties and eventually the proposal for slot 1.

This PR spawns each `LocalValidatorClient` in it's own tokio task to allow the remaining validators to start before genesis.

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2021-02-01 03:31:12 +00:00
Akihito Nakano
1a22a096c6 Fix clippy errors on tests (#2160)
## Issue Addressed

There are some clippy error on tests.


## Proposed Changes

Enable clippy check on tests and fix the errors. 💪
2021-01-28 23:31:06 +00:00
Paul Hauner
e4b62139d7 v1.1.0 (#2168)
## Issue Addressed

NA

## Proposed Changes

- Bump version
- ~~Run `cargo update`~~

## Additional Info

NA
2021-01-21 02:37:08 +00:00
Paul Hauner
2b2a358522 Detailed validator monitoring (#2151)
## Issue Addressed

- Resolves #2064

## Proposed Changes

Adds a `ValidatorMonitor` struct which provides additional logging and Grafana metrics for specific validators.

Use `lighthouse bn --validator-monitor` to automatically enable monitoring for any validator that hits the [subnet subscription](https://ethereum.github.io/eth2.0-APIs/#/Validator/prepareBeaconCommitteeSubnet) HTTP API endpoint.

Also, use `lighthouse bn --validator-monitor-pubkeys` to supply a list of validators which will always be monitored.

See the new docs included in this PR for more info.

## TODO

- [x] Track validator balance, `slashed` status, etc.
- [x] ~~Register slashings in current epoch, not offense epoch~~
- [ ] Publish Grafana dashboard, update TODO link in docs
- [x] ~~#2130 is merged into this branch, resolve that~~
2021-01-20 19:19:38 +00:00
Paul Hauner
1eb0915301 Fix bug from #2163 (#2165)
## Issue Addressed

NA

## Proposed Changes

Fixes a bug that I missed during a review in #2163. I found this bug by observing that nodes were receiving far less attestations (~1/2 of previous).

I'm not certain on *exactly* how this mistake manifested in a reduction in attestations, but the mistake touches so much code that I think it's reasonable to declare that this it the cause of the observed issue (drop in attestations).

## Additional Info

NA
2021-01-20 10:28:12 +00:00
Paul Hauner
b06559ae97 Disallow attestation production earlier than head (#2130)
## Issue Addressed

The non-finality period on Pyrmont between epochs [`9114`](https://pyrmont.beaconcha.in/epoch/9114) and [`9182`](https://pyrmont.beaconcha.in/epoch/9182) was contributed to by all the `lighthouse_team` validators going down. The nodes saw excessive CPU and RAM usage, resulting in the system to kill the `lighthouse bn` process. The `Restart=on-failure` directive for `systemd` caused the process to bounce in ~10-30m intervals.

Diagnosis with `heaptrack` showed that the `BeaconChain::produce_unaggregated_attestation` function was calling `store::beacon_state::get_full_state` and sometimes resulting in a tree hash cache allocation. These allocations were approximately the size of the hosts physical memory and still allocated when `lighthouse bn` was killed by the OS.

There was no CPU analysis (e.g., `perf`), but the `BeaconChain::produce_unaggregated_attestation` is very CPU-heavy so it is reasonable to assume it is the cause of the excessive CPU usage, too.

## Proposed Changes

`BeaconChain::produce_unaggregated_attestation` has two paths:

1. Fast path: attesting to the head slot or later.
2. Slow path: attesting to a slot earlier than the head block.

Path (2) is the only path that calls `store::beacon_state::get_full_state`, therefore it is the path causing this excessive CPU/RAM usage.

This PR removes the current functionality of path (2) and replaces it with a static error (`BeaconChainError::AttestingPriorToHead`).

This change reduces the generality of `BeaconChain::produce_unaggregated_attestation` (and therefore [`/eth/v1/validator/attestation_data`](https://ethereum.github.io/eth2.0-APIs/#/Validator/produceAttestationData)), but I argue that this functionality is an edge-case and arguably a violation of the [Honest Validator spec](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/validator.md).

It's possible that a validator goes back to a prior slot to "catch up" and submit some missed attestations. This change would prevent such behaviour, returning an error. My concerns with this catch-up behaviour is that it is:

- Not specified as "honest validator" attesting behaviour.
- Is behaviour that is risky for slashing (although, all validator clients *should* have slashing protection and will eventually fail if they do not).
- It disguises clock-sync issues between a BN and VC.

## Additional Info

It's likely feasible to implement path (2) if we implement some sort of caching mechanism. This would be a multi-week task and this PR gets the issue patched in the short term. I haven't created an issue to add path (2), instead I think we should implement it if we get user-demand.
2021-01-20 06:52:37 +00:00
Paul Hauner
d9f940613f Represent slots in secs instead of millisecs (#2163)
## Issue Addressed

NA

## Proposed Changes

Copied from #2083, changes the config milliseconds_per_slot to seconds_per_slot to avoid errors when slot duration is not a multiple of a second. To avoid deserializing old serialized data (with milliseconds instead of seconds) the Serialize and Deserialize derive got removed from the Spec struct (isn't currently used anyway).

This PR replaces #2083 for the purpose of fixing a merge conflict without requiring the input of @blacktemplar.

## Additional Info

NA


Co-authored-by: blacktemplar <blacktemplar@a1.net>
2021-01-19 09:39:51 +00:00
Paul Hauner
46cb6e204c Add lcli command to replace state pubkeys (#1999)
## Issue Addressed

NA

## Proposed Changes

Adds a command to replace all the pubkeys in a state with one generated from a mnemonic.

## Additional Info

This is not production code, it's only for testing.
2021-01-19 08:42:30 +00:00
Paul Hauner
805e152f66 Simplify enum -> str with strum (#2164)
## Issue Addressed

NA

## Proposed Changes

As per #2100, uses derives from the sturm library to implement AsRef<str> and AsStaticRef to easily get str values from enums without creating new Strings. Furthermore unifies all attestation error counter into one IntCounterVec vector.

These works are originally by @blacktemplar, I've just created this PR so I can resolve some merge conflicts.

## Additional Info

NA


Co-authored-by: blacktemplar <blacktemplar@a1.net>
2021-01-19 06:33:58 +00:00
Paul Hauner
8892114f52 Modify proto array loop (#2154)
## Issue Addressed

NA

## Proposed Changes

As discussed with @protolambda, add an additional loop inside proto_array to ensure weights are coherent.

## Additional Info

NA
2021-01-19 03:50:12 +00:00
realbigsean
51f7724c76 Automate docker version tag (#2150)
## Issue Addressed

N/A

## Proposed Changes

On any tag formatted `v*`, a full multi-arch docker build will be kicked off and automatically pushed to docker hub with the version tag.

This is a bit repetitive, because the image built will usually be the same as the image built on pushes to `stable`, but it seems like the simplest way to go about it and this will also work if we incorporate a workflow with `vX.X.X-rc` tags. 

## Additional Info

This may also need to wait for env variable updates: https://github.com/sigp/lighthouse/pull/2135#issuecomment-754977433

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-19 03:50:10 +00:00
Taneli Hukkinen
9cdfa94ba4 Update docs: Change --beacon-node to --beacon-nodes (#2145)
## Issue Addressed

The docs use the deprecated `--beacon-node` flag

## Proposed Changes

Reference the new `--beacon-nodes` flag in docs
2021-01-19 03:50:08 +00:00
Akihito Nakano
3d07934ca0 Fix: end_slot returns incorrect value (#2138)
## Issue Addressed

`Epoch::end_slot()` returns incorrect value when the epoch is the last epoch which can be represented by u64.

```rust
        let slots_per_epoch = 32;

        // The last epoch which can be represented by u64.
        let epoch = Epoch::new(u64::max_value() / slots_per_epoch);

        println!("{}", epoch.end_slot(slots_per_epoch));
       // Slot(18446744073709551614)
       // -> correctly, the result should be `Slot(18446744073709551615)`.
```
2021-01-19 03:50:06 +00:00
Akihito Nakano
a8d040c821 Fix timing issue in obtaining the Fork (#2158)
## Issue Addressed

Related PR: https://github.com/sigp/lighthouse/pull/2137#issuecomment-754712492

The Fork is required for VC to perform signing. Currently, it is not guaranteed that the Fork has been obtained at the point of the signing as the Fork is obtained at after ForkService starts. We will see the [error](851a4dca3c/validator_client/src/validator_store.rs (L127)) if VC could not perform the signing due to the timing issue.

> Unable to get Fork for signing

## Proposed Changes

Obtain the Fork on `init_from_beacon_node` to fix the timing issue.
2021-01-19 02:54:18 +00:00
realbigsean
908c8eadf3 remove protected environment (#2135)
## Issue Addressed

N/A

## Proposed Changes

Remove Github Action environments

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-19 01:29:06 +00:00
realbigsean
7a71977987 Clippy 1.49.0 updates and dht persistence test fix (#2156)
## Issue Addressed

`test_dht_persistence` failing

## Proposed Changes

Bind `NetworkService::start` to an underscore prefixed variable rather than `_`.  `_` was causing it to be dropped immediately

This was failing 5/100 times before this update, but I haven't been able to get it to fail after updating it

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-19 00:34:28 +00:00
Akihito Nakano
e5b1a37110 [simulator] Fix race condition when creating LocalBeaconNode (#2137)
## Issue Addressed

We have a race condition when counting the number of beacon nodes. The user could end up seeing a duplicated service name (node_N).

## Proposed Changes

I have updated to acquire write lock before counting the number of beacon nodes.
2021-01-14 00:04:18 +00:00
Pawan Dhananjay
28238d97b1 Disconnect from peers quicker on internet issues (#2147)
## Issue Addressed

Fixes #2146 

## Proposed Changes

Change ping timeout errors to return `LowToleranceErrors` so that we disconnect faster on internet failures/changes.
2021-01-13 08:09:10 +00:00
realbigsean
14df5d5c32 Use cross in linux x86 64 release flow (#2136)
## Issue Addressed

Resolves  #2120

## Proposed Changes

This updates github actions to use `cross` when compiling linux x86_64 binaries.

## Additional Info

I think we could alternatively be explicit with the version of macOS or ubuntu we are running actions on and that could solve #2120. I'm not sure which method is preferred here though. Github actions supports Ubuntu 16.04

Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-12 06:38:22 +00:00
Paul Hauner
1d535659d6 Add docs about redundancy (#2142)
## Issue Addressed

- Resolves #2140

## Proposed Changes

Adds some documentation on the topic of "redundancy".

## Additional Info

NA
2021-01-12 00:26:22 +00:00
realbigsean
423dea169c update smallvec (#2152)
## Issue Addressed

`cargo audit` is failing because of a potential for an overflow in the version of `smallvec` we're using

## Proposed Changes

Update to the latest version of `smallvec`, which has the fix


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-11 23:32:11 +00:00
Arthur Woimbée
851a4dca3c replace tempdir by tempfile (#2143)
## Issue Addressed

Fixes #2141 
Remove [tempdir](https://docs.rs/tempdir/0.3.7/tempdir/) in favor of [tempfile](https://docs.rs/tempfile/3.1.0/tempfile/).

## Proposed Changes

`tempfile` has a slightly different api that makes creating temp folders with a name prefix a chore (`tempdir::TempDir::new("toto")` => `tempfile::Builder::new().prefix("toto").tempdir()`).

So I removed temp folder name prefix where I deemed it not useful.

Otherwise, the functionality is the same.
2021-01-06 06:36:11 +00:00
Age Manning
7e4b190df0 Reduce ping interval (#2132)
## Issue Addressed

#2123

## Description

Reduces the TCP ping interval to increase our responsiveness to peer liveness changes.
2021-01-06 04:35:52 +00:00
Paul Hauner
c2eac8e5bd Remove duplicate log in BN fallback (#2116)
## Issue Addressed

NA

## Proposed Changes

- Removes a duplicated log in the fallback code for the VC.
- Updates the text in the remaining de-duped log.

## Additional Info

Example

```
Dec 23 05:19:54.003 WARN Beacon node is syncing                  endpoint: http://xxxx:5052/, head_slot: 88224, sync_distance: 161774
Dec 23 05:19:54.003 WARN Beacon node is not synced               endpoint: http://xxxxx:5052/
```
2021-01-06 03:01:48 +00:00
realbigsean
588b90157d Ssz state api endpoint (#2111)
## Issue Addressed

Catching up to a recently merged API spec PR: https://github.com/ethereum/eth2.0-APIs/pull/119

## Proposed Changes

- Return an SSZ beacon state on `/eth/v1/debug/beacon/states/{stateId}` when passed this header: `accept: application/octet-stream`.
- requests to this endpoint with no  `accept` header or an `accept` header and a value of `application/json` or `*/*` , or will result in a JSON response

## Additional Info


Co-authored-by: realbigsean <seananderson33@gmail.com>
2021-01-06 03:01:46 +00:00
Samuel E. Moelius
939fa717fd test_decode_malicious_status_message improvements (#2104)
## Issue Addressed

None

## Proposed Changes

* Correct typo in one comment, elaborate some others.
* Add asserts to ensure comments match code.
* Eliminate one unnecessary `clone`.

## Additional Info

None
2021-01-06 01:10:26 +00:00
Samuel E. Moelius
0245ddd37b Fix typo in ssz_snappy.rs comment (#2103)
## Issue Addressed

None

## Proposed Changes

Correct a typo in `ssz_snappy.rs`.

## Additional Info

Pedantry at it finest.
2021-01-06 01:10:24 +00:00
Paul Hauner
f183af20e3 Version v1.0.6 (#2126)
## Issue Addressed

NA

## Proposed Changes

- Bump versions
- Run `cargo update`

## Additional Info

NA
2020-12-28 23:38:02 +00:00
Pawan Dhananjay
32a60578fe Remove default beacon node value from clap (#2121)
## Issue Addressed

Fixes #2118 

## Proposed Changes

Removes the default value in clap for `--beacon-nodes`. 
This was causing issues with cli picking `--beacon-nodes` default even when not specified and overriding `--beacon-node`.
Seems like it was more evident with docker setups because it doesn't use the default `http://localhost:5052` option.

Edit: we already set the default to `http://localhost:5052` here so this shouldn't break any existing setups.
9ed65a64f8/validator_client/src/config.rs (L58) 

## Additional info
Tested this with docker-compose and binaries. Works as expected in both cases.
2020-12-28 08:23:59 +00:00
Michael Sproul
43ac3f7209 Fix slasher database schema migration to v2 (#2125)
## Issue Addressed

Closes #2119

## Proposed Changes

Update the slasher schema version to v2 for the breaking changes to the config introduced in #2079. Implement a migration from v1 to v2 so that users can seamlessly upgrade from any version of Lighthouse <=1.0.5.

Users who deleted their database for v1.0.5 can upgrade to a release including this patch without any manual intervention. Similarly, any users still on v1.0.4 or earlier can now upgrade without having to drop their database.
2020-12-28 05:09:19 +00:00
Akihito Nakano
78d17c3255 Tweak error messages for ease of investigation (#2122)
## Proposed Changes

<!-- Please list or describe the changes introduced by this PR. -->

Tweaked the error message for ease of investigation as `Failed to update eth1 cache` is used in multiple places. 😃
2020-12-28 01:25:33 +00:00
Paul Hauner
9ed65a64f8 Version v1.0.5 (#2117)
## Issue Addressed

NA

## Proposed Changes

- Bump versions to `v1.0.5`
- Run `cargo update`

## Additional Info

NA
2020-12-23 18:52:48 +00:00
Michael Sproul
c5f03f7d56 Tidy slasher logs for known slashings (#2108)
## Proposed Changes

This quiets the slasher logs when ingesting slashings that are already known. Previously we would log an `ERRO` when a slashing was rediscovered locally but had already been submitted on-chain. This is to be expected from time to time, as different users' slashers will run at different times, and it's likely that slashings will make it on-chain before all users have detected them locally.
2020-12-23 07:53:38 +00:00
Age Manning
2931b05582 Update libp2p (#2101)
This is a little bit of a tip-of-the-iceberg PR. It houses a lot of code changes in the libp2p dependency. 

This needs a bit of thorough testing before merging. 

The primary code changes are:
- General libp2p dependency update
- Gossipsub refactor to shift compression into gossipsub providing performance improvements and improved API for handling compression



Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-12-23 07:53:36 +00:00
realbigsean
b5e81eb6b2 add automated release workflow (#2077)
## Issue Addressed

Resolves #1674 

## Proposed Changes

- Whenever a tag is pushed with the prefix `v` this workflow is triggered
- creates portable and non-portable binaries for linux x86_64, linux aarch64, macOS
  - an attempt at using github actions caching
- signs each binary using GPG
- auto-generates full changelog based on commit messages since the last release
- creates a **draft** release
- hot new formatting (preview [here](https://github.com/realbigsean/lighthouse/releases/tag/v0.9.23))
- has been taking around 35 minutes

## Additional Info

TODOs:
- Figure out how we should automate dockerhub's version tag. 
  - It'd be quickest just to tag `latest`, but we'd need to make sure the docker workflow completes before this starts
- we do the same cross-compile in the `docker` workflow, we could try to use the same binary
- integrate a similar flow for unstable binaries (`-rc` tag?)
- improve caching, potentially use sccache
- if we start using a self-hosted runner this'll require some re-working

Need to add the following secrets to Github: 

- `GPG_PASSPHRASE`
- ~~`GPG_PUBLIC_KEY`~~ hard-coded this, because it was tough manage as a secret
- `GPG_SIGNING_KEY` 


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-23 07:53:34 +00:00
Samuel E. Moelius
3381266998 Eliminate uses of expect in ssz_snappy.rs (#2105)
## Issue Addressed

None

## Proposed Changes

Eliminate three uses of `expect` in `ssz_snappy.rs`.

## Additional Info

None
2020-12-22 02:28:37 +00:00
Pawan Dhananjay
166f617b19 Add docs for /lighthouse/validators/keystore api (#2071)
## Issue Addressed

Resolves #2061 
Resolves #2066 

## Proposed Changes

Document the `/lighthouse/validators/keystore` validator api method. 
The newly generated/imported keystore is always added to the key cache from this function call
65dcdc361b/validator_client/src/validator_store.rs (L105-L109)

which eventually invokes `KeyCache::add` here if enabled
65dcdc361b/validator_client/src/initialized_validators.rs (L192)
2020-12-21 07:43:04 +00:00
Michael Sproul
e5bf2576f1 Optimise tree hash caching for block production (#2106)
## Proposed Changes

`@potuz` on the Eth R&D Discord observed that Lighthouse blocks on Pyrmont were always arriving at other nodes after at least 1 second. Part of this could be due to processing and slow propagation, but metrics also revealed that the Lighthouse nodes were usually taking 400-600ms to even just produce a block before broadcasting it.

I tracked the slowness down to the lack of a pre-built tree hash cache (THC) on the states being used for block production. This was due to using the head state for block production, which lacks a THC in order to keep fork choice fast (cloning a THC takes at least 30ms for 100k validators). This PR modifies block production to clone a state from the snapshot cache rather than the head, which speeds things up by 200-400ms by avoiding the tree hash cache rebuild. In practice this seems to have cut block production time down to 300ms or less. Ideally we could _remove_ the snapshot from the cache (and save the 30ms), but it is required for when we re-process the block after signing it with the validator client.

## Alternatives

I experimented with 2 alternatives to this approach, before deciding on it:

* Alternative 1: ensure the `head` has a tree hash cache. This is too slow, as it imposes a +30ms hit on fork choice, which currently takes ~5ms (with occasional spikes).
* Alternative 2: use `Arc<BeaconSnapshot>` in the snapshot cache and share snapshots between the cache and the `head`. This made fork choice blazing fast (1ms), and block production the same as in this PR, but had a negative impact on block processing which I don't think is worth it. It ended up being necessary to clone the full state from the snapshot cache during block production, imposing the +30ms penalty there _as well_ as in block production.

In contract, the approach in this PR should only impact block production, and it improves it! Yay for pareto improvements 🎉

## Additional Info

This commit (ac59dfa) is currently running on all the Lighthouse Pyrmont nodes, and I've added a dashboard to the Pyrmont grafana instance with the metrics.

In future work we should optimise the attestation packing, which consumes around 30-60ms and is now a substantial contributor to the total.
2020-12-21 06:29:39 +00:00
Paul Hauner
a62dc65ca4 BN Fallback v2 (#2080)
## Issue Addressed

- Resolves #1883

## Proposed Changes

This follows on from @blacktemplar's work in #2018.

- Allows the VC to connect to multiple BN for redundancy.
  - Update the simulator so some nodes always need to rely on their fallback.
- Adds some extra deprecation warnings for `--eth1-endpoint`
- Pass `SignatureBytes` as a reference instead of by value.

## Additional Info

NA

Co-authored-by: blacktemplar <blacktemplar@a1.net>
2020-12-18 09:17:03 +00:00
Pawan Dhananjay
f998eff7ce Subnet discovery fixes (#2095)
## Issue Addressed

N/A

## Proposed Changes

Fixes multiple issues related to discovering of subnet peers.
1. Subnet discovery retries after yielding no results
2. Metadata updates if peer send older metadata
3. peerdb stores the peer subscriptions from gossipsub
2020-12-17 00:39:15 +00:00
realbigsean
ca08fc7831 Revert "add caching to test suite (#2089)" (#2098)
## Issue Addressed

N/A

## Proposed Changes

I didn't realize the `PORTABLE` env variable is only picked up by `install` in the `Makefile` so we are still getting `SIGILL`s:

https://github.com/sigp/lighthouse/runs/1565004525?check_suite_focus=true

## Additional Info



Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-16 23:29:07 +00:00
blacktemplar
3fcc517993 Fix Syncing Simulator (#2049)
## Issue Addressed

NA

## Proposed Changes

Fixes problems with slot times below 1 second which got revealed by running the syncing simulator with the default speedup time.
2020-12-16 05:37:38 +00:00
Michael Sproul
da1c5fe69d Delete uncompressed genesis states (#2092)
## Issue Addressed

Replaces #2091

## Proposed Changes

* Delete the uncompressed genesis states from `eth2_network_config` after they were merged accidentally in #2029.
* Tweak the build script to not overwrite `genesis.ssz` on every build, which caused spurious rebuilds.
2020-12-16 03:44:05 +00:00
realbigsean
80f47fcfff add caching to test suite (#2089)
## Issue Addressed

N/A

## Proposed Changes

Add some caching to the test suite and to the aarch64 cross-compile in the docker build. 

## Additional Info

Cache hits only occur if the Cargo.lock file is unchanged, Github Actions runner OS matches, and the cache is "in scope". Some documentation on github actions cache scoping is here:

https://docs.github.com/en/free-pro-team@latest/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key

I'm not sure how frequently we'll get cache hits, I imagine only on smaller PR's or updates to the same PR.  And there is a cache size limit that we may end up reaching quickly.  But Github actions handles evictions if we go over that limit. 

Not sure how much of an impact this will end up having but I don't really see a downside to trying it out.

Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-16 03:44:03 +00:00
Michael Sproul
0c529b8d52 Add slasher broadcast (#2079)
## Issue Addressed

Closes #2048

## Proposed Changes

* Broadcast slashings when the `--slasher-broadcast` flag is provided.
* In the process of implementing this I refactored the slasher service into its own crate so that it could access the network code without creating a circular dependency. I moved the responsibility for putting slashings into the op pool into the service as well, as it makes sense for it to handle the whole slashing lifecycle.
2020-12-16 03:44:01 +00:00
Pawan Dhananjay
63eeb14a81 Improve eth1 fallback logging (#2096)
## Issue Addressed

N/A

## Proposed Changes

There seemed to be confusion among discord users on the eth1 fallback logging
```
WARN Error connecting to eth1 node. Trying fallback ..., endpoint: http://127.0.0.1:8545/, service: eth1_rpc
```
The assumption users seem to be making here is that it is trying the fallback and fallback=endpoint in the log.

This PR improves the logging to be like
```
WARN Error connecting to eth1 node endpoint, endpoint: http://127.0.0.1:8545/, action: trying fallbacks, service: eth1_rpc
```

I think this is a bit more clear that the endpoint that failed is the one in the log.
2020-12-16 02:39:09 +00:00
divma
11c299cbf6 impl Resource Unavailable RPC error (#2072)
## Issue Addressed

Related to #1891, The error is not in the spec yet (see ethereum/eth2.0-specs#2131)

## Proposed Changes

Implement the proposed error, banning peers that send it

## Additional Info

NA
2020-12-15 00:17:32 +00:00
blacktemplar
701843aaa0 Update dependencies (#2084)
## Issue Addressed

Partially addresses dependencies mentioned in issue #1712.

## Proposed Changes

Updates dependencies (including an update avoiding a vulnerability) + add tokio compatibility to `remote_signer_test`
2020-12-14 02:28:19 +00:00
realbigsean
c1e27f4c89 Improve docker auto builds (#2078)
## Issue Addressed

N/A

## Proposed Changes

- hardcode `ubuntu-18.04` -- I don't think this was causing us issues, but github actions is in the process of migrating `ubuntu-latest` from Ubuntu 18 -> 20.. so just in case
- different source of emulation dependencies -> https://github.com/tonistiigi/binfmt 
  - this one is explicitly referenced in the `buildx` github docs
- install emulation dependencies and run `docker buildx` in the same `run` command
- enable `buildx` with  `DOCKER_CLI_EXPERIMENTAL: enabled` rather than re-building it

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-11 00:19:35 +00:00
Michael Sproul
1abc70e815 Version v1.0.4 (#2073)
## Proposed Changes

Run cargo update and bump version in prep for v1.0.4 release

## Additional Info

Planning to merge this commit to `unstable`, test on Pyrmont and canary nodes, then push to `stable`.
2020-12-10 04:01:40 +00:00
Age Manning
dfb588e521 Softer penalties for missing blocks (#2075)
## Issue Addressed

Users are reporting errors for sending attestations to peers. If the clock sync is a little out or we receive attestations before blocks, peers are being too harshly penalized. They can get scored many times per missing block and we typically need these peers on subnets. 


## Proposed Changes

This removes the penalization for missing blocks with attestations. The penalty should be handled when #635 gets built as it will allow us to group attestations per missing block and penalize once.
2020-12-10 00:40:12 +00:00
realbigsean
adbd49ddc6 Multiarch docker GitHub actions (#2065)
## Issue Addressed

Resolves #1512

## Proposed Changes

- Adds a new docker Github Actions workflow  
- Removes the Dockerhub hook
- Adds a new Dockerfile for use with pre-existing cross-compiled binaries 
- on pushes to `unstable` 
  - builds an ARM64 image and tags it `latest-arm64-unstable`
  - builds an AMD64 image and tags it `latest-amd64-unstable`
  - builds an multiarch image by creating a manifest list referencing the prior two images and tags it `latest-unstable`
- on pushes to `stable` 
  - builds an ARM64 image and tags it `latest-arm64`
  - builds an AMD64 image and tags it `latest-amd64`
  - builds an multiarch image by creating a manifest list referencing the prior two images and tags it `latest`

## Additional Info
- for ARM64, first `cross` is used to cross compile the `lighthouse` and  `lcli` binaries, then `docker buildx` is installed to actually build the docker image for the correct target platform. The image build pretty much just copies the binaries from local into the docker image (thanks @michaelsproul :) )
- The AMD64 and ARM64 builds run in parallel, in total it's been taking around 45mins on a local runner
- This PR does **not** cover version tags on docker images at the moment

Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-09 06:06:37 +00:00
Michael Sproul
aa45fa3ff7 Revert fork choice if disk write fails (#2068)
## Issue Addressed

Closes #2028
Replaces #2059

## Proposed Changes

If writing to the database fails while importing a block, revert fork choice to the last version stored on disk. This prevents fork choice from being ahead of the blocks on disk. Having fork choice ahead is particularly bad if it is later successfully written to disk, because it renders the database corrupt (see #2028).

## Additional Info

* This mitigation might fail if the head+fork choice haven't been persisted yet, which can only happen at first startup (see #2067)
* This relies on it being OK for the head tracker to be ahead of fork choice. I figure this is tolerable because blocks only get added to the head tracker after successfully being written on disk _and_ to fork choice, so even if fork choice reverts a little bit, when the pruning algorithm runs, those blocks will still be on disk and OK to prune. The pruning algorithm also doesn't rely on heads being unique, technically it's OK for multiple blocks from the same linear chain segment to be present in the head tracker. This begs the question of #1785 (i.e. things would be simpler with the head tracker out of the way). Alternatively, this PR could just revert the head tracker as well (I'll look into this tomorrow).
2020-12-09 05:10:34 +00:00
Michael Sproul
82753f842d Improve compile time (#1989)
## Issue Addressed

Closes #1264

## Proposed Changes

* Milagro BLS: tweak the feature flags so that Milagro doesn't get compiled if we're using BLST. Profiling showed that it was consuming about 1 minute of CPU time out of 60 minutes of CPU time (real time ~15 mins). A 1.6% saving.
* Reduce monomorphization: compiling for 3 different `EthSpec` types causes a heck of a lot of generic functions to be instantiated (monomorphized). Removing 2 of 3 cuts the LLVM+linking step from around 250 seconds to 180 seconds, a saving of 70 seconds (real time!). This applies only to `make` and not the CI build, because we test with the minimal spec on CI.
* Update `web3` crate to v0.13. This is perhaps the most controversial change, because it requires axing some deposit contract tools from `lcli`. I suspect these tools weren't used much anyway, and could be maintained separately, but I'm also happy to revert this change. However, it does save us a lot of compile time. With #1839, we now have 3 versions of Tokio (and all of Tokio's deps). This change brings us down to 2 versions, but 1 should be achievable once web3 (and reqwest) move to Tokio 0.3.
* Remove `lcli` from the Docker image. It's a dev tool and can be built from the repo if required.
2020-12-09 01:34:58 +00:00
Age Manning
4f85371ce8 Downgrades a valid log (#2057)
## Issue Addressed

#2046 

## Proposed Changes

The log was originally intended to verify the correct logic and ordering of events when scoring peers. The queued tasks can be structured in such a way that peers can be banned after they are disconnected. Therefore the error log is now downgraded to  debug log.
2020-12-08 10:48:45 +00:00
divma
57489e620f fix default network handling (#2029)
## Issue Addressed
#1992 and #1987, and also to be considered a continuation of #1751

## Proposed Changes
many changed files but most are renaming to align the code with the semantics of `--network` 
- remove the `--network` default value (in clap) and instead set it after checking the `network` and `testnet-dir` flags
- move `eth2_testnet_config` crate to `eth2_network_config`
- move `Eth2TestnetConfig` to `Eth2NetworkConfig`
- move `DEFAULT_HARDCODED_TESTNET` to `DEFAULT_HARDCODED_NETWORK`
- `beacon_node`s `get_eth2_testnet_config` loads the `DEFAULT_HARDCODED_NETWORK` if there is no network nor testnet provided
- `boot_node`s config loads the config same as the `beacon_node`, it was using the configuration only for preconfigured networks (That code is ~1year old so I asume it was not intended)
- removed a one year old comment stating we should try to emulate `https://github.com/eth2-clients/eth2-testnets/tree/master/nimbus/testnet1` it looks outdated (?)
- remove `lighthouse`s `load_testnet_config` in favor of `get_eth2_network_config` to centralize that logic (It had differences)
- some spelling

## Additional Info
Both the command of #1992 and the scripts of #1987 seem to work fine, same as `bn` and `vc`
2020-12-08 05:41:10 +00:00
divma
f3200784b4 More metrics + RPC tweaks (#2041)
## Issue Addressed

NA

## Proposed Changes
This was mostly done to find the reason why LH was dropping peers from Nimbus. It proved to be useful so I think it's worth it. But there is also some functional stuff here
- Add metrics for rpc errors per client, error type and direction
- Add metrics for downscoring events per source type, client and penalty type
- Add metrics for gossip validation results per client for non-accepted messages
- Make the RPC handler return errors and requests/responses in the order we see them
- Allow a small burst for the Ping rate limit, from 1 every 5 seconds to 2 every 10 seconds
- Send rate limiting errors with a particular code and use that same code to identify them. I picked something different to 128 since that is most likely what other clients are using for their own errors
- Remove some unused code in the `PeerAction` and the rpc handler
- Remove the unused variant `RateLimited`. tTis was never produced directly, since the only way to get the request's protocol is via de handler. The handler upon receiving from LH a response with an error (rate limited in this case) emits this event with the missing info (It was always like this, just pointing out that we do downscore rate limiting errors regardless of the change)

Metrics for Nimbus looked like this:
Downscoring events: `increase(libp2p_peer_actions_per_client{client="Nimbus"}[5m])`
![image](https://user-images.githubusercontent.com/26765164/101210880-862bf280-3676-11eb-94c0-399f0bf5aa2e.png)

RPC Errors: `increase(libp2p_rpc_errors_per_client{client="Nimbus"}[5m])`
![image](https://user-images.githubusercontent.com/26765164/101210997-ba071800-3676-11eb-847a-f32405ede002.png)

Unaccepted gossip message: `increase(gossipsub_unaccepted_messages_per_client{client="Nimbus"}[5m])`
![image](https://user-images.githubusercontent.com/26765164/101211124-f470b500-3676-11eb-9459-132ecff058ec.png)
2020-12-08 03:55:50 +00:00
blacktemplar
a28e8decbf update dependencies (#2032)
## Issue Addressed

NA

## Proposed Changes

Updates out of date dependencies.

## Additional Info

See also https://github.com/sigp/lighthouse/issues/1712 for a list of dependencies that are still out of date and the resasons.
2020-12-07 08:20:33 +00:00
realbigsean
9c915349d4 Remove audit ignore ws server (#2051)
## Issue Addressed

Closes #1669

## Proposed Changes

Remove cargo audit ignore for ws server related vuln now that the ws server has been removed

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-06 23:35:51 +00:00
Rémy Roy
0f5f3b522e Fix default values and --network flag in Voluntary exits book page (#2056)
## Issue Addressed

None yet reported.

## Proposed Changes

Fix the old flag in the Voluntary exits book page to use the new `--network` flag. Also fix the default value for that flag.
2020-12-06 22:16:05 +00:00
Michael Sproul
c1ec386d18 Pass failed gossip blocks to the slasher (#2047)
## Issue Addressed

Closes #2042

## Proposed Changes

Pass blocks that fail gossip verification to the slasher. Blocks that are successfully verified are not passed immediately, but will be passed as part of full block verification.
2020-12-04 05:03:30 +00:00
Pawan Dhananjay
7933596c89 Add a purge-eth1-cache cli option (#2039)
## Issue

Some eth1 clients are missing deposit logs on mainnet for multiple reasons (not fully synced, eth1 client issues) because of which we are getting `FailedToInsertDeposit` errors.
Ideally, LH should pick up where it left off after pointing it to a nice eth1 client endpoint (which has all deposits). 

However, I have seen instances where LH keeps getting `FailedToInsertDeposit` even after switching to a good endpoint. Only deleting the beacon directory (which also wipes the eth1 cache) and resyncing the eth1 caches seems to be the solution. This wouldn't be great for mainnet if you have to sync your beacon node again as well.

## Proposed Changes

Add a `--purge-eth1-db` option which just wipes the eth1 cache and doesn't touch the rest of the beacon db. 
Still need to investigate if and why LH isn't picking up where it left off for the deposit logs sync, but I think it would be good to have an option to just delete eth1 caches regardless.
2020-12-04 05:03:28 +00:00
realbigsean
fdfb81a74a Server sent events (#1920)
## Issue Addressed

Resolves #1434 (this is the last major feature in the standard spec. There are only a couple of places we may be off-spec due to recent spec changes or ongoing discussion)
Partly addresses #1669
 
## Proposed Changes

- remove the websocket server
- remove the `TeeEventHandler` and `NullEventHandler` 
- add server sent events according to the eth2 API spec

## Additional Info

This is according to the currently unmerged PR here: https://github.com/ethereum/eth2.0-APIs/pull/117


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-04 00:18:58 +00:00
realbigsean
2b5c0df9e5 Validators endpoint status code (#2040)
## Issue Addressed

Resolves #2035 

## Proposed Changes

Update 405's to 400's for failures when we are parsing path params.

## Additional Info

Haven't updated the same for non-standard endpoints

Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-03 23:10:08 +00:00
Michael Sproul
e06d040b5d Update blst to 0.3.2 (#2034)
## Issue Addressed

Should resolve `blst` build issues that previously required `cargo clean` 🤞

## Proposed Changes

BLST cleaned up some of their validation logic: https://github.com/supranational/blst/compare/v0.3.1...v0.3.2

And included my build system PR: https://github.com/supranational/blst/pull/45
2020-12-03 22:07:16 +00:00
Age Manning
2682f46025 Fingerprint new client identify agent string (#2027)
Nimbus have modified their identify agent string. 

This PR adds their new agent string to identify new nimbus peers.
2020-12-03 22:07:14 +00:00
Michael Sproul
686b605112 Pretty-print EIP-3076 tests (#1977)
## Proposed Changes

* Pretty-print the EIP-3076 tests to match https://github.com/eth2-clients/slashing-protection-interchange-tests/pull/4
* Move the `curl` invocation that downloads the tests to the test executor, removing the build script (closes #1982)
2020-12-03 22:07:12 +00:00
Pawan Dhananjay
e1353088e0 Normalize keystore passwords (#1972)
## Issue Addressed

Resolves #1879 

## Proposed Changes

Do NFKD normalization for keystore passwords.
2020-12-03 22:07:09 +00:00
Pawan Dhananjay
482695142a Minor fixes (#2038)
Fixes a couple of low hanging fruits.

- Fixes #2037 
- `validators-dir` and `secrets-dir` flags don't really need to depend upon each other
- Fixes #2006 and Fixes #1995
2020-12-03 01:10:28 +00:00
blacktemplar
d8cda2d86e Fix new clippy lints (#2036)
## Issue Addressed

NA

## Proposed Changes

Fixes new clippy lints in the whole project (mainly [manual_strip](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip) and [unnecessary_lazy_evaluations](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations)). Furthermore, removes `to_string()` calls on literals when used with the `?`-operator.
2020-12-03 01:10:26 +00:00
realbigsean
d3f0a21436 delete validator-dir path printing in subcommands (#2025)
## Issue Addressed

Resolves #2004

## Proposed Changes

Only print validator dir path once

## Additional Info

N/A

Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-12-01 00:04:48 +00:00
Paul Hauner
b8bd80d2fb Add Content-Type to metrics server (#2019)
## Issue Addressed

- Resolves #2013

## Proposed Changes

Adds the `Content-Type text/plain` header as per #2013

## Additional Info

NA
2020-12-01 00:04:46 +00:00
Conor Svensson
075eecdcb1 Fix broken custom data directories link (#2000)
## Issue Addressed

No issue - its a broken link in the docs.

## Proposed Changes

Fix the broken link.

## Additional Info

N/A in this instance.
2020-12-01 00:04:44 +00:00
Paul Hauner
65dcdc361b Bump version to v1.0.3 (#2024)
## Issue Addressed

NA

## Proposed Changes

- Set version to `v1.0.3`
- Run cargo update

## Additional Info

- ~~Blocked on #2008~~
2020-11-30 22:55:10 +00:00
Age Manning
c718e81eaf Add privacy option (#2016)
Adds a `--privacy` CLI flag to the beacon node that users may opt into. 

This does two things:
- Removes client identifying information from the identify libp2p protocol
- Changes the default graffiti to "" if no graffiti is set.
2020-11-30 22:55:08 +00:00
Paul Hauner
77f3539654 Improve eth1 block sync (#2008)
## Issue Addressed

NA

## Proposed Changes

- Log about eth1 whilst waiting for genesis.
- For the block and deposit caches, update them after each download instead of when *all* downloads are complete.
  - This prevents the case where a single timeout error can cause us to drop *all* previously download blocks/deposits.
- Set `max_log_requests_per_update` to avoid timeouts due to very large log counts in a response.
- Set `max_blocks_per_update` to prevent a single update of the block cache to download an unreasonable number of blocks.
  - This shouldn't have any affect in normal use, it's just a safe-guard against bugs.
- Increase the timeout for eth1 calls from 15s to 60s, as per @pawanjay176's experience with Infura.

## Additional Info

NA
2020-11-30 20:29:17 +00:00
divma
8fcd22992c No string in slog (#2017)
## Issue Addressed

Following slog's documentation, this should help a bit with string allocations. I left it run for two days and mem usage is lower. This is of course anecdotal, but shouldn't harm anyway 

## Proposed Changes

remove `String` creation in logs when possible
2020-11-30 10:33:00 +00:00
Mehdi Zerouali
3f036fd193 Update PGP key in README (#1986)
## Proposed Changes

Update Sigma Prime's PGP key.
2020-11-30 09:28:54 +00:00
Paul Hauner
85e69249e6 Drop discovery log to trace (#2007)
## Issue Addressed

NA

## Proposed Changes

This was causing:

```
Nov 28 21:56:08.154 ERRO slog-async: logger dropped messages due to channel overflow, count: 44, service: libp2p
```

## Additional Info

NA
2020-11-29 03:02:23 +00:00
Age Manning
f7183098ee Bump to version v1.0.2 (#2001)
Update lighthouse to version `v1.0.2`. 

There are two major updates in this version:
- Updates to the task executor to tokio 0.3 and all sub-dependencies relying on core execution, including libp2p
- Update BLST
2020-11-28 13:22:37 +00:00
Justin
cadcc9a76b Fix possible typo in build from source instructions (#1990) 2020-11-28 06:41:34 +00:00
Sean Gulley
9a37f356a9 Update blst to official crate and incorporate subgroup changes (#1979)
## Issue Addressed

Move to latest official version of blst (v0.3.1).  Incorporate all the subgroup check API changes.

## Proposed Changes

Update Cargo.toml to use official blst crate 0.3.1
Modifications to blst.rs wrapper for subgroup check API changes

## Additional Info

The overall subgroup check methodology is public keys should be check for validity using key_validate() at time of first seeing them.  This will check for infinity and in group.  Those keys can then be cached for future usage.  All calls into blst set the pk_validate boolean to false to indicate there is no need for on the fly checking of public keys in the library.  Additionally the public keys are supposed to be validated for proof of possession outside of blst.

For signatures the subgroup check can be done at time of deserialization, prior to being used in aggregation or verification, or in the blst aggregation or verification functions themselves.  In the interface wrapper the call to subgroup_check has been left for one instance, although that could be moved into the 
verify_multiple_aggregate_signatures() call if wanted.  Checking beforehand does save some compute resources in the scenario a bad signature is received.  Elsewhere the subgroup check is being done inside the higher level operations.  See comments in the code.

All checks on signature are done for subgroup only.  There are no checks for infinity.  The rationale is an aggregate signature could technically equal infinity.  If any individual signature was infinity (invalid) then it would fail at time of verification.  A loss of compute resources, although safety would be preserved.
2020-11-28 06:41:32 +00:00
Age Manning
a567f788bd Upgrade to tokio 0.3 (#1839)
## Description

This PR updates Lighthouse to tokio 0.3. It includes a number of dependency updates and some structural changes as to how we create and spawn tasks.

This also brings with it a number of various improvements:

- Discv5 update
- Libp2p update
- Fix for recompilation issues
- Improved UPnP port mapping handling
- Futures dependency update
- Log downgrade to traces for rejecting peers when we've reached our max



Co-authored-by: blacktemplar <blacktemplar@a1.net>
2020-11-28 05:30:57 +00:00
Paul Hauner
5a3b94cbb4 Update to v1.0.1, run cargo update 2020-11-27 21:16:59 +11:00
blacktemplar
38b15deccb Fallback nodes for eth1 access (#1918)
## Issue Addressed

part of  #1883

## Proposed Changes

Adds a new cli argument `--eth1-endpoints` that can be used instead of `--eth1-endpoint` to specify a comma-separated list of endpoints. If the first endpoint returns an error for some request the other endpoints are tried in the given order.

## Additional Info

Currently if the first endpoint fails the fallbacks are used silently (except for `try_fallback_test_endpoint` that is used in `do_update` which logs a `WARN` for each endpoint that is not reachable). A question is if we should add more logs so that the user gets warned if his main endpoint is for example just slow and sometimes hits timeouts.
2020-11-27 08:37:44 +00:00
Michael Sproul
1312844f29 Disable snappy in LevelDB to fix build issues (#1983)
## Proposed Changes

A user on Discord reported build issues when trying to compile Lighthouse checked out to a path with spaces in it. I've fixed the issue upstream in `leveldb-sys` (https://github.com/skade/leveldb-sys/pull/22), but rather than waiting for a new release of the `leveldb` crate, we can also work around the issue by disabling Snappy in LevelDB, which we weren't using anyway.

This may also have the side-effect of slightly improving compilation times, as LevelDB+Snappy was found to be a substantial contributor to build time (although I'm not sure how much was LevelDB and how much was Snappy).
2020-11-27 03:01:57 +00:00
Pawan Dhananjay
0589a14afe Log better error message (#1981)
## Issue Addressed

Fixes #1965 

## Proposed Changes

Log an error and don't update eth1 caches if `chain_id = 0`
2020-11-26 23:13:46 +00:00
Michael Sproul
3486d6a809 Use OS file locks in validator client (#1958)
## Issue Addressed

Closes #1823

## Proposed Changes

* Use OS-level file locking for validator keystores, eliminating problems with lockfiles lingering after ungraceful shutdowns (`SIGKILL`, power outage). I'm using the `fs2` crate because it's cross-platform (unlike `file-lock`), and it seems to have the most downloads on crates.io.
* Deprecate + disable `--delete-lockfiles` CLI param, it's no longer necessary
* Delete the `validator_dir::Manager`, as it was mostly dead code and was only used in the `validator list` command, which has been rewritten to read the validator definitions YAML instead.

## Additional Info

Tested on:

- [x] Linux
- [x] macOS
- [x] Docker Linux
- [x] Docker macOS
- [ ] Windows
2020-11-26 11:25:46 +00:00
divma
fc07cc3fdf Sync metrics (#1975)
## Issue Addressed
- Add metrics to keep track of peer counts by sync type
- Add metric to keep track of the number of syncing chains in range

## Proposed Changes
Plugin to the network metrics update interval and update too the counts for peers wrt to their sync status with us

## Additional Info
For the peer counts
- By the way it is implemented the numbers won't always match to the total peer count in the `libp2p` metric.
- Updating the gauge with every change is messy because it requires to be updated on connection (in the `eth2_libp2p` crate, while metrics are defined in the `network` crate) on Goodbye sent (for an `IrrelevantPeer`) either in the `beacon_processor` or the `peer_manager`, and on disconnection. Since this is not a critical metric I think counting once every second is enough. If you think more accuracy is needed we can do it too, but it would be harder to maintain)

ATM those look like this
![image](https://user-images.githubusercontent.com/26765164/100275387-22137b00-2f60-11eb-93b9-94b0f265240c.png)
2020-11-26 05:23:17 +00:00
Paul Hauner
26741944b1 Add metrics to VC (#1954)
## Issue Addressed

NA

## Proposed Changes

- Adds a HTTP server to the VC which provides Prometheus metrics.
- Moves the health metrics into the `lighthouse_metrics` crate so it can be shared between BN/VC.
- Sprinkle some metrics around the VC.
- Update the book to indicate that we now have VC metrics.
- Shifts the "waiting for genesis" logic later in the `ProductionValidatorClient::new_from_cli`
  - This is worth attention during the review.

## Additional Info

- ~~`clippy` has some new lints that are failing. I'll deal with that in another PR.~~
2020-11-26 01:10:51 +00:00
SjonHortensius
50558e61f7 Fix #1964: remove mainnet warnings which no longer apply (#1970)
## Issue Addressed

#1964

## Proposed Changes

* remove two mainnet warnings
* reword `testnet` in logmessage
* update test
2020-11-25 23:56:21 +00:00
Age Manning
198c4a873d Update ENR construction and mainnet bootnodes (#1968)
## Issue Addressed

Boot nodes were being successfully created and publishing valid ENRs however the `eth2` field was not being saved to disk leading to a discrepancy between published ENR and disk ENR. 

If the `eth2` field is known, it is now constructed in the initial ENR and saved to disk. 

Previous mainnet bootnodes did not contain the `eth2` field and these have also been updated.
2020-11-25 22:48:07 +00:00
realbigsean
7b6a97e73c FAQ/Doc updates (#1966)
## Issue Addressed

N/A

## Proposed Changes

Adding a few FAQ's, updating some formatting


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-25 05:51:10 +00:00
Paul Hauner
7020f5df40 Update docs whenever unstable changes (#1969)
## Issue Addressed

NA

## Proposed Changes

Presently `master` is stable (and will be sunsetted) which means our docs only update after a release. This PR sets the docs to build on the `unstable` branch, which is equivalent to what what we've always had. 

## Additional Info

This does raise the question of whether or not docs should target `stable` or `unstable`, but I'd prefer to maintain current functionality and merge #1966 for now. I think having two versions might be handy, one for stable and one for unstable; I don't imagine this very difficult to achieve.
2020-11-25 03:20:23 +00:00
divma
3b4afc27bf Status race condition (#1967)
## Issue Addressed

Sync stalls due to race conditions between dc notifications and status processing
2020-11-25 02:15:38 +00:00
Paul Hauner
c6baa0eed1 Bump to v1.0.0, run cargo update 2020-11-25 02:02:19 +11:00
Age Manning
a96893744c Update bootnodes and boot_node cli (#1961) 2020-11-25 02:01:37 +11:00
Paul Hauner
11c4968ea0 DO spec check before waiting for genesis (#1962) 2020-11-25 02:00:11 +11:00
Age Manning
b6eff50ffa Add lighthouse boot nodes (#1960) 2020-11-25 00:05:53 +11:00
Paul Hauner
61277e3a72 Add mainnet genesis state (#1959)
* Add mainnet genesis state

* Add compressed, remove uncompressed
2020-11-24 23:21:00 +11:00
Mehdi Zerouali
ead6be074e Remove experimental software warning (#1957)
## Proposed Changes

Remove warning message on startup.
2020-11-24 10:29:41 +00:00
Mehdi Zerouali
011cea93b3 Update security details in README (#1956)
## Proposed Changes

Introduces a few minor changes to the README, mainly updating mentions about security.
2020-11-24 10:29:39 +00:00
Michael Sproul
20339ade01 Refine and test slashing protection semantics (#1885)
## Issue Addressed

Closes #1873

## Proposed Changes

Fixes the bug in slashing protection import (#1873) by pruning the database upon import.

Also expands the test generator to cover this case and a few others which are under discussion here:

https://ethereum-magicians.org/t/eip-3076-validator-client-interchange-format-slashing-protection/4883

## Additional Info

Depending on the outcome of the discussion on Eth Magicians, we can either wait for consensus before merging, or merge our preferred solution and patch things later.
2020-11-24 07:21:14 +00:00
Paul Hauner
84b3387d09 Add Prysm and Teku boot nodes (#1953)
## Issue Addressed

NA

## Proposed Changes

- Adds Prysm and Teku's boot nodes.

The boot ENR were collected from [this Prysm PR](https://github.com/prysmaticlabs/prysm/pull/7925/files#diff-c20494db2dc1354ad056bcacaa192681386854bf036fdeef375dfe57336f27a7R42).

## Additional Info

NA
2020-11-24 06:02:28 +00:00
Paul Hauner
e504645767 Update validator guide for mainnet (#1951)
## Issue Addressed

NA

## Proposed Changes

Updates the validator guide to provide instructions for mainnet users.

## Additional Info

- ~~Blocked on #1751~~
2020-11-24 04:42:17 +00:00
realbigsean
a171fb8843 check if the slashing protection database is locked before creating keys (#1949)
## Issue Addressed

Closes #1790

## Proposed Changes

Make a new method that creates an empty transaction with `TransactionBehavior::Exclusive` to check whether the slashing protection is locked. Call this method before attempting to create or import new validator keystores.  

## Additional Info

N/A


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-24 03:25:40 +00:00
divma
6f890c398e Sync Bug fixes (#1950)
## Issue Addressed

Two issues related to empty batches
- Chain target's was not being advanced when the batch was successful, empty and the chain didn't have an optimistic batch
- Not switching finalized chains. We now switch finalized chains requiring a minimum work first
2020-11-24 02:11:31 +00:00
Paul Hauner
21617aa87f Change --testnet flag to --network (#1751)
## Issue Addressed

- Resolves #1689

## Proposed Changes

TBC

## Additional Info

NA
2020-11-23 23:54:03 +00:00
Michael Sproul
7d644103c6 Tweak slasher DB schema and pruning (#1948)
## Issue Addressed

Resolves #1890

## Proposed Changes

Change the slasher database schema to key indexed attestations by `(target_epoch, indexed_attestation_root)` instead of just `indexed_attestation_root`. This allows more straight-forward pruning (linear scan), that is also "re-entrant". By re-entrant, we mean that a pruning pass that gets stuck because of a `MapFull` error can attempt to commit midway, and be resumed later without issue. The previous pruning strategy for indexed attestations did not have this property. There was also a flaw in the previous pruning that could leave "zombie" indexed attestations in the database (ones not referenced by any attester record), which could build up and contribute to bloat (although in practice I think they occur quite infrequently).

## Additional Info

During testing I noticed that a `MapFull` error can still occur during the commit of the transaction itself, which is irritating, but not unbearable. This PR should at least reduce the frequency with which users need to manually resize their DB, and if the `MapFull` on commit rears its ugly head too often we could use a dynamic strategy (temporarily increase the size of the map until the transaction commits).

The extra bytes for the epoch make the database a bit heavier, so the size estimate docs have been updated to reflect this. This is also a breaking schema change, so anyone using a v0 database from a few hours ago will need to drop it and update 😅
2020-11-23 21:33:51 +00:00
Michael Sproul
5828ff1204 Implement slasher (#1567)
This is an implementation of a slasher that lives inside the BN and can be enabled via `lighthouse bn --slasher`.

Features included in this PR:

- [x] Detection of attester slashing conditions (double votes, surrounds existing, surrounded by existing)
- [x] Integration into Lighthouse's attestation verification flow
- [x] Detection of proposer slashing conditions
- [x] Extraction of attestations from blocks as they are verified
- [x] Compression of chunks
- [x] Configurable history length
- [x] Pruning of old attestations and blocks
- [x] More tests

Future work:

* Focus on a slice of history separate from the most recent N epochs (e.g. epochs `current - K` to `current - M`)
* Run out-of-process
* Ingest attestations from the chain without a resync

Design notes are here https://hackmd.io/@sproul/HJSEklmPL
2020-11-23 03:43:22 +00:00
Paul Hauner
59b2247ab8 Improve UX whilst VC is waiting for genesis (#1915)
## Issue Addressed

- Resolves #1424

## Proposed Changes

Add a `GET lighthouse/staking` that returns 200 if the node is ready to stake (i.e., `--eth1` flag is present) or a 404 otherwise.

Whilst the VC is waiting for the genesis time to start (i.e., when the genesis state is known), check the `lighthouse/staking` endpoint and log an error if the node isn't configured for staking.

## Additional Info

NA
2020-11-23 01:00:22 +00:00
Paul Hauner
65b1cf2af1 Add flag to import all attestations (#1941)
## Issue Addressed

NA

## Proposed Changes

Adds the `--import-all-attestations` flag which tells the `network::AttestationService` to import/aggregate all attestations after verification (instead of only ones for subnets that are relevant to local validators).

This is useful for testing/debugging and also for creating back-up nodes that should be all cached up and ready for any validator.

## Additional Info

NA
2020-11-22 23:58:25 +00:00
divma
d0cbf3111a move sync state to the chains KV (#1940)
## Issue Addressed
we have a log saying we add a peer to a chain, and an another one in case the chain is not syncing. To avoid needing to peer there two (and reduce log entries) simply log the chain's syncing state in the chain's KV
2020-11-22 23:58:23 +00:00
Michael Sproul
426b3001e0 Fix race condition in seen caches (#1937)
## Issue Addressed

Closes #1719

## Proposed Changes

Lift the internal `RwLock`s and `Mutex`es from the `Observed*` data structures to resolve the race conditions described in #1719.

Most of this work was done by @paulhauner on his `lift-locks` branch, I merely updated it for the current `master` and checked over it.

## Additional Info

I think it would be prudent to test this on a testnet or two before mainnet launch, just to be sure that the extra lock contention doesn't negatively impact performance.
2020-11-22 23:02:51 +00:00
Paul Hauner
0b556c4405 Fix metrics http server error messages (#1946)
## Issue Addressed

- Resolves #1945

## Proposed Changes

- As per #1945, fix a log message from the metrics server that was falsely claiming to be from the api server.
- Ensure successful api request logs are published to debug, not trace. This is something I've wanted to do for a while.

## Additional Info

NA
2020-11-22 03:39:13 +00:00
Paul Hauner
48f73b21e6 Expand eth1 block cache, add more logs (#1938)
## Issue Addressed

NA

## Proposed Changes

- Caches later blocks than is required by `ETH1_FOLLOW_DISTANCE`.
- Adds logging to `warn` if the eth1 cache is insufficiently primed.
- Use `max_by_key` instead of `max_by` in `BeaconChain::Eth1Chain` since it's simpler.
- Rename `voting_period_start_timestamp` to `voting_target_timestamp` for accuracy.

## Additional Info

The reason for eating into the `ETH1_FOLLOW_DISTANCE` and caching blocks that are closer to the head is due to possibility for `SECONDS_PER_ETH1_BLOCK` to be incorrect (as is the case for the Pyrmont testnet on Goerli).

If `SECONDS_PER_ETH1_BLOCK` is too short, we'll skip back too far from the head and skip over blocks that would be valid [`is_candidate_block`](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/validator.md#eth1-data) blocks. This was the case on the Pyrmont testnet and resulted in Lighthouse choosing blocks that were about 30 minutes older than is ideal.
2020-11-21 00:26:15 +00:00
Kirk Baird
3b405f10ea Ensure deposit signatures do not use aggregate functions (#1935)
## Issue Addressed

Resolves #1333 

## Proposed Changes

- Remove `deposit_signature_set()` function
- Prevent deposits from being in `SignatureSets`
- User `Signature.verify()` to verify deposit signatures rather than a signature set which uses `fast_aggregate_verify()`

## Additional Info

n/a
2020-11-20 03:37:20 +00:00
divma
d727e55abe Move some rpc processing to the beacon_processor (#1936)
## Issue Addressed
`BlocksByRange` requests were the main culprit of a series of timeouts to peer's requests in general because they produce build up in the router's processor. Those were moved to the blocking executor but a task is being spawned for each; also not ideal since the amount of resources we give to those is not controlled

## Proposed Changes
- Move `BlocksByRange` and `BlocksByRoots` to the `beacon_processor`. The processor crafts the responses and sends them.
- Move too the processing of `StatusMessage`s from other peers. This is a fast operation but it can also build up and won't scale if we keep it in the router (processing one at the time). These don't need to send an answer, so there is no harm in processing them "later" if that were to happen. Sending responses to status requests is still in the router, so we answer as soon as we see them.
- Some "extras" that are basically clean up:
  - Split the `Worker` logic in sync methods (chain processing and rpc blocks), gossip methods (the majority of methods) and rpc methods (the new ones)
  - Move the `status_message` function previously provided by the router's processor to a more central place since it is used by the router, sync, network_context and beacon_processor
 - Some spelling

## Additional Info
What's left to decide/test more thoroughly is the length of the queues and the priority rules. @paulhauner suggested at some point to put status above attestations, and @AgeManning had described an importance of "protecting gossipsub" so my solution is leaving status requests in the router and RPC methods below attestations. Slashings and Exits are at the end.
2020-11-19 23:33:44 +00:00
Pawan Dhananjay
e47739047d Add additional libp2p tests (#1867)
## Issue Addressed

N/A

## Proposed Changes

Adds tests for the eth2_libp2p crate.
2020-11-19 22:32:09 +00:00
Michael Sproul
37369c6a56 Document system requirements (#1934)
## Proposed Changes

Document some minimal and recommended system specs for running Lighthouse on mainnet with a modest number of validators.
2020-11-19 21:23:56 +00:00
Kirk Baird
c5e97b9bf7 Add validation to kdf parameters (#1930)
## Issue Addressed

Closes #1906 
Closes #1907 

## Proposed Changes

- Emits warnings when the KDF parameters are two low.
- Returns errors when the KDF parameters are high enough to pose a potential DoS threat.
- Validates AES IV length is 128 bits, errors if empty, warnings otherwise.

## Additional Info

NIST advice used for PBKDF2 ranges https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf. 
Scrypt ranges are based on the maximum value of the `u32` (i.e 4GB of memory)

The minimum range has been set to anything below the default fields.
2020-11-19 08:52:51 +00:00
Herman Junge
1a530e5a93 [Remote signer] Add signer consumer lib (#1763)
Adds a library `common/remote_signer_consumer`
2020-11-19 04:04:52 +00:00
Kirk Baird
3db9072fee Reject invalid utf-8 characters during encryption (#1928)
## Issue Addressed

Closes #1889 

## Proposed Changes

- Error when passwords which use invalid UTF-8 characters during encryption. 
- Add some tests

## Additional Info

I've decided to error when bad characters are used to create/encrypt a keystore but think we should allow them during decryption since either the keystore was created
-  with invalid UTF-8 characters (possibly by another client or someone whose password is random bytes) in which case we'd want them to be able to decrypt their keystore using the right key.
-  without invalid characters then the password checksum would almost certainly fail.

Happy to add them to decryption if we want to make the decryption more trigger happy 😋 , it would only be a one line change and would tell the user which character index is causing the issue.

See https://eips.ethereum.org/EIPS/eip-2335#password-requirements
2020-11-19 00:37:43 +00:00
realbigsean
79fd9b32b9 Update pool/attestations and committees endpoints (#1899)
## Issue Addressed

Catching up on a few eth2 spec updates:

## Proposed Changes

- adding query params to the `GET pool/attestations` endpoint
- allowing the `POST pool/attestations` endpoint to accept an array of attestations
    - batching attestation submission
- moving `epoch` from a path param to a query param in the `committees` endpoint

## Additional Info


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-18 23:31:39 +00:00
blacktemplar
3408de8151 Avoid string initialization in network metrics and replace by &str where possible (#1898)
## Issue Addressed

NA

## Proposed Changes

Removes most of the temporary string initializations in network metrics and replaces them by directly using `&str`. This further improves on PR https://github.com/sigp/lighthouse/pull/1895.

For the subnet id handling the current approach uses a build script to create a static map. This has the disadvantage that the build script hardcodes the number of subnets. If we want to use more than 64 subnets we need to adjust this in the build script.

## Additional Info

We still have some string initializations for the enum `PeerKind`. To also replace that by `&str` I created a PR in the libp2p dependency: https://github.com/sigp/rust-libp2p/pull/91. Either we wait with merging until this dependency PR is merged (and all conflicts with the newest libp2p version are resolved) or we just merge as is and I will create another PR when the dependency is ready.
2020-11-18 23:31:37 +00:00
Paul Hauner
bcc7f6b143 Add new flag to set blocks per eth1 query (#1931)
## Issue Addressed

NA

## Proposed Changes

Users on Discord (and @protolambda) have experienced this error (or variants of it):

```
Failed to update eth1 cache: GetDepositLogsFailed("Eth1 node returned error: {\"code\":-32005,\"message\":\"query returned more than 10000 results\"}")
```

This PR allows users to reduce the span of blocks searched for deposit logs and therefore reduce the size of the return result. Hopefully experimentation with this flag can lead to finding a better default value.


## Additional Info

NA
2020-11-18 22:18:59 +00:00
Herman Junge
0c2c2cef93 Add lighthouse bootnodes (#1929)
Gotta pump those github profile green squares!
2020-11-18 07:07:45 +00:00
Paul Hauner
7e4ee58729 Bump to v0.3.5 (#1927)
## Issue Addressed

NA

## Proposed Changes

- Bump version to `v0.3.5`
- Run `cargo update`

## Additional Info

NA
2020-11-18 00:44:28 +00:00
Paul Hauner
103103e72e Address queue congestion in migrator (#1923)
## Issue Addressed

*Should* address #1917

## Proposed Changes

Stops the `BackgroupMigrator` rx channel from backing up with big `BeaconState` messages.

Looking at some logs from my Medalla node, we can see a discrepancy between the head finalized epoch and the migrator finalized epoch:

```
Nov 17 16:50:21.606 DEBG Head beacon block                       slot: 129214, root: 0xbc7a…0b99, finalized_epoch: 4033, finalized_root: 0xf930…6562, justified_epoch: 4035, justified_root: 0x206b…9321, service: beacon
Nov 17 16:50:21.626 DEBG Batch processed                         service: sync, processed_blocks: 43, last_block_slot: 129214, chain: 8274002112260436595, first_block_slot: 129153, batch_epoch: 4036
Nov 17 16:50:21.626 DEBG Chain advanced                          processing_target: 4036, new_start: 4036, previous_start: 4034, chain: 8274002112260436595, service: sync
Nov 17 16:50:22.162 DEBG Completed batch received                awaiting_batches: 5, blocks: 47, epoch: 4048, chain: 8274002112260436595, service: sync
Nov 17 16:50:22.162 DEBG Requesting batch                        start_slot: 129601, end_slot: 129664, downloaded: 0, processed: 0, state: Downloading(16Uiu2HAmG3C3t1McaseReECjAF694tjVVjkDoneZEbxNhWm1nZaT, 0 blocks, 1273), epoch: 4050, chain: 8274002112260436595, service: sync
Nov 17 16:50:22.654 DEBG Database compaction complete            service: beacon
Nov 17 16:50:22.655 INFO Starting database pruning               new_finalized_epoch: 2193, old_finalized_epoch: 2192, service: beacon
```

I believe this indicates that the migrator rx has a backed-up queue of `MigrationNotification` items which each contain a `BeaconState`.

## TODO

- [x] Remove finalized state requirement for op-pool
2020-11-17 23:11:26 +00:00
Michael Sproul
a60ab4eff2 Refine compaction (#1916)
## Proposed Changes

In an attempt to fix OOM issues and database consistency issues observed by some users after the introduction of compaction in v0.3.4, this PR makes the following changes:

* Run compaction less often: roughly every 1024 epochs, including after long periods of non-finality. I think the division check proposed by Paul is pretty solid, and ensures we don't miss any events where we should be compacting. LevelDB lacks an easy way to check the size of the DB, which would be another good trigger.
* Make it possible to disable the compaction on finalization using `--auto-compact-db=false`
* Make it possible to trigger a manual, single-threaded foreground compaction on start-up using `--compact-db`
* Downgrade the pruning log to `DEBUG`, as it's particularly noisy during sync

I would like to ship these changes to affected users ASAP, and will document them further in the Advanced Database section of the book if they prove effective.
2020-11-17 09:10:53 +00:00
Paul Hauner
ecff8807a5 Avoid some allocations in BlockSignatureVerifier (#1922)
## Issue Addressed

NA

## Proposed Changes

Avoids growing/allocating some `Vec`s.

## Additional Info

NA
2020-11-17 06:31:01 +00:00
Paul Hauner
5114aee5cf Avoid allocations on VariableList (#1921)
## Issue Addressed

NA

## Proposed Changes

Avoids lots of grow allocations when decoding a `VariableList` of fixed-length items. This is the function used for decoding the `state.validators` list.

## Additional Info

NA
2020-11-17 04:28:40 +00:00
divma
398919b5d4 router: drop requests from peers that have dc'd (#1919)
## Issue Addressed

A peer might send a lot of requests that comply to the rate limit and the disconnect, this humongous pr makes sure we don't process them if the peer is not connected
2020-11-17 02:06:21 +00:00
Pawan Dhananjay
280334b1b0 Validate eth1 chain id (#1877)
## Issue Addressed

Resolves #1815 

## Proposed Changes

Adds extra validation for eth1 chain id apart from the existing check for eth1 network id.
2020-11-16 23:10:42 +00:00
Łukasz Sroka
4d732a1f1d Added fn to count unicode characters (#1903)
## Issue Addressed

Password length check too short (https://github.com/sigp/lighthouse/issues/1880)

## Proposed Changes

I've added function that counts number of unicode characters, instead of calling String::len()


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-11-16 09:30:34 +00:00
Age Manning
49c4630045 Performance improvement for db reads (#1909)
This PR adds a number of improvements:
- Downgrade a warning log when we ignore blocks for gossipsub processing
- Revert a a correction to improve logging of peer score changes
- Shift syncing DB reads off the core-executor allowing parallel processing of large sync messages
- Correct the timeout logic of RPC chunk sends, giving more time before timing out RPC outbound messages.
2020-11-16 07:28:30 +00:00
Paul Hauner
646c049df2 Add link to Lighthouse mailing list (#1913)
## Issue Addressed

Resolves #1851

## Proposed Changes

Adds a link to the Lighthouse mailing list.

## Additional Info

NA
2020-11-16 06:28:11 +00:00
Paul Hauner
836eaf559b Check whistle-blower index (#1911)
## Issue Addressed

- Resolves #1910

## Proposed Changes

See #1910

## Additional Info

NA
2020-11-16 06:28:09 +00:00
Paul Hauner
fe71f25c3a Add Pyrmont testnet (#1904)
## Issue Addressed

NA

## Proposed Changes

- Replace Zinken with Pyrmont (Zinken has been sun-setted).
- Ensure Mainnet is build in the build script.

## Additional Info

NA
2020-11-16 05:11:35 +00:00
divma
eb56140582 Update logs + do not downscore peers if WE time out (#1901)
## Issue Addressed

- RPC Errors were being logged twice: first in the peer manager and then again in the router, so leave just the peer manager's one 
- The "reduce peer count" warn message gets thrown to the user for every missed chunk, so instead print it when the request times out and also do not include there info that is not relevant to the user
- The processor didn't have the service tag so add it
- Impl `KV` for status message
- Do not downscore peers if we are the ones that timed out

Other small improvements
2020-11-16 04:06:14 +00:00
realbigsean
6a7d221f72 add slot validation to attestation_data endpoint (#1888)
## Issue Addressed

Resolves #1801

## Proposed Changes

Verify queries to `attestation_data` are for no later than `current_slot + 1`. If they are later than this, return a 400.


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-16 02:59:35 +00:00
divma
8a16548715 Misc Peer sync info adjustments (#1896)
## Issue Addressed
#1856 

## Proposed Changes
- For clarity, the router's processor now only decides if a peer is compatible and it disconnects it or sends it to sync accordingly. No logic here regarding how useful is the peer. 
- Update peer_sync_info's rules
- Add an `IrrelevantPeer` sync status to account for incompatible peers (maybe this should be "IncompatiblePeer" now that I think about it?) this state is update upon receiving an internal goodbye in the peer manager
- Misc code cleanups
- Reduce the need to create `StatusMessage`s (and thus, `Arc` accesses )
- Add missing calls to update the global sync state

The overall effect should be:
- More peers recognized as Behind, and less as Unknown
- Peers identified as incompatible
2020-11-13 09:00:10 +00:00
Michael Sproul
46a06069c6 Release v0.3.4 (#1894)
## Proposed Changes

Bump version to v0.3.4 and update dependencies with `cargo update`.


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-11-13 06:06:35 +00:00
Age Manning
c00e6c2c6f Small network adjustments (#1884)
## Issue Addressed

- Asymmetric pings - Currently with symmetric ping intervals, lighthouse nodes race each other to ping often ending in simultaneous ping connections. This shifts the ping interval to be asymmetric based on inbound/outbound connections
- Correct inbound/outbound peer-db registering - It appears we were accounting inbound as outbound and vice versa in the peerdb, this has been corrected
- Improved logging

There is likely more to come - I'll leave this open as we investigate further testnets
2020-11-13 06:06:33 +00:00
Paul Hauner
8772c02fa0 Reduce temp allocations in network metrics (#1895)
## Issue Addressed

Using `heaptrack` I could see that ~75% of Lighthouse temporary allocations are caused by temporary string allocations here.

## Proposed Changes

Reduces temporary `String` allocations when updating metrics in the `network` crate. The solution isn't perfect since we rebuild our caches with each call, but it's a significant improvement.

## Additional Info

NA
2020-11-13 04:19:38 +00:00
blacktemplar
c7ac967d5a handle peer state transitions on gossipsub score changes + refactoring (#1892)
## Issue Addressed

NA

## Proposed Changes

Correctly handles peer state transitions on gossipsub changes + refactors handling of peer state transitions into one function used for lighthouse score changes and gossipsub score changes.


Co-authored-by: Age Manning <Age@AgeManning.com>
2020-11-13 03:15:03 +00:00
realbigsean
cb26c15eb6 Peer endpoint updates (#1893)
## Issue Addressed

N/A

## Proposed Changes

- rename `address` -> `last_seen_p2p_address`
- state and direction filters for `peers` endpoint
- metadata count addition to `peers` endpoint
- add `peer_count` endpoint


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-13 02:02:41 +00:00
blacktemplar
fcb4893f72 do subnet discoveries until we have MESH_N_LOW many peers (#1886)
## Issue Addressed

NA

## Proposed Changes

Increases the target peers for a subnet, so that subnet queries are executed until we have at least the minimum required peers for a mesh (`MESH_N_LOW`). We keep the limit of `6` target peers for aggregated subnet discovery queries, therefore the size (and the time needed) for a query doesn't change.
2020-11-13 00:56:05 +00:00
Michael Sproul
11076912d9 Update EF tests to 1.0.0 (#1875)
## Proposed Changes

Bump the EF tests from `1.0.0-rc.0` to `1.0.0`

## Additional Info

Builds on #1862
2020-11-12 23:52:38 +00:00
blacktemplar
7404f1ce54 Gossipsub scoring (#1668)
## Issue Addressed

#1606 

## Proposed Changes

Uses dynamic gossipsub scoring parameters depending on the number of active validators as specified in https://gist.github.com/blacktemplar/5c1862cb3f0e32a1a7fb0b25e79e6e2c.

## Additional Info

Although the parameters got tested on Medalla, extensive testing using simulations on larger networks is still to be done and we expect that we need to change the parameters, although this might only affect constants within the dynamic parameter framework.
2020-11-12 01:48:28 +00:00
realbigsean
f0c9339153 Update tiny-bip39 dependency (#1887)
## Issue Addressed

Resolves #1704

## Proposed Changes

Update tiny-bip39 from using the sigp fork to the newly released v0.8.0 in the upstream.



Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-11-12 00:46:33 +00:00
Paul Hauner
9ee71d6fec Add toledo support (#1874)
## Issue Addressed

NA

## Proposed Changes

Adds support for the [Toledo](https://github.com/protolambda/toledo) dev-net.

```bash
lighthouse --testnet toledo bn --http
```

This is for development only, we do not recommend users to join this testnet.

## Additional Info

- ~~Blocked on #1862~~
2020-11-10 22:42:59 +00:00
Age Manning
5ed4c1daca Update vc testnet script (#1859)
Updates the local testnet VC scripts to match current functionality.
2020-11-10 02:36:14 +00:00
realbigsean
f8da151b0b Standard beacon api updates (#1831)
## Issue Addressed

Resolves #1809 
Resolves #1824
Resolves #1818
Resolves #1828 (hopefully)

## Proposed Changes

- add `validator_index` to the proposer duties endpoint
- add the ability to query for historical proposer duties
- `StateId` deserialization now fails with a 400 warp rejection
- add the `validator_balances` endpoint
- update the `aggregate_and_proofs` endpoint to accept an array
- updates the attester duties endpoint from a `GET` to a `POST`
- reduces the number of times we query for proposer duties from once per slot per validator to only once per slot 


Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-11-09 23:13:56 +00:00
Michael Sproul
556190ff46 Compact database on finalization (#1871)
## Issue Addressed

Closes #1866

## Proposed Changes

* Compact the database on finalization. This removes the deleted states from disk completely. Because it happens in the background migrator, it doesn't block other database operations while it runs. On my Medalla node it took about 1 minute and shrank the database from 90GB to 9GB.
* Fix an inefficiency in the pruning algorithm where it would always use the genesis checkpoint as the `old_finalized_checkpoint` when running for the first time after start-up. This would result in loading lots of states one-at-a-time back to genesis, and storing a lot of block roots in memory. The new code stores the old finalized checkpoint on disk and only uses genesis if no checkpoint is already stored. This makes it both backwards compatible _and_ forwards compatible -- no schema change required!
* Introduce two new `INFO` logs to indicate when pruning has started and completed. Users seem to want to know this information without enabling debug logs!
2020-11-09 07:02:21 +00:00
blacktemplar
b711cfe2bb Improve validator key cache lock handling (#1837)
## Issue Addressed

NA

## Proposed Changes

Improves the deletion of the validator key cache lock file in case of program interrupts.

## Additional Info

This should reduce cases where a lock file doesn't get removed on shutdown and reduce complaints on Discord. This will be superseded by issue #1823.
2020-11-09 06:03:35 +00:00
Paul Hauner
2f9999752e Add --testnet mainnet and start HTTP server before genesis (#1862)
## Issue Addressed

NA

## Proposed Changes

- Adds support for `--testnet mainnet`
- Start HTTP server prior to genesis

## Additional Info

**Note: This is an incomplete work-in-progress. Use Lighthouse for mainnet at your own risk.**

With this PR, you can check the deposits:

```bash
lighthouse --testnet mainnet bn --http
```
```bash
curl localhost:5052/lighthouse/eth1/deposit_cache | jq
```

```json
{
  "data": [
    {
      "deposit_data": {
        "pubkey": "0x854980aa9bf2e84723e1fa6ef682e3537257984cc9cb1daea2ce6b268084b414f0bb43206e9fa6fd7a202357d6eb2b0d",
        "withdrawal_credentials": "0x00cacf703c658b802d55baa2a5c1777500ef5051fc084330d2761bcb6ab6182b",
        "amount": "32000000000",
        "signature": "0xace226cdfd9da6b1d827c3a6ab93f91f53e8e090eb6ca5ee7c7c5fe3acc75558240ca9291684a2a7af5cac67f0558d1109cc95309f5cdf8c125185ec9dcd22635f900d791316924aed7c40cff2ffccdac0d44cf496853db678c8c53745b3545b"
      },
      "block_number": 3492981,
      "index": 0,
      "signature_is_valid": true
    },
    {
      "deposit_data": {
        "pubkey": "0x93da03a71bc4ed163c2f91c8a54ea3ba2461383dd615388fd494670f8ce571b46e698fc8d04b49e4a8ffe653f581806b",
        "withdrawal_credentials": "0x006ebfbb7c8269a78018c8b810492979561d0404d74ba9c234650baa7524dcc4",
        "amount": "32000000000",
        "signature": "0x8d1f4a1683f798a76effcc6e2cdb8c3eed5a79123d201c5ecd4ab91f768a03c30885455b8a952aeec3c02110457f97ae0a60724187b6d4129d7c352f2e1ac19b4210daacd892fe4629ad3260ce2911dceae3890b04ed28267b2d8cb831f6a92d"
      },
      "block_number": 3493427,
      "index": 1,
      "signature_is_valid": true
    },
```
2020-11-09 05:04:03 +00:00
Michael Sproul
b3fc48e887 Update slashing protection interchange to v5 (#1816)
## Proposed Changes

Update the slashing protection interchange format to v5 in preparation for finalisation as part of an EIP.

Also, add some more tests and update the commit hash for https://github.com/eth2-clients/slashing-protection-interchange-tests to include the new generated tests.
2020-11-09 05:04:01 +00:00
divma
b0e9e3dcef Seen addresses store port (#1841)
## Issue Addressed
#1764
2020-11-09 04:01:03 +00:00
Geoffry Song
63fe5542e7 Remove mention of OpenSSL from documentation (#1844)
If I'm not mistaken, openssl is no longer a dependency of lighthouse, so it can no longer cause build issues.
2020-11-09 02:31:31 +00:00
Marius Kjærstad
3574bad6cd Changed http:// to https:// on some links (#1869)
Changed http:// to https:// on some links in README.md
2020-11-09 01:28:39 +00:00
Herman Junge
78744cd07a Update remote signer README (#1870)
Forgot to update the executable. Also fix to the roadmap.
2020-11-07 03:06:17 +00:00
Herman Junge
492ce07ed3 Update README.md (#1868)
Just one line of doc
2020-11-06 17:23:43 +00:00
Herman Junge
e004b98eab [Remote signer] Fold signer into Lighthouse repository (#1852)
The remote signer relies on the `types` and `crypto/bls` crates from Lighthouse. Moreover, a number of tests of the remote signer consumption of LH leverages this very signer, making any important update a potential dependency nightmare.

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-11-06 06:17:11 +00:00
Age Manning
e2ae5010a6 Update libp2p (#1865)
Updates libp2p to the latest version. 

This adds tokio 0.3 support and brings back yamux support. 

This also updates some discv5 configuration parameters for leaner discovery queries
2020-11-06 04:14:14 +00:00
Herman Junge
4c4dad9fb5 Fix fn documentation 2020-11-05 17:53:35 +00:00
Paul Hauner
157e31027a Add warnings for deposits (#1858)
## Issue Addressed

NA

## Proposed Changes

Add some warnings to discourage users to user Lighthouse for mainnet.

## Additional Info

NA
2020-11-04 19:46:42 +00:00
blacktemplar
7e7fad5734 Ignore RPC messages of disconnected peers and remove old peers based on disconnection time (#1854)
## Issue Addressed

NA

## Proposed Changes

Lets the networking behavior ignore messages of peers that are not connected. Furthermore, old peers are not removed from the peerdb based on score anymore but based on the disconnection time.
2020-11-03 23:43:10 +00:00
Age Manning
0a0f4daf9d Prevent errors for stream termination race (#1853)
Prevents an error being propagated on a race condition for RPC stream termination
2020-11-03 10:37:00 +00:00
Paul Hauner
0cde4e285c Bump version to v0.3.3 (#1850)
## Issue Addressed

NA

## Proposed Changes

- Update versions
- Run `cargo update`

## Additional Info

- Blocked on #1846
2020-11-02 23:55:15 +00:00
Michael Sproul
2ff5828310 Downgrade ADX check to a warning (#1846)
## Issue Addressed

Closes #1842

## Proposed Changes

Due to the lies told to us by VPS providers about what CPU features they support, we are forced to check for the availability of CPU features like ADX by just _running code and seeing if it crashes_. The prominent warning should hopefully help users who have truly incompatible CPUs work out what is going on, while not burdening users of cheap VPSs.
2020-11-02 22:35:37 +00:00
Pawan Dhananjay
863ee7c9f2 Update to discv5 bootnodes (#1849)
## Issue Addressed

We seem to have roll backed to old discv5 bootnodes with #1799 because of which fresh nodes with no cached peers cannot find any peers.

## Proposed Changes

Updates `boot_enr.yaml` to discv5.1 bootnodes.
2020-11-02 21:29:43 +00:00
Paul Hauner
7afbaa807e Return eth1-related data via the API (#1797)
## Issue Addressed

- Related to #1691

## Proposed Changes

Adds the following API endpoints:

- `GET lighthouse/eth1/syncing`: status about how synced we are with Eth1.
- `GET lighthouse/eth1/block_cache`: all locally cached eth1 blocks.
- `GET lighthouse/eth1/deposit_cache`: all locally cached eth1 deposits.

Additionally:

- Moves some types from the `beacon_node/eth1` to the `common/eth2` crate, so they can be used in the API without duplication.
- Allow `update_deposit_cache` and `update_block_cache` to take an optional head block number to avoid duplicate requests.

## Additional Info

TBC
2020-11-02 00:37:30 +00:00
divma
6c0c050fbb Tweak head syncing (#1845)
## Issue Addressed

Fixes head syncing

## Proposed Changes

- Get back to statusing peers after removing chain segments and making the peer manager deal with status according to the Sync status, preventing an old known deadlock
- Also a bug where a chain would get removed if the optimistic batch succeeds being empty

## Additional Info

Tested on Medalla and looking good
2020-11-01 23:37:39 +00:00
realbigsean
304793a6ab add quoted serialization util for FixedVector and VariableList (#1794)
## Issue Addressed

This comment: https://github.com/sigp/lighthouse/issues/1776#issuecomment-712349841

## Proposed Changes

- Add quoted serde utils for `FixedVector` and `VariableList`
- Had to remove the dependency that `ssz_types` has on `serde_utils` to avoid a circular dependency.

## Additional Info


Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-10-29 23:25:21 +00:00
Pawan Dhananjay
56f9394141 Add cli option for voluntary exits (#1781)
## Issue Addressed

Resolve #1652 

## Proposed Changes

Adds a cli option for voluntary exits. The flow is similar to prysm's where after entering the password for the validator keystore (or load password from `secrets` if present) the user is given multiple warnings about the operation being irreversible, then redirected to the docs webpage(not added yet) which explains what a voluntary exit is and the consequences of exiting and then prompted to enter a phrase from the docs webpage as a final confirmation. 

Example usage
```
$ lighthouse --testnet zinken account validator exit --validator <validator-pubkey> --beacon-node http://localhost:5052

Running account manager for zinken testnet                                                                                                          
validator-dir path: "..."

Enter the keystore password:  for validator in ...

Password is correct

Publishing a voluntary exit for validator: ...              
WARNING: This is an irreversible operation                                                                                                                    
WARNING: Withdrawing staked eth will not be possible until Eth1/Eth2 merge Please visit [website] to make sure you understand the implications of a voluntary exit.            
                                                                                                                                             
Enter the phrase from the above URL to confirm the voluntary exit:
Exit my validator
Published voluntary exit for validator ...
```

## Additional info

Not sure if we should have batch exits (`--validator all`) option for exiting all the validators in the `validators` directory. I'm slightly leaning towards having only single exits but don't have a strong preference.
2020-10-29 23:25:19 +00:00
Paul Hauner
f64f8246db Only run http_api tests in release (#1827)
## Issue Addressed

NA

## Proposed Changes

As raised by @hermanjunge in a DM, the `http_api` tests have been observed taking 100+ minutes on debug. This PR:

- Moves the `http_api` tests to only run in release.
- Groups some `http_api` tests to reduce test-setup overhead.

## Additional Info

NA
2020-10-29 22:25:20 +00:00
realbigsean
ae0f025375 Beacon state validator id filter (#1803)
## Issue Addressed

Michael's comment here: https://github.com/sigp/lighthouse/issues/1434#issuecomment-708834079
Resolves #1808

## Proposed Changes

- Add query param `id` and `status` to the `validators` endpoint
- Add string serialization and deserialization for `ValidatorStatus`
- Drop `Epoch` from `ValidatorStatus` variants

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2020-10-29 05:13:04 +00:00
divma
9f45ac2f5e More sync edge cases + prettify range (#1834)
## Issue Addressed
Sync edge case when we get an empty optimistic batch that passes validation and is inside the download buffer. Eventually the chain would reach the batch and treat it as an ugly state. 

## Proposed Changes
- Handle the edge case advancing the chain's target + code clarification
- Some largey changes for readability + ergonomics since rust has try ops
- Better handling of bad batch and chain states
2020-10-29 02:29:24 +00:00
blacktemplar
2bd5b9182f fix unbanning of peers (#1838)
## Issue Addressed

NA

## Proposed Changes

Currently a banned peer will remain banned indefinitely as long as update is called on the score struct regularly. This fixes this bug and the score decay starts after `BANNED_BEFORE_DECAY` seconds after banning.
2020-10-29 01:25:02 +00:00
Michael Sproul
36bd4d87f0 Update to spec v1.0.0-rc.0 and BLSv4 (#1765)
## Issue Addressed

Closes #1504 
Closes #1505
Replaces #1703
Closes #1707

## Proposed Changes

* Update BLST and Milagro to versions compatible with BLSv4 spec
* Update Lighthouse to spec v1.0.0-rc.0, and update EF test vectors
* Use the v1.0.0 constants for `MainnetEthSpec`.
* Rename `InteropEthSpec` -> `V012LegacyEthSpec`
    * Change all constants to suit the mainnet `v0.12.3` specification (i.e., Medalla).
* Deprecate the `--spec` flag for the `lighthouse` binary
    * This value is now obtained from the `config_name` field of the `YamlConfig`.
        * Built in testnet YAML files have been updated.
    * Ignore the `--spec` value, if supplied, log a warning that it will be deprecated
    * `lcli` still has the spec flag, that's fine because it's dev tooling.
* Remove the `E: EthSpec` from `YamlConfig`
    * This means we need to deser the genesis `BeaconState` on-demand, but this is fine.
* Swap the old "minimal", "mainnet" strings over to the new `EthSpecId` enum.
* Always require a `CONFIG_NAME` field in `YamlConfig` (it used to have a default).

## Additional Info

Lots of breaking changes, do not merge! ~~We will likely need a Lighthouse v0.4.0 branch, and possibly a long-term v0.3.0 branch to keep Medalla alive~~.

Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-10-28 22:19:38 +00:00
divma
ad846ad280 Inform peers of requests that exceed the maximum rate limit + log downgrade (#1830)
## Issue Addressed

#1825 

## Proposed Changes

Since we penalize more blocks by range requests that have large steps, it is possible to get requests that will never be processed. We were not informing peers about this requests and also logging CRIT that is no longer relevant. Later we should check if more sophisticated handling for those requests is needed
2020-10-27 11:46:38 +00:00
Paul Hauner
92c8eba8ca Ensure eth1 deposit/chain IDs are used from YamlConfig (#1829)
## Issue Addressed

 NA

## Proposed Changes

Fixes a bug which causes the node to reject valid eth1 nodes.

- Fix core bug: failure to apply `YamlConfig` values to `ChainSpec`.
- Add a test to prevent regression in this specific case.
- Fix an invalid log message

## Additional Info

NA
2020-10-26 03:34:14 +00:00
Paul Hauner
f157d61cc7 Address clippy lints, panic in ssz_derive on overflow (#1714)
## Issue Addressed

NA

## Proposed Changes

- Panic or return error if we overflow `usize` in SSZ decoding/encoding derive macros.
  - I claim that the panics can only be triggered by a faulty type definition in lighthouse, they cannot be triggered externally on a validly defined struct.
- Use `Ordering` instead of some `if` statements, as demanded by clippy.
- Remove some old clippy `allow` that seem to no longer be required.
- Add comments to interesting clippy statements that we're going to continue to ignore.
- Create #1713

## Additional Info

NA
2020-10-25 23:27:39 +00:00
Paul Hauner
eba51f0973 Update testnet configs, change on-disk format (#1799)
## Issue Addressed

- Related to #1691

## Proposed Changes

- Add `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID` to `config.yaml`.
    - Pass the `DEPOSIT_NETWORK_ID` to the `eth1::Service`.
- Remove the unused `MAX_EPOCHS_PER_CROSSLINK` from the `altona` and `medalla` configs (see [spec commit](2befe90032 (diff-efb845ac2ebd4aafbc23df40f47ce25699255064e99d36d0406d0a14ca7953ec))).
- Change from compressing the whole testnet directory, to only compressing the genesis state file. This is the only file we need to compress and *not* compressing the others makes them work nicely with git.
    - We can modify the boot nodes, configs, etc. without incurring an eternal binary-blob cost on our git history.
    - This change is backwards compatible (i.e., non-breaking).

## Additional Info

NA
2020-10-25 22:15:46 +00:00
Age Manning
7453f39d68 Prevent unbanning of disconnected peers (#1822)
## Issue Addressed

Further testing revealed another edge case where we attempt to unban a peer that can be in a disconnected start. Although this causes no real issue, it does log an error to the user. 

This PR adds a check to prevent this edge case and prevents the error being logged to the user.
2020-10-24 05:24:20 +00:00
Age Manning
a3cc1a1e0f Call unban only when necessary (#1821)
This PR prevents a user-facing error. 

It prevents optimistically unbanning a peer and instead checks the state of the peer before requesting the peers state to be unbanned.
2020-10-24 03:24:19 +00:00
blacktemplar
1644289a08 Updates the libp2p to the second newest commit => Allow only one topic per message (#1819)
As @AgeManning mentioned the newest libp2p version had some problems and got downgraded again on lighthouse master. This is an intermediate version that makes no problems and only adds a small change of allowing only one topic per message.
2020-10-24 01:05:37 +00:00
Age Manning
7870b81ade Downgrade libp2p (#1817)
## Description

This downgrades the recent libp2p upgrade. 

There were issues with the RPC which prevented syncing of the chain and this upgrade needs to be further investigated.
2020-10-23 09:33:59 +00:00
Paul Hauner
fa2daa7d6c Update readme, add banner (#1814)
## Issue Addressed

NA

## Proposed Changes

- Update progress timeline
- Remove the qualification that the eth2 spec is "emerging".
- Remove the terminal animation, replace with new banner.

## Additional Info

NA
2020-10-23 04:16:38 +00:00
Age Manning
55eee18ebb Version bump to 0.3.1 (#1813)
## Description

Bumps Lighthouse to version 0.3.1.
2020-10-23 04:16:36 +00:00
Age Manning
64c5899d25 Adds colour help to bn and vc subcommands (#1811)
Adds coloured help to the bn and vc subcommands
2020-10-23 04:16:34 +00:00
Age Manning
2c7f362908 Discovery v5.1 (#1786)
## Overview 

This updates lighthouse to discovery v5.1

Note: This makes lighthouse's discovery not compatible with any previous version. Lighthouse cannot discover peers or send/receive ENR's from any previous version. This is a breaking change. 

This resolves #1605
2020-10-23 04:16:33 +00:00
Age Manning
ae96dab5d2 Increase UPnP logging and decrease batch sizes (#1812)
## Description

This increases the logging of the underlying UPnP tasks to inform the user of UPnP error/success. 

This also decreases the batch syncing size to two epochs per batch.
2020-10-23 03:01:33 +00:00
Age Manning
c49dd94e20 Update to latest libp2p (#1810)
## Description

Updates to the latest libp2p and includes gossipsub updates. 

Of particular note is the limitation of a single topic per gossipsub message.

Co-authored-by: blacktemplar <blacktemplar@a1.net>
2020-10-23 03:01:31 +00:00
Michael Sproul
acd49d988d Implement database temp states to reduce memory usage (#1798)
## Issue Addressed

Closes #800
Closes #1713

## Proposed Changes

Implement the temporary state storage algorithm described in #800. Specifically:

* Add `DBColumn::BeaconStateTemporary`, for storing 0-length temporary marker values.
* Store intermediate states immediately as they are created, marked temporary. Delete the temporary flag if the block is processed successfully.
* Add a garbage collection process to delete leftover temporary states on start-up.
* Bump the database schema version to 2 so that a DB with temporary states can't accidentally be used with older versions of the software. The auto-migration is a no-op, but puts in place some infra that we can use for future migrations (e.g. #1784)

## Additional Info

There are two known race conditions, one potentially causing permanent faults (hopefully rare), and the other insignificant.

### Race 1: Permanent state marked temporary

EDIT: this has been fixed by the addition of a lock around the relevant critical section

There are 2 threads that are trying to store 2 different blocks that share some intermediate states (e.g. they both skip some slots from the current head). Consider this sequence of events:

1. Thread 1 checks if state `s` already exists, and seeing that it doesn't, prepares an atomic commit of `(s, s_temporary_flag)`.
2. Thread 2 does the same, but also gets as far as committing the state txn, finishing the processing of its block, and _deleting_ the temporary flag.
3. Thread 1 is (finally) scheduled again, and marks `s` as temporary with its transaction.
4.
    a) The process is killed, or thread 1's block fails verification and the temp flag is not deleted. This is a permanent failure! Any attempt to load state `s` will fail... hope it isn't on the main chain! Alternatively (4b) happens...
    b) Thread 1 finishes, and re-deletes the temporary flag. In this case the failure is transient, state `s` will disappear temporarily, but will come back once thread 1 finishes running.

I _hope_ that steps 1-3 only happen very rarely, and 4a even more rarely. It's hard to know

This once again begs the question of why we're using LevelDB (#483), when it clearly doesn't care about atomicity! A ham-fisted fix would be to wrap the hot and cold DBs in locks, which would bring us closer to how other DBs handle read-write transactions. E.g. [LMDB only allows one R/W transaction at a time](https://docs.rs/lmdb/0.8.0/lmdb/struct.Environment.html#method.begin_rw_txn).

### Race 2: Temporary state returned from `get_state`

I don't think this race really matters, but in `load_hot_state`, if another thread stores a state between when we call `load_state_temporary_flag` and when we call `load_hot_state_summary`, then we could end up returning that state even though it's only a temporary state. I can't think of any case where this would be relevant, and I suspect if it did come up, it would be safe/recoverable (having data is safer than _not_ having data).

This could be fixed by using a LevelDB read snapshot, but that would require substantial changes to how we read all our values, so I don't think it's worth it right now.
2020-10-23 01:27:51 +00:00
Age Manning
66f0cf4430 Improve peer handling (#1796)
## Issue Addressed

Potentially resolves #1647 and sync stalls. 

## Proposed Changes

The handling of the state of banned peers was inadequate for the complex peerdb data structure. We store a limited number of disconnected and banned peers in the db. We were not tracking intermediate "disconnecting" states and the in some circumstances we were updating the peer state without informing the peerdb. This lead to a number of inconsistencies in the peer state. 

Further, the peer manager could ban a peer changing a peer's state from being connected to banned. In this circumstance, if the peer then disconnected, we didn't inform the application layer, which lead to applications like sync not being informed of a peers disconnection. This could lead to sync stalling and having to require a lighthouse restart. 

Improved handling for peer states and interactions with the peerdb is made in this PR.
2020-10-23 01:27:48 +00:00
Jim McDonald
4298efeb23 Update testnet scripts (#1807)
## Proposed Changes

A couple of minor fixes to the testnet scripts.

First, `clean.sh` only attempts to remove the directory if it exists.  This ensures a good exit code even if the directory is not present.

Second, `setup.sh` uses an updated deposit contract address to match that in the generated spec to allow the chain to start.
2020-10-23 00:18:05 +00:00
Paul Hauner
542f755ac5 Remove eth1 deposit functionality (#1780)
## Issue Addressed

- Resolves #1727

## Proposed Changes

Remove the `lighthouse account validator deposit` command.

It's a shame to let this go, but it's currently lacking any tests and contains significant, un-handled edge-cases (e.g., it will wait forever until the eth1 node gives a tx confirmation and if you ctrl+c it before it finishes it will leave the filesystem in an unknown state with lockfiles lying around)

I don't think we need to make deposit functionality a priority before mainnet, we have bigger fish to fry IMO.

We, will need to revive this functionality before the next testnet, but I think we should make private, non-production tools to handle this for SigP internally.

## Additional Info

Be sure to re-open #1331 if this PR is abandoned.
2020-10-22 07:19:30 +00:00
Paul Hauner
b829257cca Ssz state (#1749)
## Issue Addressed

NA

## Proposed Changes

Adds a `lighthouse/beacon/states/:state_id/ssz` endpoint to allow us to pull the genesis state from the API.

## Additional Info

NA
2020-10-22 06:05:49 +00:00
Michael Sproul
7f73dccebc Refine op pool pruning (#1805)
## Issue Addressed

Closes #1769
Closes #1708

## Proposed Changes

Tweaks the op pool pruning so that the attestation pool is pruned against the wall-clock epoch instead of the finalized state's epoch. This should reduce the unbounded growth that we've seen during periods without finality.

Also fixes up the voluntary exit pruning as raised in #1708.
2020-10-22 04:47:29 +00:00
Paul Hauner
a3704b971e Support pre-flight CORS check (#1772)
## Issue Addressed

- Resolves #1766 

## Proposed Changes

- Use the `warp::filters::cors` filter instead of our work-around.

## Additional Info

It's not trivial to enable/disable `cors` using `warp`, since using `routes.with(cors)` changes the type of `routes`.  This makes it difficult to apply/not apply cors at runtime. My solution has been to *always* use the `warp::filters::cors` wrapper but when cors should be disabled, just pass the HTTP server listen address as the only permissible origin.
2020-10-22 04:47:27 +00:00
realbigsean
a3552a4b70 Node endpoints (#1778)
## Issue Addressed

`node` endpoints in #1434

## Proposed Changes

Implement these:
```
 /eth/v1/node/health
 /eth/v1/node/peers/{peer_id}
 /eth/v1/node/peers
```
- Add an `Option<Enr>` to `PeerInfo`
- Finish implementation of `/eth/v1/node/identity`

## Additional Info
- should update the `peers` endpoints when #1764 is resolved



Co-authored-by: realbigsean <seananderson33@gmail.com>
2020-10-22 02:59:42 +00:00
Daniel Schonfeld
8f86baa48d Optimize attester slashing (#1745)
## Issue Addressed

Closes #1548 

## Proposed Changes

Optimizes attester slashing choice by choosing the ones that cover the most amount of validators slashed, with the highest effective balances 

## Additional Info

Initial pass, need to write a test for it
2020-10-22 01:43:54 +00:00
divma
668513b67e Sync state adjustments (#1804)
check for advanced peers and the state of the chain wrt the clock slot to decide if a chain is or not synced /transitioning to a head sync. Also a fix that prevented getting the right state while syncing heads
2020-10-22 00:26:06 +00:00
Paul Hauner
e1eec7828b Fix error in VC API docs (#1800)
## Issue Addressed

NA

## Proposed Changes

- Ensure the `description` field is included with the output (as per the implementation).

## Additional Info

NA
2020-10-22 00:26:04 +00:00
realbigsean
628891df1d fix genesis state root provided to HTTP server (#1783)
## Issue Addressed

Resolves #1776

## Proposed Changes

The beacon chain builder was using the canonical head's state root for the `genesis_state_root` field.

## Additional Info
2020-10-21 23:15:30 +00:00
realbigsean
fdb9744759 use head slot instead of the target slot for the not_while_syncing fi… (#1802)
## Issue Addressed

Resolves #1792

## Proposed Changes

Use `chain.best_slot()` instead of the sync state's target slot in the `not_while_syncing_filter`

## Additional Info

N/A
2020-10-21 22:02:25 +00:00
Paul Hauner
02d94a70b7 Allow VC to start without any validators (#1779)
## Issue Addressed

NA

## Proposed Changes

- Don't exit early if the VC is without any validators.
- When there are no validators, always create the slashing database (even without `--init-slashing-protection`).
2020-10-21 04:29:24 +00:00
divma
2acf75785c More sync updates (#1791)
## Issue Addressed
#1614 and a couple of sync-stalling problems, the most important is a cyclic dependency between the sync manager and the peer manager
2020-10-20 22:34:18 +00:00
Michael Sproul
703c33bdc7 Fix head tracker concurrency bugs (#1771)
## Issue Addressed

Closes #1557

## Proposed Changes

Modify the pruning algorithm so that it mutates the head-tracker _before_ committing the database transaction to disk, and _only if_ all the heads to be removed are still present in the head-tracker (i.e. no concurrent mutations).

In the process of writing and testing this I also had to make a few other changes:

* Use internal mutability for all `BeaconChainHarness` functions (namely the RNG and the graffiti), in order to enable parallel calls (see testing section below).
* Disable logging in harness tests unless the `test_logger` feature is turned on

And chose to make some clean-ups:

* Delete the `NullMigrator`
* Remove type-based configuration for the migrator in favour of runtime config (simpler, less duplicated code)
* Use the non-blocking migrator unless the blocking migrator is required. In the store tests we need the blocking migrator because some tests make asserts about the state of the DB after the migration has run.
* Rename `validators_keypairs` -> `validator_keypairs` in the `BeaconChainHarness`

## Testing

To confirm that the fix worked, I wrote a test using [Hiatus](https://crates.io/crates/hiatus), which can be found here:

https://github.com/michaelsproul/lighthouse/tree/hiatus-issue-1557

That test can't be merged because it inserts random breakpoints everywhere, but if you check out that branch you can run the test with:

```
$ cd beacon_node/beacon_chain
$ cargo test --release --test parallel_tests --features test_logger
```

It should pass, and the log output should show:

```
WARN Pruning deferred because of a concurrent mutation, message: this is expected only very rarely!
```

## Additional Info

This is a backwards-compatible change with no impact on consensus.
2020-10-19 05:58:39 +00:00
blacktemplar
6ba997b88e add direction information to PeerInfo (#1768)
## Issue Addressed

NA

## Proposed Changes

Adds a direction field to `PeerConnectionStatus` that can be accessed by calling `is_outgoing` which will return `true` iff the peer is connected and the first connection was an outgoing one.
2020-10-16 05:24:21 +00:00
Herman Junge
d7b9d0dd9f Implement matches! macro (#1777)
Fix #1775
2020-10-15 21:42:43 +00:00
Pawan Dhananjay
97be2ca295 Simulator and attestation service fixes (#1747)
## Issue Addressed

#1729 #1730 

Which issue # does this PR address?

## Proposed Changes

1. Fixes a bug in the simulator where nodes can't find each other due to 0 udp ports in their enr.
2. Fixes bugs in attestation service where we are unsubscribing from a subnet prematurely.

More testing is needed for attestation service fixes.
2020-10-15 07:11:31 +00:00
Pawan Dhananjay
aadbab47cc Doc fixes (#1762)
## Issue Addressed

N/A

## Proposed Changes

Minor doc fixes. Adds a section on custom data directories.



Co-authored-by: Michael Sproul <micsproul@gmail.com>
2020-10-15 00:37:00 +00:00
blacktemplar
a0634cc64f Gossipsub topic filters (#1767)
## Proposed Changes

Adds a gossipsub topic filter that only allows subscribing and incoming subscriptions from valid ETH2 topics.

## Additional Info

Currently the preparation of the valid topic hashes uses only the current fork id but in the future it must also use all possible future fork ids for planned forks. This has to get added when hard coded forks get implemented.

DO NOT MERGE: We first need to merge the libp2p changes (see https://github.com/sigp/rust-libp2p/pull/70) so that we can refer from here to a commit hash inside the lighthouse branch.
2020-10-14 10:12:57 +00:00
blacktemplar
8248afa793 Updates the message-id according to the Networking Spec (#1752)
## Proposed Changes

Implement the new message id function (see https://github.com/ethereum/eth2.0-specs/pull/2089) using an additional fast message id function for better performance + caching decompressed data.
2020-10-14 06:51:58 +00:00
Michael Sproul
467de4c8d0 Add docs for slashing protection (#1760)
## Proposed Changes

* Add documentation about slashing protection, including how to troubleshoot issues and move between clients.
* Add an error message if the validator client is started with 0 validators. Previously it would hit an error relating to the slashing protection database not existing, which wrongly pushed people towards using the unsafe `--init-slashing-protection` flag.
2020-10-13 22:10:07 +00:00
realbigsean
95c96ac567 Small doc fix (#1761)
## Issue Addressed

N/A

## Proposed Changes

Looks like there was some text left over from a merge.

## Additional Info
2020-10-12 23:56:49 +00:00
ethDreamer
e9d5bade36 Fixed cross-compiling by replacing wget with curl (#1759)
It looks like the default docker image used by cross doesn't have
wget installed. This causes builds to fail. This can be fixed by
switching to curl.

## Issue Addressed
cross-compiling was broken (at least for build-aarch64)

## Proposed Changes
swap wget for curl
2020-10-11 23:58:13 +00:00
realbigsean
83ae12a1b4 Fix epoch, slot, and effective balance quoting (#1756)
## Issue Addressed

Resolves #1717

## Proposed Changes

Add quoting for epochs, slots, and `effective_balance`

## Additional Info
2020-10-11 23:58:12 +00:00
Pawan Dhananjay
99a02fd2ab Limit snappy input stream (#1738)
## Issue Addressed

N/A

## Proposed Changes

This PR limits the length of the stream received by the snappy decoder to be the maximum allowed size for the received rpc message type. Also adds further checks to ensure that the length specified in the rpc [encoding-dependent header](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/p2p-interface.md#encoding-strategies) is within the bounds for the rpc message type being decoded.
2020-10-11 22:45:33 +00:00
Paul Hauner
b185d7bbd8 Merge pull request #1671 from sigp/v0.3.0-staging
Staging: v0.3.0
2020-10-09 17:08:32 +11:00
Paul Hauner
0e4cc50262 Remove unused deps 2020-10-09 15:58:20 +11:00
Paul Hauner
db3e0578e9 Merge branch 'v0.3.0-staging' into v3-master 2020-10-09 15:27:08 +11:00
Michael Sproul
b0833033b7 Strict slashing protection by default (#1750)
## Proposed Changes

Replace `--strict-slashing-protection` by `--init-slashing-protection` and remove mentions of `--auto-register`
2020-10-09 02:05:32 +00:00
Paul Hauner
72cc5e35af Bump version to v0.3.0 (#1743)
## Issue Addressed

NA

## Proposed Changes

- Bump version to v0.3.0
- Run `cargo update`

## Additional Info

NA
2020-10-09 02:05:30 +00:00
Paul Hauner
414138f137 Update docs for v0.3.0 (#1742)
## Issue Addressed

NA

## Proposed Changes

- Remove Metamask deposits from the docs.
    - Restructure docs to be launchpad-centric.
- Remove references to sigp/lighthouse-docker.
- Add section about binaries.


## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2020-10-09 00:43:49 +00:00
realbigsean
b69c63d486 Validator dir creation (#1746)
## Issue Addressed

Resolves #1744

## Proposed Changes

- Add `directory::ensure_dir_exists` to the `ValidatorDefinition::open_or_create` method 
- As @pawanjay176 suggested, making the `--validator-dir` non-global so users are forced to include the flag after the `validator` subcommand. Current behavior seems to be ignoring the flag if it comes after something like `validator import`

## Additional Info
N/A
2020-10-08 21:01:32 +00:00
Paul Hauner
a67fa5f4a4 Add zinken testnet (#1741)
## Issue Addressed

- Resolves #1722

## Proposed Changes

This extends @danielschonfeld's work in #1739 with:

- Use an empty boot node list
- Remove the genesis state

## Additional Info

NA


Co-authored-by: Daniel Schonfeld <daniel@schonfeld.org>
2020-10-07 10:10:35 +00:00
Herman Junge
a886afd3ca Improve command help (#1740)
A little help for the future generations.
2020-10-07 00:31:19 +00:00
Michael Sproul
56ffe91f90 Update Cargo.lock (#1735)
## Issue Addressed

Fix the lockfile after it was broken by the manual merge of https://github.com/sigp/lighthouse/pull/1654
2020-10-06 00:01:17 +00:00
blacktemplar
59adc5ba00 Implement key cache to reduce keystore loading times for validator_client (#1695)
## Issue Addressed

#1618 

## Proposed Changes

Adds an encrypted key cache that is loaded on validator_client startup. It stores the keypairs for all enabled keystores and uses as password the concatenation the passwords of all enabled keystores. This reduces the number of time intensive key derivitions for `N` validators from `N` to `1`. On changes the cache gets updated asynchronously to avoid blocking the main thread.

## Additional Info

If the cache contains the keypair of a keystore that is not in the validator_definitions.yml file during loading the cache cannot get decrypted. In this case all the keystores get decrypted and then the cache gets overwritten. To avoid that one can disable keystores in validator_definitions.yml and restart the client which will remove them from the cache, after that one can entirely remove the keystore (from the validator_definitions.yml and from the disk). 

Other solutions to the above "problem" might be:
* Add a CLI and/or API function for removing keystores which will update the cache (asynchronously).
* Add a CLI and/or API function that just updates the cache (asynchronously) after a modification of the `validator_definitions.yml` file.

Note that the cache file has a lock file which gets removed immediatly after the cache was used or updated.
2020-10-05 10:50:43 +00:00
Paul Hauner
da44821e39 Clean up obsolete TODOs (#1734)
Squashed commit of the following:

commit f99373cbae
Author: Age Manning <Age@AgeManning.com>
Date:   Mon Oct 5 18:44:09 2020 +1100

    Clean up obsolute TODOs
2020-10-05 21:08:14 +11:00
Paul Hauner
ee7c8a0b7e Update external deps (#1711)
## Issue Addressed

- Resolves #1706 

## Proposed Changes

Updates dependencies across the workspace. Any crate that was not able to be brought to the latest version is listed in #1712.

## Additional Info

NA
2020-10-05 08:22:19 +00:00
Age Manning
240181e840 Upgrade discovery and restructure task execution (#1693)
* Initial rebase

* Remove old code

* Correct release tests

* Rebase commit

* Remove eth2-testnet dep on eth2libp2p

* Remove crates lost in rebase

* Remove unused dep
2020-10-05 18:45:54 +11:00
Age Manning
bcb629564a Improve error handling in network processing (#1654)
* Improve error handling in network processing

* Cargo fmt

* Cargo fmt

* Improve error handling for prior genesis

* Remove dep
2020-10-05 17:34:56 +11:00
divma
113758a4f5 From panic to crit (#1726)
## Issue Addressed
Downgrade inconsistent chain segment states from `panic` to `crit`. I don't love this solution but since range can always bounce back from any of those, we don't panic.

Co-authored-by: Age Manning <Age@AgeManning.com>
2020-10-05 17:34:49 +11:00
Justin
cf74e0baed Document need for port 9000 to be open (fix #730) (#731)
Co-authored-by: Age Manning <Age@AgeManning.com>

Edited by Paul H when cherry-picking from master to v0.3.0-staging
2020-10-05 17:34:24 +11:00
Age Manning
a8c5af8874 Increase content-id length (#1725)
## Issue Addressed

N/A

## Proposed Changes

Increase gossipsub's content-id length to the full 32 byte hash. 

## Additional Info

N/A
2020-10-05 17:33:42 +11:00
divma
6997776494 Sync fixes (#1716)
## Issue Addressed

chain state inconsistencies

## Proposed Changes
- a batch can be fake-failed by Range if it needs to move a peer to another chain. The peer will still send blocks/ errors / produce timeouts for those  requests, so check when we get a response from the RPC that the request id matches, instead of only the peer, since a re-request can be directed to the same peer.
- if an optimistic batch succeeds, store the attempt to avoid trying it again when quickly switching chains. Also, use it only if ahead of our current target, instead of the segment's start epoch
2020-10-05 17:33:36 +11:00
Paul Hauner
e7eb99cb5e Use Drop impl to send worker idle message (#1718)
## Issue Addressed

NA

## Proposed Changes

Uses a `Drop` implementation to help ensure that `BeaconProcessor` workers are freed. This will help prevent against regression, if someone happens to add an early return and it will also help in the case of a panic.

## Additional Info

NA
2020-10-05 17:33:25 +11:00
Age Manning
fe07a3c21c Improve error handling in network processing (#1654)
* Improve error handling in network processing

* Cargo fmt

* Cargo fmt

* Improve error handling for prior genesis

* Remove dep
2020-10-05 17:30:43 +11:00
Age Manning
47c921f326 Update libp2p (#1728)
## Issue Addressed

N/A

## Proposed Changes

Updates the libp2p dependency to the latest version

## Additional Info

N/A
2020-10-05 05:16:27 +00:00
divma
b1c121b880 From panic to crit (#1726)
## Issue Addressed
Downgrade inconsistent chain segment states from `panic` to `crit`. I don't love this solution but since range can always bounce back from any of those, we don't panic.

Co-authored-by: Age Manning <Age@AgeManning.com>
2020-10-05 04:02:09 +00:00
Justin
39bd511838 Document need for port 9000 to be open (fix #730) (#731)
Co-authored-by: Age Manning <Age@AgeManning.com>
2020-10-05 03:20:53 +00:00
Paul Hauner
cee3e6483a Tidy some TODOs (#1721)
## Issue Addressed

- Resolves #1705

## Proposed Changes

Cleans up some of my TODOs in the code base.

- Adds link to issue in this repo for BLST `unsafe` block.
- Confirms that the `nextaccount` field *is* required on an EIP-2386 wallet.
    - Reference: https://github.com/mcdee/EIPs/blob/master/EIPS/eip-2386.md#json-schema
- Removes TODO about Zeroize on bip39 that was resolved in #1701 
- Removes a TODO about an early randao reveal since we use the slot clock to generate the reveal: c4bd9c86e6/validator_client/src/block_service.rs (L212-L220)

## Additional Info

NA
2020-10-05 00:39:30 +00:00
Age Manning
6b68c628df Increase content-id length (#1725)
## Issue Addressed

N/A

## Proposed Changes

Increase gossipsub's content-id length to the full 32 byte hash. 

## Additional Info

N/A
2020-10-04 23:49:16 +00:00
divma
86a18e72c4 Sync fixes (#1716)
## Issue Addressed

chain state inconsistencies

## Proposed Changes
- a batch can be fake-failed by Range if it needs to move a peer to another chain. The peer will still send blocks/ errors / produce timeouts for those  requests, so check when we get a response from the RPC that the request id matches, instead of only the peer, since a re-request can be directed to the same peer.
- if an optimistic batch succeeds, store the attempt to avoid trying it again when quickly switching chains. Also, use it only if ahead of our current target, instead of the segment's start epoch
2020-10-04 23:49:14 +00:00
divma
e3c7b58657 Address a couple of TODOs (#1724)
## Issue Addressed
couple of TODOs
2020-10-04 22:50:44 +00:00
Paul Hauner
d72c026d32 Use Drop impl to send worker idle message (#1718)
## Issue Addressed

NA

## Proposed Changes

Uses a `Drop` implementation to help ensure that `BeaconProcessor` workers are freed. This will help prevent against regression, if someone happens to add an early return and it will also help in the case of a panic.

## Additional Info

NA
2020-10-04 21:59:20 +00:00
Paul Hauner
c4bd9c86e6 Add check for head/target consistency (#1702)
## Issue Addressed

NA

## Proposed Changes

Addresses an interesting DoS vector raised by @protolambda by verifying that the head and target are consistent when processing aggregate attestations. This check prevents us from loading very old target blocks and doing lots of work to skip them to the current slot.

## Additional Info

NA
2020-10-03 10:08:06 +10:00
Sean
6af3bc9ce2 Add UPnP support for Lighthouse (#1587)
This commit was modified by Paul H whilst rebasing master onto
v0.3.0-staging

Adding UPnP support will help grow the DHT by allowing NAT traversal for peers with UPnP supported routers.

Using IGD library: https://docs.rs/igd/0.10.0/igd/

Adding the  the libp2p tcp port and discovery udp port. If this fails it simply logs the attempt and moves on

Co-authored-by: Age Manning <Age@AgeManning.com>
2020-10-03 10:07:47 +10:00
Geoffry Song
8fde9a4016 Wallet creation: Make mnemonic length configurable, default to 24 words. (#1697)
## Issue Addressed

Fixes #1665.

## Proposed Changes

`lighthouse account_manager wallet create` now generates a 24-word
mnemonic. The user can override this by passing `--mnemonic-length 12`
(or another legal bip39 length).

## Additional Info

CLI `--help`:
```
        --mnemonic-length <MNEMONIC_LENGTH>       The number of words to use for the mnemonic phrase. [default: 24]
```

In case of an invalid argument:
```
% lighthouse account_manager wallet create --mnemonic-length 25
error: Invalid value for '--mnemonic-length <MNEMONIC_LENGTH>': Mnemonic length must be one of 12, 15, 18, 21, 24
```
2020-10-03 10:01:06 +10:00
realbigsean
17c5da478e Update tiny-bip39 dependency to one implementing zeroize (#1701)
## Issue Addressed

Resolves #1130

## Proposed Changes

Use the sigp fork of tiny-bip39, which includes `Zeroize` for `Mnemonic` and `Seed`

## Additional Info
N/A
2020-10-03 10:00:58 +10:00
realbigsean
255cc25623 Weak subjectivity start from genesis (#1675)
This commit was edited by Paul H when rebasing from master to
v0.3.0-staging.

Solution 2 proposed here: https://github.com/sigp/lighthouse/issues/1435#issuecomment-692317639

- Adds an optional `--wss-checkpoint` flag that takes a string `root:epoch`
- Verify that the given checkpoint exists in the chain, or that the the chain syncs through this checkpoint. If not, shutdown and prompt the user to purge state before restarting.

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-10-03 10:00:28 +10:00
Paul Hauner
32338bcafa Add check for head/target consistency (#1702)
## Issue Addressed

NA

## Proposed Changes

Addresses an interesting DoS vector raised by @protolambda by verifying that the head and target are consistent when processing aggregate attestations. This check prevents us from loading very old target blocks and doing lots of work to skip them to the current slot.

## Additional Info

NA
2020-10-02 10:46:37 +00:00
Paul Hauner
6ea3bc5e52 Implement VC API (#1657)
## Issue Addressed

NA

## Proposed Changes

- Implements a HTTP API for the validator client.
- Creates EIP-2335 keystores with an empty `description` field, instead of a missing `description` field. Adds option to set name.
- Be more graceful with setups without any validators (yet)
    - Remove an error log when there are no validators.
    - Create the `validator` dir if it doesn't exist.
- Allow building a `ValidatorDir` without a withdrawal keystore (required for the API method where we only post a voting keystore).
- Add optional `description` field to `validator_definitions.yml`

## TODO

- [x] Signature header, as per https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855
- [x] Return validator descriptions
- [x] Return deposit data
- [x] Respect the mnemonic offset
- [x] Check that mnemonic can derive returned keys
- [x] Be strict about non-localhost
- [x] Allow graceful start without any validators (+ create validator dir)
- [x] Docs final pass
- [x] Swap to EIP-2335 description field. 
- [x] Fix Zerioze TODO in VC api types.
- [x] Zeroize secp256k1 key

## Endpoints

- [x] `GET /lighthouse/version`
- [x] `GET /lighthouse/health`
- [x] `GET /lighthouse/validators` 
- [x] `POST /lighthouse/validators/hd`
- [x] `POST /lighthouse/validators/keystore`
- [x] `PATCH /lighthouse/validators/:validator_pubkey`
- [ ] ~~`POST /lighthouse/validators/:validator_pubkey/exit/:epoch`~~ Future works


## Additional Info

TBC
2020-10-02 09:42:19 +00:00
Sean
94b17ce02b Add UPnP support for Lighthouse (#1587)
Adding UPnP support will help grow the DHT by allowing NAT traversal for peers with UPnP supported routers.

## Issue Addressed

#927 

## Proposed Changes

Using IGD library: https://docs.rs/igd/0.10.0/igd/

Adding the  the libp2p tcp port and discovery udp port. If this fails it simply logs the attempt and moves on

## Additional Info



Co-authored-by: Age Manning <Age@AgeManning.com>
2020-10-02 08:47:00 +00:00
Geoffry Song
2cc20101d4 Wallet creation: Make mnemonic length configurable, default to 24 words. (#1697)
## Issue Addressed

Fixes #1665.

## Proposed Changes

`lighthouse account_manager wallet create` now generates a 24-word
mnemonic. The user can override this by passing `--mnemonic-length 12`
(or another legal bip39 length).

## Additional Info

CLI `--help`:
```
        --mnemonic-length <MNEMONIC_LENGTH>       The number of words to use for the mnemonic phrase. [default: 24]
```

In case of an invalid argument:
```
% lighthouse account_manager wallet create --mnemonic-length 25
error: Invalid value for '--mnemonic-length <MNEMONIC_LENGTH>': Mnemonic length must be one of 12, 15, 18, 21, 24
```
2020-10-02 07:51:50 +00:00
realbigsean
b56dbc3ba0 Update tiny-bip39 dependency to one implementing zeroize (#1701)
## Issue Addressed

Resolves #1130

## Proposed Changes

Use the sigp fork of tiny-bip39, which includes `Zeroize` for `Mnemonic` and `Seed`

## Additional Info
N/A
2020-10-02 06:57:40 +00:00
Michael Sproul
1d278aaa83 Implement slashing protection interchange format (#1544)
## Issue Addressed

Implements support for importing and exporting the slashing protection DB interchange format described here:

https://hackmd.io/@sproul/Bk0Y0qdGD

Also closes #1584 

## Proposed Changes

* [x] Support for serializing and deserializing the format
* [x] Support for importing and exporting Lighthouse's database
* [x] CLI commands to invoke import and export
* [x] Export to minimal format (required when a minimal format has been previously imported)
* [x] Tests for export to minimal (utilising mixed importing and attestation signing?)
* [x] Tests for import/export of complete format, and import of minimal format
* [x] ~~Prevent attestations with sources less than our max source (Danny's suggestion). Required for the fake attestation that we put in for the minimal format to block attestations from source 0.~~
* [x] Add the concept of a "low watermark" for compatibility with the minimal format

Bonus!

* [x] A fix to a potentially nasty bug involving validators getting re-registered each time the validator client ran! Thankfully, the ordering of keys meant that the validator IDs used for attestations and blocks remained stable -- otherwise we could have had some slashings on our hands! 😱
* [x] Tests to confirm that this bug is indeed vanquished
2020-10-02 01:42:27 +00:00
realbigsean
9d2d6239cd Weak subjectivity start from genesis (#1675)
## Issue Addressed
Solution 2 proposed here: https://github.com/sigp/lighthouse/issues/1435#issuecomment-692317639

## Proposed Changes
- Adds an optional `--wss-checkpoint` flag that takes a string `root:epoch`
- Verify that the given checkpoint exists in the chain, or that the the chain syncs through this checkpoint. If not, shutdown and prompt the user to purge state before restarting.

## Additional Info


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-10-01 01:41:58 +00:00
Michael Sproul
22aedda1be Add database schema versioning (#1688)
## Issue Addressed

Closes #673

## Proposed Changes

Store a schema version in the database so that future releases can check they're running against a compatible database version. This would also enable automatic migration on breaking database changes, but that's left as future work.

The database config is also stored in the database so that the `slots_per_restore_point` value can be checked for consistency, which closes #673
2020-10-01 11:12:36 +10:00
Paul Hauner
cdec3cec18 Implement standard eth2.0 API (#1569)
- Resolves #1550
- Resolves #824
- Resolves #825
- Resolves #1131
- Resolves #1411
- Resolves #1256
- Resolve #1177

- Includes the `ShufflingId` struct initially defined in #1492. That PR is now closed and the changes are included here, with significant bug fixes.
- Implement the https://github.com/ethereum/eth2.0-APIs in a new `http_api` crate using `warp`. This replaces the `rest_api` crate.
- Add a new `common/eth2` crate which provides a wrapper around `reqwest`, providing the HTTP client that is used by the validator client and for testing. This replaces the `common/remote_beacon_node` crate.
- Create a `http_metrics` crate which is a dedicated server for Prometheus metrics (they are no longer served on the same port as the REST API). We now have flags for `--metrics`, `--metrics-address`, etc.
- Allow the `subnet_id` to be an optional parameter for `VerifiedUnaggregatedAttestation::verify`. This means it does not need to be provided unnecessarily by the validator client.
- Move `fn map_attestation_committee` in `mod beacon_chain::attestation_verification` to a new `fn with_committee_cache` on the `BeaconChain` so the same cache can be used for obtaining validator duties.
- Add some other helpers to `BeaconChain` to assist with common API duties (e.g., `block_root_at_slot`, `head_beacon_block_root`).
- Change the `NaiveAggregationPool` so it can index attestations by `hash_tree_root(attestation.data)`. This is a requirement of the API.
- Add functions to `BeaconChainHarness` to allow it to create slashings and exits.
- Allow for `eth1::Eth1NetworkId` to go to/from a `String`.
- Add functions to the `OperationPool` to allow getting all objects in the pool.
- Add function to `BeaconState` to check if a committee cache is initialized.
- Fix bug where `seconds_per_eth1_block` was not transferring over from `YamlConfig` to `ChainSpec`.
- Add the `deposit_contract_address` to `YamlConfig` and `ChainSpec`. We needed to be able to return it in an API response.
- Change some uses of serde `serialize_with` and `deserialize_with` to a single use of `with` (code quality).
- Impl `Display` and `FromStr` for several BLS fields.
- Check for clock discrepancy when VC polls BN for sync state (with +/- 1 slot tolerance). This is not intended to be comprehensive, it was just easy to do.

- See #1434 for a per-endpoint overview.
- Seeking clarity here: https://github.com/ethereum/eth2.0-APIs/issues/75

- [x] Add docs for prom port to close #1256
- [x] Follow up on this #1177
- [x] ~~Follow up with #1424~~ Will fix in future PR.
- [x] Follow up with #1411
- [x] ~~Follow up with  #1260~~ Will fix in future PR.
- [x] Add quotes to all integers.
- [x] Remove `rest_types`
- [x] Address missing beacon block error. (#1629)
- [x] ~~Add tests for lighthouse/peers endpoints~~ Wontfix
- [x] ~~Follow up with validator status proposal~~ Tracked in #1434
- [x] Unify graffiti structs
- [x] ~~Start server when waiting for genesis?~~ Will fix in future PR.
- [x] TODO in http_api tests
- [x] Move lighthouse endpoints off /eth/v1
- [x] Update docs to link to standard

- ~~Blocked on #1586~~

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-10-01 11:12:36 +10:00
Pawan Dhananjay
8e20176337 Directory restructure (#1532)
Closes #1487
Closes #1427

Directory restructure in accordance with #1487. Also has temporary migration code to move the old directories into new structure.
Also extracts all default directory names and utility functions into a `directory` crate to avoid repetitio.

~Since `validator_definition.yaml` stores absolute paths, users will have to manually change the keystore paths or delete the file to get the validators picked up by the vc.~. `validator_definition.yaml` is migrated as well from the default directories.

Co-authored-by: realbigsean <seananderson33@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-10-01 11:12:35 +10:00
Paul Hauner
dffc56ef1d Fix validator lockfiles (#1586)
## Issue Addressed

- Resolves #1313 

## Proposed Changes

Changes the way we start the validator client and beacon node to ensure that we cleanly drop the validator keystores (which therefore ensures we cleanup their lockfiles).

Previously we were holding the validator keystores in a tokio task that was being forcefully killed (i.e., without `Drop`). Now, we hold them in a task that can gracefully handle a shutdown.

Also, switches the `--strict-lockfiles` flag to `--delete-lockfiles`. This means two things:

1. We are now strict on lockfiles by default (before we weren't).
1. There's a simple way for people delete the lockfiles if they experience a crash.

## Additional Info

I've only given the option to ignore *and* delete lockfiles, not just ignore them. I can't see a strong need for ignore-only but could easily add it, if the need arises.

I've flagged this as `api-breaking` since users that have lockfiles lingering around will be required to supply `--delete-lockfiles` next time they run.
2020-10-01 11:12:35 +10:00
realbigsean
996887376d Update key derivation to latest EIP-2333 (#1633)
## Issue Addressed

#1624

## Proposed Changes

Updates to match [EIP-2333](`https://eips.ethereum.org/EIPS/eip-2333`)

## Additional Info

In order to have compatibility with the eth2.0-deposit-cli, [this PR](https://github.com/ethereum/eth2.0-deposit-cli/pull/108) must also be merged
2020-10-01 11:12:35 +10:00
Michael Sproul
fcf8419c90 Allow truncation of pubkey cache on creation (#1686)
## Issue Addressed

Closes #1680

## Proposed Changes

This PR fixes a race condition in beacon node start-up whereby the pubkey cache could be created by the beacon chain builder before the `PersistedBeaconChain` was stored to disk. When the node restarted, it would find the persisted chain missing, and attempt to start from scratch, creating a new pubkey cache in the process. This call to `ValidatorPubkeyCache::new` would fail if the file already existed (which it did). I changed the behaviour so that pubkey cache initialization now doesn't care whether there's a file already in existence (it's only a cache after all). Instead it will truncate and recreate the file in the race scenario described.
2020-09-30 04:42:52 +00:00
Age Manning
a1a6b01acb Remove macos tests (#1687)
## Issue Addressed

N/A

## Proposed Changes

Remove the MacOs tests. They routinely fail, causing bors to retry and slowing down the whole merge process.

## Additional Info

N/A


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-09-30 01:27:36 +00:00
Age Manning
c0e76d2c15 Version bump and cargo update (#1683) 2020-09-29 18:29:04 +10:00
Age Manning
13cb642f39 Update boot-node and discovery (#1682)
* Improve boot_node and upgrade discovery

* Clippy lints
2020-09-29 18:28:29 +10:00
blacktemplar
ae28773965 Networking bug fixes (#1684)
* call correct unsubscribe method for subnets

* correctly delegate closed connections in behaviour

* correct unsubscribe method name
2020-09-29 18:28:15 +10:00
Age Manning
6c1d7f55bf Update lh spadina bootnode (#1685) 2020-09-29 18:27:55 +10:00
Age Manning
7bf14908dc Spadina genesis and lighthouse bootnode (#1681)
This adds the Spadina genesis file and a lighthouse bootnode to the Spadina testnet scripts
2020-09-29 02:54:43 +00:00
Paul Hauner
1ef4f0ea12 Add gossip conditions from spec v0.12.3 (#1667)
## Issue Addressed

NA

## Proposed Changes

There are four new conditions introduced in v0.12.3:

 1. _[REJECT]_ The attestation's epoch matches its target -- i.e. `attestation.data.target.epoch ==
  compute_epoch_at_slot(attestation.data.slot)`
1. _[REJECT]_ The attestation's target block is an ancestor of the block named in the LMD vote -- i.e.
  `get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(attestation.data.target.epoch)) == attestation.data.target.root`
1. _[REJECT]_ The committee index is within the expected range -- i.e. `data.index < get_committee_count_per_slot(state, data.target.epoch)`.
1. _[REJECT]_ The number of aggregation bits matches the committee size -- i.e.
  `len(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index))`.

This PR implements new logic to suit (1) and (2). Tests are added for (3) and (4), although they were already implicitly enforced.

## Additional Info

- There's a bit of edge-case with target root verification that I raised here: https://github.com/ethereum/eth2.0-specs/pull/2001#issuecomment-699246659
- I've had to add an `--ignore` to `cargo audit` to get CI to pass. See https://github.com/sigp/lighthouse/issues/1669
2020-09-27 20:59:40 +00:00
Paul Hauner
f1180a8947 Prepare for v0.2.12 (#1672)
## Issue Addressed

NA

## Proposed Changes

- Bump versions
- Run cargo update

## Additional Info

NA
2020-09-26 06:35:45 +00:00
Paul Hauner
5688f21bbd Spadina support (v2) (#1670)
## Issue Addressed

Resolves #1651

## Description

This supercedes #1658. Great work was done by @pawanjay176, I just needed to make a change whilst he is away.

See #1658 for a description, prior reviews and approval by @michaelsproul.

## Additional info

Ignores a rustsec advisory. This is tracked in #1669.


Co-authored-by: pawan <pawandhananjay@gmail.com>
2020-09-26 01:58:31 +00:00
Michael Sproul
258b28469e Update consensus code and tests to v0.12.3 (#1655)
## Proposed Changes

Update test vectors for v0.12.3, and introduced configurable `proportional_slashing_multiplier`.

Also makes `YamlConfig` a bit safer by making every field access in `apply_to_chain_spec` explicit, and removing the `#[serde(default)]` attribute, which would instantiate missing fields to type defaults! Risky!
2020-09-26 01:58:29 +00:00
Michael Sproul
3412a3ec54 Remove saturating arith from state_processing (#1644)
## Issue Addressed

Resolves #1100

## Proposed Changes

* Implement the `SafeArith` trait for `Slot` and `Epoch`, so that methods like `safe_add` become available.
* Tweak the `SafeArith` trait to allow a different `Rhs` type (analagous to `std::ops::Add`, etc).
* Add a `legacy-arith` feature to `types` and `state_processing` that conditionally enables implementations of
  the `std` ops with saturating semantics.
* Check compilation of `types` and `state_processing` _without_ `legacy-arith` on CI,
  thus guaranteeing that they only use the `SafeArith` primitives 🎉

## Additional Info

The `legacy-arith` feature gets turned on by all higher-level crates that depend on `state_processing` or `types`, thus allowing the beacon chain, networking, and other components to continue to rely on the availability of ops like `+`, `-`, `*`, etc.

**This is a consensus-breaking change**, but brings us in line with the spec, and our incompatibilities shouldn't have been reachable with any valid configuration of Eth2 parameters.
2020-09-25 05:18:21 +00:00
Age Manning
28b6d921c6 Remove banned peers from DHT and track IPs (#1656)
## Issue Addressed

#629 

## Proposed Changes

This removes banned peers from the DHT and informs discovery to block the node_id and the known source IP's associated with this node. It has the capabilities of un banning this peer after a period of time. 

This also corrects the logic about banning specific IP addresses. We now use seen_ip addresses from libp2p rather than those sent to us via identify (which also include local addresses).
2020-09-25 01:52:39 +00:00
Pawan Dhananjay
15638d1448 Beacon node does not quit on eth1 errors (#1663)
## Issue Addressed

N/A

## Proposed Changes

Log critical errors instead of quitting if eth1 node cannot be reached or is on wrong network id.
2020-09-25 00:43:45 +00:00
divma
b8013b7b2c Super Silky Smooth Syncs, like a Sir (#1628)
## Issue Addressed
In principle.. closes #1551 but in general are improvements for performance, maintainability and readability. The logic for the optimistic sync in actually simple

## Proposed Changes
There are miscellaneous things here:
- Remove unnecessary `BatchProcessResult::Partial` to simplify the batch validation logic
- Make batches a state machine. This is done to ensure batch state transitions respect our logic (this was previously done by moving batches between `Vec`s) and to ease the cognitive load of the `SyncingChain` struct
- Move most batch-related logic to the batch
- Remove `PendingBatches` in favor of a map of peers to their batches. This is to avoid duplicating peers inside the chain (peer_pool and pending_batches)
- Add `must_use` decoration to the `ProcessingResult` so that chains that request to be removed are handled accordingly. This also means that chains are now removed in more places than before to account for unhandled cases
- Store batches in a sorted map (`BTreeMap`) access is not O(1) but since the number of _active_ batches is bounded this should be fast, and saves performing hashing ops. Batches are indexed by the epoch they start. Sorted, to easily handle chain advancements (range logic)
- Produce the chain Id from the identifying fields: target root and target slot. This, to guarantee there can't be duplicated chains and be able to consistently search chains by either Id or checkpoint
- Fix chain_id not being present in all chain loggers
- Handle mega-edge case where the processor's work queue is full and the batch can't be sent. In this case the chain would lose the blocks, remain in a "syncing" state and waiting for a result that won't arrive, effectively stalling sync.
- When a batch imports blocks or the chain starts syncing with a local finalized epoch greater that the chain's start epoch, the chain is advanced instead of reset. This is to avoid losing download progress and validate batches faster. This also means that the old `start_epoch` now means "current first unvalidated batch", so it represents more accurately the progress of the chain.
- Batch status peers from the same chain to reduce Arc access.
- Handle a couple of cases where the retry counters for a batch were not updated/checked are now handled via the batch state machine. Basically now if we forget to do it, we will know.
- Do not send back the blocks from the processor to the batch. Instead register the attempt before sending the blocks (does not count as failed)
- When re-requesting a batch, try to avoid not only the last failed peer, but all previous failed peers.
- Optimize requesting batches ahead in the buffer by shuffling idle peers just once (this is just addressing a couple of old TODOs in the code)
- In chain_collection, store chains by their id in a map
- Include a mapping from request_ids to (chain, batch) that requested the batch to avoid the double O(n) search on block responses
- Other stuff:
  - impl `slog::KV` for batches
  - impl `slog::KV` for syncing chains
  - PSA: when logging, we can use `%thing` if `thing` implements `Display`. Same for `?` and `Debug`

### Optimistic syncing:
Try first the batch that contains the current head, if the batch imports any block, advance the chain. If not, if this optimistic batch is inside the current processing window leave it there for future use, if not drop it. The tolerance for this block is the same for downloading, but just once for processing



Co-authored-by: Age Manning <Age@AgeManning.com>
2020-09-23 06:29:55 +00:00
Age Manning
80e52a0263 Subscribe to core topics after sync (#1613)
## Issue Addressed

N/A

## Proposed Changes

Prevent subscribing to core gossipsub topics until after we have achieved a full sync. This prevents us censoring gossipsub channels, getting penalised in gossipsub 1.1 scoring and saves us computation time in attempting to validate gossipsub messages which we will be unable to do with a non-sync'd chain.
2020-09-23 03:26:33 +00:00
Pawan Dhananjay
80ecafaae4 Add --staking flag (#1641)
## Issue Addressed

Closes #1472 

## Proposed Changes

Add `--staking` ~~and`staking-with-eth1-endpoint`~~ flag to improve UX for stakers.


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-09-23 01:19:58 +00:00
realbigsean
b75df29501 minimize the number of places we are calling update_pubkey_cache (#1626)
## Issue Addressed

- Resolves #1080

## Proposed Changes

- Call `update_pubkey_cache` only in the `build_all_caches` method and `get_validator_index` method. 

## Additional Info

This does reduce the number of places the cache is updated, making it simpler. But the `get_validator_index` method is used a couple times when we are iterating through the entire validator registry (or set of active validators). Before, we would only call `update_pubkey_cache` once before iterating through all validators.  So I'm not _totally_ sure this change is worth it.
2020-09-23 01:19:56 +00:00
realbigsean
1801dd1a34 Interactive account passwords (#1623)
## Issue Addressed

#1437

## Proposed Changes

- Make the `--wallet-password` flag optional and creates an interactive prompt if not provided.
- Make the `--wallet-name` flag optional and creates an interactive prompt if not provided.
- Add a minimum password requirement of a 12 character length.
- Update the `--stdin-passwords` flag to `--stdin-inputs` because we have non-password user inputs 

## Additional Info
2020-09-23 01:19:54 +00:00
Michael Sproul
62c8548ed0 Revert "Update BLST, add force-adx support (#1595)" (#1649)
This reverts commit 4fca306397.

Something in the BLST update is causing SIGILLs on aarch64 non-portable builds. While we debug the issue, I think it's best if we just revert the update.
2020-09-23 00:25:56 +00:00
Pawan Dhananjay
a97ec318c4 Subscribe to subnets an epoch in advance (#1600)
## Issue Addressed

N/A

## Proposed Changes

Subscibe to subnet an epoch in advance of the attestation slot instead of 4 slots in advance.
2020-09-22 07:29:34 +00:00
Michael Sproul
7aceff4d13 Add safe_sum and use it in state_processing (#1620)
## Issue Addressed

Closes #1098

## Proposed Changes

Add a `SafeArithIter` trait with a `safe_sum` method, and use it in `state_processing`. This seems to be the only place in `consensus` where it is relevant -- i.e. where we were using `sum` and the integer_arith lint is enabled.

## Additional Info

This PR doesn't include any Clippy linting to prevent `sum` from being called. It seems there is no existing Clippy lint that suits our purpose, but I'm going to look into that and maybe schedule writing one as a lower-priority task.

This theoretically _is_ a consensus breaking change, but it shouldn't impact Medalla (or any other testnet) because `slashings` shouldn't overflow!
2020-09-22 05:40:04 +00:00
Michael Sproul
4fca306397 Update BLST, add force-adx support (#1595)
## Issue Addressed

Closes #1504
Closes https://github.com/sigp/lighthouse/issues/1505

## Proposed Changes

* Update `blst` to the latest version, which is more portable and includes finer-grained compilation controls (see below).
* Detect the case where a binary has been explicitly compiled with ADX support but it's missing at runtime, and report a nicer error than `SIGILL`.

## Known Issues

* None. The previous issue with `make build-aarch64` (https://github.com/supranational/blst/issues/27), has been resolved.

## Additional Info

I think we should tweak our release process and our Docker builds so that we provide two options:

Binaries:

* `lighthouse`: compiled with `modern`/`force-adx`, for CPUs 2013 and newer
* `lighthouse-portable`: compiled with `portable` for older CPUs

Docker images:

* `sigp/lighthouse:latest`: multi-arch image with `modern` x86_64 and vanilla aarch64 binary
* `sigp/lighthouse:latest-portable`: multi-arch image with `portable` builds for x86_64 and aarch64

And relevant Docker images for the releases (as per https://github.com/sigp/lighthouse/pull/1574#issuecomment-687766141), tagged `v0.x.y` and `v0.x.y-portable`
2020-09-22 05:40:02 +00:00
Paul Hauner
d85d5a435e Bump to v0.2.11 (#1645)
## Issue Addressed

NA

## Proposed Changes

- Bump version to v0.2.11
- Run `cargo update`.


## Additional Info

NA
2020-09-22 04:45:15 +00:00
Paul Hauner
bd39cc8e26 Apply hotfix for inconsistent head (#1639)
## Issue Addressed

- Resolves #1616

## Proposed Changes

If we look at the function which persists fork choice and the canonical head to disk:

1db8daae0c/beacon_node/beacon_chain/src/beacon_chain.rs (L234-L280)

There is a race-condition which might cause the canonical head and fork choice values to be out-of-sync.

I believe this is the cause of #1616. I managed to recreate the issue and produce a database that was unable to sync under the `master` branch but able to sync with this branch.

These new changes solve the issue by ignoring the persisted `canonical_head_block_root` value and instead getting fork choice to generate it. This ensures that the canonical head is in-sync with fork choice.

## Additional Info

This is hotfix method that leaves some crusty code hanging around. Once this PR is merged (to satisfy the v0.2.x users) we should later update and merge #1638 so we can have a clean fix for the v0.3.x versions.
2020-09-22 02:06:10 +00:00
Pawan Dhananjay
14ff38539c Add trusted peers (#1640)
## Issue Addressed

Closes #1581 

## Proposed Changes

Adds a new cli option for trusted peers who always have the maximum possible score.
2020-09-22 01:12:36 +00:00
Michael Sproul
5d17eb899f Update LevelDB to v0.8.6, removing patch (#1636)
Removes our dependency on a fork of LevelDB now that https://github.com/skade/leveldb-sys/pull/17 is merged
2020-09-21 11:53:53 +00:00
Age Manning
1db8daae0c Shift metadata to the global network variables (#1631)
## Issue Addressed

N/A

## Proposed Changes

Shifts the local `metadata` to `network_globals` making it accessible to the HTTP API and other areas of lighthouse.

## Additional Info

N/A
2020-09-21 02:00:38 +00:00
Pawan Dhananjay
7b97c4ad30 Snappy additional sanity checks (#1625)
## Issue Addressed

N/A

## Proposed Changes

Adds the following check from the spec

> A reader SHOULD NOT read more than max_encoded_len(n) bytes after reading the SSZ length-prefix n from the header.
2020-09-21 01:06:25 +00:00
Paul Hauner
371e1c1d5d Bump version to v0.2.10 (#1630)
## Issue Addressed

NA

## Proposed Changes

Bump crate version so we can cut a new release with the fix from #1629.

## Additional Info

NA
2020-09-18 06:41:29 +00:00
Paul Hauner
a17f74896a Fix bad assumption when checking finalized descendant (#1629)
## Issue Addressed

- Resolves #1616

## Proposed Changes

Fixes a bug where we are unable to read the finalized block from fork choice.

## Detail

I had made an assumption that the finalized block always has a parent root of `None`:

e5fc6bab48/consensus/fork_choice/src/fork_choice.rs (L749-L752)

This was a faulty assumption, we don't set parent *roots* to `None`. Instead we *sometimes* set parent *indices* to `None`, depending if this pruning condition is satisfied: 

e5fc6bab48/consensus/proto_array/src/proto_array.rs (L229-L232) 

The bug manifested itself like this:

1. We attempt to get the finalized block from fork choice
1. We try to check that the block is descendant of the finalized block (note: they're the same block).
1. We expect the parent root to be `None`, but it's actually the parent root of the finalized root.
1. We therefore end up checking if the parent of the finalized root is a descendant of itself. (note: it's an *ancestor* not a *descendant*).
1. We therefore declare that the finalized block is not a descendant of (or eq to) the finalized block. Bad.

## Additional Info

In reflection, I made a poor assumption in the quest to obtain a probably negligible performance gain. The performance gain wasn't worth the risk and we got burnt.
2020-09-18 05:14:31 +00:00
Age Manning
49ab414594 Shift gossipsub validation (#1612)
## Issue Addressed

N/A

## Proposed Changes

This will consider all gossipsub messages that have either the `from`, `seqno` or `signature` field as invalid. 

## Additional Info

We should not merge this until all other clients have been sending empty fields for a while.

See https://github.com/ethereum/eth2.0-specs/issues/1981 for reference
2020-09-18 02:05:36 +00:00
Age Manning
2074beccdc Gossipsub message id to shortened bytes (#1607)
## Issue Addressed

https://github.com/ethereum/eth2.0-specs/pull/2044

## Proposed Changes

Shifts the gossipsub message id to use the first 8 bytes of the SHA256 hash of the gossipsub message data field.

## Additional Info

We should merge this in once the spec has been decided on. It will cause issues with gossipsub scoring and gossipsub propagation rates (as we won't receive IWANT) messages from clients that also haven't made this update.
2020-09-18 02:05:34 +00:00
Michael Sproul
e5fc6bab48 Remove redundant decompression in process_deposit (#1610)
## Issue Addressed

Closes #1076

## Proposed Changes

Remove an extra unnecessary decompression of the deposit public key from `process_deposit`. The key is decompressed and used to verify the signature in `verify_deposit_signature`, making this initial decompression redundant.

## Additional Info

This is _not_ a consensus-breaking change because keys which previously failed the early decompression check will not be found in the pubkey cache (they are invalid), and will be checked and rejected as part of `verify_deposit_signature`.
2020-09-14 10:58:15 +00:00
Age Manning
c9596fcf0e Temporary Sync Work-Around (#1615)
## Issue Addressed

#1590 

## Proposed Changes

This is a temporary workaround that prevents finalized chain sync from swapping chains. I'm merging this in now until the full solution is ready.
2020-09-13 23:58:49 +00:00
Age Manning
c6abc56113 Prevent large step-size parameters (#1583)
## Issue Addressed

Malicious users could request very large block ranges, more than we expect. Although technically legal, we are now quadraticaly weighting large step sizes in the filter. Therefore users may request large skips, but not a large number of blocks, to prevent requests forcing us to do long chain lookups. 

## Proposed Changes

Weight the step parameter in the RPC filter and prevent any overflows that effect us in the step parameter.

## Additional Info
2020-09-11 02:33:36 +00:00
blacktemplar
7f1b936905 ignore too early / too late attestations instead of penalizing them (#1608)
## Issue Addressed

NA

## Proposed Changes

This ignores attestations that are too early or too late as it is specified in the spec (see https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#global-topics first subpoint of `beacon_aggregate_and_proof`)
2020-09-11 01:43:15 +00:00
Daniel Schonfeld
810de2f8b7 Static testnet configs (#1603)
## Issue Addressed

#1431 

## Proposed Changes

Added an archived zip file with required files manually

## Additional Info

1) Used zip, instead of tar.gz to add a single dependency instead of two.
2) I left the download from github code for now, waiting to hear if you'd like it cleaned up or left to be used for some tooling needs.
2020-09-11 01:43:13 +00:00
Pawan Dhananjay
0525876882 Dial cached enr's before making subnet discovery query (#1376)
## Issue Addressed

Closes #1365 

## Proposed Changes

Dial peers in the `cached_enrs` who aren't connected, aren't banned and satisfy the subnet predicate before making a subnet discovery query.
2020-09-11 00:52:27 +00:00
Age Manning
d79366c503 Prevent printing binary in RPC errors (#1604)
## Issue Addressed

#1566 

## Proposed Changes

Prevents printing binary characters in the RPC error response from peers.
2020-09-10 04:43:22 +00:00
Age Manning
b19cf02d2d Penalise bad peer behaviour (#1602)
## Issue Addressed

#1386 

## Proposed Changes

Penalises peers in our scoring system that produce invalid attestations or blocks.
2020-09-10 03:51:06 +00:00
Paul Hauner
dfe507715d Remove references to rust-docs (#1601)
## Issue Addressed

- Resolves #897
- Resolves #821

## Proposed Changes

Removes references to the rust docs that we're no long maintaining.

## Additional Info

NA
2020-09-10 00:24:41 +00:00
Paul Hauner
0821e6b39f Bump version to v0.2.9 (#1598)
## Issue Addressed

NA

## Proposed Changes

- Bump version tags
- Run `cargo update`

## Additional Info

NA
2020-09-09 02:28:35 +00:00
realbigsean
9cf8f45192 Mnemonic key recovery (#1579)
## Issue Addressed

N/A

## Proposed Changes

Add a  `lighthouse am wallet recover` command that recreates a wallet from a mnemonic but no validator keys.  Add a `lighthouse am validator recover` command which would directly create keys from a mnemonic for a given index and count.

## Additional Info


Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-09-08 12:17:51 +00:00
Pawan Dhananjay
00cdc4bb35 Update state before producing attestation (#1596)
## Issue Addressed

Partly addresses #1547 

## Proposed Changes

This fix addresses the missing attestations at slot 0 of an epoch (also sometimes slot 1 when slot 0 was skipped).
There are 2 cases:
1. BN receives the block for the attestation slot after 4 seconds (1/3rd of the slot).
2. No block is proposed for this slot.

In both cases, when we produce the attestation, we pass the head state to the 
`produce_unaggregated_attestation_for_block` function here
9833eca024/beacon_node/beacon_chain/src/beacon_chain.rs (L845-L850)

Since we don't advance the state in this function, we set `attestation.data.source = state.current_justified_checkpoint` which is atleast 2 epochs lower than current_epoch(wall clock epoch). 
This attestation is invalid and cannot be included in a block because of this assert from the spec:
```python
if data.target.epoch == get_current_epoch(state):
        assert data.source == state.current_justified_checkpoint
        state.current_epoch_attestations.append(pending_attestation)
```
https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#attestations

This PR changes the `produce_unaggregated_attestation_for_block` function to ensure that it advances the state before producing the attestation at the new epoch.

Running this on my node, have missed 0 attestations across all 8 of my validators in a 100 epoch period 🎉 
To compare, I was missing ~14 attestations across all 8 validators in the same 100 epoch period before the fix. 

Will report missed attestations if any after running for another 100 epochs tomorrow.
2020-09-08 11:25:43 +00:00
Michael Sproul
19be7abfd2 Don't quote slot and epoch, for now (#1597)
Fixes a breaking change to our API that was unnecessary and can wait until #1569 is merged
2020-09-08 02:12:36 +00:00
Age Manning
9833eca024 Use simple logger builder pattern (#1594)
## Issue Addressed

`simple_logger` depricated the functions we are currently using causing our CI to fail. This updates the to the builder pattern.
2020-09-07 07:44:17 +00:00
Daniel Schonfeld
2a9a815f29 conforming to the p2p specs, requiring error_messages to be bound (#1593)
## Issue Addressed

#1421 

## Proposed Changes

Bounding the error_message that can be returned for RPC domain errors


Co-authored-by: Age Manning <Age@AgeManning.com>
2020-09-07 06:47:05 +00:00
Age Manning
a6376b4585 Update discv5 to v10 (#1592)
## Issue Addressed

Code improvements, dependency improvements and better async handling.
2020-09-07 05:53:20 +00:00
Michael Sproul
74fa87aa98 Add serde_utils module with quoted u64 support (#1588)
## Proposed Changes

This is an extraction of the quoted int code from #1569, that I've come to rely on for #1544.

It allows us to parse integers from serde strings in YAML, JSON, etc. The main differences from the code in Paul's original PR are:

* Added a submodule that makes quoting mandatory (`require_quotes`).
* Decoding is generic over the type `T` being decoded. You can use `#[serde(with = "serde_utils::quoted_u64::require_quotes")]` on `Epoch` and `Slot` fields (this is what I do in my slashing protection PR).

I've turned on quoting for `Epoch` and `Slot` in this PR, but will leave the other `types` changes to you Paul.

I opted to put everything in the `conseus/serde_utils` module so that BLS can use it without a circular dependency. In future when we want to publish `types` I think we could publish `serde_utils` as `lighthouse_serde_utils` or something. Open to other ideas on this front too.
2020-09-07 01:03:53 +00:00
Michael Sproul
211109bbc0 Revert "add a github action for build multi-arch docker images (#1574)" (#1591)
This reverts commit 2627463366.

## Issue Addressed

This is a temporary fix for #1589, by reverting #1574. The Docker image needs to be built with `--build-arg PORTABLE=true`, and we could probably integrate that into the multi-arch build, but in the interests of expediting a fix, this PR opts for a revert.
2020-09-06 04:46:25 +00:00
Sean
638daa87fe Avoid Printing Binary String to Logs (#1576)
Converts the graffiti binary data to string before printing to logs.

## Issue Addressed

#1566 

## Proposed Changes
Rather than converting graffiti to a vector the binary data less the last character is passed to String::from_utf_lossy(). This then allows us to call the to_string() function directly to give us the string

## Additional Info

Rust skills are fairly weak
2020-09-05 05:46:25 +00:00
realbigsean
2627463366 add a github action for build multi-arch docker images (#1574)
## Issue Addressed

#1512

## Proposed Changes

Use Github Actions to automate the Docker image build, so that we can make a multi-arch image.  

## Additional Info

This change will require adding the DOCKER_USERNAME and DOCKER_PASSWORD secrets in Github. It will also require disabling the Docker Hub automated build.
2020-09-04 02:43:32 +00:00
Antoine Detante
9c9176c1d1 Allow to use the same password when importing multiple keystores (#1479) (#1510)
## Issue Addressed

#1479 

## Proposed Changes

* Add an optional flag `reuse-password` in the `import` command of account_manager, allowing to use the same password for all imported keystores.
2020-09-04 01:49:21 +00:00
Pawan Dhananjay
87181204d0 Minor documentation fixes (#1297)
## Issue Addressed

N/A

## Proposed Changes

- Fix a wrong command in the validator generation example.
- Replace occurrences of 'passphrase' with 'password'. This is mostly because I felt that there was a lot of mixing of the two phrases in the documentation and the actual commands which is a bit confusing. Picked 'password' everywhere because it felt more appropriate but I don't mind changing it to 'passphrase' as long it's consistent everywhere.
2020-09-02 04:59:22 +00:00
Age Manning
fb9d828e5e Extended Gossipsub metrics (#1577)
## Issue Addressed

N/A

## Proposed Changes

Adds extended metrics to get a better idea of what is happening at the gossipsub layer of lighthouse. This provides information about mesh statistics per topics, subscriptions and peer scores. 

## Additional Info
2020-09-01 06:59:14 +00:00
Age Manning
8301a984eb Revert 1502 - Switching docker user to lighthouse (#1578)
## Issue Addressed

The lighthouse user has recently changed to `lighthouse` from root. 

This requires uses to change ownership of their current docker mounted volumes and the upgrade path is non-trivial. 
This reverts #1502 and we will include it in a major release in the future.

## Proposed Changes

N/A

## Additional Info

N/A
2020-09-01 01:32:02 +00:00
Maximilian Ehlers
7d71d98dc1 Creates a new lighthouse user and makes it the default user to be use… (#1502)
…d in the Docker image

## Issue Addressed
https://github.com/sigp/lighthouse/issues/1459

## Proposed Changes

- Create new `lighthouse` user and group in Docker container
- Set user as the default user
2020-08-31 07:52:26 +00:00
realbigsean
c34e8efb12 Increase logging channel capacity (#1570)
## Issue Addressed

#1464

## Proposed Changes

Increase the slog-async log channel size from the default of 128 to 2048 to reduce the number of dropped logs. 

## Additional Info
2020-08-31 02:36:19 +00:00
Pawan Dhananjay
adea7992f8 Eth1 network exit on wrong network id (#1563)
## Issue Addressed

Fixes #1509 

## Proposed Changes

Exit the beacon node if the eth1 endpoint points to an invalid eth1 network. Check the network id before every eth1 cache update and display an error log if the network id has changed to an invalid one.
2020-08-31 02:36:17 +00:00
blacktemplar
c18d37c202 Use Gossipsub 1.1 (#1516)
## Issue Addressed

#1172

## Proposed Changes

* updates the libp2p dependency
* small adaptions based on changes in libp2p
* report not just valid messages but also invalid and distinguish between `IGNORE`d messages and `REJECT`ed messages


Co-authored-by: Age Manning <Age@AgeManning.com>
2020-08-30 13:06:50 +00:00
tobisako
b6340ec495 fix change flag name end_after_checks to continue_after_checks (#1573)
## Issue Addressed

Resolve #1387 

## Proposed Changes

Replace flag name **end_after_checks** to ** continue_after_checks**
Change condition to simple (remove **!**, It's no change logic.)

## Additional Info

Operation check
- [x] subcommand `eth1-sim` with ganach-cli
  - [x] `./simulator eth1-sim` -> test is completes
  - [x] `./simulator eth1-sim --continue_after_checks` -> test is never completes
  - [x] `./simulator eth1-sim -c` -> test is never completes
  - [x] `./simulator eth1-sim -c true` -> error: Found (clap)
  - [x] `./simulator eth1-sim -c false` -> error: Found (clap)
- [x] subcommand `no-eth1-sim`
  - [x] `./simulator no-eth1-sim` -> test is completes
  - [x] `./simulator no-eth1-sim --continue_after_checks` -> test is never completes
  - [x] `./simulator no-eth1-sim -c` -> test is never completes
  - [x] `./simulator no-eth1-sim -c true` -> error: Found (clap)
  - [x] `./simulator no-eth1-sim -c false` -> error: Found (clap)
2020-08-27 23:21:21 +00:00
Paul Hauner
967700c1ff Bump version to v0.2.8 (#1572)
## Issue Addressed

NA

## Proposed Changes

- Bump versions
- Run `cargo update`

## Additional Info

NA
2020-08-27 07:04:12 +00:00
Adam Szkoda
d9f4819fe0 Alternative (to BeaconChainHarness) BeaconChain testing API (#1380)
The PR:

* Adds the ability to generate a crucial test scenario that isn't possible with `BeaconChainHarness` (i.e. two blocks occupying the same slot; previously forks necessitated skipping slots):

![image](https://user-images.githubusercontent.com/165678/88195404-4bce3580-cc40-11ea-8c08-b48d2e1d5959.png)

* New testing API: Instead of repeatedly calling add_block(), you generate a sorted `Vec<Slot>` and leave it up to the framework to generate blocks at those slots.
* Jumping backwards to an earlier epoch is a hard error, so that tests necessarily generate blocks in a epoch-by-epoch manner.
* Configures the test logger so that output is printed on the console in case a test fails.  The logger also plays well with `--nocapture`, contrary to the existing testing framework
* Rewrites existing fork pruning tests to use the new API
* Adds a tests that triggers finalization at a non epoch boundary slot
* Renamed `BeaconChainYoke` to `BeaconChainTestingRig` because the former has been too confusing
* Fixed multiple tests (e.g. `block_production_different_shuffling_long`, `delete_blocks_and_states`, `shuffling_compatible_simple_fork`) that relied on a weird (and accidental) feature of the old `BeaconChainHarness` that attestations aren't produced for epochs earlier than the current one, thus masking potential bugs in test cases.

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-08-26 09:24:55 +00:00
Michael Sproul
30bb7aecfb Check Cargo.lock freshness on CI (#1565)
Check that `Cargo.lock` is up-to-date on CI so we're not having to push messy lockfile fix ups after releases.
2020-08-26 00:01:08 +00:00
Michael Sproul
4763f03dcc Fix bug in database pruning (#1564)
## Issue Addressed

Closes #1488

## Proposed Changes

* Prevent the pruning algorithm from over-eagerly deleting states at skipped slots when they are shared with the canonical chain.
* Add `debug` logging to the pruning algorithm so we have so better chance of debugging future issues from logs.
* Modify the handling of the "finalized state" in the beacon chain, so that it's always the state at the first slot of the finalized epoch (previously it was the state at the finalized block). This gives database pruning a clearer and cleaner view of things, and will marginally impact the pruning of the op pool, observed proposers, etc (in ways that are safe as far as I can tell).
* Remove duplicated `RevertedFinalizedEpoch` check from `after_finalization`
* Delete useless and unused `max_finality_distance`
* Add tests that exercise pruning with shared states at skip slots
* Delete unnecessary `block_strategy` argument from `add_blocks` and friends in the test harness (will likely conflict with #1380 slightly, sorry @adaszko -- but we can fix that)
* Bonus: add a `BeaconChain::with_head` method. I didn't end up needing it, but it turned out quite nice, so I figured we could keep it?

## Additional Info

Any users who have experienced pruning errors on Medalla will need to resync after upgrading to a release including this change. This should end unbounded `chain_db` growth! 🎉
2020-08-26 00:01:06 +00:00
Pawan Dhananjay
175471a64b Fix order of testnet config load (#1558)
## Issue Addressed

Fixes #1552 

## Proposed Changes

Earlier, we were always loading the hardcoded default testnet config which is a mainnet spec. So running lighthouse with `--spec` option anything other than mainnet gave errors because we tried loading a mainnet genesis spec with `minimal`/`interop` flags.

This PR fixes the order of loading such that we load the hardcoded default spec only if neither `--testnet` and `--testnet-dir` flags are present.
2020-08-25 06:01:42 +00:00
Paul Hauner
dfd02d6179 Bump to v0.2.7 (#1561)
## Issue Addressed

NA

## Proposed Changes

- Update to v0.2.7
- Add script to make update easy.

## Additional Info

NA
2020-08-24 08:25:34 +00:00
Paul Hauner
3569506acd Remove rayon from rest_api (#1562)
## Issue Addressed

NA

## Proposed Changes

Addresses a deadlock condition described here: https://hackmd.io/ijQlqOdqSGaWmIo6zMVV-A?view

## Additional Info

NA
2020-08-24 07:28:54 +00:00
Paul Hauner
c895dc8971 Shift HTTP server heavy-lifting to blocking executor (#1518)
## Issue Addressed

NA

## Proposed Changes

Shift practically all HTTP endpoint handlers to the blocking executor (some very light tasks are left on the core executor).

## Additional Info

This PR covers the `rest_api` which will soon be refactored to suit the standard API. As such, I've cut a few corners and left some existing issues open in this patch. What I have done here should leave the API in state that is not necessary *exactly* the same, but good enough for us to run validators with. Specifically, the number of blocking workers that can be spawned is unbounded and I have not implemented a queue; this will need to be fixed when we implement the standard API.
2020-08-24 03:06:10 +00:00
blacktemplar
2bc9115a94 reuse beacon_node methods for initializing network configs in boot_node (#1520)
## Issue Addressed

#1378

## Proposed Changes

Boot node reuses code from beacon_node to initialize network config. This also enables using the network directory to store/load the enr and the private key.

## Additional Info

Note that before this PR the port cli arguments were off (the argument was named `enr-port` but used as `boot-node-enr-port`).
Therefore as port always the cli port argument was used (for both enr and listening). Now the enr-port argument can be used to overwrite the listening port as the public port others should connect to.

Last but not least note, that this restructuring reuses `ethlibp2p::NetworkConfig` that has many more options than the ones used in the boot node. For example the network config has an own `discv5_config` field that gets never used in the boot node and instead another `Discv5Config` gets created later in the boot node process.

Co-authored-by: Age Manning <Age@AgeManning.com>
2020-08-21 12:00:01 +00:00
Nat
3cfd70d7fd Docs: Fix reference to incorrect password file. (#1556)
Leftover "mywallet.pass" -> "wally.pass"

Thanks @pecurliarly (from Discord)!
2020-08-21 03:50:37 +00:00
blacktemplar
3f0a113c7f ban IP addresses if too many banned peers for this IP address (#1543)
## Issue Addressed

#1283 

## Proposed Changes

All peers with the same IP will be considered banned as long as there are more than 5 (constant) peers with this IP that have a score below the ban threshold. As soon as some of those 5 peers get unbanned (through decay) and if there are then less than 5 peers with a score below the threshold the IP will be considered not banned anymore.
2020-08-21 01:41:12 +00:00
Paul Hauner
ebb25b5569 Bump version to v0.2.6 (#1549)
## Issue Addressed

NA

## Proposed Changes

See title.

## Additional Info

NA
2020-08-19 09:31:01 +00:00
Pawan Dhananjay
bbed42f30c Refactor attestation service (#1415)
## Issue Addressed

N/A

## Proposed Changes

Refactor attestation service to send out requests to find peers for subnets as soon as we get attestation duties. 
Earlier, we had much more involved logic to send the discovery requests to the discovery service only 6 slots before the attestation slot. Now that discovery is much smarter with grouped queries, the complexity in attestation service can be reduced considerably.



Co-authored-by: Age Manning <Age@AgeManning.com>
2020-08-19 08:46:25 +00:00
divma
fdc6e2aa8e Shutdown like a Sir (#1545)
## Issue Addressed
#1494 

## Proposed Changes
- Give the TaskExecutor the sender side of a channel that a task can clone to request shutting down
- The receiver side of this channel is in environment and now we block until ctrl+c or an internal shutdown signal is received
- The swarm now informs when it has reached 0 listeners
- The network receives this message and requests the shutdown
2020-08-19 05:51:14 +00:00
Paul Hauner
8e7dd7b2b1 Add remaining network ops to queuing system (#1546)
## Issue Addressed

NA

## Proposed Changes

- Refactors the `BeaconProcessor` to remove some excessive nesting and file bloat
  - Sorry about the noise from this, it's all contained in 4d3f8c5 though.
- Adds exits, proposer slashings, attester slashings to the `BeaconProcessor` so we don't get overwhelmed with large amounts of slashings (which happened a few hours ago).

## Additional Info

NA
2020-08-19 05:09:53 +00:00
Age Manning
33b2a3d0e0 Version bump to v0.2.5 (#1540)
## Description

Version bumps lighthouse to v0.2.5
2020-08-18 11:23:08 +00:00
Paul Hauner
93b7c3b7ff Set default max skips to 700 (#1542)
## Issue Addressed

NA

## Proposed Changes

Sets the default max skips to 700 so that it can cover the 693 slot skip from `80894 - 80201`.

## Additional Info

NA
2020-08-18 09:27:04 +00:00
Age Manning
2d0b214b57 Clean up logs (#1541)
## Description

This PR improves some logging for the end-user. 

It downgrades some warning logs and removes the slots per second sync speed if we are syncing and the speed is 0. This is likely because we are syncing from a finalised checkpoint and the head doesn't change.
2020-08-18 08:11:39 +00:00
Paul Hauner
d4f763bbae Fix mistake with attestation skip slots (#1539)
## Issue Addressed

NA

## Proposed Changes

- Fixes a mistake I made in #1530 which resulted us in *not* rejecting attestations that we intended to reject.
- Adds skip-slot checks for blocks earlier in import process, so it rejects gossip and RPC blocks.

## Additional Info

NA
2020-08-18 06:28:26 +00:00
Age Manning
e1e5002d3c Fingerprint Lodestar (#1536)
Fingerprints the Lodestar client
2020-08-18 06:28:24 +00:00
Paul Hauner
46dd530476 Allow import of Prysm keystores (#1535)
## Issue Addressed

- Resolves #1361

## Proposed Changes

Loosens the constraints imposed by EIP-2335 so we can import keys from Prysm.

## Additional Info

NA
2020-08-18 06:28:20 +00:00
Age Manning
8311074d68 Purge out-dated head chains on chain completion (#1538)
## Description

There can be many head chains queued up to complete. Currently we try and process all of these to completion before we consider the node synced. 

In a chaotic network, there can be many of these and processing them to completion can be very expensive and slow. This PR removes any non-syncing head chains from the queue, and re-status's the peers. If, after we have synced to head on one chain, there is still a valid head chain to download, it will be re-established once the status has been returned. 

This should assist with getting nodes to sync on medalla faster.
2020-08-18 05:22:34 +00:00
Age Manning
3bb30754d9 Keep track of failed head chains and prevent re-lookups (#1534)
## Overview

There are forked chains which get referenced by blocks and attestations on a network. Typically if these chains are very long, we stop looking up the chain and downvote the peer. In extreme circumstances, many peers are on many chains, the chains can be very deep and become time consuming performing lookups. 

This PR adds a cache to known failed chain lookups. This prevents us from starting a parent-lookup (or stopping one half way through) if we have attempted the chain lookup in the past.
2020-08-18 03:54:09 +00:00
Age Manning
cc44a64d15 Limit parallelism of head chain sync (#1527)
## Description

Currently lighthouse load-balances across peers a single finalized chain. The chain is selected via the most peers. Once synced to the latest finalized epoch Lighthouse creates chains amongst its peers and syncs them all in parallel amongst each peer (grouped by their current head block). 

This is typically fast and relatively efficient under normal operations. However if the chain has not finalized in a long time, the head chains can grow quite long. Peer's head chains will update every slot as new blocks are added to the head. Syncing all head chains in parallel is a bottleneck and highly inefficient in block duplication leads to RPC timeouts when attempting to handle all new heads chains at once. 

This PR limits the parallelism of head syncing chains to 2. We now sync at most two head chains at a time. This allows for the possiblity of sync progressing alongside a peer being slow and holding up one chain via RPC timeouts.
2020-08-18 02:49:24 +00:00
divma
46dbf027af Do not reset batch ids & redownload out of range batches (#1528)
The changes are somewhat simple but should solve two issues:
- When quickly changing between chains once and a second time back again, batchIds would collide and cause havoc. 
- If we got an out of range response from a peer, sync would remain in syncing but without advancing

Changes:
- remove the batch id. Identify each batch (inside a chain) by its starting epoch. Target epochs for downloading and processing now advance by EPOCHS_PER_BATCH
- for the same reason, move the "to_be_downloaded_id" to be an epoch
- remove a sneaky line that dropped an out of range batch without downloading it
- bonus: put the chain_id in the log given to the chain. This is why explicitly logging the chain_id is removed
2020-08-18 01:29:51 +00:00
Paul Hauner
9a97a0b14f Prepare for v0.2.4 (#1533)
## Issue Addressed

NA

## Proposed Changes

NA

## Additional Info

NA
2020-08-17 12:13:42 +00:00
Michael Sproul
719a69aee0 Ignore blocks that skip a large distance from their parent (#1530)
## Proposed Changes

To mitigate the impact of minority forks on RAM and disk usage, this change rejects blocks whose parent lies more than 320 slots (10 epochs, ~1 hour) in the past. The behaviour is configurable via `lighthouse bn --max-skip-slots N`, and can be turned off entirely using `--max-skip-slots none`.

Co-authored-by: Paul Hauner <paul@paulhauner.com>
2020-08-17 10:54:58 +00:00
Paul Hauner
a58aa6ee55 Revert back to discv5 alpha 8 to maintain ARM support (#1531)
## Issue Addressed

NA

## Proposed Changes

See title.

## Additional Info

NA
2020-08-17 10:06:08 +00:00
Paul Hauner
73cbfbdfd0 Ensure RUSTFLAGS is passed through on cross compile (#1529)
## Issue Addressed

NA

## Proposed Changes

Tells `cross` (used for cross-compiling) to read the `RUSTFLAGS`env and pass it through during build. This allows us to use `-g` and get debug info. 

## Additional Info

NA
2020-08-17 10:06:06 +00:00
Paul Hauner
f85485884f Process gossip blocks on the GossipProcessor (#1523)
## Issue Addressed

NA

## Proposed Changes

Moves beacon block processing over to the newly-added `GossipProcessor`. This moves the task off the core executor onto the blocking one.

## Additional Info

- With this PR, gossip blocks are being ignored during sync.
2020-08-17 09:20:27 +00:00
Paul Hauner
61d5b592cb Memory usage reduction (#1522)
## Issue Addressed

NA

## Proposed Changes

- Adds a new function to allow getting a state with a bad state root history for attestation verification. This reduces unnecessary tree hashing during attestation processing, which accounted for 23% of memory allocations (by bytes) in a recent `heaptrack` observation.
- Don't clone caches on intermediate epoch-boundary states during block processing.
- Reject blocks that are known to fork choice earlier during gossip processing, instead of waiting until after state has been loaded (this only happens in edge-case).
- Avoid multiple re-allocations by creating a "forced" exact size iterator.

## Additional Info

NA
2020-08-17 08:05:13 +00:00
Age Manning
3c689a6837 Remove yamux support (#1526)
## Issue Addressed

There is currently an issue with yamux when connecting to prysm peers. The source of the issue is currently unknown. 

This PR removes yamux support to force mplex negotation. We can add back yamux support once we have isolated and corrected the issue.
2020-08-17 05:05:06 +00:00
Age Manning
afdc4fea1d Correct logic for peer sync identification (#1525)
Fix a small sync bug which can mis-classify newly connected peers.
2020-08-17 03:00:10 +00:00
Pawan Dhananjay
850a2d5985 Persist metadata and enr across restarts (#1513)
## Issue Addressed

Resolves #1489 

## Proposed Changes

- Change starting metadata seq num to 0 according to the [spec](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/p2p-interface.md#metadata).
- Remove metadata field from `NetworkGlobals`
- Persist metadata to disk on every update
- Load metadata seq number from disk on restart
- Persist enr to disk on update to ensure enr sequence number increments are persisted as well.

## Additional info

Since we modified starting metadata seq num to 0 from 1, we might still see `Invalid Sequence number provided` like in #1489  from prysm nodes if they have our metadata cached.
2020-08-17 02:13:28 +00:00
divma
113b40f321 Add multiaddr support in bootnodes (#1481)
## Issue Addressed
#1384 

Only catch, as currently implemented, when dialing the multiaddr nodes, there is no way to ask the peer manager if they are already connected or dialing
2020-08-17 02:13:26 +00:00
Age Manning
99acfb50f2 Update gossipsub duplicate cache (#1524)
This potentially handles memory leak issues by preventing adding references to already seen gossipsub messages.
2020-08-17 01:27:33 +00:00
Age Manning
c75c06cf16 Update discv5 to alpha.9 (#1517)
## Discovery v5 update

In this update we remove the openssl dependency in favour of rust-crypto. 

The update also removes a series of unnecessary async functions which may improve some of the issues we have been experiencing.
2020-08-15 04:02:14 +00:00
Paul Hauner
6aeb896480 Commit Cargo.lock changes, add build scripts (#1521)
## Issue Addressed

NA

## Proposed Changes

This PR commits the `Cargo.lock` file so it does not indicate a dirty git tree in the version tag. This code should be used for the `v0.2.3` release.

Also, adds a `Makefile` command to produce tarballs for upload on release.

## Additional Info

NA
2020-08-14 22:24:27 +00:00
Paul Hauner
f4a7311008 Update to v0.2.3 (#1519)
## Issue Addressed

NA

## Proposed Changes

Bump versions to v0.2.3.

## Additional Info

NA
2020-08-14 08:32:31 +00:00
Paul Hauner
619ad106cf Restrict fork choice getters to finalized blocks (#1475)
## Issue Addressed

- Resolves #1451

## Proposed Changes

- Restricts the `contains_block` and `contains_block` so they only indicate a block is present if it descends from the finalized root. This helps to ensure that fork choice never points to a block that has been pruned from the database.
- Resolves #1451
- Before importing a block, double-check that its parent is known and a descendant of the finalized root.
- Split a big, monolithic block verification test into smaller tests. 

## Additional Notes

I suspect there would be a craftier way to do the `is_descendant_of_finalized` check, but we're a bit tight on time now and we can optimize later if it starts showing in benches.

## TODO

- [x] Tests
2020-08-14 06:36:38 +00:00
Paul Hauner
b0a3731fff Introduce a queue for attestations from the network (#1511)
## Issue Addressed

N/A

## Proposed Changes

Introduces the `GossipProcessor`, a multi-threaded (multi-tasked?), non-blocking processor for some messages from the network which require verification and import into the `BeaconChain`.

Initial testing indicates that this massively improves system stability by (a) moving block tasks from the normal executor (b) spreading out attestation load.

## Additional Info

TBC
2020-08-14 04:38:45 +00:00
Pawan Dhananjay
e3d45eda1e Log to file without json format (#1485)
## Issue Addressed

N/A

## Proposed Changes

Earlier, to log to a file, the only options were to redirect stdout/stderr to a file or use json logging. 
Redirecting to stdout/stderr works well but causes issues with mistakenly overwriting the file instead of appending which has resulted in loss of precious logs on multiple occasions for me.

Json logging creates a timestamped backup of the file if it already exists, but the json format itself is hugely annoying.

This PR modifies the `--logfile` option to log as it does in the terminal to a logfile.
2020-08-13 07:00:37 +00:00
Adam Szkoda
05a8399769 Wind down the SSE thread when the client disconnects (#1514)
These started to appear when I `^C` `curl -N http://localhost:5052/beacon/fork/stream`: `Aug 12 13:00:01.539 ERRO Couldn't stream piece hyper::Error(ChannelClosed), service: http`

Something must have changed in hyper since SSE has been implemented because I'm sure I haven't seen those errors before.

This PR properly detects a closed SSE stream and cleans up.
2020-08-13 06:12:18 +00:00
ladidan
e6f45524f9 Update key-management.md (#1515)
## Issue Addressed

consequent use of "wally"

## Proposed Changes

Please list or describe the changes introduced by this PR.

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.
2020-08-13 05:25:51 +00:00
Adam Szkoda
8a1a4051cf Fix a bug in fork pruning (#1507)
Extracted from https://github.com/sigp/lighthouse/pull/1380 because merging #1380 proves to be contentious.

Co-authored-by: Michael Sproul <michael@sigmaprime.io>
2020-08-12 07:00:00 +00:00
ladidan
61367efa64 Update key-management.md (#1508)
## Issue Addressed

minor documentation changes in order to have identical command prompts and description below

## Proposed Changes

adjust description "wally" to align with command prompt

## Additional Info

devs might give it a thought whether command line should be "mywallet"
I personally prefer "wally" for minimization reasons =)
2020-08-12 01:24:36 +00:00
Age Manning
70089f5231 Prints the version when starting lighthouse (#1506)
Prints the version to assist with debugging logs.
2020-08-11 08:55:19 +00:00
Paul Hauner
b063df5bf9 Cross-compile to vendored x86_84, aarch64 (Raspberry Pi 4) (#1497)
## Issue Addressed

NA

## Proposed Changes

Adds support for using the [`cross`](https://github.com/rust-embedded/cross) project to produce cross-compiled binaries using Docker images.

Provides quite clean and simple cross-compiles cause all the complexity is hidden in Dockerfiles. It does require you to be in the `docker` group though.

## Details

- Adds shortcut commands to `Makefile`
- Ensures `reqwest` and `discv5` use vendored openssl libs (i.e., static not shared).
- Switches to a [commit](284f705964) of blst that has a renamed C function to avoid a collision with openssl (upstream issue: https://github.com/supranational/blst/issues/21).
- Updates `ring` to the latest satisfiable version, since an earlier version was causing issues with `cross`.
- Off-topic, but adds extra message about Windows support as suggested by Discord user.

## Additional Info

- ~~Blocked on #1495~~
- There are no tests in CI for this yet for a few reasons:
  - I'm hesitant to add more long-running tasks.
  - Short-term bitrot should be avoided since we'll use it each release.
  - In the long term I think it would be good to automate binary creation on a release.
- I observed the binaries increase in size from 50mb to 52mb after these changes.
2020-08-11 05:16:30 +00:00
Adam Szkoda
b83fcd5e5c Local testnet fixes (#1499)
Fixes some outdated instructions and improves scripts portability so that they work e.g. on NixOS.
2020-08-11 02:16:33 +00:00
divma
1a67d15701 Mitigate too many outgoing connections (#1469)
limit simultaneous outgoing connections attempts to a reasonable top as an extra layer of protection
also shift the keep alive logic of the rpc handler to avoid needing to update it by hand. I think In rare cases this could make shutting down a connection a bit faster.
2020-08-11 02:16:31 +00:00
realbigsean
ec84183e05 Add graffiti cli flag to the validator client. (#1425)
## Issue Addressed

#1419

## Proposed Changes

Creates a `--graffiti` cli flag in the validator client. If the flag is set, it overrides graffiti in the beacon node. 

## Additional Info
2020-08-11 02:16:29 +00:00
divma
95b55d7170 Block error display (#1503)
## Issue Addressed

#1486
2020-08-11 01:30:26 +00:00
Age Manning
134676fd6f Version bump to v0.2.2 (#1496)
Version bump to v0.2.2
2020-08-10 06:49:03 +00:00
Age Manning
cbfae87aa6 Upgrade logs (#1495)
## Issue Addressed

#1483 

## Proposed Changes

Upgrades the log to a critical if a listener fails. We are able to listen on many interfaces so a single instance is not critical. We should however gracefully shutdown the client if we have no listeners, although the client can still function solely on outgoing connections.

For now a critical is raised and I leave #1494 for more sophisticated handling of this. 

This also updates discv5 to handle errors of binding to a UDP socket such that lighthouse is now able to handle them.
2020-08-10 05:19:51 +00:00
Age Manning
04e4389efe Patch gossipsub (#1490)
## Issue Addressed

Some nodes not following head, high CPU usage and HTTP API delays

## Proposed Changes

Patches gossipsub. Gossipsub was using an `lru_time_cache` to check for duplicates. This contained an `O(N)` lookup for every gossipsub message to update the time cache. This was causing high cpu usage and blocking network threads. 

This PR introduces a custom cache without `O(N)` inserts. 

This also adds built in safety mechanisms to prevent gossipsub from excessively retrying connections upon failure. A maximum limit is set after which we disconnect from the node from too many failed substream connections.
2020-08-08 08:09:04 +00:00
Age Manning
08a31c5a1a Disconnect peers (#1484)
## Issue Addressed

Peers that connected after the peer limit may remain connected in some circumstances. 

This ensures peers not in the peer manager's list get disconnected. Further logging is also added to track this behaviour.
2020-08-08 06:08:44 +00:00
Age Manning
a1f9769040 Libp2p update (#1482)
Updates to latest libp2p master. 

This now has native noise support. 

This PR
- Removes secio support
- Prioritises mplex over yamux
2020-08-08 02:17:32 +00:00
Naoya Okanami
1d5d3e3ea7 Fix typo (#1476)
just a typo fix :)
managment -> management
2020-08-07 00:09:39 +00:00
979 changed files with 137070 additions and 45992 deletions

View File

@@ -1,4 +1,4 @@
tests/ef_tests/eth2.0-spec-tests
testing/ef_tests/consensus-spec-tests
target/
*.data
*.tar.gz

View File

@@ -5,7 +5,7 @@ Please provide a brief description of the issue.
## Version
Please provide your Lighthouse and Rust version. Are you building from
`master`, which commit?
`stable` or `unstable`, which commit?
## Present Behaviour

3
.github/custom/config.toml vendored Normal file
View File

@@ -0,0 +1,3 @@
# Custom Cargo config to be used for the udeps CI job
[http]
multiplexing = false

View File

@@ -3,7 +3,7 @@ name: mdbook
on:
push:
branches:
- master
- unstable
jobs:
build-and-upload-to-s3:
@@ -14,7 +14,7 @@ jobs:
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.3.5'
mdbook-version: 'latest'
- run: mdbook build
working-directory: book

View File

@@ -0,0 +1,14 @@
name: cancel previous runs
on: [push]
jobs:
cancel:
name: 'Cancel Previous Runs'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
# https://github.com/styfle/cancel-workflow-action/releases
- uses: styfle/cancel-workflow-action@514c783324374c6940d1b92bfb962d0763d22de3 # 0.7.0
with:
# https://api.github.com/repos/sigp/lighthouse/actions/workflows
workflow_id: 697364,2434944,4462424,308241,2883401,316
access_token: ${{ github.token }}

31
.github/workflows/docker-antithesis.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: docker antithesis
on:
push:
branches:
- unstable
env:
ANTITHESIS_PASSWORD: ${{ secrets.ANTITHESIS_PASSWORD }}
ANTITHESIS_USERNAME: ${{ secrets.ANTITHESIS_USERNAME }}
ANTITHESIS_SERVER: ${{ secrets.ANTITHESIS_SERVER }}
REPOSITORY: ${{ secrets.ANTITHESIS_REPOSITORY }}
IMAGE_NAME: lighthouse
TAG: libvoidstar
jobs:
build-docker:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Update Rust
run: rustup update stable
- name: Dockerhub login
run: |
echo "${ANTITHESIS_PASSWORD}" | docker login --username ${ANTITHESIS_USERNAME} https://${ANTITHESIS_SERVER} --password-stdin
- name: Build AMD64 dockerfile (with push)
run: |
docker build \
--tag ${ANTITHESIS_SERVER}/${REPOSITORY}/${IMAGE_NAME}:${TAG} \
--file ./testing/antithesis/Dockerfile.libvoidstar .
docker push ${ANTITHESIS_SERVER}/${REPOSITORY}/${IMAGE_NAME}:${TAG}

139
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,139 @@
name: docker
on:
push:
branches:
- unstable
- stable
tags:
- v*
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
IMAGE_NAME: ${{ github.repository_owner}}/lighthouse
LCLI_IMAGE_NAME: ${{ github.repository_owner }}/lcli
jobs:
# Extract the VERSION which is either `latest` or `vX.Y.Z`, and the VERSION_SUFFIX
# which is either empty or `-unstable`.
#
# It would be nice if the arch didn't get spliced into the version between `latest` and
# `unstable`, but for now we keep the two parts of the version separate for backwards
# compatibility.
extract-version:
runs-on: ubuntu-18.04
steps:
- name: Extract version (if stable)
if: github.event.ref == 'refs/heads/stable'
run: |
echo "VERSION=latest" >> $GITHUB_ENV
echo "VERSION_SUFFIX=" >> $GITHUB_ENV
- name: Extract version (if unstable)
if: github.event.ref == 'refs/heads/unstable'
run: |
echo "VERSION=latest" >> $GITHUB_ENV
echo "VERSION_SUFFIX=-unstable" >> $GITHUB_ENV
- name: Extract version (if tagged release)
if: startsWith(github.event.ref, 'refs/tags')
run: |
echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV
echo "VERSION_SUFFIX=" >> $GITHUB_ENV
outputs:
VERSION: ${{ env.VERSION }}
VERSION_SUFFIX: ${{ env.VERSION_SUFFIX }}
build-docker-single-arch:
name: build-docker-${{ matrix.binary }}
runs-on: ubuntu-18.04
strategy:
matrix:
binary: [aarch64,
aarch64-portable,
x86_64,
x86_64-portable]
needs: [extract-version]
env:
# We need to enable experimental docker features in order to use `docker buildx`
DOCKER_CLI_EXPERIMENTAL: enabled
VERSION: ${{ needs.extract-version.outputs.VERSION }}
VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }}
steps:
- uses: actions/checkout@v2
- name: Update Rust
run: rustup update stable
- name: Dockerhub login
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Cross build Lighthouse binary
run: |
cargo install cross
make build-${{ matrix.binary }}
- name: Move cross-built binary into Docker scope (if ARM)
if: startsWith(matrix.binary, 'aarch64')
run: |
mkdir ./bin;
mv ./target/aarch64-unknown-linux-gnu/release/lighthouse ./bin;
- name: Move cross-built binary into Docker scope (if x86_64)
if: startsWith(matrix.binary, 'x86_64')
run: |
mkdir ./bin;
mv ./target/x86_64-unknown-linux-gnu/release/lighthouse ./bin;
- name: Map aarch64 to arm64 short arch
if: startsWith(matrix.binary, 'aarch64')
run: echo "SHORT_ARCH=arm64" >> $GITHUB_ENV
- name: Map x86_64 to amd64 short arch
if: startsWith(matrix.binary, 'x86_64')
run: echo "SHORT_ARCH=amd64" >> $GITHUB_ENV;
- name: Set modernity suffix
if: endsWith(matrix.binary, '-portable') != true
run: echo "MODERNITY_SUFFIX=-modern" >> $GITHUB_ENV;
# Install dependencies for emulation. Have to create a new builder to pick up emulation support.
- name: Build Dockerfile and push
run: |
docker run --privileged --rm tonistiigi/binfmt --install ${SHORT_ARCH}
docker buildx create --use --name cross-builder
docker buildx build \
--platform=linux/${SHORT_ARCH} \
--file ./Dockerfile.cross . \
--tag ${IMAGE_NAME}:${VERSION}-${SHORT_ARCH}${VERSION_SUFFIX}${MODERNITY_SUFFIX} \
--push
build-docker-multiarch:
name: build-docker-multiarch${{ matrix.modernity }}
runs-on: ubuntu-18.04
needs: [build-docker-single-arch, extract-version]
strategy:
matrix:
modernity: ["", "-modern"]
env:
# We need to enable experimental docker features in order to use `docker manifest`
DOCKER_CLI_EXPERIMENTAL: enabled
VERSION: ${{ needs.extract-version.outputs.VERSION }}
VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }}
steps:
- name: Dockerhub login
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Create and push multiarch manifest
run: |
docker manifest create ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX}${{ matrix.modernity }} \
--amend ${IMAGE_NAME}:${VERSION}-arm64${VERSION_SUFFIX}${{ matrix.modernity }} \
--amend ${IMAGE_NAME}:${VERSION}-amd64${VERSION_SUFFIX}${{ matrix.modernity }};
docker manifest push ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX}${{ matrix.modernity }}
build-docker-lcli:
runs-on: ubuntu-18.04
needs: [extract-version]
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }}
steps:
- uses: actions/checkout@v2
- name: Dockerhub login
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Build lcli dockerfile (with push)
run: |
docker build \
--build-arg PORTABLE=true \
--tag ${LCLI_IMAGE_NAME}:${VERSION}${VERSION_SUFFIX} \
--file ./lcli/Dockerfile .
docker push ${LCLI_IMAGE_NAME}:${VERSION}${VERSION_SUFFIX}

30
.github/workflows/linkcheck.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: linkcheck
on:
push:
branches:
- unstable
pull_request:
paths:
- 'book/**'
jobs:
linkcheck:
name: Check broken links
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create docker network
run: docker network create book
- name: Run mdbook server
run: docker run -v ${{ github.workspace }}/book:/book --network book --name book -p 3000:3000 -d peaceiris/mdbook:latest serve --hostname 0.0.0.0
- name: Print logs
run: docker logs book
- name: Run linkcheck
run: docker run --network book tennox/linkcheck:latest book:3000

49
.github/workflows/local-testnet.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
# Test that local testnet starts successfully.
name: local testnet
on:
push:
branches:
- unstable
pull_request:
jobs:
run-local-testnet:
strategy:
matrix:
os:
- ubuntu-18.04
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Install ganache
run: npm install ganache-cli@latest --global
# https://github.com/actions/cache/blob/main/examples.md#rust---cargo
- uses: actions/cache@v2
id: cache-cargo
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install lighthouse
run: make && make install-lcli
- name: Start local testnet
run: ./start_local_testnet.sh
working-directory: scripts/local_testnet
- name: Print logs
run: ./print_logs.sh
working-directory: scripts/local_testnet
- name: Stop local testnet
run: ./stop_local_testnet.sh
working-directory: scripts/local_testnet

66
.github/workflows/publish-crate.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Publish Crate
on:
push:
tags:
- tree-hash-v*
- tree-hash-derive-v*
- eth2-ssz-v*
- eth2-ssz-derive-v*
- eth2-ssz-types-v*
- eth2-serde-util-v*
- eth2-hashing-v*
env:
CARGO_API_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
jobs:
extract-tag:
runs-on: ubuntu-latest
steps:
- name: Extract tag
run: echo "::set-output name=TAG::$(echo ${GITHUB_REF#refs/tags/})"
id: extract_tag
outputs:
TAG: ${{ steps.extract_tag.outputs.TAG }}
publish-crate:
runs-on: ubuntu-latest
needs: [extract-tag]
env:
TAG: ${{ needs.extract-tag.outputs.TAG }}
steps:
- uses: actions/checkout@v2
- name: Update Rust
run: rustup update stable
- name: Cargo login
run: |
echo "${CARGO_API_TOKEN}" | cargo login
- name: publish eth2 ssz derive
if: startsWith(env.TAG, 'eth2-ssz-derive-v')
run: |
./scripts/ci/publish.sh consensus/ssz_derive eth2_ssz_derive "$TAG"
- name: publish eth2 ssz
if: startsWith(env.TAG, 'eth2-ssz-v')
run: |
./scripts/ci/publish.sh consensus/ssz eth2_ssz "$TAG"
- name: publish eth2 hashing
if: startsWith(env.TAG, 'eth2-hashing-v')
run: |
./scripts/ci/publish.sh crypto/eth2_hashing eth2_hashing "$TAG"
- name: publish tree hash derive
if: startsWith(env.TAG, 'tree-hash-derive-v')
run: |
./scripts/ci/publish.sh consensus/tree_hash_derive tree_hash_derive "$TAG"
- name: publish tree hash
if: startsWith(env.TAG, 'tree-hash-v')
run: |
./scripts/ci/publish.sh consensus/tree_hash tree_hash "$TAG"
- name: publish ssz types
if: startsWith(env.TAG, 'eth2-ssz-types-v')
run: |
./scripts/ci/publish.sh consensus/ssz_types eth2_ssz_types "$TAG"
- name: publish serde util
if: startsWith(env.TAG, 'eth2-serde-util-v')
run: |
./scripts/ci/publish.sh consensus/serde_utils eth2_serde_utils "$TAG"

276
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,276 @@
name: Release Suite
on:
push:
tags:
- v*
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
REPO_NAME: sigp/lighthouse
IMAGE_NAME: sigp/lighthouse
jobs:
extract-version:
runs-on: ubuntu-latest
steps:
- name: Extract version
run: echo "::set-output name=VERSION::$(echo ${GITHUB_REF#refs/tags/})"
id: extract_version
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
build:
name: Build Release
strategy:
matrix:
arch: [aarch64-unknown-linux-gnu,
aarch64-unknown-linux-gnu-portable,
x86_64-unknown-linux-gnu,
x86_64-unknown-linux-gnu-portable,
x86_64-apple-darwin,
x86_64-apple-darwin-portable,
x86_64-windows,
x86_64-windows-portable]
include:
- arch: aarch64-unknown-linux-gnu
platform: ubuntu-latest
- arch: aarch64-unknown-linux-gnu-portable
platform: ubuntu-latest
- arch: x86_64-unknown-linux-gnu
platform: ubuntu-latest
- arch: x86_64-unknown-linux-gnu-portable
platform: ubuntu-latest
- arch: x86_64-apple-darwin
platform: macos-latest
- arch: x86_64-apple-darwin-portable
platform: macos-latest
- arch: x86_64-windows
platform: windows-2019
- arch: x86_64-windows-portable
platform: windows-2019
runs-on: ${{ matrix.platform }}
needs: extract-version
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
# ==============================
# Windows dependencies
# ==============================
- uses: KyleMayes/install-llvm-action@v1
if: startsWith(matrix.arch, 'x86_64-windows')
with:
version: "13.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
if: startsWith(matrix.arch, 'x86_64-windows')
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
# ==============================
# Builds
# ==============================
- name: Build Lighthouse for aarch64-unknown-linux-gnu-portable
if: matrix.arch == 'aarch64-unknown-linux-gnu-portable'
run: |
cargo install cross
make build-aarch64-portable
- name: Build Lighthouse for aarch64-unknown-linux-gnu
if: matrix.arch == 'aarch64-unknown-linux-gnu'
run: |
cargo install cross
make build-aarch64
- name: Build Lighthouse for x86_64-unknown-linux-gnu-portable
if: matrix.arch == 'x86_64-unknown-linux-gnu-portable'
run: |
cargo install cross
make build-x86_64-portable
- name: Build Lighthouse for x86_64-unknown-linux-gnu
if: matrix.arch == 'x86_64-unknown-linux-gnu'
run: |
cargo install cross
make build-x86_64
- name: Move cross-compiled binary
if: startsWith(matrix.arch, 'aarch64')
run: mv target/aarch64-unknown-linux-gnu/release/lighthouse ~/.cargo/bin/lighthouse
- name: Move cross-compiled binary
if: startsWith(matrix.arch, 'x86_64-unknown-linux-gnu')
run: mv target/x86_64-unknown-linux-gnu/release/lighthouse ~/.cargo/bin/lighthouse
- name: Build Lighthouse for x86_64-apple-darwin portable
if: matrix.arch == 'x86_64-apple-darwin-portable'
run: cargo install --path lighthouse --force --locked --features portable,gnosis
- name: Build Lighthouse for x86_64-apple-darwin modern
if: matrix.arch == 'x86_64-apple-darwin'
run: cargo install --path lighthouse --force --locked --features modern,gnosis
- name: Build Lighthouse for Windows portable
if: matrix.arch == 'x86_64-windows-portable'
run: cargo install --path lighthouse --force --locked --features portable,gnosis
- name: Build Lighthouse for Windows modern
if: matrix.arch == 'x86_64-windows'
run: cargo install --path lighthouse --force --locked --features modern,gnosis
- name: Configure GPG and create artifacts
if: startsWith(matrix.arch, 'x86_64-windows') != true
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
export GPG_TTY=$(tty)
echo "$GPG_SIGNING_KEY" | gpg --batch --import
mkdir artifacts
mv ~/.cargo/bin/lighthouse ./artifacts
cd artifacts
tar -czf lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz lighthouse
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz
mv *tar.gz* ..
- name: Configure GPG and create artifacts Windows
if: startsWith(matrix.arch, 'x86_64-windows')
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo $env:GPG_SIGNING_KEY | gpg --batch --import
mkdir artifacts
move $env:USERPROFILE/.cargo/bin/lighthouse.exe ./artifacts
cd artifacts
tar -czf lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz lighthouse.exe
gpg --passphrase "$env:GPG_PASSPHRASE" --batch --pinentry-mode loopback -ab lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz
move *tar.gz* ..
# =======================================================================
# Upload artifacts
# This is required to share artifacts between different jobs
# =======================================================================
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz
- name: Upload signature
uses: actions/upload-artifact@v2
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz.asc
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz.asc
draft-release:
name: Draft Release
needs: [build, extract-version]
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
steps:
# This is necessary for generating the changelog. It has to come before "Download Artifacts" or else it deletes the artifacts.
- name: Checkout sources
uses: actions/checkout@v2
with:
fetch-depth: 0
# ==============================
# Download artifacts
# ==============================
- name: Download artifacts
uses: actions/download-artifact@v2
# ==============================
# Create release draft
# ==============================
- name: Generate Full Changelog
id: changelog
run: echo "::set-output name=CHANGELOG::$(git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 ${{ env.VERSION }}^)..${{ env.VERSION }})"
- name: Create Release Draft
env:
GITHUB_USER: sigp
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# The formatting here is borrowed from OpenEthereum: https://github.com/openethereum/openethereum/blob/main/.github/workflows/build.yml
run: |
body=$(cat <<- "ENDBODY"
<Rick and Morty character>
## Testing Checklist (DELETE ME)
- [ ] Run on synced Prater Sigma Prime nodes.
- [ ] Run on synced Canary (mainnet) Sigma Prime nodes.
- [ ] Resync a Prater node.
- [ ] Resync a mainnet node.
## Release Checklist (DELETE ME)
- [ ] Merge `unstable` -> `stable`.
- [ ] Ensure docker images are published (check `latest` and the version tag).
- [ ] Prepare Discord post.
- [ ] Prepare Twitter post.
- [ ] Prepare mailing list email.
## Summary
Add a summary.
## Update Priority
This table provides priorities for which classes of users should update particular components.
|User Class |Beacon Node | Validator Client|
--- | --- | ---
|Staking Users| <TODO> | <TODO> |
|Non-Staking Users| <TODO>|---|
*See [Update
Priorities](https://lighthouse-book.sigmaprime.io/installation-priorities.html)
more information about this table.*
## All Changes
${{ steps.changelog.outputs.CHANGELOG }}
## Binaries
[See pre-built binaries documentation.](https://lighthouse-book.sigmaprime.io/installation-binaries.html)
The binaries are signed with Sigma Prime's PGP key: `15E66D941F697E28F49381F426416DC3F30674B0`
| System | Architecture | Binary | PGP Signature |
|:---:|:---:|:---:|:---|
| <img src="https://simpleicons.org/icons/apple.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/apple.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-apple-darwin-portable.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-apple-darwin-portable.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-apple-darwin-portable.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu-portable.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu-portable.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu-portable.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/raspberrypi.svg" style="width: 32px;"/> | aarch64 | [lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/raspberrypi.svg" style="width: 32px;"/> | aarch64 | [lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu-portable.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu-portable.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu-portable.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/windows.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz.asc) |
| <img src="https://simpleicons.org/icons/windows.svg" style="width: 32px;"/> | x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-windows-portable.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows-portable.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows-portable.tar.gz.asc) |
| | | | |
| **System** | **Option** | - | **Resource** |
| <img src="https://simpleicons.org/icons/docker.svg" style="width: 32px;"/> | Docker | [${{ env.VERSION }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}/tags?page=1&ordering=last_updated&name=${{ env.VERSION }}) | [${{ env.IMAGE_NAME }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}) |
ENDBODY
)
assets=()
for asset in ./lighthouse-*.tar.gz*; do
assets+=("-a" "$asset/$asset")
done
tag_name="${{ env.VERSION }}"
echo "$body" | hub release create --draft "${assets[@]}" -F "-" "$tag_name"

View File

@@ -3,7 +3,7 @@ name: test-suite
on:
push:
branches:
- master
- stable
- staging
- trying
- 'pr/*'
@@ -11,7 +11,16 @@ on:
env:
# Deny warnings in CI
RUSTFLAGS: "-D warnings"
# The Nightly version used for cargo-udeps, might need updating from time to time.
PINNED_NIGHTLY: nightly-2021-12-01
jobs:
target-branch-check:
name: target-branch-check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Check that the pull request is not targeting the stable branch
run: test ${{ github.base_ref }} != "stable"
cargo-fmt:
name: cargo-fmt
runs-on: ubuntu-latest
@@ -33,20 +42,46 @@ jobs:
run: sudo npm install -g ganache-cli
- name: Run tests in release
run: make test-release
release-tests-and-install-macos:
name: release-tests-and-install-macos
runs-on: macos-latest
release-tests-windows:
name: release-tests-windows
runs-on: windows-2019
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Install ganache-cli
run: sudo npm install -g ganache-cli
run: npm install -g ganache-cli
- name: Install make
run: choco install -y make
- uses: KyleMayes/install-llvm-action@v1
with:
version: "13.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
- name: Run tests in release
run: make test-release
- name: Install Lighthouse
run: make
beacon-chain-tests:
name: beacon-chain-tests
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Run beacon_chain tests for all known forks
run: make test-beacon-chain
op-pool-tests:
name: op-pool-tests
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Run operation_pool tests for all known forks
run: make test-op-pool
debug-tests-ubuntu:
name: debug-tests-ubuntu
runs-on: ubuntu-latest
@@ -77,7 +112,7 @@ jobs:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Run eth2.0-spec-tests with blst, milagro and fake_crypto
- name: Run consensus-spec-tests with blst, milagro and fake_crypto
run: make test-ef
dockerfile-ubuntu:
name: dockerfile-ubuntu
@@ -85,14 +120,20 @@ jobs:
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Build the root Dockerfile
run: docker build .
run: docker build --build-arg FEATURES=portable -t lighthouse:local .
- name: Test the built image
run: docker run -t lighthouse:local lighthouse --version
eth1-simulator-ubuntu:
name: eth1-simulator-ubuntu
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Install ganache-cli
run: sudo npm install -g ganache-cli
- name: Run the beacon chain sim that starts from an eth1 contract
@@ -103,32 +144,99 @@ jobs:
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Install ganache-cli
run: sudo npm install -g ganache-cli
- name: Run the beacon chain sim without an eth1 connection
run: cargo run --release --bin simulator no-eth1-sim
syncing-simulator-ubuntu:
name: syncing-simulator-ubuntu
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Install ganache-cli
run: sudo npm install -g ganache-cli
- name: Run the syncing simulator
run: cargo run --release --bin simulator syncing-sim
doppelganger-protection-test:
name: doppelganger-protection-test
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Install ganache-cli
run: sudo npm install -g ganache-cli
- name: Install lighthouse and lcli
run: |
make
make install-lcli
- name: Run the doppelganger protection success test script
run: |
cd scripts/tests
./doppelganger_protection.sh success
- name: Run the doppelganger protection failure test script
run: |
cd scripts/tests
./doppelganger_protection.sh failure
execution-engine-integration-ubuntu:
name: execution-engine-integration-ubuntu
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- uses: actions/setup-go@v2
with:
go-version: '1.17'
- name: Get latest version of stable Rust
run: rustup update stable
- name: Run exec engine integration tests in release
run: make test-exec-engine
check-benchmarks:
name: check-benchmarks
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Typecheck benchmark code without running it
run: make check-benches
check-consensus:
name: check-consensus
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Typecheck consensus code in strict mode
run: make check-consensus
clippy:
name: clippy
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Lint code for quality and style with Clippy
run: make lint
- name: Certify Cargo.lock freshness
run: git diff --exit-code Cargo.lock
arbitrary-check:
name: arbitrary-check
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Validate state_processing feature arbitrary-fuzz
run: make arbitrary-fuzz
cargo-audit:
@@ -137,18 +245,32 @@ jobs:
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Run cargo audit to identify known security vulnerabilities reported to the RustSec Advisory Database
run: make audit
cargo-vendor:
name: cargo-vendor
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Run cargo vendor to make sure dependencies can be vendored for packaging, reproducibility and archival purpose
run: CARGO_HOME=$(readlink -f $HOME) make vendor
cargo-udeps:
name: cargo-udeps
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Install a nightly compiler with rustfmt, as a kind of quality control
run: rustup toolchain install --component=rustfmt nightly
- name: Install Rust (${{ env.PINNED_NIGHTLY }})
run: rustup toolchain install $PINNED_NIGHTLY
- name: Install cargo-udeps
run: cargo install cargo-udeps --locked
- name: Create Cargo config dir
run: mkdir -p .cargo
- name: Install custom Cargo config
run: cp -f .github/custom/config.toml .cargo/config.toml
- name: Run cargo udeps to identify unused crates in the dependency graph
run: make udeps
env:

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ target/
flamegraph.svg
perf.data*
*.tar.gz
/bin
genesis.ssz

6624
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,13 @@ members = [
"beacon_node/beacon_chain",
"beacon_node/client",
"beacon_node/eth1",
"beacon_node/eth2_libp2p",
"beacon_node/lighthouse_network",
"beacon_node/execution_layer",
"beacon_node/http_api",
"beacon_node/http_metrics",
"beacon_node/network",
"beacon_node/rest_api",
"beacon_node/store",
"beacon_node/timer",
"beacon_node/websocket_server",
"boot_node",
@@ -20,19 +21,29 @@ members = [
"common/compare_fields",
"common/compare_fields_derive",
"common/deposit_contract",
"common/directory",
"common/eth2",
"common/eth2_config",
"common/eth2_interop_keypairs",
"common/eth2_testnet_config",
"common/eth2_network_config",
"common/eth2_wallet_manager",
"common/hashset_delay",
"common/lighthouse_metrics",
"common/lighthouse_version",
"common/lockfile",
"common/logging",
"common/remote_beacon_node",
"common/rest_types",
"common/lru_cache",
"common/malloc_utils",
"common/sensitive_url",
"common/slot_clock",
"common/task_executor",
"common/target_check",
"common/test_random_derive",
"common/unused_port",
"common/validator_dir",
"common/warp_utils",
"common/fallback",
"common/monitoring_api",
"consensus/cached_tree_hash",
"consensus/int_to_bytes",
@@ -42,7 +53,7 @@ members = [
"consensus/ssz",
"consensus/ssz_derive",
"consensus/ssz_types",
"consensus/serde_hex",
"consensus/serde_utils",
"consensus/state_processing",
"consensus/swap_or_not_shuffle",
"consensus/tree_hash",
@@ -59,11 +70,17 @@ members = [
"lighthouse",
"lighthouse/environment",
"testing/simulator",
"slasher",
"slasher/service",
"testing/ef_tests",
"testing/eth1_test_rig",
"testing/execution_engine_integration",
"testing/node_test_rig",
"testing/simulator",
"testing/test-test_logger",
"testing/state_transition_vectors",
"testing/web3signer_tests",
"validator_client",
"validator_client/slashing_protection",
@@ -71,10 +88,9 @@ members = [
[patch]
[patch.crates-io]
tree_hash = { path = "consensus/tree_hash" }
tree_hash_derive = { path = "consensus/tree_hash_derive" }
fixed-hash = { git = "https://github.com/paritytech/parity-common", rev="df638ab0885293d21d656dc300d39236b69ce57d" }
warp = { git = "https://github.com/macladson/warp", rev ="7e75acc" }
eth2_ssz = { path = "consensus/ssz" }
eth2_ssz_derive = { path = "consensus/ssz_derive" }
eth2_ssz_types = { path = "consensus/ssz_types" }
eth2_hashing = { path = "crypto/eth2_hashing" }
leveldb-sys = { git = "https://github.com/michaelsproul/leveldb-sys", branch = "v2.0.6-cmake" }
tree_hash = { path = "consensus/tree_hash" }
eth2_serde_utils = { path = "consensus/serde_utils" }

15
Cross.toml Normal file
View File

@@ -0,0 +1,15 @@
[build.env]
passthrough = [
"RUSTFLAGS",
]
# These custom images are required to work around the lack of Clang in the default `cross` images.
# We need Clang to run `bindgen` for MDBX, and the `BINDGEN_EXTRA_CLANG_ARGS` flags must also be set
# while cross-compiling for ARM to prevent bindgen from attempting to include headers from the host.
#
# For more information see https://github.com/rust-embedded/cross/pull/608
[target.x86_64-unknown-linux-gnu]
image = "michaelsproul/cross-clang:x86_64-latest"
[target.aarch64-unknown-linux-gnu]
image = "michaelsproul/cross-clang:aarch64-latest"

View File

@@ -1,16 +1,14 @@
FROM rust:1.45.1 AS builder
RUN apt-get update && apt-get install -y cmake
FROM rust:1.58.1-bullseye AS builder
RUN apt-get update && apt-get -y upgrade && apt-get install -y cmake libclang-dev
COPY . lighthouse
ARG PORTABLE
ENV PORTABLE $PORTABLE
ARG FEATURES
ENV FEATURES $FEATURES
RUN cd lighthouse && make
RUN cd lighthouse && make install-lcli
FROM debian:buster-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
FROM ubuntu:latest
RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends \
libssl-dev \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/cargo/bin/lighthouse /usr/local/bin/lighthouse
COPY --from=builder /usr/local/cargo/bin/lcli /usr/local/bin/lcli

10
Dockerfile.cross Normal file
View File

@@ -0,0 +1,10 @@
# This image is meant to enable cross-architecture builds.
# It assumes the lighthouse binary has already been
# compiled for `$TARGETPLATFORM` and moved to `./bin`.
FROM --platform=$TARGETPLATFORM ubuntu:latest
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY ./bin/lighthouse /usr/local/bin/lighthouse

130
Makefile
View File

@@ -2,34 +2,86 @@
EF_TESTS = "testing/ef_tests"
STATE_TRANSITION_VECTORS = "testing/state_transition_vectors"
EXECUTION_ENGINE_INTEGRATION = "testing/execution_engine_integration"
GIT_TAG := $(shell git describe --tags --candidates 1)
BIN_DIR = "bin"
X86_64_TAG = "x86_64-unknown-linux-gnu"
BUILD_PATH_X86_64 = "target/$(X86_64_TAG)/release"
AARCH64_TAG = "aarch64-unknown-linux-gnu"
BUILD_PATH_AARCH64 = "target/$(AARCH64_TAG)/release"
PINNED_NIGHTLY ?= nightly
# List of all hard forks. This list is used to set env variables for several tests so that
# they run for different forks.
FORKS=phase0 altair merge
# Builds the Lighthouse binary in release (optimized).
#
# Binaries will most likely be found in `./target/release`
install:
ifeq ($(PORTABLE), true)
cargo install --path lighthouse --force --locked --features portable
else
cargo install --path lighthouse --force --locked
endif
cargo install --path lighthouse --force --locked --features "$(FEATURES)"
# Builds the lcli binary in release (optimized).
install-lcli:
ifeq ($(PORTABLE), true)
cargo install --path lcli --force --locked --features portable
else
cargo install --path lcli --force --locked
endif
cargo install --path lcli --force --locked --features "$(FEATURES)"
# The following commands use `cross` to build a cross-compile.
#
# These commands require that:
#
# - `cross` is installed (`cargo install cross`).
# - Docker is running.
# - The current user is in the `docker` group.
#
# The resulting binaries will be created in the `target/` directory.
#
# The *-portable options compile the blst library *without* the use of some
# optimized CPU functions that may not be available on some systems. This
# results in a more portable binary with ~20% slower BLS verification.
build-x86_64:
cross build --release --bin lighthouse --target x86_64-unknown-linux-gnu --features modern,gnosis
build-x86_64-portable:
cross build --release --bin lighthouse --target x86_64-unknown-linux-gnu --features portable,gnosis
build-aarch64:
cross build --release --bin lighthouse --target aarch64-unknown-linux-gnu --features gnosis
build-aarch64-portable:
cross build --release --bin lighthouse --target aarch64-unknown-linux-gnu --features portable,gnosis
# Create a `.tar.gz` containing a binary for a specific target.
define tarball_release_binary
cp $(1)/lighthouse $(BIN_DIR)/lighthouse
cd $(BIN_DIR) && \
tar -czf lighthouse-$(GIT_TAG)-$(2)$(3).tar.gz lighthouse && \
rm lighthouse
endef
# Create a series of `.tar.gz` files in the BIN_DIR directory, each containing
# a `lighthouse` binary for a different target.
#
# The current git tag will be used as the version in the output file names. You
# will likely need to use `git tag` and create a semver tag (e.g., `v0.2.3`).
build-release-tarballs:
[ -d $(BIN_DIR) ] || mkdir -p $(BIN_DIR)
$(MAKE) build-x86_64
$(call tarball_release_binary,$(BUILD_PATH_X86_64),$(X86_64_TAG),"")
$(MAKE) build-x86_64-portable
$(call tarball_release_binary,$(BUILD_PATH_X86_64),$(X86_64_TAG),"-portable")
$(MAKE) build-aarch64
$(call tarball_release_binary,$(BUILD_PATH_AARCH64),$(AARCH64_TAG),"")
$(MAKE) build-aarch64-portable
$(call tarball_release_binary,$(BUILD_PATH_AARCH64),$(AARCH64_TAG),"-portable")
# Runs the full workspace tests in **release**, without downloading any additional
# test vectors.
test-release:
cargo test --all --release --exclude ef_tests
cargo test --workspace --release --exclude ef_tests --exclude beacon_chain
# Runs the full workspace tests in **debug**, without downloading any additional test
# vectors.
test-debug:
cargo test --all --exclude ef_tests
cargo test --workspace --exclude ef_tests --exclude beacon_chain
# Runs cargo-fmt (linter).
cargo-fmt:
@@ -37,13 +89,33 @@ cargo-fmt:
# Typechecks benchmark code
check-benches:
cargo check --all --benches
cargo check --workspace --benches
# Typechecks consensus code *without* allowing deprecated legacy arithmetic or metrics.
check-consensus:
cargo check -p state_processing --no-default-features
# Runs only the ef-test vectors.
run-ef-tests:
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests"
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,fake_crypto"
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,milagro"
rm -rf $(EF_TESTS)/.accessed_file_log.txt
cargo test --release -p ef_tests --features "ef_tests"
cargo test --release -p ef_tests --features "ef_tests,fake_crypto"
cargo test --release -p ef_tests --features "ef_tests,milagro"
./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests
# Run the tests in the `beacon_chain` crate for all known forks.
test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(FORKS))
test-beacon-chain-%:
env FORK_NAME=$* cargo test --release --features fork_from_env -p beacon_chain
# Run the tests in the `operation_pool` crate for all known forks.
test-op-pool: $(patsubst %,test-op-pool-%,$(FORKS))
test-op-pool-%:
env FORK_NAME=$* cargo test --release \
--features 'beacon_chain/fork_from_env'\
-p operation_pool
# Runs only the tests/state_transition_vectors tests.
run-state-transition-tests:
@@ -52,17 +124,26 @@ run-state-transition-tests:
# Downloads and runs the EF test vectors.
test-ef: make-ef-tests run-ef-tests
# Runs tests checking interop between Lighthouse and execution clients.
test-exec-engine:
make -C $(EXECUTION_ENGINE_INTEGRATION) test
# Runs the full workspace tests in release, without downloading any additional
# test vectors.
test: test-release
# Runs the entire test suite, downloading test vectors if required.
test-full: cargo-fmt test-release test-debug test-ef
test-full: cargo-fmt test-release test-debug test-ef test-exec-engine
# Lints the code for bad style and potentially unsafe arithmetic using Clippy.
# Clippy lints are opt-in per-crate for now. By default, everything is allowed except for performance and correctness lints.
lint:
cargo clippy --all -- -D warnings
cargo clippy --workspace --tests -- \
-D clippy::fn_to_numeric_cast_any \
-D warnings \
-A clippy::from-over-into \
-A clippy::upper-case-acronyms \
-A clippy::vec-init-then-push
# Runs the makefile in the `ef_tests` repo.
#
@@ -72,18 +153,23 @@ lint:
make-ef-tests:
make -C $(EF_TESTS)
# Verifies that state_processing feature arbitrary-fuzz will compile
# Verifies that crates compile with fuzzing features enabled
arbitrary-fuzz:
cargo check --manifest-path=consensus/state_processing/Cargo.toml --features arbitrary-fuzz
cargo check -p state_processing --features arbitrary-fuzz
cargo check -p slashing_protection --features arbitrary-fuzz
# Runs cargo audit (Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database)
audit:
cargo install --force cargo-audit
cargo audit
cargo audit --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2020-0159
# Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose.
vendor:
cargo vendor
# Runs `cargo udeps` to check for unused dependencies
udeps:
cargo +nightly udeps --tests --all-targets --release
cargo +$(PINNED_NIGHTLY) udeps --tests --all-targets --release
# Performs a `cargo` clean and cleans the `ef_tests` directory.
clean:

View File

@@ -1,87 +1,78 @@
# Lighthouse: Ethereum 2.0
# Lighthouse: Ethereum consensus client
An open-source Ethereum 2.0 client, written in Rust and maintained by Sigma Prime.
An open-source Ethereum consensus client, written in Rust and maintained by Sigma Prime.
[![Build Status]][Build Link] [![Book Status]][Book Link] [![RustDoc Status]][RustDoc Link] [![Chat Badge]][Chat Link]
[![Build Status]][Build Link] [![Book Status]][Book Link] [![Chat Badge]][Chat Link]
[Build Status]: https://github.com/sigp/lighthouse/workflows/test-suite/badge.svg?branch=master
[Build Status]: https://github.com/sigp/lighthouse/workflows/test-suite/badge.svg?branch=stable
[Build Link]: https://github.com/sigp/lighthouse/actions
[Chat Badge]: https://img.shields.io/badge/chat-discord-%237289da
[Chat Link]: https://discord.gg/cyAszAh
[Book Status]:https://img.shields.io/badge/user--docs-master-informational
[Book Link]: http://lighthouse-book.sigmaprime.io/
[RustDoc Status]:https://img.shields.io/badge/code--docs-master-orange
[RustDoc Link]: http://lighthouse-docs.sigmaprime.io/
[Book Status]:https://img.shields.io/badge/user--docs-unstable-informational
[Book Link]: https://lighthouse-book.sigmaprime.io
[stable]: https://github.com/sigp/lighthouse/tree/stable
[unstable]: https://github.com/sigp/lighthouse/tree/unstable
[blog]: https://lighthouse.sigmaprime.io
[Documentation](http://lighthouse-book.sigmaprime.io/)
[Documentation](https://lighthouse-book.sigmaprime.io)
![terminalize](https://i.postimg.cc/kG11dpCW/lighthouse-cli-png.gif)
![Banner](https://i.postimg.cc/hjdTGKPd/photo-2020-10-23-09-52-16.jpg)
## Overview
Lighthouse is:
- Ready for use on Ethereum consensus mainnet.
- Fully open-source, licensed under Apache 2.0.
- Security-focused. Fuzzing has begun and security reviews are underway.
- Built in [Rust](https://www.rust-lang.org/), a modern language providing unique safety guarantees and
- Security-focused. Fuzzing techniques have been continuously applied and several external security reviews have been performed.
- Built in [Rust](https://www.rust-lang.org), a modern language providing unique safety guarantees and
excellent performance (comparable to C++).
- Funded by various organisations, including Sigma Prime, the
Ethereum Foundation, ConsenSys and private individuals.
- Actively involved in the specification and security analysis of the emerging
Ethereum 2.0 specification.
Ethereum Foundation, ConsenSys, the Decentralization Foundation and private individuals.
- Actively involved in the specification and security analysis of the
Ethereum proof-of-stake consensus specification.
Like all Ethereum 2.0 clients, Lighthouse is a work-in-progress.
## Development Status
Current development overview:
- Specification `v0.12.1` implemented, optimized and passing test vectors.
- Rust-native libp2p with Gossipsub and Discv5.
- RESTful JSON API via HTTP server.
- Events via WebSocket.
- Metrics via Prometheus.
### Roadmap
- ~~**April 2019**: Inital single-client testnets.~~
- ~~**September 2019**: Inter-operability with other Ethereum 2.0 clients.~~
- ~~**Q1 2020**: `lighthouse-0.1.0` release: All major phase 0 features implemented.~~
- ~~**Q2 2020**: Public, multi-client testnet with user-facing functionality.~~
- ~~**Q2 2020**: Third-party security review.~~
- **Q3 2020**: Additional third-party security reviews.
- **Q3 2020**: Long-lived, multi-client Beacon Chain testnet
- **Q4 2020**: Production Beacon Chain (tentative).
## Staking Deposit Contract
The Lighthouse team acknowledges
[`0x00000000219ab540356cBB839Cbe05303d7705Fa`](https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa)
as the canonical staking deposit contract address.
## Documentation
The [Lighthouse Book](http://lighthouse-book.sigmaprime.io/) contains information
for testnet users and developers.
The [Lighthouse Book](https://lighthouse-book.sigmaprime.io) contains information for users and
developers.
Code documentation is generated via `cargo doc` and hosted at
[lighthouse-docs.sigmaprime.io](http://lighthouse-docs.sigmaprime.io/).
The Lighthouse team maintains a blog at [lighthouse.sigmaprime.io][blog] which contains periodical
progress updates, roadmap insights and interesting findings.
If you'd like some background on Sigma Prime, please see the [Lighthouse Update
\#00](https://lighthouse.sigmaprime.io/update-00.html) blog post or
[sigmaprime.io](https://sigmaprime.io).
## Branches
Lighthouse maintains two permanent branches:
- [`stable`][stable]: Always points to the latest stable release.
- This is ideal for most users.
- [`unstable`][unstable]: Used for development, contains the latest PRs.
- Developers should base their PRs on this branch.
## Contributing
Lighthouse welcomes contributors.
If you are looking to contribute, please head to the
[Contributing](http://lighthouse-book.sigmaprime.io/contributing.html) section
[Contributing](https://lighthouse-book.sigmaprime.io/contributing.html) section
of the Lighthouse book.
## Contact
The best place for discussion is the [Lighthouse Discord
server](https://discord.gg/cyAszAh). Alternatively, you may use the
[sigp/lighthouse gitter](https://gitter.im/sigp/lighthouse).
server](https://discord.gg/cyAszAh).
Sign up to the [Lighthouse Development Updates](http://eepurl.com/dh9Lvb) mailing list for email
notifications about releases, network status and other important information.
Encrypt sensitive messages using our [PGP
key](https://keybase.io/sigp/pgp_keys.asc?fingerprint=dcf37e025d6c9d42ea795b119e7c6cf9988604be).
key](https://keybase.io/sigp/pgp_keys.asc?fingerprint=15e66d941f697e28f49381f426416dc3f30674b0).
## Donations

13
SECURITY.md Normal file
View File

@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
Please see [Releases](https://github.com/sigp/lighthouse/releases/). We recommend using the [most recently released version](https://github.com/sigp/lighthouse/releases/latest).
## Reporting a Vulnerability
Please send vulnerability reports to security@sigmaprime.io and encrypt sensitive messages using our [PGP
key](https://keybase.io/sigp/pgp_keys.asc?fingerprint=15e66d941f697e28f49381f426416dc3f30674b0).
**Please do not file a public ticket** mentioning the vulnerability, as doing so could increase the likelihood of the vulnerability being used before a fix has been created, released and installed on the network.

View File

@@ -1,33 +1,29 @@
[package]
name = "account_manager"
version = "0.2.0"
version = "0.3.5"
authors = ["Paul Hauner <paul@paulhauner.com>", "Luke Anderson <luke@sigmaprime.io>"]
edition = "2018"
edition = "2021"
[dependencies]
bls = { path = "../crypto/bls" }
clap = "2.33.0"
slog = "2.5.2"
slog-term = "2.5.0"
slog-async = "2.5.0"
clap = "2.33.3"
types = { path = "../consensus/types" }
state_processing = { path = "../consensus/state_processing" }
dirs = "2.0.2"
environment = { path = "../lighthouse/environment" }
deposit_contract = { path = "../common/deposit_contract" }
libc = "0.2.65"
eth2_ssz = "0.1.2"
eth2_ssz_derive = "0.1.0"
hex = "0.4.2"
rayon = "1.3.0"
eth2_testnet_config = { path = "../common/eth2_testnet_config" }
web3 = "0.11.0"
futures = { version = "0.3.5", features = ["compat"] }
eth2_network_config = { path = "../common/eth2_network_config" }
clap_utils = { path = "../common/clap_utils" }
directory = { path = "../common/directory" }
eth2_wallet = { path = "../crypto/eth2_wallet" }
eth2_wallet_manager = { path = "../common/eth2_wallet_manager" }
rand = "0.7.2"
validator_dir = { path = "../common/validator_dir" }
tokio = { version = "0.2.21", features = ["full"] }
tokio = { version = "1.14.0", features = ["full"] }
eth2_keystore = { path = "../crypto/eth2_keystore" }
account_utils = { path = "../common/account_utils" }
slashing_protection = { path = "../validator_client/slashing_protection" }
eth2 = {path = "../common/eth2"}
safe_arith = {path = "../consensus/safe_arith"}
slot_clock = { path = "../common/slot_clock" }
filesystem = { path = "../common/filesystem" }
sensitive_url = { path = "../common/sensitive_url" }
[dev-dependencies]
tempfile = "3.1.0"

View File

@@ -1,21 +1,67 @@
use clap::ArgMatches;
use std::fs::create_dir_all;
use std::path::{Path, PathBuf};
use account_utils::PlainText;
use account_utils::{read_input_from_user, strip_off_newlines};
use eth2_wallet::bip39::{Language, Mnemonic};
use std::fs;
use std::path::PathBuf;
use std::str::from_utf8;
use std::thread::sleep;
use std::time::Duration;
pub fn ensure_dir_exists<P: AsRef<Path>>(path: P) -> Result<(), String> {
let path = path.as_ref();
pub const MNEMONIC_PROMPT: &str = "Enter the mnemonic phrase:";
pub const WALLET_NAME_PROMPT: &str = "Enter wallet name:";
if !path.exists() {
create_dir_all(path).map_err(|e| format!("Unable to create {:?}: {:?}", path, e))?;
pub fn read_mnemonic_from_cli(
mnemonic_path: Option<PathBuf>,
stdin_inputs: bool,
) -> Result<Mnemonic, String> {
let mnemonic = match mnemonic_path {
Some(path) => fs::read(&path)
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))
.and_then(|bytes| {
let bytes_no_newlines: PlainText = strip_off_newlines(bytes).into();
let phrase = from_utf8(bytes_no_newlines.as_ref())
.map_err(|e| format!("Unable to derive mnemonic: {:?}", e))?;
Mnemonic::from_phrase(phrase, Language::English).map_err(|e| {
format!(
"Unable to derive mnemonic from string {:?}: {:?}",
phrase, e
)
})
})?,
None => loop {
eprintln!();
eprintln!("{}", MNEMONIC_PROMPT);
let mnemonic = read_input_from_user(stdin_inputs)?;
match Mnemonic::from_phrase(mnemonic.as_str(), Language::English) {
Ok(mnemonic_m) => {
eprintln!("Valid mnemonic provided.");
eprintln!();
sleep(Duration::from_secs(1));
break mnemonic_m;
}
Err(_) => {
eprintln!("Invalid mnemonic");
}
}
},
};
Ok(mnemonic)
}
/// Reads in a wallet name from the user. If the `--wallet-name` flag is provided, use it. Otherwise
/// read from an interactive prompt using tty unless the `--stdin-inputs` flag is provided.
pub fn read_wallet_name_from_cli(
wallet_name: Option<String>,
stdin_inputs: bool,
) -> Result<String, String> {
match wallet_name {
Some(name) => Ok(name),
None => {
eprintln!("{}", WALLET_NAME_PROMPT);
read_input_from_user(stdin_inputs)
}
}
Ok(())
}
pub fn base_wallet_dir(matches: &ArgMatches, arg: &'static str) -> Result<PathBuf, String> {
clap_utils::parse_path_with_default_in_home_dir(
matches,
arg,
PathBuf::new().join(".lighthouse").join("wallets"),
)
}

View File

@@ -10,7 +10,8 @@ use types::EthSpec;
pub const CMD: &str = "account_manager";
pub const SECRETS_DIR_FLAG: &str = "secrets-dir";
pub const VALIDATOR_DIR_FLAG: &str = "validator-dir";
pub const BASE_DIR_FLAG: &str = "base-dir";
pub const VALIDATOR_DIR_FLAG_ALIAS: &str = "validators-dir";
pub const WALLETS_DIR_FLAG: &str = "wallets-dir";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)

View File

@@ -1,9 +1,16 @@
use crate::{common::ensure_dir_exists, SECRETS_DIR_FLAG, VALIDATOR_DIR_FLAG};
use account_utils::{random_password, strip_off_newlines, validator_definitions};
use crate::common::read_wallet_name_from_cli;
use crate::wallet::create::STDIN_INPUTS_FLAG;
use crate::{SECRETS_DIR_FLAG, WALLETS_DIR_FLAG};
use account_utils::{
random_password, read_password_from_user, strip_off_newlines, validator_definitions, PlainText,
};
use clap::{App, Arg, ArgMatches};
use directory::{
ensure_dir_exists, parse_path_or_default_with_flag, DEFAULT_SECRET_DIR, DEFAULT_WALLET_DIR,
};
use environment::Environment;
use eth2_wallet::PlainText;
use eth2_wallet_manager::WalletManager;
use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
@@ -11,13 +18,13 @@ use types::EthSpec;
use validator_dir::Builder as ValidatorDirBuilder;
pub const CMD: &str = "create";
pub const BASE_DIR_FLAG: &str = "base-dir";
pub const WALLET_NAME_FLAG: &str = "wallet-name";
pub const WALLET_PASSPHRASE_FLAG: &str = "wallet-passphrase";
pub const WALLET_PASSWORD_FLAG: &str = "wallet-password";
pub const DEPOSIT_GWEI_FLAG: &str = "deposit-gwei";
pub const STORE_WITHDRAW_FLAG: &str = "store-withdrawal-keystore";
pub const COUNT_FLAG: &str = "count";
pub const AT_MOST_FLAG: &str = "at-most";
pub const WALLET_PASSWORD_PROMPT: &str = "Enter your wallet's password:";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
@@ -30,26 +37,22 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.long(WALLET_NAME_FLAG)
.value_name("WALLET_NAME")
.help("Use the wallet identified by this name")
.takes_value(true)
.required(true),
.takes_value(true),
)
.arg(
Arg::with_name(WALLET_PASSPHRASE_FLAG)
.long(WALLET_PASSPHRASE_FLAG)
Arg::with_name(WALLET_PASSWORD_FLAG)
.long(WALLET_PASSWORD_FLAG)
.value_name("WALLET_PASSWORD_PATH")
.help("A path to a file containing the password which will unlock the wallet.")
.takes_value(true)
.required(true),
.takes_value(true),
)
.arg(
Arg::with_name(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG)
.value_name("VALIDATOR_DIRECTORY")
.help(
"The path where the validator directories will be created. \
Defaults to ~/.lighthouse/validators",
)
.takes_value(true),
Arg::with_name(WALLETS_DIR_FLAG)
.long(WALLETS_DIR_FLAG)
.value_name(WALLETS_DIR_FLAG)
.help("A path containing Eth2 EIP-2386 wallets. Defaults to ~/.lighthouse/{network}/wallets")
.takes_value(true)
.conflicts_with("datadir"),
)
.arg(
Arg::with_name(SECRETS_DIR_FLAG)
@@ -57,8 +60,9 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.value_name("SECRETS_DIR")
.help(
"The path where the validator keystore passwords will be stored. \
Defaults to ~/.lighthouse/secrets",
Defaults to ~/.lighthouse/{network}/secrets",
)
.conflicts_with("datadir")
.takes_value(true),
)
.arg(
@@ -99,36 +103,61 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.conflicts_with("count")
.takes_value(true),
)
.arg(
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
}
pub fn cli_run<T: EthSpec>(
matches: &ArgMatches,
mut env: Environment<T>,
wallet_base_dir: PathBuf,
validator_dir: PathBuf,
) -> Result<(), String> {
let spec = env.core_context().eth2_config.spec;
let name: String = clap_utils::parse_required(matches, WALLET_NAME_FLAG)?;
let wallet_password_path: PathBuf =
clap_utils::parse_required(matches, WALLET_PASSPHRASE_FLAG)?;
let validator_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
VALIDATOR_DIR_FLAG,
PathBuf::new().join(".lighthouse").join("validators"),
)?;
let secrets_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
SECRETS_DIR_FLAG,
PathBuf::new().join(".lighthouse").join("secrets"),
)?;
let name: Option<String> = clap_utils::parse_optional(matches, WALLET_NAME_FLAG)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
let wallet_base_dir = if matches.value_of("datadir").is_some() {
let path: PathBuf = clap_utils::parse_required(matches, "datadir")?;
path.join(DEFAULT_WALLET_DIR)
} else {
parse_path_or_default_with_flag(matches, WALLETS_DIR_FLAG, DEFAULT_WALLET_DIR)?
};
let secrets_dir = if matches.value_of("datadir").is_some() {
let path: PathBuf = clap_utils::parse_required(matches, "datadir")?;
path.join(DEFAULT_SECRET_DIR)
} else {
parse_path_or_default_with_flag(matches, SECRETS_DIR_FLAG, DEFAULT_SECRET_DIR)?
};
let deposit_gwei = clap_utils::parse_optional(matches, DEPOSIT_GWEI_FLAG)?
.unwrap_or_else(|| spec.max_effective_balance);
.unwrap_or(spec.max_effective_balance);
let count: Option<usize> = clap_utils::parse_optional(matches, COUNT_FLAG)?;
let at_most: Option<usize> = clap_utils::parse_optional(matches, AT_MOST_FLAG)?;
// The command will always fail if the wallet dir does not exist.
if !wallet_base_dir.exists() {
return Err(format!(
"No wallet directory at {:?}. Use the `lighthouse --network {} {} {} {}` command to create a wallet",
wallet_base_dir,
matches.value_of("network").unwrap_or("<NETWORK>"),
crate::CMD,
crate::wallet::CMD,
crate::wallet::create::CMD
));
}
ensure_dir_exists(&validator_dir)?;
ensure_dir_exists(&secrets_dir)?;
eprintln!("secrets-dir path {:?}", secrets_dir);
eprintln!("wallets-dir path {:?}", wallet_base_dir);
let starting_validator_count = existing_validator_count(&validator_dir)?;
let n = match (count, at_most) {
@@ -152,17 +181,37 @@ pub fn cli_run<T: EthSpec>(
return Ok(());
}
let wallet_password = fs::read(&wallet_password_path)
.map_err(|e| format!("Unable to read {:?}: {:?}", wallet_password_path, e))
.map(|bytes| PlainText::from(strip_off_newlines(bytes)))?;
let wallet_password_path: Option<PathBuf> =
clap_utils::parse_optional(matches, WALLET_PASSWORD_FLAG)?;
let wallet_name = read_wallet_name_from_cli(name, stdin_inputs)?;
let wallet_password = read_wallet_password_from_cli(wallet_password_path, stdin_inputs)?;
let mgr = WalletManager::open(&wallet_base_dir)
.map_err(|e| format!("Unable to open --{}: {:?}", BASE_DIR_FLAG, e))?;
.map_err(|e| format!("Unable to open --{}: {:?}", WALLETS_DIR_FLAG, e))?;
let mut wallet = mgr
.wallet_by_name(&name)
.wallet_by_name(&wallet_name)
.map_err(|e| format!("Unable to open wallet: {:?}", e))?;
let slashing_protection_path = validator_dir.join(SLASHING_PROTECTION_FILENAME);
let slashing_protection =
SlashingDatabase::open_or_create(&slashing_protection_path).map_err(|e| {
format!(
"Unable to open or create slashing protection database at {}: {:?}",
slashing_protection_path.display(),
e
)
})?;
// Create an empty transaction and drops it. Used to test if the database is locked.
slashing_protection.test_transaction().map_err(|e| {
format!(
"Cannot create keys while the validator client is running: {:?}",
e
)
})?;
for i in 0..n {
let voting_password = random_password();
let withdrawal_password = random_password();
@@ -175,9 +224,25 @@ pub fn cli_run<T: EthSpec>(
)
.map_err(|e| format!("Unable to create validator keys: {:?}", e))?;
let voting_pubkey = keystores.voting.pubkey().to_string();
let voting_pubkey = keystores.voting.public_key().ok_or_else(|| {
format!(
"Keystore public key is invalid: {}",
keystores.voting.pubkey()
)
})?;
ValidatorDirBuilder::new(validator_dir.clone(), secrets_dir.clone())
slashing_protection
.register_validator(voting_pubkey.compress())
.map_err(|e| {
format!(
"Error registering validator {}: {:?}",
voting_pubkey.as_hex_string(),
e
)
})?;
ValidatorDirBuilder::new(validator_dir.clone())
.password_dir(secrets_dir.clone())
.voting_keystore(keystores.voting, voting_password.as_bytes())
.withdrawal_keystore(keystores.withdrawal, withdrawal_password.as_bytes())
.create_eth1_tx_data(deposit_gwei, &spec)
@@ -185,7 +250,7 @@ pub fn cli_run<T: EthSpec>(
.build()
.map_err(|e| format!("Unable to build validator directory: {:?}", e))?;
println!("{}/{}\t0x{}", i + 1, n, voting_pubkey);
println!("{}/{}\t{}", i + 1, n, voting_pubkey.as_hex_string());
}
Ok(())
@@ -193,15 +258,40 @@ pub fn cli_run<T: EthSpec>(
/// Returns the number of validators that exist in the given `validator_dir`.
///
/// This function just assumes all files and directories, excluding the validator definitions YAML,
/// are validator directories, making it likely to return a higher number than accurate
/// but never a lower one.
/// This function just assumes all files and directories, excluding the validator definitions YAML
/// and slashing protection database are validator directories, making it likely to return a higher
/// number than accurate but never a lower one.
fn existing_validator_count<P: AsRef<Path>>(validator_dir: P) -> Result<usize, String> {
fs::read_dir(validator_dir.as_ref())
.map(|iter| {
iter.filter_map(|e| e.ok())
.filter(|e| e.file_name() != OsStr::new(validator_definitions::CONFIG_FILENAME))
.filter(|e| {
e.file_name() != OsStr::new(validator_definitions::CONFIG_FILENAME)
&& e.file_name()
!= OsStr::new(slashing_protection::SLASHING_PROTECTION_FILENAME)
})
.count()
})
.map_err(|e| format!("Unable to read {:?}: {}", validator_dir.as_ref(), e))
}
/// Used when a user is accessing an existing wallet. Read in a wallet password from a file if the password file
/// path is provided. Otherwise, read from an interactive prompt using tty unless the `--stdin-inputs`
/// flag is provided.
pub fn read_wallet_password_from_cli(
password_file_path: Option<PathBuf>,
stdin_inputs: bool,
) -> Result<PlainText, String> {
match password_file_path {
Some(path) => fs::read(&path)
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))
.map(|bytes| strip_off_newlines(bytes).into()),
None => {
eprintln!();
eprintln!("{}", WALLET_PASSWORD_PROMPT);
let password =
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
Ok(password)
}
}
}

View File

@@ -1,405 +0,0 @@
use crate::VALIDATOR_DIR_FLAG;
use clap::{App, Arg, ArgMatches};
use deposit_contract::DEPOSIT_GAS;
use environment::Environment;
use futures::{
compat::Future01CompatExt,
stream::{FuturesUnordered, StreamExt},
};
use slog::{info, Logger};
use state_processing::per_block_processing::verify_deposit_signature;
use std::path::PathBuf;
use tokio::time::{delay_until, Duration, Instant};
use types::EthSpec;
use validator_dir::{Eth1DepositData, Manager as ValidatorManager, ValidatorDir};
use web3::{
transports::Http,
transports::Ipc,
types::{Address, SyncInfo, SyncState, TransactionRequest, U256},
Transport, Web3,
};
pub const CMD: &str = "deposit";
pub const VALIDATOR_FLAG: &str = "validator";
pub const ETH1_IPC_FLAG: &str = "eth1-ipc";
pub const ETH1_HTTP_FLAG: &str = "eth1-http";
pub const FROM_ADDRESS_FLAG: &str = "from-address";
pub const CONFIRMATION_COUNT_FLAG: &str = "confirmation-count";
pub const CONFIRMATION_BATCH_SIZE_FLAG: &str = "confirmation-batch-size";
const GWEI: u64 = 1_000_000_000;
const SYNCING_STATE_RETRY_DELAY: Duration = Duration::from_secs(2);
const CONFIRMATIONS_POLL_TIME: Duration = Duration::from_secs(2);
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new("deposit")
.about(
"Submits a deposit to an Eth1 validator registration contract via an IPC endpoint \
of an Eth1 client (e.g., Geth, OpenEthereum, etc.). The validators must already \
have been created and exist on the file-system. The process will exit immediately \
with an error if any error occurs. After each deposit is submitted to the Eth1 \
node, a file will be saved in the validator directory with the transaction hash. \
If confirmations are set to non-zero then the application will wait for confirmations \
before saving the transaction hash and moving onto the next batch of deposits. \
The deposit contract address will be determined by the --testnet-dir flag on the \
primary Lighthouse binary.",
)
.arg(
Arg::with_name(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG)
.value_name("VALIDATOR_DIRECTORY")
.help(
"The path to the validator client data directory. \
Defaults to ~/.lighthouse/validators",
)
.takes_value(true),
)
.arg(
Arg::with_name(VALIDATOR_FLAG)
.long(VALIDATOR_FLAG)
.value_name("VALIDATOR_NAME")
.help(
"The name of the directory in --data-dir for which to deposit. \
Set to 'all' to deposit all validators in the --data-dir.",
)
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(ETH1_IPC_FLAG)
.long(ETH1_IPC_FLAG)
.value_name("ETH1_IPC_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.required(false),
)
.arg(
Arg::with_name(ETH1_HTTP_FLAG)
.long(ETH1_HTTP_FLAG)
.value_name("ETH1_HTTP_URL")
.help("URL to an Eth1 JSON-RPC endpoint")
.takes_value(true)
.required(false),
)
.arg(
Arg::with_name(FROM_ADDRESS_FLAG)
.long(FROM_ADDRESS_FLAG)
.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(CONFIRMATION_COUNT_FLAG)
.long(CONFIRMATION_COUNT_FLAG)
.value_name("CONFIRMATION_COUNT")
.help(
"The number of Eth1 block confirmations required \
before a transaction is considered complete. Set to \
0 for no confirmations.",
)
.takes_value(true)
.default_value("1"),
)
.arg(
Arg::with_name(CONFIRMATION_BATCH_SIZE_FLAG)
.long(CONFIRMATION_BATCH_SIZE_FLAG)
.value_name("BATCH_SIZE")
.help(
"Perform BATCH_SIZE deposits and wait for confirmations \
in parallel. Useful for achieving faster bulk deposits.",
)
.takes_value(true)
.default_value("10"),
)
}
#[allow(clippy::too_many_arguments)]
fn send_deposit_transactions<T1, T2: 'static>(
mut env: Environment<T1>,
log: Logger,
mut eth1_deposit_datas: Vec<(ValidatorDir, Eth1DepositData)>,
from_address: Address,
deposit_contract: Address,
transport: T2,
confirmation_count: usize,
confirmation_batch_size: usize,
) -> Result<(), String>
where
T1: EthSpec,
T2: Transport + std::marker::Send,
<T2 as web3::Transport>::Out: std::marker::Send,
{
let web3 = Web3::new(transport);
let spec = env.eth2_config.spec.clone();
let deposits_fut = async {
poll_until_synced(web3.clone(), log.clone()).await?;
for chunk in eth1_deposit_datas.chunks_mut(confirmation_batch_size) {
let futures = FuturesUnordered::default();
for (ref mut validator_dir, eth1_deposit_data) in chunk.iter_mut() {
verify_deposit_signature(&eth1_deposit_data.deposit_data, &spec).map_err(|e| {
format!(
"Deposit for {:?} fails verification, \
are you using the correct testnet configuration?\nError: {:?}",
eth1_deposit_data.deposit_data.pubkey, e
)
})?;
let web3 = web3.clone();
let log = log.clone();
futures.push(async move {
let tx_hash = web3
.send_transaction_with_confirmation(
TransactionRequest {
from: from_address,
to: Some(deposit_contract),
gas: Some(DEPOSIT_GAS.into()),
gas_price: None,
value: Some(from_gwei(eth1_deposit_data.deposit_data.amount)),
data: Some(eth1_deposit_data.rlp.clone().into()),
nonce: None,
condition: None,
},
CONFIRMATIONS_POLL_TIME,
confirmation_count,
)
.compat()
.await
.map_err(|e| format!("Failed to send transaction: {:?}", e))?;
info!(
log,
"Submitted deposit";
"tx_hash" => format!("{:?}", tx_hash),
);
validator_dir
.save_eth1_deposit_tx_hash(&format!("{:?}", tx_hash))
.map_err(|e| {
format!("Failed to save tx hash {:?} to disk: {:?}", tx_hash, e)
})?;
Ok::<(), String>(())
});
}
futures
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<Result<_, _>>()?;
}
Ok::<(), String>(())
};
env.runtime().block_on(deposits_fut)?;
Ok(())
}
pub fn cli_run<T: EthSpec>(
matches: &ArgMatches<'_>,
mut env: Environment<T>,
) -> Result<(), String> {
let log = env.core_context().log().clone();
let data_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
VALIDATOR_DIR_FLAG,
PathBuf::new().join(".lighthouse").join("validators"),
)?;
let validator: String = clap_utils::parse_required(matches, VALIDATOR_FLAG)?;
let eth1_ipc_path: Option<PathBuf> = clap_utils::parse_optional(matches, ETH1_IPC_FLAG)?;
let eth1_http_url: Option<String> = clap_utils::parse_optional(matches, ETH1_HTTP_FLAG)?;
let from_address: Address = clap_utils::parse_required(matches, FROM_ADDRESS_FLAG)?;
let confirmation_count: usize = clap_utils::parse_required(matches, CONFIRMATION_COUNT_FLAG)?;
let confirmation_batch_size: usize =
clap_utils::parse_required(matches, CONFIRMATION_BATCH_SIZE_FLAG)?;
let manager = ValidatorManager::open(&data_dir)
.map_err(|e| format!("Unable to read --{}: {:?}", VALIDATOR_DIR_FLAG, e))?;
let validators = match validator.as_ref() {
"all" => manager
.open_all_validators()
.map_err(|e| format!("Unable to read all validators: {:?}", e)),
name => {
let path = manager
.directory_names()
.map_err(|e| {
format!(
"Unable to read --{} directory names: {:?}",
VALIDATOR_DIR_FLAG, e
)
})?
.get(name)
.ok_or_else(|| format!("Unknown validator: {}", name))?
.clone();
manager
.open_validator(&path)
.map_err(|e| format!("Unable to open {}: {:?}", name, e))
.map(|v| vec![v])
}
}?;
let eth1_deposit_datas = validators
.into_iter()
.filter(|v| !v.eth1_deposit_tx_hash_exists())
.map(|v| match v.eth1_deposit_data() {
Ok(Some(data)) => Ok((v, data)),
Ok(None) => Err(format!(
"Validator is missing deposit data file: {:?}",
v.dir()
)),
Err(e) => Err(format!(
"Unable to read deposit data for {:?}: {:?}",
v.dir(),
e
)),
})
.collect::<Result<Vec<_>, _>>()?;
let total_gwei: u64 = eth1_deposit_datas
.iter()
.map(|(_, d)| d.deposit_data.amount)
.sum();
if eth1_deposit_datas.is_empty() {
info!(log, "No validators to deposit");
return Ok(());
}
info!(
log,
"Starting deposits";
"deposit_count" => eth1_deposit_datas.len(),
"total_eth" => total_gwei / GWEI,
);
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());
}
match (eth1_ipc_path, eth1_http_url) {
(Some(_), Some(_)) => Err(format!(
"error: Cannot supply both --{} and --{}",
ETH1_IPC_FLAG, ETH1_HTTP_FLAG
)),
(None, None) => Err(format!(
"error: Must supply one of --{} or --{}",
ETH1_IPC_FLAG, ETH1_HTTP_FLAG
)),
(Some(ipc_path), None) => {
let (_event_loop_handle, ipc_transport) = Ipc::new(ipc_path)
.map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
send_deposit_transactions(
env,
log,
eth1_deposit_datas,
from_address,
deposit_contract,
ipc_transport,
confirmation_count,
confirmation_batch_size,
)
}
(None, Some(http_url)) => {
let (_event_loop_handle, http_transport) = Http::new(http_url.as_str())
.map_err(|e| format!("Unable to connect to eth1 http RPC: {:?}", e))?;
send_deposit_transactions(
env,
log,
eth1_deposit_datas,
from_address,
deposit_contract,
http_transport,
confirmation_count,
confirmation_batch_size,
)
}
}
}
/// Converts gwei to wei.
fn from_gwei(gwei: u64) -> U256 {
U256::from(gwei) * U256::exp10(9)
}
/// Run a poll on the `eth_syncing` endpoint, blocking until the node is synced.
async fn poll_until_synced<T>(web3: Web3<T>, log: Logger) -> Result<(), String>
where
T: Transport + Send + 'static,
<T as Transport>::Out: Send,
{
loop {
let sync_state = web3
.clone()
.eth()
.syncing()
.compat()
.await
.map_err(|e| format!("Unable to read syncing state from eth1 node: {:?}", e))?;
match sync_state {
SyncState::Syncing(SyncInfo {
current_block,
highest_block,
..
}) => {
info!(
log,
"Waiting for eth1 node to sync";
"est_highest_block" => format!("{}", highest_block),
"current_block" => format!("{}", current_block),
);
delay_until(Instant::now() + SYNCING_STATE_RETRY_DELAY).await;
}
SyncState::NotSyncing => {
let block_number = web3
.clone()
.eth()
.block_number()
.compat()
.await
.map_err(|e| format!("Unable to read block number from eth1 node: {:?}", e))?;
if block_number > 0.into() {
info!(
log,
"Eth1 node is synced";
"head_block" => format!("{}", block_number),
);
break;
} else {
delay_until(Instant::now() + SYNCING_STATE_RETRY_DELAY).await;
info!(
log,
"Waiting for eth1 node to sync";
"current_block" => 0,
);
}
}
}
}
Ok(())
}

View File

@@ -0,0 +1,430 @@
use crate::wallet::create::STDIN_INPUTS_FLAG;
use bls::{Keypair, PublicKey};
use clap::{App, Arg, ArgMatches};
use environment::Environment;
use eth2::{
types::{GenesisData, StateId, ValidatorData, ValidatorId, ValidatorStatus},
BeaconNodeHttpClient, Timeouts,
};
use eth2_keystore::Keystore;
use eth2_network_config::Eth2NetworkConfig;
use safe_arith::SafeArith;
use sensitive_url::SensitiveUrl;
use slot_clock::{SlotClock, SystemTimeSlotClock};
use std::path::{Path, PathBuf};
use std::time::Duration;
use tokio::time::sleep;
use types::{ChainSpec, Epoch, EthSpec, Fork, VoluntaryExit};
pub const CMD: &str = "exit";
pub const KEYSTORE_FLAG: &str = "keystore";
pub const PASSWORD_FILE_FLAG: &str = "password-file";
pub const BEACON_SERVER_FLAG: &str = "beacon-node";
pub const NO_WAIT: &str = "no-wait";
pub const NO_CONFIRMATION: &str = "no-confirmation";
pub const PASSWORD_PROMPT: &str = "Enter the keystore password";
pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/";
pub const CONFIRMATION_PHRASE: &str = "Exit my validator";
pub const WEBSITE_URL: &str = "https://lighthouse-book.sigmaprime.io/voluntary-exit.html";
pub const PROMPT: &str = "WARNING: WITHDRAWING STAKED ETH IS NOT CURRENTLY POSSIBLE";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new("exit")
.about("Submits a VoluntaryExit to the beacon chain for a given validator keystore.")
.arg(
Arg::with_name(KEYSTORE_FLAG)
.long(KEYSTORE_FLAG)
.value_name("KEYSTORE_PATH")
.help("The path to the EIP-2335 voting keystore for the validator")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(PASSWORD_FILE_FLAG)
.long(PASSWORD_FILE_FLAG)
.value_name("PASSWORD_FILE_PATH")
.help("The path to the password file which unlocks the validator voting keystore")
.takes_value(true),
)
.arg(
Arg::with_name(BEACON_SERVER_FLAG)
.long(BEACON_SERVER_FLAG)
.value_name("NETWORK_ADDRESS")
.help("Address to a beacon node HTTP API")
.default_value(DEFAULT_BEACON_NODE)
.takes_value(true),
)
.arg(
Arg::with_name(NO_WAIT)
.long(NO_WAIT)
.help("Exits after publishing the voluntary exit without waiting for confirmation that the exit was included in the beacon chain")
)
.arg(
Arg::with_name(NO_CONFIRMATION)
.long(NO_CONFIRMATION)
.help("Exits without prompting for confirmation that you understand the implications of a voluntary exit. This should be used with caution")
)
.arg(
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
}
pub fn cli_run<E: EthSpec>(matches: &ArgMatches, env: Environment<E>) -> Result<(), String> {
let keystore_path: PathBuf = clap_utils::parse_required(matches, KEYSTORE_FLAG)?;
let password_file_path: Option<PathBuf> =
clap_utils::parse_optional(matches, PASSWORD_FILE_FLAG)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
let no_wait = matches.is_present(NO_WAIT);
let no_confirmation = matches.is_present(NO_CONFIRMATION);
let spec = env.eth2_config().spec.clone();
let server_url: String = clap_utils::parse_required(matches, BEACON_SERVER_FLAG)?;
let client = BeaconNodeHttpClient::new(
SensitiveUrl::parse(&server_url)
.map_err(|e| format!("Failed to parse beacon http server: {:?}", e))?,
Timeouts::set_all(Duration::from_secs(env.eth2_config.spec.seconds_per_slot)),
);
let eth2_network_config = env
.eth2_network_config
.clone()
.expect("network should have a valid config");
env.runtime().block_on(publish_voluntary_exit::<E>(
&keystore_path,
password_file_path.as_ref(),
&client,
&spec,
stdin_inputs,
&eth2_network_config,
no_wait,
no_confirmation,
))?;
Ok(())
}
/// Gets the keypair and validator_index for every validator and calls `publish_voluntary_exit` on it.
#[allow(clippy::too_many_arguments)]
async fn publish_voluntary_exit<E: EthSpec>(
keystore_path: &Path,
password_file_path: Option<&PathBuf>,
client: &BeaconNodeHttpClient,
spec: &ChainSpec,
stdin_inputs: bool,
eth2_network_config: &Eth2NetworkConfig,
no_wait: bool,
no_confirmation: bool,
) -> Result<(), String> {
let genesis_data = get_geneisis_data(client).await?;
let testnet_genesis_root = eth2_network_config
.beacon_state::<E>()
.as_ref()
.expect("network should have valid genesis state")
.genesis_validators_root();
// Verify that the beacon node and validator being exited are on the same network.
if genesis_data.genesis_validators_root != testnet_genesis_root {
return Err(
"Invalid genesis state. Please ensure that your beacon node is on the same network \
as the validator you are publishing an exit for"
.to_string(),
);
}
// Return immediately if beacon node is not synced
if is_syncing(client).await? {
return Err("Beacon node is still syncing".to_string());
}
let keypair = load_voting_keypair(keystore_path, password_file_path, stdin_inputs)?;
let epoch = get_current_epoch::<E>(genesis_data.genesis_time, spec)
.ok_or("Failed to get current epoch. Please check your system time")?;
let validator_index = get_validator_index_for_exit(client, &keypair.pk, epoch, spec).await?;
let fork = get_beacon_state_fork(client).await?;
let voluntary_exit = VoluntaryExit {
epoch,
validator_index,
};
eprintln!(
"Publishing a voluntary exit for validator: {} \n",
keypair.pk
);
if !no_confirmation {
eprintln!("WARNING: THIS IS AN IRREVERSIBLE OPERATION\n");
eprintln!("{}\n", PROMPT);
eprintln!(
"PLEASE VISIT {} TO MAKE SURE YOU UNDERSTAND THE IMPLICATIONS OF A VOLUNTARY EXIT.",
WEBSITE_URL
);
eprintln!("Enter the exit phrase from the above URL to confirm the voluntary exit: ");
}
let confirmation = if !no_confirmation {
account_utils::read_input_from_user(stdin_inputs)?
} else {
CONFIRMATION_PHRASE.to_string()
};
if confirmation == CONFIRMATION_PHRASE {
// Sign and publish the voluntary exit to network
let signed_voluntary_exit = voluntary_exit.sign(
&keypair.sk,
&fork,
genesis_data.genesis_validators_root,
spec,
);
client
.post_beacon_pool_voluntary_exits(&signed_voluntary_exit)
.await
.map_err(|e| format!("Failed to publish voluntary exit: {}", e))?;
tokio::time::sleep(std::time::Duration::from_secs(1)).await; // Provides nicer UX.
eprintln!(
"Successfully validated and published voluntary exit for validator {}",
keypair.pk
);
} else {
eprintln!(
"Did not publish voluntary exit for validator {}. Please check that you entered the correct exit phrase.",
keypair.pk
);
return Ok(());
}
if no_wait {
return Ok(());
}
loop {
// Sleep for a slot duration and then check if voluntary exit was processed
// by checking the validator status.
sleep(Duration::from_secs(spec.seconds_per_slot)).await;
let validator_data = get_validator_data(client, &keypair.pk).await?;
match validator_data.status {
ValidatorStatus::ActiveExiting => {
let exit_epoch = validator_data.validator.exit_epoch;
let withdrawal_epoch = validator_data.validator.withdrawable_epoch;
let current_epoch = get_current_epoch::<E>(genesis_data.genesis_time, spec)
.ok_or("Failed to get current epoch. Please check your system time")?;
eprintln!("Voluntary exit has been accepted into the beacon chain, but not yet finalized. \
Finalization may take several minutes or longer. Before finalization there is a low \
probability that the exit may be reverted.");
eprintln!(
"Current epoch: {}, Exit epoch: {}, Withdrawable epoch: {}",
current_epoch, exit_epoch, withdrawal_epoch
);
eprintln!("Please keep your validator running till exit epoch");
eprintln!(
"Exit epoch in approximately {} secs",
(exit_epoch - current_epoch) * spec.seconds_per_slot * E::slots_per_epoch()
);
break;
}
ValidatorStatus::ExitedSlashed | ValidatorStatus::ExitedUnslashed => {
eprintln!(
"Validator has exited on epoch: {}",
validator_data.validator.exit_epoch
);
break;
}
_ => eprintln!("Waiting for voluntary exit to be accepted into the beacon chain..."),
}
}
Ok(())
}
/// Get the validator index of a given the validator public key by querying the beacon node endpoint.
///
/// Returns an error if the beacon endpoint returns an error or given validator is not eligible for an exit.
async fn get_validator_index_for_exit(
client: &BeaconNodeHttpClient,
validator_pubkey: &PublicKey,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<u64, String> {
let validator_data = get_validator_data(client, validator_pubkey).await?;
match validator_data.status {
ValidatorStatus::ActiveOngoing => {
let eligible_epoch = validator_data
.validator
.activation_epoch
.safe_add(spec.shard_committee_period)
.map_err(|e| format!("Failed to calculate eligible epoch, validator activation epoch too high: {:?}", e))?;
if epoch >= eligible_epoch {
Ok(validator_data.index)
} else {
Err(format!(
"Validator {:?} is not eligible for exit. It will become eligible on epoch {}",
validator_pubkey, eligible_epoch
))
}
}
status => Err(format!(
"Validator {:?} is not eligible for voluntary exit. Validator status: {:?}",
validator_pubkey, status
)),
}
}
/// Returns the validator data by querying the beacon node client.
async fn get_validator_data(
client: &BeaconNodeHttpClient,
validator_pubkey: &PublicKey,
) -> Result<ValidatorData, String> {
Ok(client
.get_beacon_states_validator_id(
StateId::Head,
&ValidatorId::PublicKey(validator_pubkey.into()),
)
.await
.map_err(|e| format!("Failed to get validator details: {:?}", e))?
.ok_or_else(|| {
format!(
"Validator {} is not present in the beacon state. \
Please ensure that your beacon node is synced and the validator has been deposited.",
validator_pubkey
)
})?
.data)
}
/// Get genesis data by querying the beacon node client.
async fn get_geneisis_data(client: &BeaconNodeHttpClient) -> Result<GenesisData, String> {
Ok(client
.get_beacon_genesis()
.await
.map_err(|e| format!("Failed to get beacon genesis: {}", e))?
.data)
}
/// Gets syncing status from beacon node client and returns true if syncing and false otherwise.
async fn is_syncing(client: &BeaconNodeHttpClient) -> Result<bool, String> {
Ok(client
.get_node_syncing()
.await
.map_err(|e| format!("Failed to get sync status: {:?}", e))?
.data
.is_syncing)
}
/// Get fork object for the current state by querying the beacon node client.
async fn get_beacon_state_fork(client: &BeaconNodeHttpClient) -> Result<Fork, String> {
Ok(client
.get_beacon_states_fork(StateId::Head)
.await
.map_err(|e| format!("Failed to get get fork: {:?}", e))?
.ok_or("Failed to get fork, state not found")?
.data)
}
/// Calculates the current epoch from the genesis time and current time.
fn get_current_epoch<E: EthSpec>(genesis_time: u64, spec: &ChainSpec) -> Option<Epoch> {
let slot_clock = SystemTimeSlotClock::new(
spec.genesis_slot,
Duration::from_secs(genesis_time),
Duration::from_secs(spec.seconds_per_slot),
);
slot_clock.now().map(|s| s.epoch(E::slots_per_epoch()))
}
/// Load the voting keypair by loading and decrypting the keystore.
///
/// If the `password_file_path` is Some, unlock keystore using password in given file
/// otherwise, prompts user for a password to unlock the keystore.
fn load_voting_keypair(
voting_keystore_path: &Path,
password_file_path: Option<&PathBuf>,
stdin_inputs: bool,
) -> Result<Keypair, String> {
let keystore = Keystore::from_json_file(&voting_keystore_path).map_err(|e| {
format!(
"Unable to read keystore JSON {:?}: {:?}",
voting_keystore_path, e
)
})?;
// Get password from password file.
if let Some(password_file) = password_file_path {
validator_dir::unlock_keypair_from_password_path(voting_keystore_path, password_file)
.map_err(|e| format!("Error while decrypting keypair: {:?}", e))
} else {
// Prompt password from user.
eprintln!();
eprintln!(
"{} for validator in {:?}: ",
PASSWORD_PROMPT, voting_keystore_path
);
let password = account_utils::read_password_from_user(stdin_inputs)?;
match keystore.decrypt_keypair(password.as_ref()) {
Ok(keypair) => {
eprintln!("Password is correct.");
eprintln!();
std::thread::sleep(std::time::Duration::from_secs(1)); // Provides nicer UX.
Ok(keypair)
}
Err(eth2_keystore::Error::InvalidPassword) => Err("Invalid password".to_string()),
Err(e) => Err(format!("Error while decrypting keypair: {:?}", e)),
}
}
}
#[cfg(test)]
#[cfg(not(debug_assertions))]
mod tests {
use super::*;
use eth2_keystore::KeystoreBuilder;
use std::fs::File;
use std::io::Write;
use tempfile::{tempdir, TempDir};
const PASSWORD: &str = "cats";
const KEYSTORE_NAME: &str = "keystore-m_12381_3600_0_0_0-1595406747.json";
const PASSWORD_FILE: &str = "password.pass";
fn create_and_save_keystore(dir: &TempDir, save_password: bool) -> PublicKey {
let keypair = Keypair::random();
let keystore = KeystoreBuilder::new(&keypair, PASSWORD.as_bytes(), "".into())
.unwrap()
.build()
.unwrap();
// Create a keystore.
File::create(dir.path().join(KEYSTORE_NAME))
.map(|mut file| keystore.to_json_writer(&mut file).unwrap())
.unwrap();
if save_password {
File::create(dir.path().join(PASSWORD_FILE))
.map(|mut file| file.write_all(PASSWORD.as_bytes()).unwrap())
.unwrap();
}
keystore.public_key().unwrap()
}
#[test]
fn test_load_keypair_password_file() {
let dir = tempdir().unwrap();
let expected_pk = create_and_save_keystore(&dir, true);
let kp = load_voting_keypair(
&dir.path().join(KEYSTORE_NAME),
Some(&dir.path().join(PASSWORD_FILE)),
false,
)
.unwrap();
assert_eq!(expected_pk, kp.pk.into());
}
}

View File

@@ -1,4 +1,5 @@
use crate::{common::ensure_dir_exists, VALIDATOR_DIR_FLAG};
use crate::wallet::create::{PASSWORD_FLAG, STDIN_INPUTS_FLAG};
use account_utils::validator_definitions::SigningDefinition;
use account_utils::{
eth2_keystore::Keystore,
read_password_from_user,
@@ -6,8 +7,10 @@ use account_utils::{
recursively_find_voting_keystores, ValidatorDefinition, ValidatorDefinitions,
CONFIG_FILENAME,
},
ZeroizeString,
};
use clap::{App, Arg, ArgMatches};
use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
use std::fs;
use std::path::PathBuf;
use std::thread::sleep;
@@ -16,7 +19,7 @@ use std::time::Duration;
pub const CMD: &str = "import";
pub const KEYSTORE_FLAG: &str = "keystore";
pub const DIR_FLAG: &str = "directory";
pub const STDIN_PASSWORD_FLAG: &str = "stdin-passwords";
pub const REUSE_PASSWORD_FLAG: &str = "reuse-password";
pub const PASSWORD_PROMPT: &str = "Enter the keystore password, or press enter to omit it:";
pub const KEYSTORE_REUSE_WARNING: &str = "DO NOT USE THE ORIGINAL KEYSTORES TO VALIDATE WITH \
@@ -54,37 +57,61 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.takes_value(true),
)
.arg(
Arg::with_name(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG)
.value_name("VALIDATOR_DIRECTORY")
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
.arg(
Arg::with_name(REUSE_PASSWORD_FLAG)
.long(REUSE_PASSWORD_FLAG)
.help("If present, the same password will be used for all imported keystores."),
)
.arg(
Arg::with_name(PASSWORD_FLAG)
.long(PASSWORD_FLAG)
.value_name("KEYSTORE_PASSWORD_PATH")
.requires(REUSE_PASSWORD_FLAG)
.help(
"The path where the validator directories will be created. \
Defaults to ~/.lighthouse/validators",
"The path to the file containing the password which will unlock all \
keystores being imported. This flag must be used with `--reuse-password`. \
The password will be copied to the `validator_definitions.yml` file, so after \
import we strongly recommend you delete the file at KEYSTORE_PASSWORD_PATH.",
)
.takes_value(true),
)
.arg(
Arg::with_name(STDIN_PASSWORD_FLAG)
.long(STDIN_PASSWORD_FLAG)
.help("If present, read passwords from stdin instead of tty."),
)
}
pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), String> {
let keystore: Option<PathBuf> = clap_utils::parse_optional(matches, KEYSTORE_FLAG)?;
let keystores_dir: Option<PathBuf> = clap_utils::parse_optional(matches, DIR_FLAG)?;
let validator_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
VALIDATOR_DIR_FLAG,
PathBuf::new().join(".lighthouse").join("validators"),
)?;
let stdin_password = matches.is_present(STDIN_PASSWORD_FLAG);
ensure_dir_exists(&validator_dir)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
let reuse_password = matches.is_present(REUSE_PASSWORD_FLAG);
let keystore_password_path: Option<PathBuf> =
clap_utils::parse_optional(matches, PASSWORD_FLAG)?;
let mut defs = ValidatorDefinitions::open_or_create(&validator_dir)
.map_err(|e| format!("Unable to open {}: {:?}", CONFIG_FILENAME, e))?;
let slashing_protection_path = validator_dir.join(SLASHING_PROTECTION_FILENAME);
let slashing_protection =
SlashingDatabase::open_or_create(&slashing_protection_path).map_err(|e| {
format!(
"Unable to open or create slashing protection database at {}: {:?}",
slashing_protection_path.display(),
e
)
})?;
// Create an empty transaction and drop it. Used to test if the database is locked.
slashing_protection.test_transaction().map_err(|e| {
format!(
"Cannot import keys while the validator client is running: {:?}",
e
)
})?;
// Collect the paths for the keystores that should be imported.
let keystore_paths = match (keystore, keystores_dir) {
(Some(keystore), None) => vec![keystore],
@@ -115,20 +142,24 @@ pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
//
// - Obtain the keystore password, if the user desires.
// - Copy the keystore into the `validator_dir`.
// - Register the voting key with the slashing protection database.
// - Add the keystore to the validator definitions file.
//
// Skip keystores that already exist, but exit early if any operation fails.
// Reuses the same password for all keystores if the `REUSE_PASSWORD_FLAG` flag is set.
let mut num_imported_keystores = 0;
let mut previous_password: Option<ZeroizeString> = None;
for src_keystore in &keystore_paths {
let keystore = Keystore::from_json_file(src_keystore)
.map_err(|e| format!("Unable to read keystore JSON {:?}: {:?}", src_keystore, e))?;
eprintln!("");
eprintln!();
eprintln!("Keystore found at {:?}:", src_keystore);
eprintln!("");
eprintln!();
eprintln!(" - Public key: 0x{}", keystore.pubkey());
eprintln!(" - UUID: {}", keystore.uuid());
eprintln!("");
eprintln!();
eprintln!(
"If you enter the password it will be stored as plain-text in {} so that it is not \
required each time the validator client starts.",
@@ -136,22 +167,39 @@ pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
);
let password_opt = loop {
eprintln!("");
if let Some(password) = previous_password.clone() {
eprintln!("Reuse previous password.");
break Some(password);
}
eprintln!();
eprintln!("{}", PASSWORD_PROMPT);
let password = read_password_from_user(stdin_password)?;
if password.as_ref().is_empty() {
eprintln!("Continuing without password.");
sleep(Duration::from_secs(1)); // Provides nicer UX.
break None;
}
let password = match keystore_password_path.as_ref() {
Some(path) => {
let password_from_file: ZeroizeString = fs::read_to_string(&path)
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))?
.into();
password_from_file.without_newlines()
}
None => {
let password_from_user = read_password_from_user(stdin_inputs)?;
if password_from_user.as_ref().is_empty() {
eprintln!("Continuing without password.");
sleep(Duration::from_secs(1)); // Provides nicer UX.
break None;
}
password_from_user
}
};
match keystore.decrypt_keypair(password.as_ref()) {
Ok(_) => {
eprintln!("Password is correct.");
eprintln!("");
eprintln!();
sleep(Duration::from_secs(1)); // Provides nicer UX.
if reuse_password {
previous_password = Some(password.clone());
}
break Some(password);
}
Err(eth2_keystore::Error::InvalidPassword) => {
@@ -161,10 +209,35 @@ pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
}
};
let voting_pubkey = keystore
.public_key()
.ok_or_else(|| format!("Keystore public key is invalid: {}", keystore.pubkey()))?;
// The keystore is placed in a directory that matches the name of the public key. This
// provides some loose protection against adding the same keystore twice.
let dest_dir = validator_dir.join(format!("0x{}", keystore.pubkey()));
if dest_dir.exists() {
// Check if we should update password for existing validator in case if it was provided via reimport: #2854
let old_validator_def_opt = defs
.as_mut_slice()
.iter_mut()
.find(|def| def.voting_public_key == voting_pubkey);
if let Some(ValidatorDefinition {
signing_definition:
SigningDefinition::LocalKeystore {
voting_keystore_password: ref mut old_passwd,
..
},
..
}) = old_validator_def_opt
{
if old_passwd.is_none() && password_opt.is_some() {
*old_passwd = password_opt;
defs.save(&validator_dir)
.map_err(|e| format!("Unable to save {}: {:?}", CONFIG_FILENAME, e))?;
eprintln!("Password updated for public key {}", voting_pubkey);
}
}
eprintln!(
"Skipping import of keystore for existing public key: {:?}",
src_keystore
@@ -186,12 +259,29 @@ pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
fs::copy(&src_keystore, &dest_keystore)
.map_err(|e| format!("Unable to copy keystore: {:?}", e))?;
// Register with slashing protection.
slashing_protection
.register_validator(voting_pubkey.compress())
.map_err(|e| {
format!(
"Error registering validator {}: {:?}",
voting_pubkey.as_hex_string(),
e
)
})?;
eprintln!("Successfully imported keystore.");
num_imported_keystores += 1;
let validator_def =
ValidatorDefinition::new_keystore_with_password(&dest_keystore, password_opt)
.map_err(|e| format!("Unable to create new validator definition: {:?}", e))?;
let graffiti = None;
let suggested_fee_recipient = None;
let validator_def = ValidatorDefinition::new_keystore_with_password(
&dest_keystore,
password_opt,
graffiti,
suggested_fee_recipient,
)
.map_err(|e| format!("Unable to create new validator definition: {:?}", e))?;
defs.push(validator_def);
@@ -201,13 +291,13 @@ pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
eprintln!("Successfully updated {}.", CONFIG_FILENAME);
}
eprintln!("");
eprintln!();
eprintln!(
"Successfully imported {} validators ({} skipped).",
num_imported_keystores,
keystore_paths.len() - num_imported_keystores
);
eprintln!("");
eprintln!();
eprintln!("WARNING: {}", KEYSTORE_REUSE_WARNING);
Ok(())

View File

@@ -1,40 +1,27 @@
use crate::VALIDATOR_DIR_FLAG;
use clap::{App, Arg, ArgMatches};
use account_utils::validator_definitions::ValidatorDefinitions;
use clap::App;
use std::path::PathBuf;
use validator_dir::Manager as ValidatorManager;
pub const CMD: &str = "list";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.arg(
Arg::with_name(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG)
.value_name("VALIDATOR_DIRECTORY")
.help(
"The path to search for validator directories. \
Defaults to ~/.lighthouse/validators",
)
.takes_value(true),
)
.about("Lists the names of all validators.")
App::new(CMD).about("Lists the public keys of all validators.")
}
pub fn cli_run(matches: &ArgMatches<'_>) -> Result<(), String> {
let data_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
VALIDATOR_DIR_FLAG,
PathBuf::new().join(".lighthouse").join("validators"),
)?;
pub fn cli_run(validator_dir: PathBuf) -> Result<(), String> {
let validator_definitions = ValidatorDefinitions::open(&validator_dir).map_err(|e| {
format!(
"No validator definitions found in {:?}: {:?}",
validator_dir, e
)
})?;
let mgr = ValidatorManager::open(&data_dir)
.map_err(|e| format!("Unable to read --{}: {:?}", VALIDATOR_DIR_FLAG, e))?;
for (name, _path) in mgr
.directory_names()
.map_err(|e| format!("Unable to list wallets: {:?}", e))?
{
println!("{}", name)
for def in validator_definitions.as_slice() {
println!(
"{} ({})",
def.voting_public_key,
if def.enabled { "enabled" } else { "disabled" }
);
}
Ok(())

View File

@@ -1,11 +1,16 @@
pub mod create;
pub mod deposit;
pub mod exit;
pub mod import;
pub mod list;
pub mod modify;
pub mod recover;
pub mod slashing_protection;
use crate::common::base_wallet_dir;
use crate::{VALIDATOR_DIR_FLAG, VALIDATOR_DIR_FLAG_ALIAS};
use clap::{App, Arg, ArgMatches};
use directory::{parse_path_or_default_with_flag, DEFAULT_VALIDATOR_DIR};
use environment::Environment;
use std::path::PathBuf;
use types::EthSpec;
pub const CMD: &str = "validator";
@@ -14,26 +19,45 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about("Provides commands for managing Eth2 validators.")
.arg(
Arg::with_name("base-dir")
.long("base-dir")
.value_name("BASE_DIRECTORY")
.help("A path containing Eth2 EIP-2386 wallets. Defaults to ~/.lighthouse/wallets")
.takes_value(true),
Arg::with_name(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG)
.alias(VALIDATOR_DIR_FLAG_ALIAS)
.value_name("VALIDATOR_DIRECTORY")
.help(
"The path to search for validator directories. \
Defaults to ~/.lighthouse/{network}/validators",
)
.takes_value(true)
.conflicts_with("datadir"),
)
.subcommand(create::cli_app())
.subcommand(deposit::cli_app())
.subcommand(modify::cli_app())
.subcommand(import::cli_app())
.subcommand(list::cli_app())
.subcommand(recover::cli_app())
.subcommand(slashing_protection::cli_app())
.subcommand(exit::cli_app())
}
pub fn cli_run<T: EthSpec>(matches: &ArgMatches, env: Environment<T>) -> Result<(), String> {
let base_wallet_dir = base_wallet_dir(matches, "base-dir")?;
let validator_base_dir = if matches.value_of("datadir").is_some() {
let path: PathBuf = clap_utils::parse_required(matches, "datadir")?;
path.join(DEFAULT_VALIDATOR_DIR)
} else {
parse_path_or_default_with_flag(matches, VALIDATOR_DIR_FLAG, DEFAULT_VALIDATOR_DIR)?
};
eprintln!("validator-dir path: {:?}", validator_base_dir);
match matches.subcommand() {
(create::CMD, Some(matches)) => create::cli_run::<T>(matches, env, base_wallet_dir),
(deposit::CMD, Some(matches)) => deposit::cli_run::<T>(matches, env),
(import::CMD, Some(matches)) => import::cli_run(matches),
(list::CMD, Some(matches)) => list::cli_run(matches),
(create::CMD, Some(matches)) => create::cli_run::<T>(matches, env, validator_base_dir),
(modify::CMD, Some(matches)) => modify::cli_run(matches, validator_base_dir),
(import::CMD, Some(matches)) => import::cli_run(matches, validator_base_dir),
(list::CMD, Some(_)) => list::cli_run(validator_base_dir),
(recover::CMD, Some(matches)) => recover::cli_run(matches, validator_base_dir),
(slashing_protection::CMD, Some(matches)) => {
slashing_protection::cli_run(matches, env, validator_base_dir)
}
(exit::CMD, Some(matches)) => exit::cli_run(matches, env),
(unknown, _) => Err(format!(
"{} does not have a {} command. See --help",
CMD, unknown

View File

@@ -0,0 +1,100 @@
use account_utils::validator_definitions::ValidatorDefinitions;
use bls::PublicKey;
use clap::{App, Arg, ArgMatches};
use std::{collections::HashSet, path::PathBuf};
pub const CMD: &str = "modify";
pub const ENABLE: &str = "enable";
pub const DISABLE: &str = "disable";
pub const PUBKEY_FLAG: &str = "pubkey";
pub const ALL: &str = "all";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about("Modify validator status in validator_definitions.yml.")
.subcommand(
App::new(ENABLE)
.about("Enable validator(s) in validator_definitions.yml.")
.arg(
Arg::with_name(PUBKEY_FLAG)
.long(PUBKEY_FLAG)
.value_name("PUBKEY")
.help("Validator pubkey to enable")
.takes_value(true),
)
.arg(
Arg::with_name(ALL)
.long(ALL)
.help("Enable all validators in the validator directory")
.takes_value(false)
.conflicts_with(PUBKEY_FLAG),
),
)
.subcommand(
App::new(DISABLE)
.about("Disable validator(s) in validator_definitions.yml.")
.arg(
Arg::with_name(PUBKEY_FLAG)
.long(PUBKEY_FLAG)
.value_name("PUBKEY")
.help("Validator pubkey to disable")
.takes_value(true),
)
.arg(
Arg::with_name(ALL)
.long(ALL)
.help("Disable all validators in the validator directory")
.takes_value(false)
.conflicts_with(PUBKEY_FLAG),
),
)
}
pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), String> {
// `true` implies we are setting `validator_definition.enabled = true` and
// vice versa.
let (enabled, sub_matches) = match matches.subcommand() {
(ENABLE, Some(sub_matches)) => (true, sub_matches),
(DISABLE, Some(sub_matches)) => (false, sub_matches),
(unknown, _) => {
return Err(format!(
"{} does not have a {} command. See --help",
CMD, unknown
))
}
};
let mut defs = ValidatorDefinitions::open(&validator_dir).map_err(|e| {
format!(
"No validator definitions found in {:?}: {:?}",
validator_dir, e
)
})?;
let pubkeys_to_modify = if sub_matches.is_present(ALL) {
defs.as_slice()
.iter()
.map(|def| def.voting_public_key.clone())
.collect::<HashSet<_>>()
} else {
let public_key: PublicKey = clap_utils::parse_required(sub_matches, PUBKEY_FLAG)?;
std::iter::once(public_key).collect::<HashSet<PublicKey>>()
};
// Modify required entries from validator_definitions.
for def in defs.as_mut_slice() {
if pubkeys_to_modify.contains(&def.voting_public_key) {
def.enabled = enabled;
eprintln!(
"Validator {} {}",
def.voting_public_key,
if enabled { "enabled" } else { "disabled" }
);
}
}
defs.save(&validator_dir)
.map_err(|e| format!("Unable to modify validator definitions: {:?}", e))?;
eprintln!("\nSuccessfully modified validator_definitions.yml");
Ok(())
}

View File

@@ -0,0 +1,149 @@
use super::create::STORE_WITHDRAW_FLAG;
use crate::common::read_mnemonic_from_cli;
use crate::validator::create::COUNT_FLAG;
use crate::wallet::create::STDIN_INPUTS_FLAG;
use crate::SECRETS_DIR_FLAG;
use account_utils::eth2_keystore::{keypair_from_secret, Keystore, KeystoreBuilder};
use account_utils::random_password;
use clap::{App, Arg, ArgMatches};
use directory::ensure_dir_exists;
use directory::{parse_path_or_default_with_flag, DEFAULT_SECRET_DIR};
use eth2_wallet::bip39::Seed;
use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType, ValidatorKeystores};
use std::path::PathBuf;
use validator_dir::Builder as ValidatorDirBuilder;
pub const CMD: &str = "recover";
pub const FIRST_INDEX_FLAG: &str = "first-index";
pub const MNEMONIC_FLAG: &str = "mnemonic-path";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about(
"Recovers validator private keys given a BIP-39 mnemonic phrase. \
If you did not specify a `--first-index` or count `--count`, by default this will \
only recover the keys associated with the validator at index 0 for an HD wallet \
in accordance with the EIP-2333 spec.")
.arg(
Arg::with_name(FIRST_INDEX_FLAG)
.long(FIRST_INDEX_FLAG)
.value_name("FIRST_INDEX")
.help("The first of consecutive key indexes you wish to recover.")
.takes_value(true)
.required(false)
.default_value("0"),
)
.arg(
Arg::with_name(COUNT_FLAG)
.long(COUNT_FLAG)
.value_name("COUNT")
.help("The number of validator keys you wish to recover. Counted consecutively from the provided `--first_index`.")
.takes_value(true)
.required(false)
.default_value("1"),
)
.arg(
Arg::with_name(MNEMONIC_FLAG)
.long(MNEMONIC_FLAG)
.value_name("MNEMONIC_PATH")
.help(
"If present, the mnemonic will be read in from this file.",
)
.takes_value(true)
)
.arg(
Arg::with_name(SECRETS_DIR_FLAG)
.long(SECRETS_DIR_FLAG)
.value_name("SECRETS_DIR")
.help(
"The path where the validator keystore passwords will be stored. \
Defaults to ~/.lighthouse/{network}/secrets",
)
.takes_value(true),
)
.arg(
Arg::with_name(STORE_WITHDRAW_FLAG)
.long(STORE_WITHDRAW_FLAG)
.help(
"If present, the withdrawal keystore will be stored alongside the voting \
keypair. It is generally recommended to *not* store the withdrawal key and \
instead generate them from the wallet seed when required.",
),
)
.arg(
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
}
pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), String> {
let secrets_dir = if matches.value_of("datadir").is_some() {
let path: PathBuf = clap_utils::parse_required(matches, "datadir")?;
path.join(DEFAULT_SECRET_DIR)
} else {
parse_path_or_default_with_flag(matches, SECRETS_DIR_FLAG, DEFAULT_SECRET_DIR)?
};
let first_index: u32 = clap_utils::parse_required(matches, FIRST_INDEX_FLAG)?;
let count: u32 = clap_utils::parse_required(matches, COUNT_FLAG)?;
let mnemonic_path: Option<PathBuf> = clap_utils::parse_optional(matches, MNEMONIC_FLAG)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
eprintln!("secrets-dir path: {:?}", secrets_dir);
ensure_dir_exists(&validator_dir)?;
ensure_dir_exists(&secrets_dir)?;
eprintln!();
eprintln!("WARNING: KEY RECOVERY CAN LEAD TO DUPLICATING VALIDATORS KEYS, WHICH CAN LEAD TO SLASHING.");
eprintln!();
let mnemonic = read_mnemonic_from_cli(mnemonic_path, stdin_inputs)?;
let seed = Seed::new(&mnemonic, "");
for index in first_index..first_index + count {
let voting_password = random_password();
let withdrawal_password = random_password();
let derive = |key_type: KeyType, password: &[u8]| -> Result<Keystore, String> {
let (secret, path) =
recover_validator_secret_from_mnemonic(seed.as_bytes(), index, key_type)
.map_err(|e| format!("Unable to recover validator keys: {:?}", e))?;
let keypair = keypair_from_secret(secret.as_bytes())
.map_err(|e| format!("Unable build keystore: {:?}", e))?;
KeystoreBuilder::new(&keypair, password, format!("{}", path))
.map_err(|e| format!("Unable build keystore: {:?}", e))?
.build()
.map_err(|e| format!("Unable build keystore: {:?}", e))
};
let keystores = ValidatorKeystores {
voting: derive(KeyType::Voting, voting_password.as_bytes())?,
withdrawal: derive(KeyType::Withdrawal, withdrawal_password.as_bytes())?,
};
let voting_pubkey = keystores.voting.pubkey().to_string();
ValidatorDirBuilder::new(validator_dir.clone())
.password_dir(secrets_dir.clone())
.voting_keystore(keystores.voting, voting_password.as_bytes())
.withdrawal_keystore(keystores.withdrawal, withdrawal_password.as_bytes())
.store_withdrawal_keystore(matches.is_present(STORE_WITHDRAW_FLAG))
.build()
.map_err(|e| format!("Unable to build validator directory: {:?}", e))?;
println!(
"{}/{}\tIndex: {}\t0x{}",
index - first_index,
count - first_index,
index,
voting_pubkey
);
}
Ok(())
}

View File

@@ -0,0 +1,272 @@
use clap::{App, Arg, ArgMatches};
use environment::Environment;
use slashing_protection::{
interchange::Interchange, InterchangeError, InterchangeImportOutcome, SlashingDatabase,
SLASHING_PROTECTION_FILENAME,
};
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use types::{BeaconState, Epoch, EthSpec, PublicKeyBytes, Slot};
pub const CMD: &str = "slashing-protection";
pub const IMPORT_CMD: &str = "import";
pub const EXPORT_CMD: &str = "export";
pub const IMPORT_FILE_ARG: &str = "IMPORT-FILE";
pub const EXPORT_FILE_ARG: &str = "EXPORT-FILE";
pub const MINIFY_FLAG: &str = "minify";
pub const PUBKEYS_FLAG: &str = "pubkeys";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about("Import or export slashing protection data to or from another client")
.subcommand(
App::new(IMPORT_CMD)
.about("Import an interchange file")
.arg(
Arg::with_name(IMPORT_FILE_ARG)
.takes_value(true)
.value_name("FILE")
.help("The slashing protection interchange file to import (.json)"),
)
.arg(
Arg::with_name(MINIFY_FLAG)
.long(MINIFY_FLAG)
.takes_value(true)
.possible_values(&["false", "true"])
.help(
"Deprecated: Lighthouse no longer requires minification on import \
because it always minifies",
),
),
)
.subcommand(
App::new(EXPORT_CMD)
.about("Export an interchange file")
.arg(
Arg::with_name(EXPORT_FILE_ARG)
.takes_value(true)
.value_name("FILE")
.help("The filename to export the interchange file to"),
)
.arg(
Arg::with_name(PUBKEYS_FLAG)
.long(PUBKEYS_FLAG)
.takes_value(true)
.value_name("PUBKEYS")
.help(
"List of public keys to export history for. Keys should be 0x-prefixed, \
comma-separated. All known keys will be exported if omitted",
),
)
.arg(
Arg::with_name(MINIFY_FLAG)
.long(MINIFY_FLAG)
.takes_value(true)
.default_value("false")
.possible_values(&["false", "true"])
.help(
"Minify the output file. This will make it smaller and faster to \
import, but not faster to generate.",
),
),
)
}
pub fn cli_run<T: EthSpec>(
matches: &ArgMatches<'_>,
env: Environment<T>,
validator_base_dir: PathBuf,
) -> Result<(), String> {
let slashing_protection_db_path = validator_base_dir.join(SLASHING_PROTECTION_FILENAME);
let eth2_network_config = env
.eth2_network_config
.ok_or("Unable to get testnet configuration from the environment")?;
let genesis_validators_root = eth2_network_config
.beacon_state::<T>()
.map(|state: BeaconState<T>| state.genesis_validators_root())
.map_err(|e| {
format!(
"Unable to get genesis state, has genesis occurred? Detail: {:?}",
e
)
})?;
match matches.subcommand() {
(IMPORT_CMD, Some(matches)) => {
let import_filename: PathBuf = clap_utils::parse_required(matches, IMPORT_FILE_ARG)?;
let minify: Option<bool> = clap_utils::parse_optional(matches, MINIFY_FLAG)?;
let import_file = File::open(&import_filename).map_err(|e| {
format!(
"Unable to open import file at {}: {:?}",
import_filename.display(),
e
)
})?;
eprint!("Loading JSON file into memory & deserializing");
let mut interchange = Interchange::from_json_reader(&import_file)
.map_err(|e| format!("Error parsing file for import: {:?}", e))?;
eprintln!(" [done].");
if let Some(minify) = minify {
eprintln!(
"WARNING: --minify flag is deprecated and will be removed in a future release"
);
if minify {
eprint!("Minifying input file for faster loading");
interchange = interchange
.minify()
.map_err(|e| format!("Minification failed: {:?}", e))?;
eprintln!(" [done].");
}
}
let slashing_protection_database =
SlashingDatabase::open_or_create(&slashing_protection_db_path).map_err(|e| {
format!(
"Unable to open database at {}: {:?}",
slashing_protection_db_path.display(),
e
)
})?;
let display_slot = |slot: Option<Slot>| {
slot.map_or("none".to_string(), |slot| format!("slot {}", slot.as_u64()))
};
let display_epoch = |epoch: Option<Epoch>| {
epoch.map_or("?".to_string(), |epoch| format!("epoch {}", epoch.as_u64()))
};
let display_attestation = |source, target| match (source, target) {
(None, None) => "none".to_string(),
(source, target) => {
format!("{} => {}", display_epoch(source), display_epoch(target))
}
};
match slashing_protection_database
.import_interchange_info(interchange, genesis_validators_root)
{
Ok(outcomes) => {
eprintln!("All records imported successfully:");
for outcome in &outcomes {
match outcome {
InterchangeImportOutcome::Success { pubkey, summary } => {
eprintln!("- {:?}", pubkey);
eprintln!(
" - latest block: {}",
display_slot(summary.max_block_slot)
);
eprintln!(
" - latest attestation: {}",
display_attestation(
summary.max_attestation_source,
summary.max_attestation_target
)
);
}
InterchangeImportOutcome::Failure { pubkey, error } => {
panic!(
"import should be atomic, but key {:?} was imported despite error: {:?}",
pubkey, error
);
}
}
}
}
Err(InterchangeError::AtomicBatchAborted(outcomes)) => {
eprintln!("ERROR: import aborted due to one or more errors");
for outcome in &outcomes {
if let InterchangeImportOutcome::Failure { pubkey, error } = outcome {
eprintln!("- {:?}", pubkey);
eprintln!(" - error: {:?}", error);
}
}
return Err("ERROR: import aborted due to errors, see above.\n\
No data has been imported and the slashing protection \
database is in the same state it was in before the import.\n\
Due to the failed import it is NOT SAFE to start validating\n\
with any newly imported validator keys, as your database lacks\n\
slashing protection data for them."
.to_string());
}
Err(e) => {
return Err(format!(
"Fatal error during import: {:?}\n\
IT IS NOT SAFE TO START VALIDATING",
e
));
}
}
eprintln!("Import completed successfully.");
eprintln!(
"Please double-check that the latest blocks and attestations above \
match your expectations."
);
Ok(())
}
(EXPORT_CMD, Some(matches)) => {
let export_filename: PathBuf = clap_utils::parse_required(matches, EXPORT_FILE_ARG)?;
let minify: bool = clap_utils::parse_required(matches, MINIFY_FLAG)?;
let selected_pubkeys = if let Some(pubkeys) =
clap_utils::parse_optional::<String>(matches, PUBKEYS_FLAG)?
{
let pubkeys = pubkeys
.split(',')
.map(PublicKeyBytes::from_str)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Invalid --{} value: {:?}", PUBKEYS_FLAG, e))?;
Some(pubkeys)
} else {
None
};
if !slashing_protection_db_path.exists() {
return Err(format!(
"No slashing protection database exists at: {}",
slashing_protection_db_path.display()
));
}
let slashing_protection_database = SlashingDatabase::open(&slashing_protection_db_path)
.map_err(|e| {
format!(
"Unable to open database at {}: {:?}",
slashing_protection_db_path.display(),
e
)
})?;
let mut interchange = slashing_protection_database
.export_interchange_info(genesis_validators_root, selected_pubkeys.as_deref())
.map_err(|e| format!("Error during export: {:?}", e))?;
if minify {
eprintln!("Minifying output file");
interchange = interchange
.minify()
.map_err(|e| format!("Unable to minify output: {:?}", e))?;
}
let output_file = File::create(export_filename)
.map_err(|e| format!("Error creating output file: {:?}", e))?;
interchange
.write_to(&output_file)
.map_err(|e| format!("Error writing output file: {:?}", e))?;
eprintln!("Export completed successfully");
Ok(())
}
("", _) => Err("No subcommand provided, see --help for options".to_string()),
(command, _) => Err(format!("No such subcommand `{}`", command)),
}
}

View File

@@ -1,23 +1,37 @@
use crate::BASE_DIR_FLAG;
use account_utils::{random_password, strip_off_newlines};
use crate::common::read_wallet_name_from_cli;
use crate::WALLETS_DIR_FLAG;
use account_utils::{
is_password_sufficiently_complex, random_password, read_password_from_user, strip_off_newlines,
};
use clap::{App, Arg, ArgMatches};
use eth2_wallet::{
bip39::{Language, Mnemonic, MnemonicType},
PlainText,
};
use eth2_wallet_manager::{WalletManager, WalletType};
use eth2_wallet_manager::{LockedWallet, WalletManager, WalletType};
use filesystem::create_with_600_perms;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::prelude::*;
use std::os::unix::fs::PermissionsExt;
use std::fs;
use std::path::{Path, PathBuf};
pub const CMD: &str = "create";
pub const HD_TYPE: &str = "hd";
pub const NAME_FLAG: &str = "name";
pub const PASSPHRASE_FLAG: &str = "passphrase-file";
pub const PASSWORD_FLAG: &str = "password-file";
pub const TYPE_FLAG: &str = "type";
pub const MNEMONIC_FLAG: &str = "mnemonic-output-path";
pub const STDIN_INPUTS_FLAG: &str = "stdin-inputs";
pub const MNEMONIC_LENGTH_FLAG: &str = "mnemonic-length";
pub const MNEMONIC_TYPES: &[MnemonicType] = &[
MnemonicType::Words12,
MnemonicType::Words15,
MnemonicType::Words18,
MnemonicType::Words21,
MnemonicType::Words24,
];
pub const NEW_WALLET_PASSWORD_PROMPT: &str =
"Enter a password for your new wallet that is at least 12 characters long:";
pub const RETYPE_PASSWORD_PROMPT: &str = "Please re-enter your wallet's new password:";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
@@ -30,12 +44,11 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
"The wallet will be created with this name. It is not allowed to \
create two wallets with the same name for the same --base-dir.",
)
.takes_value(true)
.required(true),
.takes_value(true),
)
.arg(
Arg::with_name(PASSPHRASE_FLAG)
.long(PASSPHRASE_FLAG)
Arg::with_name(PASSWORD_FLAG)
.long(PASSWORD_FLAG)
.value_name("WALLET_PASSWORD_PATH")
.help(
"A path to a file containing the password which will unlock the wallet. \
@@ -43,8 +56,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
saved at that path. To avoid confusion, if the file does not already \
exist it must include a '.pass' suffix.",
)
.takes_value(true)
.required(true),
.takes_value(true),
)
.arg(
Arg::with_name(TYPE_FLAG)
@@ -67,56 +79,49 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
)
.takes_value(true)
)
.arg(
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
.arg(
Arg::with_name(MNEMONIC_LENGTH_FLAG)
.long(MNEMONIC_LENGTH_FLAG)
.value_name("MNEMONIC_LENGTH")
.help("The number of words to use for the mnemonic phrase.")
.takes_value(true)
.validator(|len| {
match len.parse::<usize>().ok().and_then(|words| MnemonicType::for_word_count(words).ok()) {
Some(_) => Ok(()),
None => Err(format!("Mnemonic length must be one of {}", MNEMONIC_TYPES.iter().map(|t| t.word_count().to_string()).collect::<Vec<_>>().join(", "))),
}
})
.default_value("24"),
)
}
pub fn cli_run(matches: &ArgMatches, base_dir: PathBuf) -> Result<(), String> {
let name: String = clap_utils::parse_required(matches, NAME_FLAG)?;
let wallet_password_path: PathBuf = clap_utils::parse_required(matches, PASSPHRASE_FLAG)?;
pub fn cli_run(matches: &ArgMatches, wallet_base_dir: PathBuf) -> Result<(), String> {
let mnemonic_output_path: Option<PathBuf> = clap_utils::parse_optional(matches, MNEMONIC_FLAG)?;
let type_field: String = clap_utils::parse_required(matches, TYPE_FLAG)?;
let wallet_type = match type_field.as_ref() {
HD_TYPE => WalletType::Hd,
unknown => return Err(format!("--{} {} is not supported", TYPE_FLAG, unknown)),
};
let mgr = WalletManager::open(&base_dir)
.map_err(|e| format!("Unable to open --{}: {:?}", BASE_DIR_FLAG, e))?;
// Create a new random mnemonic.
//
// The `tiny-bip39` crate uses `thread_rng()` for this entropy.
let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
let mnemonic_length = clap_utils::parse_required(matches, MNEMONIC_LENGTH_FLAG)?;
let mnemonic = Mnemonic::new(
MnemonicType::for_word_count(mnemonic_length).expect("Mnemonic length already validated"),
Language::English,
);
// Create a random password if the file does not exist.
if !wallet_password_path.exists() {
// To prevent users from accidentally supplying their password to the PASSPHRASE_FLAG and
// create a file with that name, we require that the password has a .pass suffix.
if wallet_password_path.extension() != Some(&OsStr::new("pass")) {
return Err(format!(
"Only creates a password file if that file ends in .pass: {:?}",
wallet_password_path
));
}
create_with_600_perms(&wallet_password_path, random_password().as_bytes())
.map_err(|e| format!("Unable to write to {:?}: {:?}", wallet_password_path, e))?;
}
let wallet_password = fs::read(&wallet_password_path)
.map_err(|e| format!("Unable to read {:?}: {:?}", wallet_password_path, e))
.map(|bytes| PlainText::from(strip_off_newlines(bytes)))?;
let wallet = mgr
.create_wallet(name, wallet_type, &mnemonic, wallet_password.as_bytes())
.map_err(|e| format!("Unable to create wallet: {:?}", e))?;
let wallet = create_wallet_from_mnemonic(matches, wallet_base_dir.as_path(), &mnemonic)?;
if let Some(path) = mnemonic_output_path {
create_with_600_perms(&path, mnemonic.phrase().as_bytes())
.map_err(|e| format!("Unable to write mnemonic to {:?}: {:?}", path, e))?;
}
println!("Your wallet's 12-word BIP-39 mnemonic is:");
println!("Your wallet's {}-word BIP-39 mnemonic is:", mnemonic_length);
println!();
println!("\t{}", mnemonic.phrase());
println!();
@@ -140,25 +145,94 @@ pub fn cli_run(matches: &ArgMatches, base_dir: PathBuf) -> Result<(), String> {
Ok(())
}
/// Creates a file with `600 (-rw-------)` permissions.
pub fn create_with_600_perms<P: AsRef<Path>>(path: P, bytes: &[u8]) -> Result<(), String> {
let path = path.as_ref();
pub fn create_wallet_from_mnemonic(
matches: &ArgMatches,
wallet_base_dir: &Path,
mnemonic: &Mnemonic,
) -> Result<LockedWallet, String> {
let name: Option<String> = clap_utils::parse_optional(matches, NAME_FLAG)?;
let wallet_password_path: Option<PathBuf> = clap_utils::parse_optional(matches, PASSWORD_FLAG)?;
let type_field: String = clap_utils::parse_required(matches, TYPE_FLAG)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
let wallet_type = match type_field.as_ref() {
HD_TYPE => WalletType::Hd,
unknown => return Err(format!("--{} {} is not supported", TYPE_FLAG, unknown)),
};
let mut file =
File::create(&path).map_err(|e| format!("Unable to create {:?}: {}", path, e))?;
let mgr = WalletManager::open(&wallet_base_dir)
.map_err(|e| format!("Unable to open --{}: {:?}", WALLETS_DIR_FLAG, e))?;
let mut perm = file
.metadata()
.map_err(|e| format!("Unable to get {:?} metadata: {}", path, e))?
.permissions();
let wallet_password: PlainText = match wallet_password_path {
Some(path) => {
// Create a random password if the file does not exist.
if !path.exists() {
// To prevent users from accidentally supplying their password to the PASSWORD_FLAG and
// create a file with that name, we require that the password has a .pass suffix.
if path.extension() != Some(OsStr::new("pass")) {
return Err(format!(
"Only creates a password file if that file ends in .pass: {:?}",
path
));
}
perm.set_mode(0o600);
create_with_600_perms(&path, random_password().as_bytes())
.map_err(|e| format!("Unable to write to {:?}: {:?}", path, e))?;
}
read_new_wallet_password_from_cli(Some(path), stdin_inputs)?
}
None => read_new_wallet_password_from_cli(None, stdin_inputs)?,
};
file.set_permissions(perm)
.map_err(|e| format!("Unable to set {:?} permissions: {}", path, e))?;
let wallet_name = read_wallet_name_from_cli(name, stdin_inputs)?;
file.write_all(bytes)
.map_err(|e| format!("Unable to write to {:?}: {}", path, e))?;
Ok(())
let wallet = mgr
.create_wallet(
wallet_name,
wallet_type,
mnemonic,
wallet_password.as_bytes(),
)
.map_err(|e| format!("Unable to create wallet: {:?}", e))?;
Ok(wallet)
}
/// Used when a user is creating a new wallet. Read in a wallet password from a file if the password file
/// path is provided. Otherwise, read from an interactive prompt using tty unless the `--stdin-inputs`
/// flag is provided. This verifies the password complexity and verifies the password is correctly re-entered.
pub fn read_new_wallet_password_from_cli(
password_file_path: Option<PathBuf>,
stdin_inputs: bool,
) -> Result<PlainText, String> {
match password_file_path {
Some(path) => {
let password: PlainText = fs::read(&path)
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))
.map(|bytes| strip_off_newlines(bytes).into())?;
// Ensure the password meets the minimum requirements.
is_password_sufficiently_complex(password.as_bytes())?;
Ok(password)
}
None => loop {
eprintln!();
eprintln!("{}", NEW_WALLET_PASSWORD_PROMPT);
let password =
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
// Ensure the password meets the minimum requirements.
match is_password_sufficiently_complex(password.as_bytes()) {
Ok(_) => {
eprintln!("{}", RETYPE_PASSWORD_PROMPT);
let retyped_password =
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
if retyped_password == password {
break Ok(password);
} else {
eprintln!("Passwords do not match.");
}
}
Err(message) => eprintln!("{}", message),
}
},
}
}

View File

@@ -1,4 +1,4 @@
use crate::BASE_DIR_FLAG;
use crate::WALLETS_DIR_FLAG;
use clap::App;
use eth2_wallet_manager::WalletManager;
use std::path::PathBuf;
@@ -9,9 +9,9 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD).about("Lists the names of all wallets.")
}
pub fn cli_run(base_dir: PathBuf) -> Result<(), String> {
let mgr = WalletManager::open(&base_dir)
.map_err(|e| format!("Unable to open --{}: {:?}", BASE_DIR_FLAG, e))?;
pub fn cli_run(wallet_base_dir: PathBuf) -> Result<(), String> {
let mgr = WalletManager::open(&wallet_base_dir)
.map_err(|e| format!("Unable to open --{}: {:?}", WALLETS_DIR_FLAG, e))?;
for (name, _uuid) in mgr
.wallets()

View File

@@ -1,11 +1,11 @@
pub mod create;
pub mod list;
pub mod recover;
use crate::{
common::{base_wallet_dir, ensure_dir_exists},
BASE_DIR_FLAG,
};
use crate::WALLETS_DIR_FLAG;
use clap::{App, Arg, ArgMatches};
use directory::{ensure_dir_exists, parse_path_or_default_with_flag, DEFAULT_WALLET_DIR};
use std::path::PathBuf;
pub const CMD: &str = "wallet";
@@ -13,23 +13,33 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about("Manage wallets, from which validator keys can be derived.")
.arg(
Arg::with_name(BASE_DIR_FLAG)
.long(BASE_DIR_FLAG)
.value_name("BASE_DIRECTORY")
.help("A path containing Eth2 EIP-2386 wallets. Defaults to ~/.lighthouse/wallets")
.takes_value(true),
Arg::with_name(WALLETS_DIR_FLAG)
.long(WALLETS_DIR_FLAG)
.value_name("WALLETS_DIRECTORY")
.help("A path containing Eth2 EIP-2386 wallets. Defaults to ~/.lighthouse/{network}/wallets")
.takes_value(true)
.conflicts_with("datadir"),
)
.subcommand(create::cli_app())
.subcommand(list::cli_app())
.subcommand(recover::cli_app())
}
pub fn cli_run(matches: &ArgMatches) -> Result<(), String> {
let base_dir = base_wallet_dir(matches, BASE_DIR_FLAG)?;
ensure_dir_exists(&base_dir)?;
let wallet_base_dir = if matches.value_of("datadir").is_some() {
let path: PathBuf = clap_utils::parse_required(matches, "datadir")?;
path.join(DEFAULT_WALLET_DIR)
} else {
parse_path_or_default_with_flag(matches, WALLETS_DIR_FLAG, DEFAULT_WALLET_DIR)?
};
ensure_dir_exists(&wallet_base_dir)?;
eprintln!("wallet-dir path: {:?}", wallet_base_dir);
match matches.subcommand() {
(create::CMD, Some(matches)) => create::cli_run(matches, base_dir),
(list::CMD, Some(_)) => list::cli_run(base_dir),
(create::CMD, Some(matches)) => create::cli_run(matches, wallet_base_dir),
(list::CMD, Some(_)) => list::cli_run(wallet_base_dir),
(recover::CMD, Some(matches)) => recover::cli_run(matches, wallet_base_dir),
(unknown, _) => Err(format!(
"{} does not have a {} command. See --help",
CMD, unknown

View File

@@ -0,0 +1,86 @@
use crate::common::read_mnemonic_from_cli;
use crate::wallet::create::{create_wallet_from_mnemonic, STDIN_INPUTS_FLAG};
use crate::wallet::create::{HD_TYPE, NAME_FLAG, PASSWORD_FLAG, TYPE_FLAG};
use clap::{App, Arg, ArgMatches};
use std::path::PathBuf;
pub const CMD: &str = "recover";
pub const MNEMONIC_FLAG: &str = "mnemonic-path";
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new(CMD)
.about("Recovers an EIP-2386 wallet from a given a BIP-39 mnemonic phrase.")
.arg(
Arg::with_name(NAME_FLAG)
.long(NAME_FLAG)
.value_name("WALLET_NAME")
.help(
"The wallet will be created with this name. It is not allowed to \
create two wallets with the same name for the same --base-dir.",
)
.takes_value(true),
)
.arg(
Arg::with_name(PASSWORD_FLAG)
.long(PASSWORD_FLAG)
.value_name("PASSWORD_FILE_PATH")
.help(
"This will be the new password for your recovered wallet. \
A path to a file containing the password which will unlock the wallet. \
If the file does not exist, a random password will be generated and \
saved at that path. To avoid confusion, if the file does not already \
exist it must include a '.pass' suffix.",
)
.takes_value(true),
)
.arg(
Arg::with_name(MNEMONIC_FLAG)
.long(MNEMONIC_FLAG)
.value_name("MNEMONIC_PATH")
.help("If present, the mnemonic will be read in from this file.")
.takes_value(true),
)
.arg(
Arg::with_name(TYPE_FLAG)
.long(TYPE_FLAG)
.value_name("WALLET_TYPE")
.help(
"The type of wallet to create. Only HD (hierarchical-deterministic) \
wallets are supported presently..",
)
.takes_value(true)
.possible_values(&[HD_TYPE])
.default_value(HD_TYPE),
)
.arg(
Arg::with_name(STDIN_INPUTS_FLAG)
.takes_value(false)
.hidden(cfg!(windows))
.long(STDIN_INPUTS_FLAG)
.help("If present, read all user inputs from stdin instead of tty."),
)
}
pub fn cli_run(matches: &ArgMatches, wallet_base_dir: PathBuf) -> Result<(), String> {
let mnemonic_path: Option<PathBuf> = clap_utils::parse_optional(matches, MNEMONIC_FLAG)?;
let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
eprintln!();
eprintln!("WARNING: KEY RECOVERY CAN LEAD TO DUPLICATING VALIDATORS KEYS, WHICH CAN LEAD TO SLASHING.");
eprintln!();
let mnemonic = read_mnemonic_from_cli(mnemonic_path, stdin_inputs)?;
let wallet = create_wallet_from_mnemonic(matches, wallet_base_dir.as_path(), &mnemonic)
.map_err(|e| format!("Unable to create wallet: {:?}", e))?;
println!("Your wallet has been successfully recovered.");
println!();
println!("Your wallet's UUID is:");
println!();
println!("\t{}", wallet.wallet().uuid());
println!();
println!("You do not need to backup your UUID or keep it secret.");
Ok(())
}

View File

@@ -1,8 +1,8 @@
[package]
name = "beacon_node"
version = "0.2.0"
version = "2.1.4"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com"]
edition = "2018"
edition = "2021"
[lib]
name = "beacon_node"
@@ -20,23 +20,24 @@ beacon_chain = { path = "beacon_chain" }
types = { path = "../consensus/types" }
store = { path = "./store" }
client = { path = "client" }
clap = "2.33.0"
rand = "0.7.3"
clap = "2.33.3"
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
slog-term = "2.5.0"
slog-async = "2.5.0"
ctrlc = { version = "3.1.4", features = ["termination"] }
tokio = { version = "0.2.21", features = ["time"] }
exit-future = "0.2.0"
dirs = "2.0.2"
logging = { path = "../common/logging" }
futures = "0.3.5"
dirs = "3.0.1"
directory = {path = "../common/directory"}
futures = "0.3.7"
environment = { path = "../lighthouse/environment" }
task_executor = { path = "../common/task_executor" }
genesis = { path = "genesis" }
eth2_testnet_config = { path = "../common/eth2_testnet_config" }
eth2_libp2p = { path = "./eth2_libp2p" }
eth2_ssz = "0.1.2"
serde = "1.0.110"
eth2_network_config = { path = "../common/eth2_network_config" }
execution_layer = { path = "execution_layer" }
lighthouse_network = { path = "./lighthouse_network" }
serde = "1.0.116"
clap_utils = { path = "../common/clap_utils" }
hyper = "0.13.5"
hyper = "0.14.4"
lighthouse_version = { path = "../common/lighthouse_version" }
hex = "0.4.2"
slasher = { path = "../slasher" }
monitoring_api = { path = "../common/monitoring_api" }
sensitive_url = { path = "../common/sensitive_url" }
http_api = { path = "http_api" }
unused_port = { path = "../common/unused_port" }

View File

@@ -2,57 +2,65 @@
name = "beacon_chain"
version = "0.2.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com>"]
edition = "2018"
edition = "2021"
autotests = false # using a single test binary compiles faster
[features]
default = ["participation_metrics"]
write_ssz_files = [] # Writes debugging .ssz files to /tmp during block processing.
participation_metrics = [] # Exposes validator participation metrics to Prometheus.
fork_from_env = [] # Initialise the harness chain spec from the FORK_NAME env variable
[dev-dependencies]
int_to_bytes = { path = "../../consensus/int_to_bytes" }
maplit = "1.0.2"
environment = { path = "../../lighthouse/environment" }
serde_json = "1.0.58"
[dependencies]
eth2_config = { path = "../../common/eth2_config" }
merkle_proof = { path = "../../consensus/merkle_proof" }
store = { path = "../store" }
parking_lot = "0.11.0"
lazy_static = "1.4.0"
smallvec = "1.4.1"
smallvec = "1.6.1"
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
log = "0.4.8"
operation_pool = { path = "../operation_pool" }
rayon = "1.3.0"
serde = "1.0.110"
serde_derive = "1.0.110"
serde_yaml = "0.8.11"
serde_json = "1.0.52"
rayon = "1.4.1"
serde = "1.0.116"
serde_derive = "1.0.116"
slog = { version = "2.5.2", features = ["max_level_trace"] }
slog-term = "2.6.0"
sloggers = "1.0.0"
sloggers = { version = "2.1.1", features = ["json"] }
slot_clock = { path = "../../common/slot_clock" }
eth2_hashing = "0.1.0"
eth2_ssz = "0.1.2"
eth2_ssz_types = { path = "../../consensus/ssz_types" }
eth2_ssz_derive = "0.1.0"
eth2_hashing = "0.2.0"
eth2_ssz = "0.4.1"
eth2_ssz_types = "0.2.2"
eth2_ssz_derive = "0.3.0"
state_processing = { path = "../../consensus/state_processing" }
tree_hash = "0.1.0"
tree_hash = "0.4.1"
types = { path = "../../consensus/types" }
tokio = "0.2.21"
tokio = "1.14.0"
eth1 = { path = "../eth1" }
websocket_server = { path = "../websocket_server" }
futures = "0.3.5"
futures = "0.3.7"
genesis = { path = "../genesis" }
integer-sqrt = "0.1.3"
int_to_bytes = { path = "../../consensus/int_to_bytes" }
rand = "0.7.3"
proto_array = { path = "../../consensus/proto_array" }
lru = "0.5.1"
lru = "0.7.1"
tempfile = "3.1.0"
bitvec = "0.17.4"
bitvec = "0.19.3"
bls = { path = "../../crypto/bls" }
safe_arith = { path = "../../consensus/safe_arith" }
fork_choice = { path = "../../consensus/fork_choice" }
environment = { path = "../../lighthouse/environment" }
bus = "2.2.3"
task_executor = { path = "../../common/task_executor" }
derivative = "2.1.1"
itertools = "0.9.0"
itertools = "0.10.0"
slasher = { path = "../../slasher" }
eth2 = { path = "../../common/eth2" }
strum = { version = "0.21.0", features = ["derive"] }
logging = { path = "../../common/logging" }
execution_layer = { path = "../execution_layer" }
sensitive_url = { path = "../../common/sensitive_url" }
superstruct = "0.4.0"
[[test]]
name = "beacon_chain_tests"
path = "tests/main.rs"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
//! These two `batch_...` functions provide verification of batches of attestations. They provide
//! significant CPU-time savings by performing batch verification of BLS signatures.
//!
//! In each function, attestations are "indexed" (i.e., the `IndexedAttestation` is computed), to
//! determine if they should progress to signature verification. Then, all attestations which were
//! successfully indexed have their signatures verified in a batch. If that signature batch fails
//! then all attestation signatures are verified independently.
//!
//! The outcome of each function is a `Vec<Result>` with a one-to-one mapping to the attestations
//! supplied as input. Each result provides the exact success or failure result of the corresponding
//! attestation, with no loss of fidelity when compared to individual verification.
use super::{
CheckAttestationSignature, Error, IndexedAggregatedAttestation, IndexedUnaggregatedAttestation,
VerifiedAggregatedAttestation, VerifiedUnaggregatedAttestation,
};
use crate::{
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, metrics, BeaconChain, BeaconChainError,
BeaconChainTypes,
};
use bls::verify_signature_sets;
use state_processing::signature_sets::{
indexed_attestation_signature_set_from_pubkeys, signed_aggregate_selection_proof_signature_set,
signed_aggregate_signature_set,
};
use std::borrow::Cow;
use types::*;
/// Verify aggregated attestations using batch BLS signature verification.
///
/// See module-level docs for more info.
pub fn batch_verify_aggregated_attestations<'a, T, I>(
aggregates: I,
chain: &BeaconChain<T>,
) -> Result<Vec<Result<VerifiedAggregatedAttestation<'a, T>, Error>>, Error>
where
T: BeaconChainTypes,
I: Iterator<Item = &'a SignedAggregateAndProof<T::EthSpec>> + ExactSizeIterator,
{
let mut num_indexed = 0;
let mut num_failed = 0;
// Perform indexing of all attestations, collecting the results.
let indexing_results = aggregates
.map(|aggregate| {
let result = IndexedAggregatedAttestation::verify(aggregate, chain);
if result.is_ok() {
num_indexed += 1;
} else {
num_failed += 1;
}
result
})
.collect::<Vec<_>>();
// May be set to `No` if batch verification succeeds.
let mut check_signatures = CheckAttestationSignature::Yes;
// Perform batch BLS verification, if any attestation signatures are worth checking.
if num_indexed > 0 {
let signature_setup_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_BATCH_AGG_SIGNATURE_SETUP_TIMES);
let pubkey_cache = chain
.validator_pubkey_cache
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
let fork = chain.with_head(|head| Ok::<_, BeaconChainError>(head.beacon_state.fork()))?;
let mut signature_sets = Vec::with_capacity(num_indexed * 3);
// Iterate, flattening to get only the `Ok` values.
for indexed in indexing_results.iter().flatten() {
let signed_aggregate = &indexed.signed_aggregate;
let indexed_attestation = &indexed.indexed_attestation;
signature_sets.push(
signed_aggregate_selection_proof_signature_set(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
signed_aggregate,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
);
signature_sets.push(
signed_aggregate_signature_set(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
signed_aggregate,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
);
signature_sets.push(
indexed_attestation_signature_set_from_pubkeys(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
&indexed_attestation.signature,
indexed_attestation,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
);
}
metrics::stop_timer(signature_setup_timer);
let _signature_verification_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_BATCH_AGG_SIGNATURE_TIMES);
if verify_signature_sets(signature_sets.iter()) {
// Since all the signatures verified in a batch, there's no reason for them to be
// checked again later.
check_signatures = CheckAttestationSignature::No
}
}
// Complete the attestation verification, potentially verifying all signatures independently.
let final_results = indexing_results
.into_iter()
.map(|result| match result {
Ok(indexed) => {
VerifiedAggregatedAttestation::from_indexed(indexed, chain, check_signatures)
}
Err(e) => Err(e),
})
.collect();
Ok(final_results)
}
/// Verify unaggregated attestations using batch BLS signature verification.
///
/// See module-level docs for more info.
pub fn batch_verify_unaggregated_attestations<'a, T, I>(
attestations: I,
chain: &BeaconChain<T>,
) -> Result<Vec<Result<VerifiedUnaggregatedAttestation<'a, T>, Error>>, Error>
where
T: BeaconChainTypes,
I: Iterator<Item = (&'a Attestation<T::EthSpec>, Option<SubnetId>)> + ExactSizeIterator,
{
let mut num_partially_verified = 0;
let mut num_failed = 0;
// Perform partial verification of all attestations, collecting the results.
let partial_results = attestations
.map(|(attn, subnet_opt)| {
let result = IndexedUnaggregatedAttestation::verify(attn, subnet_opt, chain);
if result.is_ok() {
num_partially_verified += 1;
} else {
num_failed += 1;
}
result
})
.collect::<Vec<_>>();
// May be set to `No` if batch verification succeeds.
let mut check_signatures = CheckAttestationSignature::Yes;
// Perform batch BLS verification, if any attestation signatures are worth checking.
if num_partially_verified > 0 {
let signature_setup_timer = metrics::start_timer(
&metrics::ATTESTATION_PROCESSING_BATCH_UNAGG_SIGNATURE_SETUP_TIMES,
);
let pubkey_cache = chain
.validator_pubkey_cache
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
let fork = chain.with_head(|head| Ok::<_, BeaconChainError>(head.beacon_state.fork()))?;
let mut signature_sets = Vec::with_capacity(num_partially_verified);
// Iterate, flattening to get only the `Ok` values.
for partially_verified in partial_results.iter().flatten() {
let indexed_attestation = &partially_verified.indexed_attestation;
let signature_set = indexed_attestation_signature_set_from_pubkeys(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
&indexed_attestation.signature,
indexed_attestation,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?;
signature_sets.push(signature_set);
}
metrics::stop_timer(signature_setup_timer);
let _signature_verification_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_BATCH_UNAGG_SIGNATURE_TIMES);
if verify_signature_sets(signature_sets.iter()) {
// Since all the signatures verified in a batch, there's no reason for them to be
// checked again later.
check_signatures = CheckAttestationSignature::No
}
}
// Complete the attestation verification, potentially verifying all signatures independently.
let final_results = partial_results
.into_iter()
.map(|result| match result {
Ok(partial) => {
VerifiedUnaggregatedAttestation::from_indexed(partial, chain, check_signatures)
}
Err(e) => Err(e),
})
.collect();
Ok(final_results)
}

View File

@@ -0,0 +1,384 @@
//! This module provides the `AttesterCache`, a cache designed for reducing state-reads when
//! validators produce `AttestationData`.
//!
//! This cache is required *as well as* the `ShufflingCache` since the `ShufflingCache` does not
//! provide any information about the `state.current_justified_checkpoint`. It is not trivial to add
//! the justified checkpoint to the `ShufflingCache` since that cache is keyed by shuffling decision
//! root, which is not suitable for the justified checkpoint. Whilst we can know the shuffling for
//! epoch `n` during `n - 1`, we *cannot* know the justified checkpoint. Instead, we *must* perform
//! `per_epoch_processing` to transform the state from epoch `n - 1` to epoch `n` so that rewards
//! and penalties can be computed and the `state.current_justified_checkpoint` can be updated.
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use parking_lot::RwLock;
use state_processing::state_advance::{partial_state_advance, Error as StateAdvanceError};
use std::collections::HashMap;
use std::ops::Range;
use types::{
beacon_state::{
compute_committee_index_in_epoch, compute_committee_range_in_epoch, epoch_committee_count,
},
BeaconState, BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, RelativeEpoch,
Slot,
};
type JustifiedCheckpoint = Checkpoint;
type CommitteeLength = usize;
type CommitteeIndex = u64;
type CacheHashMap = HashMap<AttesterCacheKey, AttesterCacheValue>;
/// The maximum number of `AttesterCacheValues` to be kept in memory.
///
/// Each `AttesterCacheValues` is very small (~16 bytes) and the cache will generally be kept small
/// by pruning on finality.
///
/// The value provided here is much larger than will be used during ideal network conditions,
/// however we make it large since the values are so small.
const MAX_CACHE_LEN: usize = 1_024;
#[derive(Debug)]
pub enum Error {
BeaconState(BeaconStateError),
// Boxed to avoid an infinite-size recursion issue.
BeaconChain(Box<BeaconChainError>),
MissingBeaconState(Hash256),
FailedToTransitionState(StateAdvanceError),
CannotAttestToFutureState {
state_slot: Slot,
request_slot: Slot,
},
/// Indicates a cache inconsistency.
WrongEpoch {
request_epoch: Epoch,
epoch: Epoch,
},
InvalidCommitteeIndex {
committee_index: u64,
},
/// Indicates an inconsistency with the beacon state committees.
InverseRange {
range: Range<usize>,
},
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Self {
Error::BeaconState(e)
}
}
impl From<BeaconChainError> for Error {
fn from(e: BeaconChainError) -> Self {
Error::BeaconChain(Box::new(e))
}
}
/// Stores the minimal amount of data required to compute the committee length for any committee at any
/// slot in a given `epoch`.
pub struct CommitteeLengths {
/// The `epoch` to which the lengths pertain.
epoch: Epoch,
/// The length of the shuffling in `self.epoch`.
active_validator_indices_len: usize,
}
impl CommitteeLengths {
/// Instantiate `Self` using `state.current_epoch()`.
pub fn new<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Result<Self, Error> {
let active_validator_indices_len = if let Ok(committee_cache) =
state.committee_cache(RelativeEpoch::Current)
{
committee_cache.active_validator_indices().len()
} else {
// Building the cache like this avoids taking a mutable reference to `BeaconState`.
let committee_cache = state.initialize_committee_cache(state.current_epoch(), spec)?;
committee_cache.active_validator_indices().len()
};
Ok(Self {
epoch: state.current_epoch(),
active_validator_indices_len,
})
}
/// Get the count of committees per each slot of `self.epoch`.
pub fn get_committee_count_per_slot<T: EthSpec>(
&self,
spec: &ChainSpec,
) -> Result<usize, Error> {
T::get_committee_count_per_slot(self.active_validator_indices_len, spec).map_err(Into::into)
}
/// Get the length of the committee at the given `slot` and `committee_index`.
pub fn get_committee_length<T: EthSpec>(
&self,
slot: Slot,
committee_index: CommitteeIndex,
spec: &ChainSpec,
) -> Result<CommitteeLength, Error> {
let slots_per_epoch = T::slots_per_epoch();
let request_epoch = slot.epoch(slots_per_epoch);
// Sanity check.
if request_epoch != self.epoch {
return Err(Error::WrongEpoch {
request_epoch,
epoch: self.epoch,
});
}
let slots_per_epoch = slots_per_epoch as usize;
let committees_per_slot = self.get_committee_count_per_slot::<T>(spec)?;
let index_in_epoch = compute_committee_index_in_epoch(
slot,
slots_per_epoch,
committees_per_slot,
committee_index as usize,
);
let range = compute_committee_range_in_epoch(
epoch_committee_count(committees_per_slot, slots_per_epoch),
index_in_epoch,
self.active_validator_indices_len,
)
.ok_or(Error::InvalidCommitteeIndex { committee_index })?;
range
.end
.checked_sub(range.start)
.ok_or(Error::InverseRange { range })
}
}
/// Provides the following information for some epoch:
///
/// - The `state.current_justified_checkpoint` value.
/// - The committee lengths for all indices and slots.
///
/// These values are used during attestation production.
pub struct AttesterCacheValue {
current_justified_checkpoint: Checkpoint,
committee_lengths: CommitteeLengths,
}
impl AttesterCacheValue {
/// Instantiate `Self` using `state.current_epoch()`.
pub fn new<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Result<Self, Error> {
let current_justified_checkpoint = state.current_justified_checkpoint();
let committee_lengths = CommitteeLengths::new(state, spec)?;
Ok(Self {
current_justified_checkpoint,
committee_lengths,
})
}
/// Get the justified checkpoint and committee length for some `slot` and `committee_index`.
fn get<T: EthSpec>(
&self,
slot: Slot,
committee_index: CommitteeIndex,
spec: &ChainSpec,
) -> Result<(JustifiedCheckpoint, CommitteeLength), Error> {
self.committee_lengths
.get_committee_length::<T>(slot, committee_index, spec)
.map(|committee_length| (self.current_justified_checkpoint, committee_length))
}
}
/// The `AttesterCacheKey` is fundamentally the same thing as the proposer shuffling decision root,
/// however here we use it as an identity for both of the following values:
///
/// 1. The `state.current_justified_checkpoint`.
/// 2. The attester shuffling.
///
/// This struct relies upon the premise that the `state.current_justified_checkpoint` in epoch `n`
/// is determined by the root of the latest block in epoch `n - 1`. Notably, this is identical to
/// how the proposer shuffling is keyed in `BeaconProposerCache`.
///
/// It is also safe, but not maximally efficient, to key the attester shuffling with the same
/// strategy. For better shuffling keying strategies, see the `ShufflingCache`.
#[derive(Eq, PartialEq, Hash, Clone, Copy)]
pub struct AttesterCacheKey {
/// The epoch from which the justified checkpoint should be observed.
///
/// Attestations which use `self.epoch` as `target.epoch` should use this key.
epoch: Epoch,
/// The root of the block at the last slot of `self.epoch - 1`.
decision_root: Hash256,
}
impl AttesterCacheKey {
/// Instantiate `Self` to key `state.current_epoch()`.
///
/// The `latest_block_root` should be the latest block that has been applied to `state`. This
/// parameter is required since the state does not store the block root for any block with the
/// same slot as `state.slot()`.
///
/// ## Errors
///
/// May error if `epoch` is out of the range of `state.block_roots`.
pub fn new<T: EthSpec>(
epoch: Epoch,
state: &BeaconState<T>,
latest_block_root: Hash256,
) -> Result<Self, Error> {
let slots_per_epoch = T::slots_per_epoch();
let decision_slot = epoch.start_slot(slots_per_epoch).saturating_sub(1_u64);
let decision_root = if decision_slot.epoch(slots_per_epoch) == epoch {
// This scenario is only possible during the genesis epoch. In this scenario, all-zeros
// is used as an alias to the genesis block.
Hash256::zero()
} else if epoch > state.current_epoch() {
// If the requested epoch is higher than the current epoch, the latest block will always
// be the decision root.
latest_block_root
} else {
*state.get_block_root(decision_slot)?
};
Ok(Self {
epoch,
decision_root,
})
}
}
/// Provides a cache for the justified checkpoint and committee length when producing an
/// attestation.
///
/// See the module-level documentation for more information.
#[derive(Default)]
pub struct AttesterCache {
cache: RwLock<CacheHashMap>,
}
impl AttesterCache {
/// Get the justified checkpoint and committee length for the `slot` and `committee_index` in
/// the state identified by the cache `key`.
pub fn get<T: EthSpec>(
&self,
key: &AttesterCacheKey,
slot: Slot,
committee_index: CommitteeIndex,
spec: &ChainSpec,
) -> Result<Option<(JustifiedCheckpoint, CommitteeLength)>, Error> {
self.cache
.read()
.get(key)
.map(|cache_item| cache_item.get::<T>(slot, committee_index, spec))
.transpose()
}
/// Cache the `state.current_epoch()` values if they are not already present in the state.
pub fn maybe_cache_state<T: EthSpec>(
&self,
state: &BeaconState<T>,
latest_block_root: Hash256,
spec: &ChainSpec,
) -> Result<(), Error> {
let key = AttesterCacheKey::new(state.current_epoch(), state, latest_block_root)?;
let mut cache = self.cache.write();
if !cache.contains_key(&key) {
let cache_item = AttesterCacheValue::new(state, spec)?;
Self::insert_respecting_max_len(&mut cache, key, cache_item);
}
Ok(())
}
/// Read the state identified by `state_root` from the database, advance it to the required
/// slot, use it to prime the cache and return the values for the provided `slot` and
/// `committee_index`.
///
/// ## Notes
///
/// This function takes a write-lock on the internal cache. Prefer attempting a `Self::get` call
/// before running this function as `Self::get` only takes a read-lock and is therefore less
/// likely to create contention.
pub fn load_and_cache_state<T: BeaconChainTypes>(
&self,
state_root: Hash256,
key: AttesterCacheKey,
slot: Slot,
committee_index: CommitteeIndex,
chain: &BeaconChain<T>,
) -> Result<(JustifiedCheckpoint, CommitteeLength), Error> {
let spec = &chain.spec;
let slots_per_epoch = T::EthSpec::slots_per_epoch();
let epoch = slot.epoch(slots_per_epoch);
// Take a write-lock on the cache before starting the state read.
//
// Whilst holding the write-lock during the state read will create contention, it prevents
// the scenario where multiple requests from separate threads cause duplicate state reads.
let mut cache = self.cache.write();
// Try the cache to see if someone has already primed it between the time the function was
// called and when the cache write-lock was obtained. This avoids performing duplicate state
// reads.
if let Some(value) = cache
.get(&key)
.map(|cache_item| cache_item.get::<T::EthSpec>(slot, committee_index, spec))
.transpose()?
{
return Ok(value);
}
let mut state: BeaconState<T::EthSpec> = chain
.get_state(&state_root, None)?
.ok_or(Error::MissingBeaconState(state_root))?;
if state.slot() > slot {
// This indicates an internal inconsistency.
return Err(Error::CannotAttestToFutureState {
state_slot: state.slot(),
request_slot: slot,
});
} else if state.current_epoch() < epoch {
// Only perform a "partial" state advance since we do not require the state roots to be
// accurate.
partial_state_advance(
&mut state,
Some(state_root),
epoch.start_slot(slots_per_epoch),
spec,
)
.map_err(Error::FailedToTransitionState)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
}
let cache_item = AttesterCacheValue::new(&state, spec)?;
let value = cache_item.get::<T::EthSpec>(slot, committee_index, spec)?;
Self::insert_respecting_max_len(&mut cache, key, cache_item);
Ok(value)
}
/// Insert a value to `cache`, ensuring it does not exceed the maximum length.
///
/// If the cache is already full, the item with the lowest epoch will be removed.
fn insert_respecting_max_len(
cache: &mut CacheHashMap,
key: AttesterCacheKey,
value: AttesterCacheValue,
) {
while cache.len() >= MAX_CACHE_LEN {
if let Some(oldest) = cache
.iter()
.map(|(key, _)| *key)
.min_by_key(|key| key.epoch)
{
cache.remove(&oldest);
} else {
break;
}
}
cache.insert(key, value);
}
/// Remove all entries where the `key.epoch` is lower than the given `epoch`.
///
/// Generally, the provided `epoch` should be the finalized epoch.
pub fn prune_below(&self, epoch: Epoch) {
self.cache.write().retain(|target, _| target.epoch >= epoch);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,19 @@
//! Defines the `BeaconForkChoiceStore` which provides the persistent storage for the `ForkChoice`
//! struct.
//!
//! Additionally, the private `BalancesCache` struct is defined; a cache designed to avoid database
//! Additionally, the `BalancesCache` struct is defined; a cache designed to avoid database
//! reads when fork choice requires the validator balances of the justified state.
use crate::{metrics, BeaconSnapshot};
use derivative::Derivative;
use fork_choice::ForkChoiceStore;
use ssz_derive::{Decode, Encode};
use std::marker::PhantomData;
use std::sync::Arc;
use store::{Error as StoreError, HotColdDB, ItemStore};
use superstruct::superstruct;
use types::{
BeaconBlock, BeaconState, BeaconStateError, Checkpoint, EthSpec, Hash256, SignedBeaconBlock,
Slot,
BeaconBlock, BeaconState, BeaconStateError, Checkpoint, Epoch, EthSpec, Hash256, Slot,
};
#[derive(Debug)]
@@ -45,7 +46,7 @@ const MAX_BALANCE_CACHE_SIZE: usize = 4;
/// zero.
pub fn get_effective_balances<T: EthSpec>(state: &BeaconState<T>) -> Vec<u64> {
state
.validators
.validators()
.iter()
.map(|validator| {
if validator.is_active_at(state.current_epoch()) {
@@ -57,24 +58,34 @@ pub fn get_effective_balances<T: EthSpec>(state: &BeaconState<T>) -> Vec<u64> {
.collect()
}
/// An item that is stored in the `BalancesCache`.
#[derive(PartialEq, Clone, Debug, Encode, Decode)]
struct CacheItem {
/// The block root at which `self.balances` are valid.
block_root: Hash256,
/// The effective balances from a `BeaconState` validator registry.
balances: Vec<u64>,
#[superstruct(
variants(V1, V8),
variant_attributes(derive(PartialEq, Clone, Debug, Encode, Decode)),
no_enum
)]
pub(crate) struct CacheItem {
pub(crate) block_root: Hash256,
#[superstruct(only(V8))]
pub(crate) epoch: Epoch,
pub(crate) balances: Vec<u64>,
}
/// Provides a cache to avoid reading `BeaconState` from disk when updating the current justified
/// checkpoint.
///
/// It is effectively a mapping of `epoch_boundary_block_root -> state.balances`.
#[derive(PartialEq, Clone, Default, Debug, Encode, Decode)]
struct BalancesCache {
items: Vec<CacheItem>,
pub(crate) type CacheItem = CacheItemV8;
#[superstruct(
variants(V1, V8),
variant_attributes(derive(PartialEq, Clone, Default, Debug, Encode, Decode)),
no_enum
)]
pub struct BalancesCache {
#[superstruct(only(V1))]
pub(crate) items: Vec<CacheItemV1>,
#[superstruct(only(V8))]
pub(crate) items: Vec<CacheItemV8>,
}
pub type BalancesCache = BalancesCacheV8;
impl BalancesCache {
/// Inspect the given `state` and determine the root of the block at the first slot of
/// `state.current_epoch`. If there is not already some entry for the given block root, then
@@ -84,14 +95,9 @@ impl BalancesCache {
block_root: Hash256,
state: &BeaconState<E>,
) -> Result<(), Error> {
// We are only interested in balances from states that are at the start of an epoch,
// because this is where the `current_justified_checkpoint.root` will point.
if !Self::is_first_block_in_epoch(block_root, state)? {
return Ok(());
}
let epoch_boundary_slot = state.current_epoch().start_slot(E::slots_per_epoch());
let epoch_boundary_root = if epoch_boundary_slot == state.slot {
let epoch = state.current_epoch();
let epoch_boundary_slot = epoch.start_slot(E::slots_per_epoch());
let epoch_boundary_root = if epoch_boundary_slot == state.slot() {
block_root
} else {
// This call remains sensible as long as `state.block_roots` is larger than a single
@@ -99,9 +105,14 @@ impl BalancesCache {
*state.get_block_root(epoch_boundary_slot)?
};
if self.position(epoch_boundary_root).is_none() {
// Check if there already exists a cache entry for the epoch boundary block of the current
// epoch. We rely on the invariant that effective balances do not change for the duration
// of a single epoch, so even if the block on the epoch boundary itself is skipped we can
// still update its cache entry from any subsequent state in that epoch.
if self.position(epoch_boundary_root, epoch).is_none() {
let item = CacheItem {
block_root: epoch_boundary_root,
epoch,
balances: get_effective_balances(state),
};
@@ -115,50 +126,27 @@ impl BalancesCache {
Ok(())
}
/// Returns `true` if the given `block_root` is the first/only block to have been processed in
/// the epoch of the given `state`.
///
/// We can determine if it is the first block by looking back through `state.block_roots` to
/// see if there is a block in the current epoch with a different root.
fn is_first_block_in_epoch<E: EthSpec>(
block_root: Hash256,
state: &BeaconState<E>,
) -> Result<bool, Error> {
let mut prior_block_found = false;
for slot in state.current_epoch().slot_iter(E::slots_per_epoch()) {
if slot < state.slot {
if *state.get_block_root(slot)? != block_root {
prior_block_found = true;
break;
}
} else {
break;
}
}
Ok(!prior_block_found)
}
fn position(&self, block_root: Hash256) -> Option<usize> {
fn position(&self, block_root: Hash256, epoch: Epoch) -> Option<usize> {
self.items
.iter()
.position(|item| item.block_root == block_root)
.position(|item| item.block_root == block_root && item.epoch == epoch)
}
/// Get the balances for the given `block_root`, if any.
///
/// If some balances are found, they are removed from the cache.
pub fn get(&mut self, block_root: Hash256) -> Option<Vec<u64>> {
let i = self.position(block_root)?;
Some(self.items.remove(i).balances)
/// If some balances are found, they are cloned from the cache.
pub fn get(&mut self, block_root: Hash256, epoch: Epoch) -> Option<Vec<u64>> {
let i = self.position(block_root, epoch)?;
Some(self.items[i].balances.clone())
}
}
/// Implements `fork_choice::ForkChoiceStore` in order to provide a persistent backing to the
/// `fork_choice::ForkChoice` struct.
#[derive(Debug)]
#[derive(Debug, Derivative)]
#[derivative(PartialEq(bound = "E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>"))]
pub struct BeaconForkChoiceStore<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
#[derivative(PartialEq = "ignore")]
store: Arc<HotColdDB<E, Hot, Cold>>,
balances_cache: BalancesCache,
time: Slot,
@@ -166,26 +154,10 @@ pub struct BeaconForkChoiceStore<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<
justified_checkpoint: Checkpoint,
justified_balances: Vec<u64>,
best_justified_checkpoint: Checkpoint,
proposer_boost_root: Hash256,
_phantom: PhantomData<E>,
}
impl<E, Hot, Cold> PartialEq for BeaconForkChoiceStore<E, Hot, Cold>
where
E: EthSpec,
Hot: ItemStore<E>,
Cold: ItemStore<E>,
{
/// This implementation ignores the `store` and `slot_clock`.
fn eq(&self, other: &Self) -> bool {
self.balances_cache == other.balances_cache
&& self.time == other.time
&& self.finalized_checkpoint == other.finalized_checkpoint
&& self.justified_checkpoint == other.justified_checkpoint
&& self.justified_balances == other.justified_balances
&& self.best_justified_checkpoint == other.best_justified_checkpoint
}
}
impl<E, Hot, Cold> BeaconForkChoiceStore<E, Hot, Cold>
where
E: EthSpec,
@@ -208,9 +180,9 @@ where
anchor: &BeaconSnapshot<E>,
) -> Self {
let anchor_state = &anchor.beacon_state;
let mut anchor_block_header = anchor_state.latest_block_header.clone();
let mut anchor_block_header = anchor_state.latest_block_header().clone();
if anchor_block_header.state_root == Hash256::zero() {
anchor_block_header.state_root = anchor.beacon_state_root;
anchor_block_header.state_root = anchor.beacon_state_root();
}
let anchor_root = anchor_block_header.canonical_root();
let anchor_epoch = anchor_state.current_epoch();
@@ -223,11 +195,12 @@ where
Self {
store,
balances_cache: <_>::default(),
time: anchor_state.slot,
time: anchor_state.slot(),
justified_checkpoint,
justified_balances: anchor_state.balances.clone().into(),
justified_balances: anchor_state.balances().clone().into(),
finalized_checkpoint,
best_justified_checkpoint: justified_checkpoint,
proposer_boost_root: Hash256::zero(),
_phantom: PhantomData,
}
}
@@ -242,6 +215,7 @@ where
justified_checkpoint: self.justified_checkpoint,
justified_balances: self.justified_balances.clone(),
best_justified_checkpoint: self.best_justified_checkpoint,
proposer_boost_root: self.proposer_boost_root,
}
}
@@ -258,6 +232,7 @@ where
justified_checkpoint: persisted.justified_checkpoint,
justified_balances: persisted.justified_balances,
best_justified_checkpoint: persisted.best_justified_checkpoint,
proposer_boost_root: persisted.proposer_boost_root,
_phantom: PhantomData,
})
}
@@ -304,6 +279,10 @@ where
&self.finalized_checkpoint
}
fn proposer_boost_root(&self) -> Hash256 {
self.proposer_boost_root
}
fn set_finalized_checkpoint(&mut self, checkpoint: Checkpoint) {
self.finalized_checkpoint = checkpoint
}
@@ -311,25 +290,29 @@ where
fn set_justified_checkpoint(&mut self, checkpoint: Checkpoint) -> Result<(), Error> {
self.justified_checkpoint = checkpoint;
if let Some(balances) = self.balances_cache.get(self.justified_checkpoint.root) {
if let Some(balances) = self.balances_cache.get(
self.justified_checkpoint.root,
self.justified_checkpoint.epoch,
) {
metrics::inc_counter(&metrics::BALANCES_CACHE_HITS);
self.justified_balances = balances;
} else {
metrics::inc_counter(&metrics::BALANCES_CACHE_MISSES);
let justified_block = self
.store
.get_item::<SignedBeaconBlock<E>>(&self.justified_checkpoint.root)
.get_block(&self.justified_checkpoint.root)
.map_err(Error::FailedToReadBlock)?
.ok_or_else(|| Error::MissingBlock(self.justified_checkpoint.root))?
.message;
.ok_or(Error::MissingBlock(self.justified_checkpoint.root))?
.deconstruct()
.0;
self.justified_balances = self
let state = self
.store
.get_state(&justified_block.state_root, Some(justified_block.slot))
.get_state(&justified_block.state_root(), Some(justified_block.slot()))
.map_err(Error::FailedToReadState)?
.ok_or_else(|| Error::MissingState(justified_block.state_root))?
.balances
.into();
.ok_or_else(|| Error::MissingState(justified_block.state_root()))?;
self.justified_balances = get_effective_balances(&state);
}
Ok(())
@@ -338,15 +321,30 @@ where
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint) {
self.best_justified_checkpoint = checkpoint
}
fn set_proposer_boost_root(&mut self, proposer_boost_root: Hash256) {
self.proposer_boost_root = proposer_boost_root;
}
}
/// A container which allows persisting the `BeaconForkChoiceStore` to the on-disk database.
#[derive(Encode, Decode)]
#[superstruct(
variants(V1, V7, V8),
variant_attributes(derive(Encode, Decode)),
no_enum
)]
pub struct PersistedForkChoiceStore {
balances_cache: BalancesCache,
time: Slot,
finalized_checkpoint: Checkpoint,
justified_checkpoint: Checkpoint,
justified_balances: Vec<u64>,
best_justified_checkpoint: Checkpoint,
#[superstruct(only(V1, V7))]
pub balances_cache: BalancesCacheV1,
#[superstruct(only(V8))]
pub balances_cache: BalancesCacheV8,
pub time: Slot,
pub finalized_checkpoint: Checkpoint,
pub justified_checkpoint: Checkpoint,
pub justified_balances: Vec<u64>,
pub best_justified_checkpoint: Checkpoint,
#[superstruct(only(V7, V8))]
pub proposer_boost_root: Hash256,
}
pub type PersistedForkChoiceStore = PersistedForkChoiceStoreV8;

View File

@@ -0,0 +1,185 @@
//! The `BeaconProposer` cache stores the proposer indices for some epoch.
//!
//! This cache is keyed by `(epoch, block_root)` where `block_root` is the block root at
//! `end_slot(epoch - 1)`. We make the assertion that the proposer shuffling is identical for all
//! blocks in `epoch` which share the common ancestor of `block_root`.
//!
//! The cache is a fairly unintelligent LRU cache that is not pruned after finality. This makes it
//! very simple to reason about, but it might store values that are useless due to finalization. The
//! values it stores are very small, so this should not be an issue.
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use lru::LruCache;
use smallvec::SmallVec;
use state_processing::state_advance::partial_state_advance;
use std::cmp::Ordering;
use types::{
BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Fork, Hash256, Slot, Unsigned,
};
/// The number of sets of proposer indices that should be cached.
const CACHE_SIZE: usize = 16;
/// This value is fairly unimportant, it's used to avoid heap allocations. The result of it being
/// incorrect is non-substantial from a consensus perspective (and probably also from a
/// performance perspective).
const TYPICAL_SLOTS_PER_EPOCH: usize = 32;
/// For some given slot, this contains the proposer index (`index`) and the `fork` that should be
/// used to verify their signature.
pub struct Proposer {
pub index: usize,
pub fork: Fork,
}
/// The list of proposers for some given `epoch`, alongside the `fork` that should be used to verify
/// their signatures.
pub struct EpochBlockProposers {
/// The epoch to which the proposers pertain.
epoch: Epoch,
/// The fork that should be used to verify proposer signatures.
fork: Fork,
/// A list of length `T::EthSpec::slots_per_epoch()`, representing the proposers for each slot
/// in that epoch.
///
/// E.g., if `self.epoch == 1`, then `self.proposers[0]` contains the proposer for slot `32`.
proposers: SmallVec<[usize; TYPICAL_SLOTS_PER_EPOCH]>,
}
/// A cache to store the proposers for some epoch.
///
/// See the module-level documentation for more information.
pub struct BeaconProposerCache {
cache: LruCache<(Epoch, Hash256), EpochBlockProposers>,
}
impl Default for BeaconProposerCache {
fn default() -> Self {
Self {
cache: LruCache::new(CACHE_SIZE),
}
}
}
impl BeaconProposerCache {
/// If it is cached, returns the proposer for the block at `slot` where the block has the
/// ancestor block root of `shuffling_decision_block` at `end_slot(slot.epoch() - 1)`.
pub fn get_slot<T: EthSpec>(
&mut self,
shuffling_decision_block: Hash256,
slot: Slot,
) -> Option<Proposer> {
let epoch = slot.epoch(T::slots_per_epoch());
let key = (epoch, shuffling_decision_block);
if let Some(cache) = self.cache.get(&key) {
// This `if` statement is likely unnecessary, but it feels like good practice.
if epoch == cache.epoch {
cache
.proposers
.get(slot.as_usize() % T::SlotsPerEpoch::to_usize())
.map(|&index| Proposer {
index,
fork: cache.fork,
})
} else {
None
}
} else {
None
}
}
/// As per `Self::get_slot`, but returns all proposers in all slots for the given `epoch`.
///
/// The nth slot in the returned `SmallVec` will be equal to the nth slot in the given `epoch`.
/// E.g., if `epoch == 1` then `smallvec[0]` refers to slot 32 (assuming `SLOTS_PER_EPOCH ==
/// 32`).
pub fn get_epoch<T: EthSpec>(
&mut self,
shuffling_decision_block: Hash256,
epoch: Epoch,
) -> Option<&SmallVec<[usize; TYPICAL_SLOTS_PER_EPOCH]>> {
let key = (epoch, shuffling_decision_block);
self.cache.get(&key).map(|cache| &cache.proposers)
}
/// Insert the proposers into the cache.
///
/// See `Self::get` for a description of `shuffling_decision_block`.
///
/// The `fork` value must be valid to verify proposer signatures in `epoch`.
pub fn insert(
&mut self,
epoch: Epoch,
shuffling_decision_block: Hash256,
proposers: Vec<usize>,
fork: Fork,
) -> Result<(), BeaconStateError> {
let key = (epoch, shuffling_decision_block);
if !self.cache.contains(&key) {
self.cache.put(
key,
EpochBlockProposers {
epoch,
fork,
proposers: proposers.into(),
},
);
}
Ok(())
}
}
/// Compute the proposer duties using the head state without cache.
pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
current_epoch: Epoch,
chain: &BeaconChain<T>,
) -> Result<(Vec<usize>, Hash256, Fork), BeaconChainError> {
// Take a copy of the head of the chain.
let head = chain.head()?;
let mut state = head.beacon_state;
let head_state_root = head.beacon_block.state_root();
// Advance the state into the requested epoch.
ensure_state_is_in_epoch(&mut state, head_state_root, current_epoch, &chain.spec)?;
let indices = state
.get_beacon_proposer_indices(&chain.spec)
.map_err(BeaconChainError::from)?;
let dependent_root = state
// The only block which decides its own shuffling is the genesis block.
.proposer_shuffling_decision_root(chain.genesis_block_root)
.map_err(BeaconChainError::from)?;
Ok((indices, dependent_root, state.fork()))
}
/// If required, advance `state` to `target_epoch`.
///
/// ## Details
///
/// - Returns an error if `state.current_epoch() > target_epoch`.
/// - No-op if `state.current_epoch() == target_epoch`.
/// - It must be the case that `state.canonical_root() == state_root`, but this function will not
/// check that.
pub fn ensure_state_is_in_epoch<E: EthSpec>(
state: &mut BeaconState<E>,
state_root: Hash256,
target_epoch: Epoch,
spec: &ChainSpec,
) -> Result<(), BeaconChainError> {
match state.current_epoch().cmp(&target_epoch) {
// Protects against an inconsistent slot clock.
Ordering::Greater => Err(BeaconStateError::SlotOutOfBounds.into()),
// The state needs to be advanced.
Ordering::Less => {
let target_slot = target_epoch.start_slot(E::slots_per_epoch());
partial_state_advance(state, Some(state_root), target_slot, spec)
.map_err(BeaconChainError::from)
}
// The state is suitable, nothing to do.
Ordering::Equal => Ok(()),
}
}

View File

@@ -1,15 +1,13 @@
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode};
use types::{BeaconState, EthSpec, Hash256, SignedBeaconBlock};
use types::{beacon_state::CloneConfig, BeaconState, EthSpec, Hash256, SignedBeaconBlock};
/// Represents some block and its associated state. Generally, this will be used for tracking the
/// head, justified head and finalized head.
#[derive(Clone, Serialize, PartialEq, Debug, Encode, Decode)]
#[derive(Clone, Serialize, PartialEq, Debug)]
pub struct BeaconSnapshot<E: EthSpec> {
pub beacon_block: SignedBeaconBlock<E>,
pub beacon_block_root: Hash256,
pub beacon_state: BeaconState<E>,
pub beacon_state_root: Hash256,
}
impl<E: EthSpec> BeaconSnapshot<E> {
@@ -18,36 +16,40 @@ impl<E: EthSpec> BeaconSnapshot<E> {
beacon_block: SignedBeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,
) -> Self {
Self {
beacon_block,
beacon_block_root,
beacon_state,
beacon_state_root,
}
}
/// Returns the state root from `self.beacon_block`.
///
/// ## Caution
///
/// It is not strictly enforced that `root(self.beacon_state) == self.beacon_state_root()`.
pub fn beacon_state_root(&self) -> Hash256 {
self.beacon_block.message().state_root()
}
/// Update all fields of the checkpoint.
pub fn update(
&mut self,
beacon_block: SignedBeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,
) {
self.beacon_block = beacon_block;
self.beacon_block_root = beacon_block_root;
self.beacon_state = beacon_state;
self.beacon_state_root = beacon_state_root;
}
pub fn clone_with_only_committee_caches(&self) -> Self {
pub fn clone_with(&self, clone_config: CloneConfig) -> Self {
Self {
beacon_block: self.beacon_block.clone(),
beacon_block_root: self.beacon_block_root,
beacon_state: self.beacon_state.clone_with_only_committee_caches(),
beacon_state_root: self.beacon_state_root,
beacon_state: self.beacon_state.clone_with(clone_config),
}
}
}

View File

@@ -0,0 +1,97 @@
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use eth2::lighthouse::{AttestationRewards, BlockReward, BlockRewardMeta};
use operation_pool::{AttMaxCover, MaxCover};
use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards;
use types::{BeaconBlockRef, BeaconState, EthSpec, Hash256, RelativeEpoch};
impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn compute_block_reward(
&self,
block: BeaconBlockRef<'_, T::EthSpec>,
block_root: Hash256,
state: &BeaconState<T::EthSpec>,
) -> Result<BlockReward, BeaconChainError> {
if block.slot() != state.slot() {
return Err(BeaconChainError::BlockRewardSlotError);
}
let active_indices = state.get_cached_active_validator_indices(RelativeEpoch::Current)?;
let total_active_balance = state.get_total_balance(active_indices, &self.spec)?;
let mut per_attestation_rewards = block
.body()
.attestations()
.iter()
.map(|att| {
AttMaxCover::new(att, state, total_active_balance, &self.spec)
.ok_or(BeaconChainError::BlockRewardAttestationError)
})
.collect::<Result<Vec<_>, _>>()?;
// Update the attestation rewards for each previous attestation included.
// This is O(n^2) in the number of attestations n.
for i in 0..per_attestation_rewards.len() {
let (updated, to_update) = per_attestation_rewards.split_at_mut(i + 1);
let latest_att = &updated[i];
for att in to_update {
att.update_covering_set(latest_att.object(), latest_att.covering_set());
}
}
let mut prev_epoch_total = 0;
let mut curr_epoch_total = 0;
for cover in &per_attestation_rewards {
for &reward in cover.fresh_validators_rewards.values() {
if cover.att.data.slot.epoch(T::EthSpec::slots_per_epoch()) == state.current_epoch()
{
curr_epoch_total += reward;
} else {
prev_epoch_total += reward;
}
}
}
let attestation_total = prev_epoch_total + curr_epoch_total;
// Drop the covers.
let per_attestation_rewards = per_attestation_rewards
.into_iter()
.map(|cover| cover.fresh_validators_rewards)
.collect();
let attestation_rewards = AttestationRewards {
total: attestation_total,
prev_epoch_total,
curr_epoch_total,
per_attestation_rewards,
};
// Sync committee rewards.
let sync_committee_rewards = if let Ok(sync_aggregate) = block.body().sync_aggregate() {
let (_, proposer_reward_per_bit) = compute_sync_aggregate_rewards(state, &self.spec)
.map_err(|_| BeaconChainError::BlockRewardSyncError)?;
sync_aggregate.sync_committee_bits.num_set_bits() as u64 * proposer_reward_per_bit
} else {
0
};
// Total, metadata
let total = attestation_total + sync_committee_rewards;
let meta = BlockRewardMeta {
slot: block.slot(),
parent_slot: state.latest_block_header().slot,
proposer_index: block.proposer_index(),
graffiti: block.body().graffiti().as_utf8_lossy(),
};
Ok(BlockReward {
total,
block_root,
meta,
attestation_rewards,
sync_committee_rewards,
})
}
}

View File

@@ -0,0 +1,143 @@
//! This module provides the `BlockTimesCache' which contains information regarding block timings.
//!
//! This provides `BeaconChain` and associated functions with access to the timestamps of when a
//! certain block was observed, imported and set as head.
//! This allows for better traceability and allows us to determine the root cause for why a block
//! was set as head late.
//! This allows us to distingush between the following scenarios:
//! - The block was observed late.
//! - We were too slow to import it.
//! - We were too slow to set it as head.
use eth2::types::{Hash256, Slot};
use std::collections::HashMap;
use std::time::Duration;
type BlockRoot = Hash256;
#[derive(Clone, Default)]
pub struct Timestamps {
pub observed: Option<Duration>,
pub imported: Option<Duration>,
pub set_as_head: Option<Duration>,
}
// Helps arrange delay data so it is more relevant to metrics.
#[derive(Default)]
pub struct BlockDelays {
pub observed: Option<Duration>,
pub imported: Option<Duration>,
pub set_as_head: Option<Duration>,
}
impl BlockDelays {
fn new(times: Timestamps, slot_start_time: Duration) -> BlockDelays {
let observed = times
.observed
.and_then(|observed_time| observed_time.checked_sub(slot_start_time));
let imported = times
.imported
.and_then(|imported_time| imported_time.checked_sub(times.observed?));
let set_as_head = times
.set_as_head
.and_then(|set_as_head_time| set_as_head_time.checked_sub(times.imported?));
BlockDelays {
observed,
imported,
set_as_head,
}
}
}
// If the block was received via gossip, we can record the client type of the peer which sent us
// the block.
#[derive(Clone, Default)]
pub struct BlockPeerInfo {
pub id: Option<String>,
pub client: Option<String>,
}
pub struct BlockTimesCacheValue {
pub slot: Slot,
pub timestamps: Timestamps,
pub peer_info: BlockPeerInfo,
}
impl BlockTimesCacheValue {
fn new(slot: Slot) -> Self {
BlockTimesCacheValue {
slot,
timestamps: Default::default(),
peer_info: Default::default(),
}
}
}
#[derive(Default)]
pub struct BlockTimesCache {
pub cache: HashMap<BlockRoot, BlockTimesCacheValue>,
}
/// Helper methods to read from and write to the cache.
impl BlockTimesCache {
pub fn set_time_observed(
&mut self,
block_root: BlockRoot,
slot: Slot,
timestamp: Duration,
peer_id: Option<String>,
peer_client: Option<String>,
) {
let block_times = self
.cache
.entry(block_root)
.or_insert_with(|| BlockTimesCacheValue::new(slot));
block_times.timestamps.observed = Some(timestamp);
block_times.peer_info = BlockPeerInfo {
id: peer_id,
client: peer_client,
};
}
pub fn set_time_imported(&mut self, block_root: BlockRoot, slot: Slot, timestamp: Duration) {
let block_times = self
.cache
.entry(block_root)
.or_insert_with(|| BlockTimesCacheValue::new(slot));
block_times.timestamps.imported = Some(timestamp);
}
pub fn set_time_set_as_head(&mut self, block_root: BlockRoot, slot: Slot, timestamp: Duration) {
let block_times = self
.cache
.entry(block_root)
.or_insert_with(|| BlockTimesCacheValue::new(slot));
block_times.timestamps.set_as_head = Some(timestamp);
}
pub fn get_block_delays(
&self,
block_root: BlockRoot,
slot_start_time: Duration,
) -> BlockDelays {
if let Some(block_times) = self.cache.get(&block_root) {
BlockDelays::new(block_times.timestamps.clone(), slot_start_time)
} else {
BlockDelays::default()
}
}
pub fn get_peer_info(&self, block_root: BlockRoot) -> BlockPeerInfo {
if let Some(block_info) = self.cache.get(&block_root) {
block_info.peer_info.clone()
} else {
BlockPeerInfo::default()
}
}
// Prune the cache to only store the most recent 2 epochs.
pub fn prune(&mut self, current_slot: Slot) {
self.cache
.retain(|_, cache| cache.slot > current_slot.saturating_sub(64_u64));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
use serde_derive::{Deserialize, Serialize};
use types::Checkpoint;
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct ChainConfig {
/// Maximum number of slots to skip when importing a consensus message (e.g., block,
/// attestation, etc).
///
/// If `None`, there is no limit.
pub import_max_skip_slots: Option<u64>,
/// A user-input `Checkpoint` that must exist in the beacon chain's sync path.
///
/// If `None`, there is no weak subjectivity verification.
pub weak_subjectivity_checkpoint: Option<Checkpoint>,
/// Determine whether to reconstruct historic states, usually after a checkpoint sync.
pub reconstruct_historic_states: bool,
/// Whether timeouts on `TimeoutRwLock`s are enabled or not.
pub enable_lock_timeouts: bool,
/// The max size of a message that can be sent over the network.
pub max_network_size: usize,
}
impl Default for ChainConfig {
fn default() -> Self {
Self {
import_max_skip_slots: None,
weak_subjectivity_checkpoint: None,
reconstruct_historic_states: false,
enable_lock_timeouts: true,
max_network_size: 10 * 1_048_576, // 10M
}
}
}

View File

@@ -0,0 +1,161 @@
use crate::{
attester_cache::{CommitteeLengths, Error},
metrics,
};
use parking_lot::RwLock;
use proto_array::Block as ProtoBlock;
use types::*;
pub struct CacheItem<E: EthSpec> {
/*
* Values used to create attestations.
*/
epoch: Epoch,
committee_lengths: CommitteeLengths,
beacon_block_root: Hash256,
source: Checkpoint,
target: Checkpoint,
/*
* Values used to make the block available.
*/
block: SignedBeaconBlock<E>,
proto_block: ProtoBlock,
}
/// Provides a single-item cache which allows for attesting to blocks before those blocks have
/// reached the database.
///
/// This cache stores enough information to allow Lighthouse to:
///
/// - Produce an attestation without using `chain.canonical_head`.
/// - Verify that a block root exists (i.e., will be imported in the future) during attestation
/// verification.
/// - Provide a block which can be sent to peers via RPC.
#[derive(Default)]
pub struct EarlyAttesterCache<E: EthSpec> {
item: RwLock<Option<CacheItem<E>>>,
}
impl<E: EthSpec> EarlyAttesterCache<E> {
/// Removes the cached item, meaning that all future calls to `Self::try_attest` will return
/// `None` until a new cache item is added.
pub fn clear(&self) {
*self.item.write() = None
}
/// Updates the cache item, so that `Self::try_attest` with return `Some` when given suitable
/// parameters.
pub fn add_head_block(
&self,
beacon_block_root: Hash256,
block: SignedBeaconBlock<E>,
proto_block: ProtoBlock,
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), Error> {
let epoch = state.current_epoch();
let committee_lengths = CommitteeLengths::new(state, spec)?;
let source = state.current_justified_checkpoint();
let target_slot = epoch.start_slot(E::slots_per_epoch());
let target = Checkpoint {
epoch,
root: if state.slot() <= target_slot {
beacon_block_root
} else {
*state.get_block_root(target_slot)?
},
};
let item = CacheItem {
epoch,
committee_lengths,
beacon_block_root,
source,
target,
block,
proto_block,
};
*self.item.write() = Some(item);
Ok(())
}
/// Will return `Some(attestation)` if all the following conditions are met:
///
/// - There is a cache `item` present.
/// - If `request_slot` is in the same epoch as `item.epoch`.
/// - If `request_index` does not exceed `item.comittee_count`.
pub fn try_attest(
&self,
request_slot: Slot,
request_index: CommitteeIndex,
spec: &ChainSpec,
) -> Result<Option<Attestation<E>>, Error> {
let lock = self.item.read();
let item = if let Some(item) = lock.as_ref() {
item
} else {
return Ok(None);
};
let request_epoch = request_slot.epoch(E::slots_per_epoch());
if request_epoch != item.epoch {
return Ok(None);
}
let committee_count = item
.committee_lengths
.get_committee_count_per_slot::<E>(spec)?;
if request_index >= committee_count as u64 {
return Ok(None);
}
let committee_len =
item.committee_lengths
.get_committee_length::<E>(request_slot, request_index, spec)?;
let attestation = Attestation {
aggregation_bits: BitList::with_capacity(committee_len)
.map_err(BeaconStateError::from)?,
data: AttestationData {
slot: request_slot,
index: request_index,
beacon_block_root: item.beacon_block_root,
source: item.source,
target: item.target,
},
signature: AggregateSignature::empty(),
};
metrics::inc_counter(&metrics::BEACON_EARLY_ATTESTER_CACHE_HITS);
Ok(Some(attestation))
}
/// Returns `true` if `block_root` matches the cached item.
pub fn contains_block(&self, block_root: Hash256) -> bool {
self.item
.read()
.as_ref()
.map_or(false, |item| item.beacon_block_root == block_root)
}
/// Returns the block, if `block_root` matches the cached item.
pub fn get_block(&self, block_root: Hash256) -> Option<SignedBeaconBlock<E>> {
self.item
.read()
.as_ref()
.filter(|item| item.beacon_block_root == block_root)
.map(|item| item.block.clone())
}
/// Returns the proto-array block, if `block_root` matches the cached item.
pub fn get_proto_block(&self, block_root: Hash256) -> Option<ProtoBlock> {
self.item
.read()
.as_ref()
.filter(|item| item.beacon_block_root == block_root)
.map(|item| item.proto_block.clone())
}
}

View File

@@ -1,9 +1,15 @@
use crate::attester_cache::Error as AttesterCacheError;
use crate::beacon_chain::ForkChoiceError;
use crate::beacon_fork_choice_store::Error as ForkChoiceStoreError;
use crate::eth1_chain::Error as Eth1ChainError;
use crate::historical_blocks::HistoricalBlockError;
use crate::migrate::PruningError;
use crate::naive_aggregation_pool::Error as NaiveAggregationError;
use crate::observed_attestations::Error as ObservedAttestationsError;
use crate::observed_aggregates::Error as ObservedAttestationsError;
use crate::observed_attesters::Error as ObservedAttestersError;
use crate::observed_block_producers::Error as ObservedBlockProducersError;
use execution_layer::PayloadStatus;
use futures::channel::mpsc::TrySendError;
use operation_pool::OpPoolError;
use safe_arith::ArithError;
use ssz_types::Error as SszTypesError;
@@ -11,12 +17,14 @@ use state_processing::{
block_signature_verifier::Error as BlockSignatureVerifierError,
per_block_processing::errors::{
AttestationValidationError, AttesterSlashingValidationError, ExitValidationError,
ProposerSlashingValidationError,
ProposerSlashingValidationError, SyncCommitteeMessageValidationError,
},
signature_sets::Error as SignatureSetError,
BlockProcessingError, SlotProcessingError,
state_advance::Error as StateAdvanceError,
BlockProcessingError, BlockReplayError, SlotProcessingError,
};
use std::time::Duration;
use task_executor::ShutdownReason;
use types::*;
macro_rules! easy_from_to {
@@ -33,6 +41,7 @@ macro_rules! easy_from_to {
pub enum BeaconChainError {
InsufficientValidators,
UnableToReadSlot,
UnableToComputeTimeAtSlot,
RevertedFinalizedEpoch {
previous_epoch: Epoch,
new_epoch: Epoch,
@@ -44,15 +53,18 @@ pub enum BeaconChainError {
DBInconsistent(String),
DBError(store::Error),
ForkChoiceError(ForkChoiceError),
ForkChoiceStoreError(ForkChoiceStoreError),
MissingBeaconBlock(Hash256),
MissingBeaconState(Hash256),
SlotProcessingError(SlotProcessingError),
StateAdvanceError(StateAdvanceError),
UnableToAdvanceState(String),
NoStateForAttestation {
beacon_block_root: Hash256,
},
CannotAttestToFutureState,
AttestationValidationError(AttestationValidationError),
SyncCommitteeMessageValidationError(SyncCommitteeMessageValidationError),
ExitValidationError(ExitValidationError),
ProposerSlashingValidationError(ProposerSlashingValidationError),
AttesterSlashingValidationError(AttesterSlashingValidationError),
@@ -61,29 +73,100 @@ pub enum BeaconChainError {
requested_slot: Slot,
max_task_runtime: Duration,
},
MissingFinalizedStateRoot(Slot),
/// Returned when an internal check fails, indicating corrupt data.
InvariantViolated(String),
SszTypesError(SszTypesError),
NoProposerForSlot(Slot),
CanonicalHeadLockTimeout,
AttestationCacheLockTimeout,
ValidatorPubkeyCacheLockTimeout,
SnapshotCacheLockTimeout,
IncorrectStateForAttestation(RelativeEpochError),
InvalidValidatorPubkeyBytes(bls::Error),
ValidatorPubkeyCacheIncomplete(usize),
SignatureSetError(SignatureSetError),
BlockSignatureVerifierError(state_processing::block_signature_verifier::Error),
BlockReplayError(BlockReplayError),
DuplicateValidatorPublicKey,
ValidatorPubkeyCacheFileError(String),
ValidatorIndexUnknown(usize),
ValidatorPubkeyUnknown(PublicKeyBytes),
OpPoolError(OpPoolError),
NaiveAggregationError(NaiveAggregationError),
ObservedAttestationsError(ObservedAttestationsError),
ObservedAttestersError(ObservedAttestersError),
ObservedBlockProducersError(ObservedBlockProducersError),
AttesterCacheError(AttesterCacheError),
PruningError(PruningError),
ArithError(ArithError),
InvalidShufflingId {
shuffling_epoch: Epoch,
head_block_epoch: Epoch,
},
WeakSubjectivtyVerificationFailure,
WeakSubjectivtyShutdownError(TrySendError<ShutdownReason>),
AttestingToFinalizedSlot {
finalized_slot: Slot,
request_slot: Slot,
},
AttestingToAncientSlot {
lowest_permissible_slot: Slot,
request_slot: Slot,
},
BadPreState {
parent_root: Hash256,
parent_slot: Slot,
block_root: Hash256,
block_slot: Slot,
state_slot: Slot,
},
HistoricalBlockError(HistoricalBlockError),
InvalidStateForShuffling {
state_epoch: Epoch,
shuffling_epoch: Epoch,
},
SyncDutiesError(BeaconStateError),
InconsistentForwardsIter {
request_slot: Slot,
slot: Slot,
},
InvalidReorgSlotIter {
old_slot: Slot,
new_slot: Slot,
},
AltairForkDisabled,
ExecutionLayerMissing,
ExecutionForkChoiceUpdateFailed(execution_layer::Error),
PrepareProposerBlockingFailed(execution_layer::Error),
ExecutionForkChoiceUpdateInvalid {
status: PayloadStatus,
},
BlockRewardSlotError,
BlockRewardAttestationError,
BlockRewardSyncError,
HeadMissingFromForkChoice(Hash256),
FinalizedBlockMissingFromForkChoice(Hash256),
InvalidFinalizedPayload {
finalized_root: Hash256,
execution_block_hash: ExecutionBlockHash,
},
InvalidFinalizedPayloadShutdownError(TrySendError<ShutdownReason>),
JustifiedPayloadInvalid {
justified_root: Hash256,
execution_block_hash: Option<ExecutionBlockHash>,
},
ForkchoiceUpdate(execution_layer::Error),
FinalizedCheckpointMismatch {
head_state: Checkpoint,
fork_choice: Hash256,
},
InvalidSlot(Slot),
}
easy_from_to!(SlotProcessingError, BeaconChainError);
easy_from_to!(AttestationValidationError, BeaconChainError);
easy_from_to!(SyncCommitteeMessageValidationError, BeaconChainError);
easy_from_to!(ExitValidationError, BeaconChainError);
easy_from_to!(ProposerSlashingValidationError, BeaconChainError);
easy_from_to!(AttesterSlashingValidationError, BeaconChainError);
@@ -93,11 +176,18 @@ easy_from_to!(NaiveAggregationError, BeaconChainError);
easy_from_to!(ObservedAttestationsError, BeaconChainError);
easy_from_to!(ObservedAttestersError, BeaconChainError);
easy_from_to!(ObservedBlockProducersError, BeaconChainError);
easy_from_to!(AttesterCacheError, BeaconChainError);
easy_from_to!(BlockSignatureVerifierError, BeaconChainError);
easy_from_to!(PruningError, BeaconChainError);
easy_from_to!(ArithError, BeaconChainError);
easy_from_to!(ForkChoiceStoreError, BeaconChainError);
easy_from_to!(HistoricalBlockError, BeaconChainError);
easy_from_to!(StateAdvanceError, BeaconChainError);
easy_from_to!(BlockReplayError, BeaconChainError);
#[derive(Debug)]
pub enum BlockProductionError {
UnableToGetHeadInfo(BeaconChainError),
UnableToGetBlockRootFromState,
UnableToReadSlot,
UnableToProduceAtSlot(Slot),
@@ -105,13 +195,26 @@ pub enum BlockProductionError {
BlockProcessingError(BlockProcessingError),
Eth1ChainError(Eth1ChainError),
BeaconStateError(BeaconStateError),
StateAdvanceError(StateAdvanceError),
OpPoolError(OpPoolError),
/// The `BeaconChain` was explicitly configured _without_ a connection to eth1, therefore it
/// cannot produce blocks.
NoEth1ChainConnection,
StateSlotTooHigh {
produce_at_slot: Slot,
state_slot: Slot,
},
ExecutionLayerMissing,
BlockingFailed(execution_layer::Error),
TerminalPoWBlockLookupFailed(execution_layer::Error),
GetPayloadFailed(execution_layer::Error),
FailedToReadFinalizedBlock(store::Error),
MissingFinalizedBlock(Hash256),
BlockTooLarge(usize),
}
easy_from_to!(BlockProcessingError, BlockProductionError);
easy_from_to!(BeaconStateError, BlockProductionError);
easy_from_to!(SlotProcessingError, BlockProductionError);
easy_from_to!(Eth1ChainError, BlockProductionError);
easy_from_to!(StateAdvanceError, BlockProductionError);

View File

@@ -1,7 +1,8 @@
use crate::metrics;
use environment::TaskExecutor;
use eth1::{Config as Eth1Config, Eth1Block, Service as HttpService};
use eth2::lighthouse::Eth1SyncStatusData;
use eth2_hashing::hash;
use int_to_bytes::int_to_bytes32;
use slog::{debug, error, trace, Logger};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
@@ -10,7 +11,9 @@ use std::cmp::Ordering;
use std::collections::HashMap;
use std::iter::DoubleEndedIterator;
use std::marker::PhantomData;
use std::time::{SystemTime, UNIX_EPOCH};
use store::{DBColumn, Error as StoreError, StoreItem};
use task_executor::TaskExecutor;
use types::{
BeaconState, BeaconStateError, ChainSpec, Deposit, Eth1Data, EthSpec, Hash256, Slot, Unsigned,
DEPOSIT_TREE_DEPTH,
@@ -19,6 +22,11 @@ use types::{
type BlockNumber = u64;
type Eth1DataVoteCount = HashMap<(Eth1Data, BlockNumber), u64>;
/// We will declare ourself synced with the Eth1 chain, even if we are this many blocks behind.
///
/// This number (8) was chosen somewhat arbitrarily.
const ETH1_SYNC_TOLERANCE: u64 = 8;
#[derive(Debug)]
pub enum Error {
/// Unable to return an Eth1Data for the given epoch.
@@ -53,6 +61,113 @@ impl From<safe_arith::ArithError> for Error {
}
}
/// Returns an `Eth1SyncStatusData` given some parameters:
///
/// - `latest_cached_block`: The latest eth1 block in our cache, if any.
/// - `head_block`: The block at the very head of our eth1 node (ignoring follow distance, etc).
/// - `genesis_time`: beacon chain genesis time.
/// - `current_slot`: current beacon chain slot.
/// - `spec`: current beacon chain specification.
fn get_sync_status<T: EthSpec>(
latest_cached_block: Option<&Eth1Block>,
head_block: Option<&Eth1Block>,
genesis_time: u64,
current_slot: Option<Slot>,
spec: &ChainSpec,
) -> Option<Eth1SyncStatusData> {
let eth1_follow_distance_seconds = spec
.seconds_per_eth1_block
.saturating_mul(spec.eth1_follow_distance);
// The voting target timestamp needs to be special-cased when we're before
// genesis (as defined by `current_slot == None`).
//
// For the sake of this status, when prior to genesis we want to invent some voting periods
// that are *before* genesis, so that we can indicate to users that we're actually adequately
// cached for where they are in time.
let voting_target_timestamp = if let Some(current_slot) = current_slot {
let period = T::SlotsPerEth1VotingPeriod::to_u64();
let voting_period_start_slot = (current_slot / period) * period;
let period_start = slot_start_seconds::<T>(
genesis_time,
spec.seconds_per_slot,
voting_period_start_slot,
);
period_start.saturating_sub(eth1_follow_distance_seconds)
} else {
// The number of seconds in an eth1 voting period.
let voting_period_duration =
T::slots_per_eth1_voting_period() as u64 * spec.seconds_per_slot;
let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
// The number of seconds between now and genesis.
let seconds_till_genesis = genesis_time.saturating_sub(now);
// Determine how many voting periods are contained in distance between
// now and genesis, rounding up.
let voting_periods_past =
(seconds_till_genesis + voting_period_duration - 1) / voting_period_duration;
// Return the start time of the current voting period*.
//
// *: This voting period doesn't *actually* exist, we're just using it to
// give useful logs prior to genesis.
genesis_time
.saturating_sub(voting_periods_past * voting_period_duration)
.saturating_sub(eth1_follow_distance_seconds)
};
let latest_cached_block_number = latest_cached_block.map(|b| b.number);
let latest_cached_block_timestamp = latest_cached_block.map(|b| b.timestamp);
let head_block_number = head_block.map(|b| b.number);
let head_block_timestamp = head_block.map(|b| b.timestamp);
let eth1_node_sync_status_percentage = if let Some(head_block) = head_block {
let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
let head_age = now.saturating_sub(head_block.timestamp);
if head_age < ETH1_SYNC_TOLERANCE * spec.seconds_per_eth1_block {
// Always indicate we are fully synced if it's within the sync threshold.
100.0
} else {
let blocks_behind = head_age
.checked_div(spec.seconds_per_eth1_block)
.unwrap_or(0);
let part = f64::from(head_block.number as u32);
let whole = f64::from(head_block.number.saturating_add(blocks_behind) as u32);
if whole > 0.0 {
(part / whole) * 100.0
} else {
// Avoids a divide-by-zero.
0.0
}
}
} else {
// Always return 0% synced if the head block of the eth1 chain is unknown.
0.0
};
// Lighthouse is "cached and ready" when it has cached enough blocks to cover the start of the
// current voting period.
let lighthouse_is_cached_and_ready =
latest_cached_block_timestamp.map_or(false, |t| t >= voting_target_timestamp);
Some(Eth1SyncStatusData {
head_block_number,
head_block_timestamp,
latest_cached_block_number,
latest_cached_block_timestamp,
voting_target_timestamp,
eth1_node_sync_status_percentage,
lighthouse_is_cached_and_ready,
})
}
#[derive(Encode, Decode, Clone)]
pub struct SszEth1 {
use_dummy_backend: bool,
@@ -106,6 +221,11 @@ where
}
}
/// Returns `true` if the "dummy" backend is being used.
pub fn is_dummy_backend(&self) -> bool {
self.use_dummy_backend
}
/// Returns the `Eth1Data` that should be included in a block being produced for the given
/// `state`.
pub fn eth1_data_for_block_production(
@@ -143,6 +263,22 @@ where
}
}
/// Returns a status indicating how synced our caches are with the eth1 chain.
pub fn sync_status(
&self,
genesis_time: u64,
current_slot: Option<Slot>,
spec: &ChainSpec,
) -> Option<Eth1SyncStatusData> {
get_sync_status::<E>(
self.backend.latest_cached_block().as_ref(),
self.backend.head_block().as_ref(),
genesis_time,
current_slot,
spec,
)
}
/// Instantiate `Eth1Chain` from a persisted `SszEth1`.
///
/// The `Eth1Chain` will have the same caches as the persisted `SszEth1`.
@@ -195,6 +331,14 @@ pub trait Eth1ChainBackend<T: EthSpec>: Sized + Send + Sync {
spec: &ChainSpec,
) -> Result<Vec<Deposit>, Error>;
/// Returns the latest block stored in the cache. Used to obtain an idea of how up-to-date the
/// beacon node eth1 cache is.
fn latest_cached_block(&self) -> Option<Eth1Block>;
/// Returns the block at the head of the chain (ignoring follow distance, etc). Used to obtain
/// an idea of how up-to-date the remote eth1 node is.
fn head_block(&self) -> Option<Eth1Block>;
/// Encode the `Eth1ChainBackend` instance to bytes.
fn as_bytes(&self) -> Vec<u8>;
@@ -226,7 +370,7 @@ impl<T: EthSpec> Eth1ChainBackend<T> for DummyEth1ChainBackend<T> {
Ok(Eth1Data {
deposit_root: Hash256::from_slice(&deposit_root),
deposit_count: state.eth1_deposit_index,
deposit_count: state.eth1_deposit_index(),
block_hash: Hash256::from_slice(&block_hash),
})
}
@@ -241,6 +385,14 @@ impl<T: EthSpec> Eth1ChainBackend<T> for DummyEth1ChainBackend<T> {
Ok(vec![])
}
fn latest_cached_block(&self) -> Option<Eth1Block> {
None
}
fn head_block(&self) -> Option<Eth1Block> {
None
}
/// Return empty Vec<u8> for dummy backend.
fn as_bytes(&self) -> Vec<u8> {
Vec::new()
@@ -305,10 +457,10 @@ impl<T: EthSpec> CachingEth1Backend<T> {
impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
fn eth1_data(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Result<Eth1Data, Error> {
let period = T::SlotsPerEth1VotingPeriod::to_u64();
let voting_period_start_slot = (state.slot / period) * period;
let voting_period_start_slot = (state.slot() / period) * period;
let voting_period_start_seconds = slot_start_seconds::<T>(
state.genesis_time,
spec.milliseconds_per_slot,
state.genesis_time(),
spec.seconds_per_slot,
voting_period_start_slot,
);
@@ -333,24 +485,25 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
// If no votes exist, choose `state.eth1_data` as default vote.
votes_to_consider
.iter()
.max_by(|(_, x), (_, y)| x.cmp(y))
.max_by_key(|(_, block_number)| *block_number)
.map(|vote| {
let vote = vote.0.clone();
debug!(
self.log,
"No valid eth1_data votes";
"outcome" => "Casting vote corresponding to last candidate eth1 block",
"vote" => ?vote
);
vote
})
.unwrap_or_else(|| {
let vote = state.eth1_data.clone();
let vote = state.eth1_data().clone();
error!(
self.log,
"No valid eth1_data votes, `votes_to_consider` empty";
"lowest_block_number" => self.core.lowest_block_number(),
"earliest_block_timestamp" => self.core.earliest_block_timestamp(),
"genesis_time" => state.genesis_time,
"genesis_time" => state.genesis_time(),
"outcome" => "casting `state.eth1_data` as eth1 vote"
);
metrics::inc_counter(&metrics::DEFAULT_ETH1_VOTES);
@@ -375,11 +528,11 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
eth1_data_vote: &Eth1Data,
_spec: &ChainSpec,
) -> Result<Vec<Deposit>, Error> {
let deposit_index = state.eth1_deposit_index;
let deposit_index = state.eth1_deposit_index();
let deposit_count = if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data_vote)? {
new_eth1_data.deposit_count
} else {
state.eth1_data.deposit_count
state.eth1_data().deposit_count
};
match deposit_index.cmp(&deposit_count) {
@@ -400,6 +553,14 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
}
}
fn latest_cached_block(&self) -> Option<Eth1Block> {
self.core.latest_cached_block()
}
fn head_block(&self) -> Option<Eth1Block> {
self.core.head_block()
}
/// Return encoded byte representation of the block and deposit caches.
fn as_bytes(&self) -> Vec<u8> {
self.core.as_bytes()
@@ -454,14 +615,12 @@ fn collect_valid_votes<T: EthSpec>(
) -> Eth1DataVoteCount {
let mut valid_votes = HashMap::new();
state
.eth1_data_votes
.eth1_data_votes()
.iter()
.filter_map(|vote| {
if let Some(block_num) = votes_to_consider.get(vote) {
Some((vote.clone(), *block_num))
} else {
None
}
votes_to_consider
.get(vote)
.map(|block_num| (vote.clone(), *block_num))
})
.for_each(|(eth1_data, block_number)| {
valid_votes
@@ -480,20 +639,13 @@ fn find_winning_vote(valid_votes: Eth1DataVoteCount) -> Option<Eth1Data> {
.map(|((eth1_data, _), _)| eth1_data.clone())
}
/// Returns `int` as little-endian bytes with a length of 32.
fn int_to_bytes32(int: u64) -> Vec<u8> {
let mut vec = int.to_le_bytes().to_vec();
vec.resize(32, 0);
vec
}
/// Returns the unix-epoch seconds at the start of the given `slot`.
fn slot_start_seconds<T: EthSpec>(
genesis_unix_seconds: u64,
milliseconds_per_slot: u64,
seconds_per_slot: u64,
slot: Slot,
) -> u64 {
genesis_unix_seconds + slot.as_u64() * milliseconds_per_slot / 1_000
genesis_unix_seconds + slot.as_u64() * seconds_per_slot
}
/// Returns a boolean denoting if a given `Eth1Block` is a candidate for `Eth1Data` calculation
@@ -513,8 +665,7 @@ fn is_candidate_block(block: &Eth1Block, period_start: u64, spec: &ChainSpec) ->
mod test {
use super::*;
use environment::null_logger;
use std::iter::FromIterator;
use types::{test_utils::DepositTestTask, MinimalEthSpec};
use types::{DepositData, MinimalEthSpec, Signature};
type E = MinimalEthSpec;
@@ -528,10 +679,10 @@ mod test {
fn get_voting_period_start_seconds(state: &BeaconState<E>, spec: &ChainSpec) -> u64 {
let period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let voting_period_start_slot = (state.slot / period) * period;
let voting_period_start_slot = (state.slot() / period) * period;
slot_start_seconds::<E>(
state.genesis_time,
spec.milliseconds_per_slot,
state.genesis_time(),
spec.seconds_per_slot,
voting_period_start_slot,
)
}
@@ -541,21 +692,21 @@ mod test {
let zero_sec = 0;
assert_eq!(slot_start_seconds::<E>(100, zero_sec, Slot::new(2)), 100);
let half_sec = 500;
assert_eq!(slot_start_seconds::<E>(100, half_sec, Slot::new(0)), 100);
assert_eq!(slot_start_seconds::<E>(100, half_sec, Slot::new(1)), 100);
assert_eq!(slot_start_seconds::<E>(100, half_sec, Slot::new(2)), 101);
assert_eq!(slot_start_seconds::<E>(100, half_sec, Slot::new(3)), 101);
let one_sec = 1_000;
let one_sec = 1;
assert_eq!(slot_start_seconds::<E>(100, one_sec, Slot::new(0)), 100);
assert_eq!(slot_start_seconds::<E>(100, one_sec, Slot::new(1)), 101);
assert_eq!(slot_start_seconds::<E>(100, one_sec, Slot::new(2)), 102);
let three_sec = 3_000;
let three_sec = 3;
assert_eq!(slot_start_seconds::<E>(100, three_sec, Slot::new(0)), 100);
assert_eq!(slot_start_seconds::<E>(100, three_sec, Slot::new(1)), 103);
assert_eq!(slot_start_seconds::<E>(100, three_sec, Slot::new(2)), 106);
let five_sec = 5;
assert_eq!(slot_start_seconds::<E>(100, five_sec, Slot::new(0)), 100);
assert_eq!(slot_start_seconds::<E>(100, five_sec, Slot::new(1)), 105);
assert_eq!(slot_start_seconds::<E>(100, five_sec, Slot::new(2)), 110);
assert_eq!(slot_start_seconds::<E>(100, five_sec, Slot::new(3)), 115);
}
fn get_eth1_block(timestamp: u64, number: u64) -> Eth1Block {
@@ -571,10 +722,7 @@ mod test {
mod eth1_chain_json_backend {
use super::*;
use eth1::DepositLog;
use types::{
test_utils::{generate_deterministic_keypair, TestingDepositBuilder},
EthSpec, MainnetEthSpec,
};
use types::{test_utils::generate_deterministic_keypair, EthSpec, MainnetEthSpec};
fn get_eth1_chain() -> Eth1Chain<CachingEth1Backend<E>, E> {
let eth1_config = Eth1Config {
@@ -591,13 +739,17 @@ mod test {
fn get_deposit_log(i: u64, spec: &ChainSpec) -> DepositLog {
let keypair = generate_deterministic_keypair(i as usize);
let mut builder =
TestingDepositBuilder::new(keypair.pk.clone(), spec.max_effective_balance);
builder.sign(DepositTestTask::Valid, &keypair, spec);
let deposit_data = builder.build().data;
let mut deposit = DepositData {
pubkey: keypair.pk.into(),
withdrawal_credentials: Hash256::zero(),
amount: spec.max_effective_balance,
signature: Signature::empty().into(),
};
deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec());
DepositLog {
deposit_data,
deposit_data: deposit,
block_number: i,
index: i,
signature_is_valid: true,
@@ -610,14 +762,14 @@ mod test {
let eth1_chain = get_eth1_chain();
assert_eq!(
eth1_chain.use_dummy_backend, false,
assert!(
!eth1_chain.use_dummy_backend,
"test should not use dummy backend"
);
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
state.eth1_deposit_index = 0;
state.eth1_data.deposit_count = 0;
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
*state.eth1_deposit_index_mut() = 0;
state.eth1_data_mut().deposit_count = 0;
assert!(
eth1_chain
@@ -626,7 +778,7 @@ mod test {
"should succeed if cache is empty but no deposits are required"
);
state.eth1_data.deposit_count = 1;
state.eth1_data_mut().deposit_count = 1;
assert!(
eth1_chain
@@ -643,8 +795,8 @@ mod test {
let eth1_chain = get_eth1_chain();
let max_deposits = <E as EthSpec>::MaxDeposits::to_u64();
assert_eq!(
eth1_chain.use_dummy_backend, false,
assert!(
!eth1_chain.use_dummy_backend,
"test should not use dummy backend"
);
@@ -658,7 +810,7 @@ mod test {
.write()
.cache
.insert_log(log.clone())
.expect("should insert log")
.expect("should insert log");
})
.collect();
@@ -668,9 +820,9 @@ mod test {
"cache should store all logs"
);
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
state.eth1_deposit_index = 0;
state.eth1_data.deposit_count = 0;
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
*state.eth1_deposit_index_mut() = 0;
state.eth1_data_mut().deposit_count = 0;
assert!(
eth1_chain
@@ -680,10 +832,10 @@ mod test {
);
(0..3).for_each(|initial_deposit_index| {
state.eth1_deposit_index = initial_deposit_index as u64;
*state.eth1_deposit_index_mut() = initial_deposit_index as u64;
(initial_deposit_index..deposits.len()).for_each(|i| {
state.eth1_data.deposit_count = i as u64;
state.eth1_data_mut().deposit_count = i as u64;
let deposits_for_inclusion = eth1_chain
.deposits_for_block_inclusion(&state, &Eth1Data::default(), spec)
@@ -725,18 +877,19 @@ mod test {
let eth1_chain = get_eth1_chain();
assert_eq!(
eth1_chain.use_dummy_backend, false,
assert!(
!eth1_chain.use_dummy_backend,
"test should not use dummy backend"
);
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let a = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.eth1_data_for_block_production(&state, spec)
.expect("should produce default eth1 data vote");
assert_eq!(
a, state.eth1_data,
a,
*state.eth1_data(),
"default vote should be same as state.eth1_data"
);
}
@@ -749,16 +902,16 @@ mod test {
let eth1_chain = get_eth1_chain();
assert_eq!(
eth1_chain.use_dummy_backend, false,
assert!(
!eth1_chain.use_dummy_backend,
"test should not use dummy backend"
);
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
state.slot = Slot::from(slots_per_eth1_voting_period * 10);
*state.slot_mut() = Slot::from(slots_per_eth1_voting_period * 10);
let follow_distance_seconds = eth1_follow_distance * spec.seconds_per_eth1_block;
let voting_period_start = get_voting_period_start_seconds(&state, &spec);
let voting_period_start = get_voting_period_start_seconds(&state, spec);
let start_eth1_block = voting_period_start - follow_distance_seconds * 2;
let end_eth1_block = voting_period_start - follow_distance_seconds;
@@ -778,7 +931,7 @@ mod test {
});
let vote = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.eth1_data_for_block_production(&state, spec)
.expect("should produce default eth1 data vote");
assert_eq!(
@@ -808,7 +961,7 @@ mod test {
get_votes_to_consider(
blocks.iter(),
get_voting_period_start_seconds(&state, spec),
&spec,
spec,
),
HashMap::new()
);
@@ -822,8 +975,8 @@ mod test {
let eth1_follow_distance = spec.eth1_follow_distance;
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
state.genesis_time = 0;
state.slot = Slot::from(slots_per_eth1_voting_period * 10);
*state.genesis_time_mut() = 0;
*state.slot_mut() = Slot::from(slots_per_eth1_voting_period * 10);
let follow_distance_seconds = eth1_follow_distance * spec.seconds_per_eth1_block;
let voting_period_start = get_voting_period_start_seconds(&state, &spec);
@@ -887,10 +1040,7 @@ mod test {
let votes_to_consider = get_eth1_data_vec(slots, 0);
let votes = collect_valid_votes(
&state,
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
let votes = collect_valid_votes(&state, &votes_to_consider.into_iter().collect());
assert_eq!(
votes.len(),
0,
@@ -906,17 +1056,15 @@ mod test {
let votes_to_consider = get_eth1_data_vec(slots, 0);
state.eth1_data_votes = votes_to_consider[0..slots as usize / 4]
*state.eth1_data_votes_mut() = votes_to_consider[0..slots as usize / 4]
.iter()
.map(|(eth1_data, _)| eth1_data)
.cloned()
.collect::<Vec<_>>()
.into();
let votes = collect_valid_votes(
&state,
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
let votes =
collect_valid_votes(&state, &votes_to_consider.clone().into_iter().collect());
assert_votes!(
votes,
votes_to_consider[0..slots as usize / 4].to_vec(),
@@ -937,17 +1085,14 @@ mod test {
.expect("should have some eth1 data")
.clone();
state.eth1_data_votes = vec![duplicate_eth1_data.clone(); 4]
*state.eth1_data_votes_mut() = vec![duplicate_eth1_data.clone(); 4]
.iter()
.map(|(eth1_data, _)| eth1_data)
.cloned()
.collect::<Vec<_>>()
.into();
let votes = collect_valid_votes(
&state,
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
let votes = collect_valid_votes(&state, &votes_to_consider.into_iter().collect());
assert_votes!(
votes,
// There should only be one value if there's a duplicate
@@ -995,8 +1140,7 @@ mod test {
assert_eq!(
// Favour the highest block number when there are no votes.
vote_data(&no_votes[2]),
find_winning_vote(Eth1DataVoteCount::from_iter(no_votes.into_iter()))
.expect("should find winner")
find_winning_vote(no_votes.into_iter().collect()).expect("should find winner")
);
}
@@ -1007,8 +1151,7 @@ mod test {
assert_eq!(
// Favour the highest block number when there are equal votes.
vote_data(&votes[2]),
find_winning_vote(Eth1DataVoteCount::from_iter(votes.into_iter()))
.expect("should find winner")
find_winning_vote(votes.into_iter().collect()).expect("should find winner")
);
}
@@ -1019,8 +1162,7 @@ mod test {
assert_eq!(
// Favour the highest vote over the highest block number.
vote_data(&votes[3]),
find_winning_vote(Eth1DataVoteCount::from_iter(votes.into_iter()))
.expect("should find winner")
find_winning_vote(votes.into_iter().collect()).expect("should find winner")
);
}
@@ -1031,8 +1173,7 @@ mod test {
assert_eq!(
// Favour the highest block number for tying votes.
vote_data(&votes[3]),
find_winning_vote(Eth1DataVoteCount::from_iter(votes.into_iter()))
.expect("should find winner")
find_winning_vote(votes.into_iter().collect()).expect("should find winner")
);
}
@@ -1043,8 +1184,7 @@ mod test {
assert_eq!(
// Favour the highest block number for tying votes.
vote_data(&votes[0]),
find_winning_vote(Eth1DataVoteCount::from_iter(votes.into_iter()))
.expect("should find winner")
find_winning_vote(votes.into_iter().collect()).expect("should find winner")
);
}
}

View File

@@ -1,147 +1,152 @@
use bus::Bus;
use parking_lot::Mutex;
use serde_derive::{Deserialize, Serialize};
use slog::{error, Logger};
use std::marker::PhantomData;
use std::sync::Arc;
use types::{Attestation, Epoch, EthSpec, Hash256, SignedBeaconBlock, SignedBeaconBlockHash};
pub use websocket_server::WebSocketSender;
pub use eth2::types::{EventKind, SseBlock, SseFinalizedCheckpoint, SseHead};
use slog::{trace, Logger};
use tokio::sync::broadcast;
use tokio::sync::broadcast::{error::SendError, Receiver, Sender};
use types::EthSpec;
pub trait EventHandler<T: EthSpec>: Sized + Send + Sync {
fn register(&self, kind: EventKind<T>) -> Result<(), String>;
}
const DEFAULT_CHANNEL_CAPACITY: usize = 16;
pub struct NullEventHandler<T: EthSpec>(PhantomData<T>);
impl<T: EthSpec> EventHandler<T> for WebSocketSender<T> {
fn register(&self, kind: EventKind<T>) -> Result<(), String> {
self.send_string(
serde_json::to_string(&kind)
.map_err(|e| format!("Unable to serialize event: {:?}", e))?,
)
}
}
pub struct ServerSentEvents<T: EthSpec> {
// Bus<> is itself Sync + Send. We use Mutex<> here only because of the surrounding code does
// not enforce mutability statically (i.e. relies on interior mutability).
head_changed_queue: Arc<Mutex<Bus<SignedBeaconBlockHash>>>,
pub struct ServerSentEventHandler<T: EthSpec> {
attestation_tx: Sender<EventKind<T>>,
block_tx: Sender<EventKind<T>>,
finalized_tx: Sender<EventKind<T>>,
head_tx: Sender<EventKind<T>>,
exit_tx: Sender<EventKind<T>>,
chain_reorg_tx: Sender<EventKind<T>>,
contribution_tx: Sender<EventKind<T>>,
late_head: Sender<EventKind<T>>,
block_reward_tx: Sender<EventKind<T>>,
log: Logger,
_phantom: PhantomData<T>,
}
impl<T: EthSpec> ServerSentEvents<T> {
pub fn new(log: Logger) -> (Self, Arc<Mutex<Bus<SignedBeaconBlockHash>>>) {
let bus = Bus::new(T::slots_per_epoch() as usize);
let mutex = Mutex::new(bus);
let arc = Arc::new(mutex);
let this = Self {
head_changed_queue: arc.clone(),
log,
_phantom: PhantomData,
};
(this, arc)
impl<T: EthSpec> ServerSentEventHandler<T> {
pub fn new(log: Logger) -> Self {
Self::new_with_capacity(log, DEFAULT_CHANNEL_CAPACITY)
}
}
impl<T: EthSpec> EventHandler<T> for ServerSentEvents<T> {
fn register(&self, kind: EventKind<T>) -> Result<(), String> {
match kind {
EventKind::BeaconHeadChanged {
current_head_beacon_block_root,
..
} => {
let mut guard = self.head_changed_queue.lock();
if guard
.try_broadcast(current_head_beacon_block_root.into())
.is_err()
{
error!(
self.log,
"Head change streaming queue full";
"dropped_change" => format!("{}", current_head_beacon_block_root),
);
}
Ok(())
}
_ => Ok(()),
pub fn new_with_capacity(log: Logger, capacity: usize) -> Self {
let (attestation_tx, _) = broadcast::channel(capacity);
let (block_tx, _) = broadcast::channel(capacity);
let (finalized_tx, _) = broadcast::channel(capacity);
let (head_tx, _) = broadcast::channel(capacity);
let (exit_tx, _) = broadcast::channel(capacity);
let (chain_reorg_tx, _) = broadcast::channel(capacity);
let (contribution_tx, _) = broadcast::channel(capacity);
let (late_head, _) = broadcast::channel(capacity);
let (block_reward_tx, _) = broadcast::channel(capacity);
Self {
attestation_tx,
block_tx,
finalized_tx,
head_tx,
exit_tx,
chain_reorg_tx,
contribution_tx,
late_head,
block_reward_tx,
log,
}
}
}
// An event handler that pushes events to both the websockets handler and the SSE handler.
// Named after the unix `tee` command. Meant as a temporary solution before ditching WebSockets
// completely once SSE functions well enough.
pub struct TeeEventHandler<E: EthSpec> {
websockets_handler: WebSocketSender<E>,
sse_handler: ServerSentEvents<E>,
}
impl<E: EthSpec> TeeEventHandler<E> {
#[allow(clippy::type_complexity)]
pub fn new(
log: Logger,
websockets_handler: WebSocketSender<E>,
) -> Result<(Self, Arc<Mutex<Bus<SignedBeaconBlockHash>>>), String> {
let (sse_handler, bus) = ServerSentEvents::new(log);
let result = Self {
websockets_handler,
sse_handler,
pub fn register(&self, kind: EventKind<T>) {
let result = match kind {
EventKind::Attestation(attestation) => self
.attestation_tx
.send(EventKind::Attestation(attestation))
.map(|count| trace!(self.log, "Registering server-sent attestation event"; "receiver_count" => count)),
EventKind::Block(block) => self.block_tx.send(EventKind::Block(block))
.map(|count| trace!(self.log, "Registering server-sent block event"; "receiver_count" => count)),
EventKind::FinalizedCheckpoint(checkpoint) => self.finalized_tx
.send(EventKind::FinalizedCheckpoint(checkpoint))
.map(|count| trace!(self.log, "Registering server-sent finalized checkpoint event"; "receiver_count" => count)),
EventKind::Head(head) => self.head_tx.send(EventKind::Head(head))
.map(|count| trace!(self.log, "Registering server-sent head event"; "receiver_count" => count)),
EventKind::VoluntaryExit(exit) => self.exit_tx.send(EventKind::VoluntaryExit(exit))
.map(|count| trace!(self.log, "Registering server-sent voluntary exit event"; "receiver_count" => count)),
EventKind::ChainReorg(reorg) => self.chain_reorg_tx.send(EventKind::ChainReorg(reorg))
.map(|count| trace!(self.log, "Registering server-sent chain reorg event"; "receiver_count" => count)),
EventKind::ContributionAndProof(contribution_and_proof) => self.contribution_tx.send(EventKind::ContributionAndProof(contribution_and_proof))
.map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)),
EventKind::LateHead(late_head) => self.late_head.send(EventKind::LateHead(late_head))
.map(|count| trace!(self.log, "Registering server-sent late head event"; "receiver_count" => count)),
EventKind::BlockReward(block_reward) => self.block_reward_tx.send(EventKind::BlockReward(block_reward))
.map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)),
};
Ok((result, bus))
if let Err(SendError(event)) = result {
trace!(self.log, "No receivers registered to listen for event"; "event" => ?event);
}
}
pub fn subscribe_attestation(&self) -> Receiver<EventKind<T>> {
self.attestation_tx.subscribe()
}
pub fn subscribe_block(&self) -> Receiver<EventKind<T>> {
self.block_tx.subscribe()
}
pub fn subscribe_finalized(&self) -> Receiver<EventKind<T>> {
self.finalized_tx.subscribe()
}
pub fn subscribe_head(&self) -> Receiver<EventKind<T>> {
self.head_tx.subscribe()
}
pub fn subscribe_exit(&self) -> Receiver<EventKind<T>> {
self.exit_tx.subscribe()
}
pub fn subscribe_reorgs(&self) -> Receiver<EventKind<T>> {
self.chain_reorg_tx.subscribe()
}
pub fn subscribe_contributions(&self) -> Receiver<EventKind<T>> {
self.contribution_tx.subscribe()
}
pub fn subscribe_late_head(&self) -> Receiver<EventKind<T>> {
self.late_head.subscribe()
}
pub fn subscribe_block_reward(&self) -> Receiver<EventKind<T>> {
self.block_reward_tx.subscribe()
}
pub fn has_attestation_subscribers(&self) -> bool {
self.attestation_tx.receiver_count() > 0
}
pub fn has_block_subscribers(&self) -> bool {
self.block_tx.receiver_count() > 0
}
pub fn has_finalized_subscribers(&self) -> bool {
self.finalized_tx.receiver_count() > 0
}
pub fn has_head_subscribers(&self) -> bool {
self.head_tx.receiver_count() > 0
}
pub fn has_exit_subscribers(&self) -> bool {
self.exit_tx.receiver_count() > 0
}
pub fn has_reorg_subscribers(&self) -> bool {
self.chain_reorg_tx.receiver_count() > 0
}
pub fn has_contribution_subscribers(&self) -> bool {
self.contribution_tx.receiver_count() > 0
}
pub fn has_late_head_subscribers(&self) -> bool {
self.late_head.receiver_count() > 0
}
pub fn has_block_reward_subscribers(&self) -> bool {
self.block_reward_tx.receiver_count() > 0
}
}
impl<E: EthSpec> EventHandler<E> for TeeEventHandler<E> {
fn register(&self, kind: EventKind<E>) -> Result<(), String> {
self.websockets_handler.register(kind.clone())?;
self.sse_handler.register(kind)?;
Ok(())
}
}
impl<T: EthSpec> EventHandler<T> for NullEventHandler<T> {
fn register(&self, _kind: EventKind<T>) -> Result<(), String> {
Ok(())
}
}
impl<T: EthSpec> Default for NullEventHandler<T> {
fn default() -> Self {
NullEventHandler(PhantomData)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(
bound = "T: EthSpec",
rename_all = "snake_case",
tag = "event",
content = "data"
)]
pub enum EventKind<T: EthSpec> {
BeaconHeadChanged {
reorg: bool,
current_head_beacon_block_root: Hash256,
previous_head_beacon_block_root: Hash256,
},
BeaconFinalization {
epoch: Epoch,
root: Hash256,
},
BeaconBlockImported {
block_root: Hash256,
block: Box<SignedBeaconBlock<T>>,
},
BeaconBlockRejected {
reason: String,
block: Box<SignedBeaconBlock<T>>,
},
BeaconAttestationImported {
attestation: Box<Attestation<T>>,
},
BeaconAttestationRejected {
reason: String,
attestation: Box<Attestation<T>>,
},
}

View File

@@ -0,0 +1,354 @@
//! This module contains various functions for producing and verifying `ExecutionPayloads`.
//!
//! Lighthouse tends to do payload tasks in *slightly* different locations to the specification.
//! This is because some tasks involve calling out to external servers and it's nice to keep those
//! away from our pure `state_processing` and `fork_choice` crates.
//!
//! So, this module contains functions that one might expect to find in other crates, but they live
//! here for good reason.
use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, BlockProductionError,
ExecutionPayloadError,
};
use execution_layer::PayloadStatus;
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
use slog::debug;
use slot_clock::SlotClock;
use state_processing::per_block_processing::{
compute_timestamp_at_slot, is_execution_enabled, is_merge_transition_complete,
partially_verify_execution_payload,
};
use types::*;
/// Verify that `execution_payload` contained by `block` is considered valid by an execution
/// engine.
///
/// ## Specification
///
/// Equivalent to the `notify_new_payload` function in the merge Beacon Chain Changes, although it
/// contains a few extra checks by running `partially_verify_execution_payload` first:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.9/specs/bellatrix/beacon-chain.md#notify_new_payload
pub fn notify_new_payload<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
state: &BeaconState<T::EthSpec>,
block: BeaconBlockRef<T::EthSpec>,
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
if !is_execution_enabled(state, block.body()) {
return Ok(PayloadVerificationStatus::Irrelevant);
}
let execution_payload = block.execution_payload()?;
// Perform the initial stages of payload verification.
//
// We will duplicate these checks again during `per_block_processing`, however these checks
// are cheap and doing them here ensures we protect the execution payload from junk.
partially_verify_execution_payload(state, execution_payload, &chain.spec)
.map_err(BlockError::PerBlockProcessingError)?;
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
let new_payload_response = execution_layer
.block_on(|execution_layer| execution_layer.notify_new_payload(execution_payload));
match new_payload_response {
Ok(status) => match status {
PayloadStatus::Valid => Ok(PayloadVerificationStatus::Verified),
PayloadStatus::Syncing | PayloadStatus::Accepted => {
Ok(PayloadVerificationStatus::NotVerified)
}
PayloadStatus::Invalid {
latest_valid_hash, ..
} => {
// This block has not yet been applied to fork choice, so the latest block that was
// imported to fork choice was the parent.
let latest_root = block.parent_root();
chain.process_invalid_execution_payload(
&InvalidationOperation::InvalidateMany {
head_block_root: latest_root,
always_invalidate_head: false,
latest_valid_ancestor: latest_valid_hash,
},
)?;
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
}
PayloadStatus::InvalidTerminalBlock { .. } | PayloadStatus::InvalidBlockHash { .. } => {
// Returning an error here should be sufficient to invalidate the block. We have no
// information to indicate its parent is invalid, so no need to run
// `BeaconChain::process_invalid_execution_payload`.
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
}
},
Err(e) => Err(ExecutionPayloadError::RequestFailed(e).into()),
}
}
/// Verify that the block which triggers the merge is valid to be imported to fork choice.
///
/// ## Errors
///
/// Will return an error when using a pre-merge fork `state`. Ensure to only run this function
/// after the merge fork.
///
/// ## Specification
///
/// Equivalent to the `validate_merge_block` function in the merge Fork Choice Changes:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/fork-choice.md#validate_merge_block
pub fn validate_merge_block<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
block: BeaconBlockRef<T::EthSpec>,
) -> Result<(), BlockError<T::EthSpec>> {
let spec = &chain.spec;
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
let execution_payload = block.execution_payload()?;
if spec.terminal_block_hash != ExecutionBlockHash::zero() {
if block_epoch < spec.terminal_block_hash_activation_epoch {
return Err(ExecutionPayloadError::InvalidActivationEpoch {
activation_epoch: spec.terminal_block_hash_activation_epoch,
epoch: block_epoch,
}
.into());
}
if execution_payload.parent_hash != spec.terminal_block_hash {
return Err(ExecutionPayloadError::InvalidTerminalBlockHash {
terminal_block_hash: spec.terminal_block_hash,
payload_parent_hash: execution_payload.parent_hash,
}
.into());
}
return Ok(());
}
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
let is_valid_terminal_pow_block = execution_layer
.block_on(|execution_layer| {
execution_layer.is_valid_terminal_pow_block_hash(execution_payload.parent_hash, spec)
})
.map_err(ExecutionPayloadError::from)?;
match is_valid_terminal_pow_block {
Some(true) => Ok(()),
Some(false) => Err(ExecutionPayloadError::InvalidTerminalPoWBlock {
parent_hash: execution_payload.parent_hash,
}
.into()),
None => {
let current_slot = chain
.slot_clock
.now()
.ok_or(BeaconChainError::UnableToReadSlot)?;
// Ensure the block is a candidate for optimistic import.
if chain
.fork_choice
.read()
.is_optimistic_candidate_block(
current_slot,
block.slot(),
&block.parent_root(),
&chain.spec,
)
.map_err(BeaconChainError::from)?
{
debug!(
chain.log,
"Optimistically accepting terminal block";
"block_hash" => ?execution_payload.parent_hash,
"msg" => "the terminal block/parent was unavailable"
);
Ok(())
} else {
Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into())
}
}
}
}
/// Validate the gossip block's execution_payload according to the checks described here:
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block
pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
parent_block: &ProtoBlock,
block: BeaconBlockRef<'_, T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<(), BlockError<T::EthSpec>> {
// Only apply this validation if this is a merge beacon block.
if let Ok(execution_payload) = block.body().execution_payload() {
// This logic should match `is_execution_enabled`. We use only the execution block hash of
// the parent here in order to avoid loading the parent state during gossip verification.
let is_merge_transition_complete = match parent_block.execution_status {
// Optimistically declare that an "unknown" status block has completed the merge.
ExecutionStatus::Valid(_) | ExecutionStatus::Unknown(_) => true,
// It's impossible for an irrelevant block to have completed the merge. It is pre-merge
// by definition.
ExecutionStatus::Irrelevant(_) => false,
// If the parent has an invalid payload then it's impossible to build a valid block upon
// it. Reject the block.
ExecutionStatus::Invalid(_) => {
return Err(BlockError::ParentExecutionPayloadInvalid {
parent_root: parent_block.root,
})
}
};
if is_merge_transition_complete || execution_payload != &<_>::default() {
let expected_timestamp = chain
.slot_clock
.start_of(block.slot())
.map(|d| d.as_secs())
.ok_or(BlockError::BeaconChainError(
BeaconChainError::UnableToComputeTimeAtSlot,
))?;
// The block's execution payload timestamp is correct with respect to the slot
if execution_payload.timestamp != expected_timestamp {
return Err(BlockError::ExecutionPayloadError(
ExecutionPayloadError::InvalidPayloadTimestamp {
expected: expected_timestamp,
found: execution_payload.timestamp,
},
));
}
}
}
Ok(())
}
/// Gets an execution payload for inclusion in a block.
///
/// ## Errors
///
/// Will return an error when using a pre-merge fork `state`. Ensure to only run this function
/// after the merge fork.
///
/// ## Specification
///
/// Equivalent to the `get_execution_payload` function in the Validator Guide:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
pub fn get_execution_payload<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
) -> Result<ExecutionPayload<T::EthSpec>, BlockProductionError> {
Ok(prepare_execution_payload_blocking(chain, state, proposer_index)?.unwrap_or_default())
}
/// Wraps the async `prepare_execution_payload` function as a blocking task.
pub fn prepare_execution_payload_blocking<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
) -> Result<Option<ExecutionPayload<T::EthSpec>>, BlockProductionError> {
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
execution_layer
.block_on_generic(|_| async {
prepare_execution_payload(chain, state, proposer_index).await
})
.map_err(BlockProductionError::BlockingFailed)?
}
/// Prepares an execution payload for inclusion in a block.
///
/// Will return `Ok(None)` if the merge fork has occurred, but a terminal block has not been found.
///
/// ## Errors
///
/// Will return an error when using a pre-merge fork `state`. Ensure to only run this function
/// after the merge fork.
///
/// ## Specification
///
/// Equivalent to the `prepare_execution_payload` function in the Validator Guide:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
pub async fn prepare_execution_payload<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
) -> Result<Option<ExecutionPayload<T::EthSpec>>, BlockProductionError> {
let spec = &chain.spec;
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
let parent_hash = if !is_merge_transition_complete(state) {
let is_terminal_block_hash_set = spec.terminal_block_hash != ExecutionBlockHash::zero();
let is_activation_epoch_reached =
state.current_epoch() >= spec.terminal_block_hash_activation_epoch;
if is_terminal_block_hash_set && !is_activation_epoch_reached {
return Ok(None);
}
let terminal_pow_block_hash = execution_layer
.get_terminal_pow_block_hash(spec)
.await
.map_err(BlockProductionError::TerminalPoWBlockLookupFailed)?;
if let Some(terminal_pow_block_hash) = terminal_pow_block_hash {
terminal_pow_block_hash
} else {
return Ok(None);
}
} else {
state.latest_execution_payload_header()?.block_hash
};
let timestamp = compute_timestamp_at_slot(state, spec).map_err(BeaconStateError::from)?;
let random = *state.get_randao_mix(state.current_epoch())?;
let finalized_root = state.finalized_checkpoint().root;
// The finalized block hash is not included in the specification, however we provide this
// parameter so that the execution layer can produce a payload id if one is not already known
// (e.g., due to a recent reorg).
let finalized_block_hash =
if let Some(block) = chain.fork_choice.read().get_block(&finalized_root) {
block.execution_status.block_hash()
} else {
chain
.store
.get_block(&finalized_root)
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
.ok_or(BlockProductionError::MissingFinalizedBlock(finalized_root))?
.message()
.body()
.execution_payload()
.ok()
.map(|ep| ep.block_hash)
};
// Note: the suggested_fee_recipient is stored in the `execution_layer`, it will add this parameter.
let execution_payload = execution_layer
.get_payload(
parent_hash,
timestamp,
random,
finalized_block_hash.unwrap_or_else(ExecutionBlockHash::zero),
proposer_index,
)
.await
.map_err(BlockProductionError::GetPayloadFailed)?;
Ok(Some(execution_payload))
}

View File

@@ -0,0 +1,193 @@
use crate::{BeaconForkChoiceStore, BeaconSnapshot};
use fork_choice::{ForkChoice, PayloadVerificationStatus};
use itertools::process_results;
use slog::{info, warn, Logger};
use state_processing::state_advance::complete_state_advance;
use state_processing::{
per_block_processing, per_block_processing::BlockSignatureStrategy, VerifyBlockRoot,
};
use std::sync::Arc;
use std::time::Duration;
use store::{iter::ParentRootBlockIterator, HotColdDB, ItemStore};
use types::{BeaconState, ChainSpec, EthSpec, ForkName, Hash256, SignedBeaconBlock, Slot};
const CORRUPT_DB_MESSAGE: &str = "The database could be corrupt. Check its file permissions or \
consider deleting it by running with the --purge-db flag.";
/// Revert the head to the last block before the most recent hard fork.
///
/// This function is destructive and should only be used if there is no viable alternative. It will
/// cause the reverted blocks and states to be completely forgotten, lying dormant in the database
/// forever.
///
/// Return the `(head_block_root, head_block)` that should be used post-reversion.
pub fn revert_to_fork_boundary<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
current_slot: Slot,
head_block_root: Hash256,
store: Arc<HotColdDB<E, Hot, Cold>>,
spec: &ChainSpec,
log: &Logger,
) -> Result<(Hash256, SignedBeaconBlock<E>), String> {
let current_fork = spec.fork_name_at_slot::<E>(current_slot);
let fork_epoch = spec
.fork_epoch(current_fork)
.ok_or_else(|| format!("Current fork '{}' never activates", current_fork))?;
if current_fork == ForkName::Base {
return Err(format!(
"Cannot revert to before phase0 hard fork. {}",
CORRUPT_DB_MESSAGE
));
}
warn!(
log,
"Reverting invalid head block";
"target_fork" => %current_fork,
"fork_epoch" => fork_epoch,
);
let block_iter = ParentRootBlockIterator::fork_tolerant(&store, head_block_root);
process_results(block_iter, |mut iter| {
iter.find_map(|(block_root, block)| {
if block.slot() < fork_epoch.start_slot(E::slots_per_epoch()) {
Some((block_root, block))
} else {
info!(
log,
"Reverting block";
"block_root" => ?block_root,
"slot" => block.slot(),
);
None
}
})
})
.map_err(|e| {
format!(
"Error fetching blocks to revert: {:?}. {}",
e, CORRUPT_DB_MESSAGE
)
})?
.ok_or_else(|| format!("No pre-fork blocks found. {}", CORRUPT_DB_MESSAGE))
}
/// Reset fork choice to the finalized checkpoint of the supplied head state.
///
/// The supplied `head_block_root` should correspond to the most recently applied block on
/// `head_state`.
///
/// This function avoids quirks of fork choice initialization by replaying all of the blocks from
/// the checkpoint to the head.
///
/// See this issue for details: https://github.com/ethereum/consensus-specs/issues/2566
///
/// It will fail if the finalized state or any of the blocks to replay are unavailable.
///
/// WARNING: this function is destructive and causes fork choice to permanently forget all
/// chains other than the chain leading to `head_block_root`. It should only be used in extreme
/// circumstances when there is no better alternative.
pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
head_block_root: Hash256,
head_state: &BeaconState<E>,
store: Arc<HotColdDB<E, Hot, Cold>>,
spec: &ChainSpec,
) -> Result<ForkChoice<BeaconForkChoiceStore<E, Hot, Cold>, E>, String> {
// Fetch finalized block.
let finalized_checkpoint = head_state.finalized_checkpoint();
let finalized_block_root = finalized_checkpoint.root;
let finalized_block = store
.get_block(&finalized_block_root)
.map_err(|e| format!("Error loading finalized block: {:?}", e))?
.ok_or_else(|| {
format!(
"Finalized block missing for revert: {:?}",
finalized_block_root
)
})?;
// Advance finalized state to finalized epoch (to handle skipped slots).
let finalized_state_root = finalized_block.state_root();
let mut finalized_state = store
.get_state(&finalized_state_root, Some(finalized_block.slot()))
.map_err(|e| format!("Error loading finalized state: {:?}", e))?
.ok_or_else(|| {
format!(
"Finalized block state missing from database: {:?}",
finalized_state_root
)
})?;
let finalized_slot = finalized_checkpoint.epoch.start_slot(E::slots_per_epoch());
complete_state_advance(
&mut finalized_state,
Some(finalized_state_root),
finalized_slot,
spec,
)
.map_err(|e| {
format!(
"Error advancing finalized state to finalized epoch: {:?}",
e
)
})?;
let finalized_snapshot = BeaconSnapshot {
beacon_block_root: finalized_block_root,
beacon_block: finalized_block,
beacon_state: finalized_state,
};
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store.clone(), &finalized_snapshot);
let mut fork_choice = ForkChoice::from_anchor(
fc_store,
finalized_block_root,
&finalized_snapshot.beacon_block,
&finalized_snapshot.beacon_state,
)
.map_err(|e| format!("Unable to reset fork choice for revert: {:?}", e))?;
// Replay blocks from finalized checkpoint back to head.
// We do not replay attestations presently, relying on the absence of other blocks
// to guarantee `head_block_root` as the head.
let blocks = store
.load_blocks_to_replay(finalized_slot + 1, head_state.slot(), head_block_root)
.map_err(|e| format!("Error loading blocks to replay for fork choice: {:?}", e))?;
let mut state = finalized_snapshot.beacon_state;
for block in blocks {
complete_state_advance(&mut state, None, block.slot(), spec)
.map_err(|e| format!("State advance failed: {:?}", e))?;
per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::NoVerification,
VerifyBlockRoot::True,
spec,
)
.map_err(|e| format!("Error replaying block: {:?}", e))?;
// Setting this to unverified is the safest solution, since we don't have a way to
// retro-actively determine if they were valid or not.
//
// This scenario is so rare that it seems OK to double-verify some blocks.
let payload_verification_status = PayloadVerificationStatus::NotVerified;
let (block, _) = block.deconstruct();
fork_choice
.on_block(
block.slot(),
&block,
block.canonical_root(),
// Reward proposer boost. We are reinforcing the canonical chain.
Duration::from_secs(0),
&state,
payload_verification_status,
spec,
)
.map_err(|e| format!("Error applying replayed block to fork choice: {:?}", e))?;
}
Ok(fork_choice)
}

View File

@@ -1,7 +1,6 @@
use parking_lot::RwLock;
use ssz_derive::{Decode, Encode};
use std::collections::HashMap;
use std::iter::FromIterator;
use types::{Hash256, Slot};
#[derive(Debug, PartialEq)]
@@ -15,7 +14,7 @@ pub enum Error {
/// In order for this struct to be effective, every single block that is imported must be
/// registered here.
#[derive(Default, Debug)]
pub struct HeadTracker(RwLock<HashMap<Hash256, Slot>>);
pub struct HeadTracker(pub RwLock<HashMap<Hash256, Slot>>);
impl HeadTracker {
/// Register a block with `Self`, so it may or may not be included in a `Self::heads` call.
@@ -29,13 +28,6 @@ impl HeadTracker {
map.insert(block_root, slot);
}
/// Removes abandoned head.
pub fn remove_head(&self, block_root: Hash256) {
let mut map = self.0.write();
debug_assert!(map.contains_key(&block_root));
map.remove(&block_root);
}
/// Returns true iff `block_root` is a recognized head.
pub fn contains_head(&self, block_root: Hash256) -> bool {
self.0.read().contains_key(&block_root)
@@ -53,14 +45,7 @@ impl HeadTracker {
/// Returns a `SszHeadTracker`, which contains all necessary information to restore the state
/// of `Self` at some later point.
pub fn to_ssz_container(&self) -> SszHeadTracker {
let (roots, slots) = self
.0
.read()
.iter()
.map(|(hash, slot)| (*hash, *slot))
.unzip();
SszHeadTracker { roots, slots }
SszHeadTracker::from_map(&*self.0.read())
}
/// Creates a new `Self` from the given `SszHeadTracker`, restoring `Self` to the same state of
@@ -75,13 +60,12 @@ impl HeadTracker {
slots_len,
})
} else {
let map = HashMap::from_iter(
ssz_container
.roots
.iter()
.zip(ssz_container.slots.iter())
.map(|(root, slot)| (*root, *slot)),
);
let map = ssz_container
.roots
.iter()
.zip(ssz_container.slots.iter())
.map(|(root, slot)| (*root, *slot))
.collect::<HashMap<_, _>>();
Ok(Self(RwLock::new(map)))
}
@@ -103,6 +87,13 @@ pub struct SszHeadTracker {
slots: Vec<Slot>,
}
impl SszHeadTracker {
pub fn from_map(map: &HashMap<Hash256, Slot>) -> Self {
let (roots, slots) = map.iter().map(|(hash, slot)| (*hash, *slot)).unzip();
SszHeadTracker { roots, slots }
}
}
#[cfg(test)]
mod test {
use super::*;
@@ -121,14 +112,14 @@ mod test {
let mut block: BeaconBlock<E> = BeaconBlock::empty(spec);
let block_root = Hash256::from_low_u64_be(i);
block.slot = Slot::new(i);
block.parent_root = if i == 0 {
*block.slot_mut() = Slot::new(i);
*block.parent_root_mut() = if i == 0 {
Hash256::random()
} else {
Hash256::from_low_u64_be(i - 1)
};
head_tracker.register_block(block_root, block.parent_root, block.slot);
head_tracker.register_block(block_root, block.parent_root(), block.slot());
}
assert_eq!(
@@ -139,9 +130,9 @@ mod test {
let mut block: BeaconBlock<E> = BeaconBlock::empty(spec);
let block_root = Hash256::from_low_u64_be(42);
block.slot = Slot::new(15);
block.parent_root = Hash256::from_low_u64_be(14);
head_tracker.register_block(block_root, block.parent_root, block.slot);
*block.slot_mut() = Slot::new(15);
*block.parent_root_mut() = Hash256::from_low_u64_be(14);
head_tracker.register_block(block_root, block.parent_root(), block.slot());
let heads = head_tracker.heads();

View File

@@ -0,0 +1,202 @@
use crate::{errors::BeaconChainError as Error, metrics, BeaconChain, BeaconChainTypes};
use itertools::Itertools;
use slog::debug;
use state_processing::{
per_block_processing::ParallelSignatureSets,
signature_sets::{block_proposal_signature_set_from_parts, Error as SignatureSetError},
};
use std::borrow::Cow;
use std::iter;
use std::time::Duration;
use store::{chunked_vector::BlockRoots, AnchorInfo, ChunkWriter, KeyValueStore};
use types::{Hash256, SignedBeaconBlock, Slot};
/// Use a longer timeout on the pubkey cache.
///
/// It's ok if historical sync is stalled due to writes from forwards block processing.
const PUBKEY_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub enum HistoricalBlockError {
/// Block is not available (only returned when fetching historic blocks).
BlockOutOfRange { slot: Slot, oldest_block_slot: Slot },
/// Block root mismatch, caller should retry with different blocks.
MismatchedBlockRoot {
block_root: Hash256,
expected_block_root: Hash256,
},
/// Bad signature, caller should retry with different blocks.
SignatureSet(SignatureSetError),
/// Bad signature, caller should retry with different blocks.
InvalidSignature,
/// Transitory error, caller should retry with the same blocks.
ValidatorPubkeyCacheTimeout,
/// No historical sync needed.
NoAnchorInfo,
/// Logic error: should never occur.
IndexOutOfBounds,
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Store a batch of historical blocks in the database.
///
/// The `blocks` should be given in slot-ascending order. One of the blocks should have a block
/// root corresponding to the `oldest_block_parent` from the store's `AnchorInfo`.
///
/// The block roots and proposer signatures are verified. If any block doesn't match the parent
/// root listed in its successor, then the whole batch will be discarded and
/// `MismatchedBlockRoot` will be returned. If any proposer signature is invalid then
/// `SignatureSetError` or `InvalidSignature` will be returned.
///
/// To align with sync we allow some excess blocks with slots greater than or equal to
/// `oldest_block_slot` to be provided. They will be ignored without being checked.
///
/// This function should not be called concurrently with any other function that mutates
/// the anchor info (including this function itself). If a concurrent mutation occurs that
/// would violate consistency then an `AnchorInfoConcurrentMutation` error will be returned.
///
/// Return the number of blocks successfully imported.
pub fn import_historical_block_batch(
&self,
blocks: &[SignedBeaconBlock<T::EthSpec>],
) -> Result<usize, Error> {
let anchor_info = self
.store
.get_anchor_info()
.ok_or(HistoricalBlockError::NoAnchorInfo)?;
// Take all blocks with slots less than the oldest block slot.
let num_relevant =
blocks.partition_point(|block| block.slot() < anchor_info.oldest_block_slot);
let blocks_to_import = &blocks
.get(..num_relevant)
.ok_or(HistoricalBlockError::IndexOutOfBounds)?;
if blocks_to_import.len() != blocks.len() {
debug!(
self.log,
"Ignoring some historic blocks";
"oldest_block_slot" => anchor_info.oldest_block_slot,
"total_blocks" => blocks.len(),
"ignored" => blocks.len().saturating_sub(blocks_to_import.len()),
);
}
if blocks_to_import.is_empty() {
return Ok(0);
}
let mut expected_block_root = anchor_info.oldest_block_parent;
let mut prev_block_slot = anchor_info.oldest_block_slot;
let mut chunk_writer =
ChunkWriter::<BlockRoots, _, _>::new(&self.store.cold_db, prev_block_slot.as_usize())?;
let mut cold_batch = Vec::with_capacity(blocks.len());
let mut hot_batch = Vec::with_capacity(blocks.len());
for block in blocks_to_import.iter().rev() {
// Check chain integrity.
let block_root = block.canonical_root();
if block_root != expected_block_root {
return Err(HistoricalBlockError::MismatchedBlockRoot {
block_root,
expected_block_root,
}
.into());
}
// Store block in the hot database.
hot_batch.push(self.store.block_as_kv_store_op(&block_root, block));
// Store block roots, including at all skip slots in the freezer DB.
for slot in (block.slot().as_usize()..prev_block_slot.as_usize()).rev() {
chunk_writer.set(slot, block_root, &mut cold_batch)?;
}
prev_block_slot = block.slot();
expected_block_root = block.message().parent_root();
// If we've reached genesis, add the genesis block root to the batch and set the
// anchor slot to 0 to indicate completion.
if expected_block_root == self.genesis_block_root {
let genesis_slot = self.spec.genesis_slot;
chunk_writer.set(
genesis_slot.as_usize(),
self.genesis_block_root,
&mut cold_batch,
)?;
prev_block_slot = genesis_slot;
expected_block_root = Hash256::zero();
break;
}
}
chunk_writer.write(&mut cold_batch)?;
// Verify signatures in one batch, holding the pubkey cache lock for the shortest duration
// possible. For each block fetch the parent root from its successor. Slicing from index 1
// is safe because we've already checked that `blocks_to_import` is non-empty.
let sig_timer = metrics::start_timer(&metrics::BACKFILL_SIGNATURE_TOTAL_TIMES);
let setup_timer = metrics::start_timer(&metrics::BACKFILL_SIGNATURE_SETUP_TIMES);
let pubkey_cache = self
.validator_pubkey_cache
.try_read_for(PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(HistoricalBlockError::ValidatorPubkeyCacheTimeout)?;
let block_roots = blocks_to_import
.get(1..)
.ok_or(HistoricalBlockError::IndexOutOfBounds)?
.iter()
.map(|block| block.parent_root())
.chain(iter::once(anchor_info.oldest_block_parent));
let signature_set = blocks_to_import
.iter()
.zip_eq(block_roots)
.map(|(block, block_root)| {
block_proposal_signature_set_from_parts(
block,
Some(block_root),
block.message().proposer_index(),
&self.spec.fork_at_epoch(block.message().epoch()),
self.genesis_validators_root,
|validator_index| pubkey_cache.get(validator_index).cloned().map(Cow::Owned),
&self.spec,
)
})
.collect::<Result<Vec<_>, _>>()
.map_err(HistoricalBlockError::SignatureSet)
.map(ParallelSignatureSets::from)?;
drop(pubkey_cache);
drop(setup_timer);
let verify_timer = metrics::start_timer(&metrics::BACKFILL_SIGNATURE_VERIFY_TIMES);
if !signature_set.verify() {
return Err(HistoricalBlockError::InvalidSignature.into());
}
drop(verify_timer);
drop(sig_timer);
// Write the I/O batches to disk, writing the blocks themselves first, as it's better
// for the hot DB to contain extra blocks than for the cold DB to point to blocks that
// do not exist.
self.store.hot_db.do_atomically(hot_batch)?;
self.store.cold_db.do_atomically(cold_batch)?;
// Update the anchor.
let new_anchor = AnchorInfo {
oldest_block_slot: prev_block_slot,
oldest_block_parent: expected_block_root,
..anchor_info
};
let backfill_complete = new_anchor.block_backfill_complete();
self.store
.compare_and_set_anchor_info_with_write(Some(anchor_info), Some(new_anchor))?;
// If backfill has completed and the chain is configured to reconstruct historic states,
// send a message to the background migrator instructing it to begin reconstruction.
if backfill_complete && self.config.reconstruct_historic_states {
self.store_migrator.process_reconstruction();
}
Ok(blocks_to_import.len())
}
}

View File

@@ -1,43 +1,58 @@
#![recursion_limit = "128"] // For lazy-static
#[macro_use]
extern crate lazy_static;
pub mod attestation_verification;
mod attester_cache;
mod beacon_chain;
mod beacon_fork_choice_store;
pub mod beacon_proposer_cache;
mod beacon_snapshot;
pub mod block_reward;
mod block_times_cache;
mod block_verification;
pub mod builder;
pub mod chain_config;
mod early_attester_cache;
mod errors;
pub mod eth1_chain;
pub mod events;
mod execution_payload;
pub mod fork_revert;
mod head_tracker;
pub mod historical_blocks;
mod metrics;
pub mod migrate;
mod naive_aggregation_pool;
mod observed_attestations;
mod observed_aggregates;
mod observed_attesters;
mod observed_block_producers;
pub mod observed_operations;
mod persisted_beacon_chain;
mod persisted_fork_choice;
mod pre_finalization_cache;
pub mod proposer_prep_service;
pub mod schema_change;
mod shuffling_cache;
mod snapshot_cache;
pub mod state_advance_timer;
pub mod sync_committee_verification;
pub mod test_utils;
mod timeout_rw_lock;
pub mod validator_monitor;
mod validator_pubkey_cache;
pub use self::beacon_chain::{
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, ChainSegmentResult,
ForkChoiceError, StateSkipConfig,
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult,
ForkChoiceError, HeadInfo, HeadSafetyStatus, StateSkipConfig, WhenSlotSkipped,
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
};
pub use self::beacon_snapshot::BeaconSnapshot;
pub use self::chain_config::ChainConfig;
pub use self::errors::{BeaconChainError, BlockProductionError};
pub use self::historical_blocks::HistoricalBlockError;
pub use attestation_verification::Error as AttestationError;
pub use beacon_fork_choice_store::{BeaconForkChoiceStore, Error as ForkChoiceStoreError};
pub use block_verification::{BlockError, GossipVerifiedBlock};
pub use block_verification::{BlockError, ExecutionPayloadError, GossipVerifiedBlock};
pub use eth1_chain::{Eth1Chain, Eth1ChainBackend};
pub use events::EventHandler;
pub use events::ServerSentEventHandler;
pub use metrics::scrape_for_metrics;
pub use parking_lot;
pub use slot_clock;
@@ -46,4 +61,5 @@ pub use state_processing::per_block_processing::errors::{
ExitValidationError, ProposerSlashingValidationError,
};
pub use store;
pub use timeout_rw_lock::TimeoutRwLock;
pub use types;

View File

@@ -1,8 +1,15 @@
use crate::{BeaconChain, BeaconChainTypes};
use crate::observed_attesters::SlotSubcommitteeIndex;
use crate::types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use lazy_static::lazy_static;
pub use lighthouse_metrics::*;
use slot_clock::SlotClock;
use std::time::Duration;
use types::{BeaconState, Epoch, EthSpec, Hash256, Slot};
/// The maximum time to wait for the snapshot cache lock during a metrics scrape.
const SNAPSHOT_CACHE_TIMEOUT: Duration = Duration::from_millis(100);
lazy_static! {
/*
* Block Processing
@@ -15,6 +22,18 @@ lazy_static! {
"beacon_block_processing_successes_total",
"Count of blocks processed without error"
);
pub static ref BLOCK_PROCESSING_SNAPSHOT_CACHE_SIZE: Result<IntGauge> = try_create_int_gauge(
"beacon_block_processing_snapshot_cache_size",
"Count snapshots in the snapshot cache"
);
pub static ref BLOCK_PROCESSING_SNAPSHOT_CACHE_MISSES: Result<IntCounter> = try_create_int_counter(
"beacon_block_processing_snapshot_cache_misses",
"Count of snapshot cache misses"
);
pub static ref BLOCK_PROCESSING_SNAPSHOT_CACHE_CLONES: Result<IntCounter> = try_create_int_counter(
"beacon_block_processing_snapshot_cache_clones",
"Count of snapshot cache clones"
);
pub static ref BLOCK_PROCESSING_TIMES: Result<Histogram> =
try_create_histogram("beacon_block_processing_seconds", "Full runtime of block processing");
pub static ref BLOCK_PROCESSING_BLOCK_ROOT: Result<Histogram> = try_create_histogram(
@@ -53,6 +72,10 @@ lazy_static! {
"beacon_block_processing_attestation_observation_seconds",
"Time spent hashing and remembering all the attestations in the block"
);
pub static ref BLOCK_SYNC_AGGREGATE_SET_BITS: Result<IntGauge> = try_create_int_gauge(
"block_sync_aggregate_set_bits",
"The number of true bits in the last sync aggregate in a block"
);
/*
* Block Production
@@ -67,6 +90,30 @@ lazy_static! {
);
pub static ref BLOCK_PRODUCTION_TIMES: Result<Histogram> =
try_create_histogram("beacon_block_production_seconds", "Full runtime of block production");
pub static ref BLOCK_PRODUCTION_STATE_LOAD_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_state_load_seconds",
"Time taken to load the base state for block production"
);
pub static ref BLOCK_PRODUCTION_SLOT_PROCESS_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_slot_process_seconds",
"Time taken to advance the state to the block production slot"
);
pub static ref BLOCK_PRODUCTION_UNAGGREGATED_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_unaggregated_seconds",
"Time taken to import the naive aggregation pool for block production"
);
pub static ref BLOCK_PRODUCTION_ATTESTATION_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_attestation_seconds",
"Time taken to pack attestations into a block"
);
pub static ref BLOCK_PRODUCTION_PROCESS_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_process_seconds",
"Time taken to process the block produced"
);
pub static ref BLOCK_PRODUCTION_STATE_ROOT_TIMES: Result<Histogram> = try_create_histogram(
"beacon_block_production_state_root_seconds",
"Time taken to calculate the block's state root"
);
/*
* Block Statistics
@@ -76,6 +123,11 @@ lazy_static! {
"Number of attestations in a block"
);
pub static ref BLOCK_SIZE: Result<Histogram> = try_create_histogram(
"beacon_block_total_size",
"Size of a signed beacon block"
);
/*
* Unaggregated Attestation Verification
*/
@@ -115,10 +167,6 @@ lazy_static! {
"beacon_attestation_processing_apply_to_agg_pool",
"Time spent applying an attestation to the naive aggregation pool"
);
pub static ref ATTESTATION_PROCESSING_AGG_POOL_MAPS_WRITE_LOCK: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_agg_pool_maps_write_lock",
"Time spent waiting for the maps write lock when adding to the agg poll"
);
pub static ref ATTESTATION_PROCESSING_AGG_POOL_PRUNE: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_agg_pool_prune",
"Time spent for the agg pool to prune"
@@ -172,6 +220,26 @@ lazy_static! {
"Time spent on the signature verification of attestation processing"
);
/*
* Batch Attestation Processing
*/
pub static ref ATTESTATION_PROCESSING_BATCH_AGG_SIGNATURE_SETUP_TIMES: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_batch_agg_signature_setup_times",
"Time spent on setting up for the signature verification of batch aggregate processing"
);
pub static ref ATTESTATION_PROCESSING_BATCH_AGG_SIGNATURE_TIMES: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_batch_agg_signature_times",
"Time spent on the signature verification of batch aggregate attestation processing"
);
pub static ref ATTESTATION_PROCESSING_BATCH_UNAGG_SIGNATURE_SETUP_TIMES: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_batch_unagg_signature_setup_times",
"Time spent on setting up for the signature verification of batch unaggregate processing"
);
pub static ref ATTESTATION_PROCESSING_BATCH_UNAGG_SIGNATURE_TIMES: Result<Histogram> = try_create_histogram(
"beacon_attestation_processing_batch_unagg_signature_times",
"Time spent on the signature verification of batch unaggregate attestation processing"
);
/*
* Shuffling cache
*/
@@ -180,21 +248,33 @@ lazy_static! {
pub static ref SHUFFLING_CACHE_MISSES: Result<IntCounter> =
try_create_int_counter("beacon_shuffling_cache_misses_total", "Count of times shuffling cache fulfils request");
/*
* Early attester cache
*/
pub static ref BEACON_EARLY_ATTESTER_CACHE_HITS: Result<IntCounter> = try_create_int_counter(
"beacon_early_attester_cache_hits",
"Count of times the early attester cache returns an attestation"
);
/*
* Attestation Production
*/
pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result<IntCounter> = try_create_int_counter(
"beacon_attestation_production_requests_total",
"Count of all attestation production requests"
);
pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result<IntCounter> = try_create_int_counter(
"beacon_attestation_production_successes_total",
"Count of attestations processed without error"
);
pub static ref ATTESTATION_PRODUCTION_TIMES: Result<Histogram> = try_create_histogram(
pub static ref ATTESTATION_PRODUCTION_SECONDS: Result<Histogram> = try_create_histogram(
"beacon_attestation_production_seconds",
"Full runtime of attestation production"
);
pub static ref ATTESTATION_PRODUCTION_HEAD_SCRAPE_SECONDS: Result<Histogram> = try_create_histogram(
"attestation_production_head_scrape_seconds",
"Time taken to read the head state"
);
pub static ref ATTESTATION_PRODUCTION_CACHE_INTERACTION_SECONDS: Result<Histogram> = try_create_histogram(
"attestation_production_cache_interaction_seconds",
"Time spent interacting with the attester cache"
);
pub static ref ATTESTATION_PRODUCTION_CACHE_PRIME_SECONDS: Result<Histogram> = try_create_histogram(
"attestation_production_cache_prime_seconds",
"Time spent loading a new state from the disk due to a cache miss"
);
}
// Second lazy-static block is used to account for macro recursion limit.
@@ -218,6 +298,10 @@ lazy_static! {
"beacon_fork_choice_reorg_total",
"Count of occasions fork choice has switched to a different chain"
);
pub static ref FORK_CHOICE_REORG_COUNT_INTEROP: Result<IntCounter> = try_create_int_counter(
"beacon_reorgs_total",
"Count of occasions fork choice has switched to a different chain"
);
pub static ref FORK_CHOICE_TIMES: Result<Histogram> =
try_create_histogram("beacon_fork_choice_seconds", "Full runtime of fork choice");
pub static ref FORK_CHOICE_FIND_HEAD_TIMES: Result<Histogram> =
@@ -230,6 +314,10 @@ lazy_static! {
"beacon_fork_choice_process_attestation_seconds",
"Time taken to add an attestation to fork choice"
);
pub static ref FORK_CHOICE_SET_HEAD_LAG_TIMES: Result<Histogram> = try_create_histogram(
"beacon_fork_choice_set_head_lag_times",
"Time taken between finding the head and setting the canonical head value"
);
pub static ref BALANCES_CACHE_HITS: Result<IntCounter> =
try_create_int_counter("beacon_balances_cache_hits_total", "Count of times balances cache fulfils request");
pub static ref BALANCES_CACHE_MISSES: Result<IntCounter> =
@@ -260,6 +348,8 @@ lazy_static! {
try_create_histogram("beacon_update_head_seconds", "Time taken to update the canonical head");
pub static ref HEAD_STATE_SLOT: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_slot", "Slot of the block at the head of the chain");
pub static ref HEAD_STATE_SLOT_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_head_slot", "Slot of the block at the head of the chain");
pub static ref HEAD_STATE_ROOT: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_root", "Root of the block at the head of the chain");
pub static ref HEAD_STATE_LATEST_BLOCK_SLOT: Result<IntGauge> =
@@ -268,18 +358,26 @@ lazy_static! {
try_create_int_gauge("beacon_head_state_current_justified_root", "Current justified root at the head of the chain");
pub static ref HEAD_STATE_CURRENT_JUSTIFIED_EPOCH: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_current_justified_epoch", "Current justified epoch at the head of the chain");
pub static ref HEAD_STATE_CURRENT_JUSTIFIED_EPOCH_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_current_justified_epoch", "Current justified epoch at the head of the chain");
pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_ROOT: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_previous_justified_root", "Previous justified root at the head of the chain");
pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_previous_justified_epoch", "Previous justified epoch at the head of the chain");
pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_previous_justified_epoch", "Previous justified epoch at the head of the chain");
pub static ref HEAD_STATE_FINALIZED_ROOT: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_finalized_root", "Finalized root at the head of the chain");
pub static ref HEAD_STATE_FINALIZED_EPOCH: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_finalized_epoch", "Finalized epoch at the head of the chain");
pub static ref HEAD_STATE_FINALIZED_EPOCH_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_finalized_epoch", "Finalized epoch at the head of the chain");
pub static ref HEAD_STATE_TOTAL_VALIDATORS: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_total_validators_total", "Count of validators at the head of the chain");
pub static ref HEAD_STATE_ACTIVE_VALIDATORS: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_active_validators_total", "Count of active validators at the head of the chain");
pub static ref HEAD_STATE_ACTIVE_VALIDATORS_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_current_active_validators", "Count of active validators at the head of the chain");
pub static ref HEAD_STATE_VALIDATOR_BALANCES: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_validator_balances_total", "Sum of all validator balances at the head of the chain");
pub static ref HEAD_STATE_SLASHED_VALIDATORS: Result<IntGauge> =
@@ -288,34 +386,27 @@ lazy_static! {
try_create_int_gauge("beacon_head_state_withdrawn_validators_total", "Sum of all validator balances at the head of the chain");
pub static ref HEAD_STATE_ETH1_DEPOSIT_INDEX: Result<IntGauge> =
try_create_int_gauge("beacon_head_state_eth1_deposit_index", "Eth1 deposit index at the head of the chain");
pub static ref HEAD_STATE_ETH1_DEPOSITS_INTEROP: Result<IntGauge> =
try_create_int_gauge("beacon_processed_deposits_total", "Total Eth1 deposits at the head of the chain");
/*
* Operation Pool
*/
pub static ref OP_POOL_NUM_ATTESTATIONS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attestations_total", "Count of attestations in the op pool");
pub static ref OP_POOL_NUM_ATTESTATION_DATA: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attestation_data_total", "Count of attestation data in the op pool");
pub static ref OP_POOL_MAX_AGGREGATES_PER_DATA: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_max_aggregates_per_data", "Max aggregates per AttestationData");
pub static ref OP_POOL_NUM_ATTESTER_SLASHINGS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attester_slashings_total", "Count of attester slashings in the op pool");
pub static ref OP_POOL_NUM_PROPOSER_SLASHINGS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_proposer_slashings_total", "Count of proposer slashings in the op pool");
pub static ref OP_POOL_NUM_VOLUNTARY_EXITS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_voluntary_exits_total", "Count of voluntary exits in the op pool");
pub static ref OP_POOL_NUM_SYNC_CONTRIBUTIONS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_sync_contributions_total", "Count of sync contributions in the op pool");
/*
* Participation Metrics
*/
pub static ref PARTICIPATION_PREV_EPOCH_ATTESTER: Result<Gauge> = try_create_float_gauge(
"beacon_participation_prev_epoch_attester",
"Ratio of attesting balances to total balances"
);
pub static ref PARTICIPATION_PREV_EPOCH_TARGET_ATTESTER: Result<Gauge> = try_create_float_gauge(
"beacon_participation_prev_epoch_target_attester",
"Ratio of target-attesting balances to total balances"
);
pub static ref PARTICIPATION_PREV_EPOCH_HEAD_ATTESTER: Result<Gauge> = try_create_float_gauge(
"beacon_participation_prev_epoch_head_attester",
"Ratio of head-attesting balances to total balances"
);
/*
* Attestation Observation Metrics
@@ -328,22 +419,548 @@ lazy_static! {
"beacon_attn_observation_epoch_aggregators",
"Count of aggregators that have been seen by the beacon chain in the previous epoch"
);
/*
* Sync Committee Observation Metrics
*/
pub static ref SYNC_COMM_OBSERVATION_PREV_SLOT_SIGNERS: Result<IntGauge> = try_create_int_gauge(
"beacon_sync_comm_observation_slot_signers",
"Count of sync committee contributors that have been seen by the beacon chain in the previous slot"
);
pub static ref SYNC_COMM_OBSERVATION_PREV_SLOT_AGGREGATORS: Result<IntGauge> = try_create_int_gauge(
"beacon_sync_comm_observation_slot_aggregators",
"Count of sync committee aggregators that have been seen by the beacon chain in the previous slot"
);
}
// Third lazy-static block is used to account for macro recursion limit.
lazy_static! {
/*
* Validator Monitor Metrics (balances, etc)
*/
pub static ref VALIDATOR_MONITOR_BALANCE_GWEI: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_balance_gwei",
"The validator's balance in gwei.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_EFFECTIVE_BALANCE_GWEI: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_effective_balance_gwei",
"The validator's effective balance in gwei.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_SLASHED: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_slashed",
"Set to 1 if the validator is slashed.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_ACTIVE: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_active",
"Set to 1 if the validator is active.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_EXITED: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_exited",
"Set to 1 if the validator is exited.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_WITHDRAWABLE: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_withdrawable",
"Set to 1 if the validator is withdrawable.",
&["validator"]
);
pub static ref VALIDATOR_ACTIVATION_ELIGIBILITY_EPOCH: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_activation_eligibility_epoch",
"Set to the epoch where the validator will be eligible for activation.",
&["validator"]
);
pub static ref VALIDATOR_ACTIVATION_EPOCH: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_activation_epoch",
"Set to the epoch where the validator will activate.",
&["validator"]
);
pub static ref VALIDATOR_EXIT_EPOCH: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_exit_epoch",
"Set to the epoch where the validator will exit.",
&["validator"]
);
pub static ref VALIDATOR_WITHDRAWABLE_EPOCH: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_withdrawable_epoch",
"Set to the epoch where the validator will be withdrawable.",
&["validator"]
);
/*
* Validator Monitor Metrics (per-epoch summaries)
*/
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_ATTESTER_HIT: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_attester_hit",
"Incremented if the validator is flagged as a previous epoch attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_ATTESTER_MISS: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_attester_miss",
"Incremented if the validator is not flagged as a previous epoch attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_HEAD_ATTESTER_HIT: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_head_attester_hit",
"Incremented if the validator is flagged as a previous epoch head attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_HEAD_ATTESTER_MISS: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_head_attester_miss",
"Incremented if the validator is not flagged as a previous epoch head attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_TARGET_ATTESTER_HIT: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_target_attester_hit",
"Incremented if the validator is flagged as a previous epoch target attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_TARGET_ATTESTER_MISS: Result<IntCounterVec> =
try_create_int_counter_vec(
"validator_monitor_prev_epoch_on_chain_target_attester_miss",
"Incremented if the validator is not flagged as a previous epoch target attester \
during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_INCLUSION_DISTANCE: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_on_chain_inclusion_distance",
"The attestation inclusion distance calculated during per epoch processing",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTATIONS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_attestations_total",
"The number of unagg. attestations seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTATIONS_MIN_DELAY_SECONDS: Result<HistogramVec> =
try_create_histogram_vec(
"validator_monitor_prev_epoch_attestations_min_delay_seconds",
"The min delay between when the validator should send the attestation and when it was received.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTATION_AGGREGATE_INCLUSIONS: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_attestation_aggregate_inclusions",
"The count of times an attestation was seen inside an aggregate.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTATION_BLOCK_INCLUSIONS: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_attestation_block_inclusions",
"The count of times an attestation was seen inside a block.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTATION_BLOCK_MIN_INCLUSION_DISTANCE: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_attestation_block_min_inclusion_distance",
"The minimum inclusion distance observed for the inclusion of an attestation in a block.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_BEACON_BLOCKS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_beacon_blocks_total",
"The number of beacon_blocks seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_BEACON_BLOCKS_MIN_DELAY_SECONDS: Result<HistogramVec> =
try_create_histogram_vec(
"validator_monitor_prev_epoch_beacon_blocks_min_delay_seconds",
"The min delay between when the validator should send the block and when it was received.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_AGGREGATES_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_aggregates_total",
"The number of aggregates seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_AGGREGATES_MIN_DELAY_SECONDS: Result<HistogramVec> =
try_create_histogram_vec(
"validator_monitor_prev_epoch_aggregates_min_delay_seconds",
"The min delay between when the validator should send the aggregate and when it was received.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_EXITS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_exits_total",
"The number of exits seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_PROPOSER_SLASHINGS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_proposer_slashings_total",
"The number of proposer slashings seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_ATTESTER_SLASHINGS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_attester_slashings_total",
"The number of attester slashings seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_COMMITTEE_MESSAGES_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_sync_committee_messages_total",
"The number of sync committee messages seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_COMMITTEE_MESSAGES_MIN_DELAY_SECONDS: Result<HistogramVec> =
try_create_histogram_vec(
"validator_monitor_prev_epoch_sync_committee_messages_min_delay_seconds",
"The min delay between when the validator should send the sync committee message and when it was received.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_CONTRIBUTION_INCLUSIONS: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_sync_contribution_inclusions",
"The count of times a sync signature was seen inside a sync contribution.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_SIGNATURE_BLOCK_INCLUSIONS: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_sync_signature_block_inclusions",
"The count of times a sync signature was seen inside a block.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_CONTRIBUTIONS_TOTAL: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_prev_epoch_sync_contributions_total",
"The number of sync contributions seen in the previous epoch.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_PREV_EPOCH_SYNC_CONTRIBUTION_MIN_DELAY_SECONDS: Result<HistogramVec> =
try_create_histogram_vec(
"validator_monitor_prev_epoch_sync_contribution_min_delay_seconds",
"The min delay between when the validator should send the sync contribution and when it was received.",
&["validator"]
);
pub static ref VALIDATOR_MONITOR_VALIDATOR_IN_CURRENT_SYNC_COMMITTEE: Result<IntGaugeVec> =
try_create_int_gauge_vec(
"validator_monitor_validator_in_current_sync_committee",
"Is the validator in the current sync committee (1 for true and 0 for false)",
&["validator"]
);
/*
* Validator Monitor Metrics (real-time)
*/
pub static ref VALIDATOR_MONITOR_VALIDATORS_TOTAL: Result<IntGauge> = try_create_int_gauge(
"validator_monitor_validators_total",
"Count of validators that are specifically monitored by this beacon node"
);
pub static ref VALIDATOR_MONITOR_UNAGGREGATED_ATTESTATION_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_unaggregated_attestation_total",
"Number of unaggregated attestations seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_UNAGGREGATED_ATTESTATION_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_unaggregated_attestation_delay_seconds",
"The delay between when the validator should send the attestation and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_COMMITTEE_MESSAGES_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_sync_committee_messages_total",
"Number of sync committee messages seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_COMMITTEE_MESSAGES_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_sync_committee_messages_delay_seconds",
"The delay between when the validator should send the sync committee message and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_CONTRIBUTIONS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_sync_contributions_total",
"Number of sync contributions seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_CONTRIBUTIONS_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_sync_contributions_delay_seconds",
"The delay between when the aggregator should send the sync contribution and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_AGGREGATED_ATTESTATION_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_aggregated_attestation_total",
"Number of aggregated attestations seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_AGGREGATED_ATTESTATION_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_aggregated_attestation_delay_seconds",
"The delay between then the validator should send the aggregate and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_ATTESTATION_IN_AGGREGATE_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_attestation_in_aggregate_total",
"Number of times an attestation has been seen in an aggregate",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_COMMITTEE_MESSAGE_IN_CONTRIBUTION_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_sync_committee_message_in_contribution_total",
"Number of times a sync committee message has been seen in a sync contribution",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_ATTESTATION_IN_AGGREGATE_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_attestation_in_aggregate_delay_seconds",
"The delay between when the validator should send the aggregate and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_ATTESTATION_IN_BLOCK_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_attestation_in_block_total",
"Number of times an attestation has been seen in a block",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_SYNC_COMMITTEE_MESSAGE_IN_BLOCK_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_sync_committee_message_in_block_total",
"Number of times a validator's sync committee message has been seen in a sync aggregate",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_ATTESTATION_IN_BLOCK_DELAY_SLOTS: Result<IntGaugeVec> = try_create_int_gauge_vec(
"validator_monitor_attestation_in_block_delay_slots",
"The excess slots (beyond the minimum delay) between the attestation slot and the block slot.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_BEACON_BLOCK_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_beacon_block_total",
"Number of beacon blocks seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_BEACON_BLOCK_DELAY_SECONDS: Result<HistogramVec> = try_create_histogram_vec(
"validator_monitor_beacon_block_delay_seconds",
"The delay between when the validator should send the block and when it was received.",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_EXIT_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_exit_total",
"Number of beacon exits seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_PROPOSER_SLASHING_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_proposer_slashing_total",
"Number of proposer slashings seen",
&["src", "validator"]
);
pub static ref VALIDATOR_MONITOR_ATTESTER_SLASHING_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
"validator_monitor_attester_slashing_total",
"Number of attester slashings seen",
&["src", "validator"]
);
/*
* Block Delay Metrics
*/
pub static ref BEACON_BLOCK_OBSERVED_SLOT_START_DELAY_TIME: Result<Histogram> = try_create_histogram(
"beacon_block_observed_slot_start_delay_time",
"Duration between the start of the block's slot and the time the block was observed.",
);
pub static ref BEACON_BLOCK_IMPORTED_OBSERVED_DELAY_TIME: Result<Histogram> = try_create_histogram(
"beacon_block_imported_observed_delay_time",
"Duration between the time the block was observed and the time when it was imported.",
);
pub static ref BEACON_BLOCK_HEAD_IMPORTED_DELAY_TIME: Result<Histogram> = try_create_histogram(
"beacon_block_head_imported_delay_time",
"Duration between the time the block was imported and the time when it was set as head.",
);
pub static ref BEACON_BLOCK_HEAD_SLOT_START_DELAY_TIME: Result<Histogram> = try_create_histogram(
"beacon_block_head_slot_start_delay_time",
"Duration between the start of the block's slot and the time when it was set as head.",
);
pub static ref BEACON_BLOCK_HEAD_SLOT_START_DELAY_EXCEEDED_TOTAL: Result<IntCounter> = try_create_int_counter(
"beacon_block_head_slot_start_delay_exceeded_total",
"Triggered when the duration between the start of the block's slot and the current time \
will result in failed attestations.",
);
/*
* General block metrics
*/
pub static ref GOSSIP_BEACON_BLOCK_SKIPPED_SLOTS: Result<IntGauge> =
try_create_int_gauge(
"gossip_beacon_block_skipped_slots",
"For each gossip blocks, the number of skip slots between it and its parent"
);
}
// Fourth lazy-static block is used to account for macro recursion limit.
lazy_static! {
/*
* Sync Committee Message Verification
*/
pub static ref SYNC_MESSAGE_PROCESSING_REQUESTS: Result<IntCounter> = try_create_int_counter(
"beacon_sync_committee_message_processing_requests_total",
"Count of all sync messages submitted for processing"
);
pub static ref SYNC_MESSAGE_PROCESSING_SUCCESSES: Result<IntCounter> = try_create_int_counter(
"beacon_sync_committee_message_processing_successes_total",
"Number of sync messages verified for gossip"
);
pub static ref SYNC_MESSAGE_GOSSIP_VERIFICATION_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_committee_message_gossip_verification_seconds",
"Full runtime of sync contribution gossip verification"
);
/*
* Sync Committee Contribution Verification
*/
pub static ref SYNC_CONTRIBUTION_PROCESSING_REQUESTS: Result<IntCounter> = try_create_int_counter(
"beacon_sync_contribution_processing_requests_total",
"Count of all sync contributions submitted for processing"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_SUCCESSES: Result<IntCounter> = try_create_int_counter(
"beacon_sync_contribution_processing_successes_total",
"Number of sync contributions verified for gossip"
);
pub static ref SYNC_CONTRIBUTION_GOSSIP_VERIFICATION_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_gossip_verification_seconds",
"Full runtime of sync contribution gossip verification"
);
/*
* General Sync Committee Contribution Processing
*/
pub static ref SYNC_CONTRIBUTION_PROCESSING_APPLY_TO_AGG_POOL: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_apply_to_agg_pool",
"Time spent applying a sync contribution to the naive aggregation pool"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_PRUNE: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_agg_pool_prune",
"Time spent for the agg pool to prune"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_INSERT: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_agg_pool_insert",
"Time spent for the outer pool.insert() function of agg pool"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_CORE_INSERT: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_agg_pool_core_insert",
"Time spent for the core map.insert() function of agg pool"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_AGGREGATION: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_agg_pool_aggregation",
"Time spent doing signature aggregation when adding to the agg poll"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_CREATE_MAP: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_agg_pool_create_map",
"Time spent for creating a map for a new slot"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_APPLY_TO_OP_POOL: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_apply_to_op_pool",
"Time spent applying a sync contribution to the block inclusion pool"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_SIGNATURE_SETUP_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_signature_setup_seconds",
"Time spent on setting up for the signature verification of sync contribution processing"
);
pub static ref SYNC_CONTRIBUTION_PROCESSING_SIGNATURE_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_contribution_processing_signature_seconds",
"Time spent on the signature verification of sync contribution processing"
);
/*
* General Sync Committee Contribution Processing
*/
pub static ref SYNC_MESSAGE_PROCESSING_SIGNATURE_SETUP_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_committee_message_processing_signature_setup_seconds",
"Time spent on setting up for the signature verification of sync message processing"
);
pub static ref SYNC_MESSAGE_PROCESSING_SIGNATURE_TIMES: Result<Histogram> = try_create_histogram(
"beacon_sync_committee_message_processing_signature_seconds",
"Time spent on the signature verification of sync message processing"
);
/*
* Checkpoint sync & backfill
*/
pub static ref BACKFILL_SIGNATURE_SETUP_TIMES: Result<Histogram> = try_create_histogram(
"beacon_backfill_signature_setup_seconds",
"Time spent constructing the signature set during backfill sync"
);
pub static ref BACKFILL_SIGNATURE_VERIFY_TIMES: Result<Histogram> = try_create_histogram(
"beacon_backfill_signature_verify_seconds",
"Time spent verifying the signature set during backfill sync"
);
pub static ref BACKFILL_SIGNATURE_TOTAL_TIMES: Result<Histogram> = try_create_histogram(
"beacon_backfill_signature_total_seconds",
"Time spent verifying the signature set during backfill sync, including setup"
);
/*
* Pre-finalization block cache.
*/
pub static ref PRE_FINALIZATION_BLOCK_CACHE_SIZE: Result<IntGauge> =
try_create_int_gauge(
"beacon_pre_finalization_block_cache_size",
"Number of pre-finalization block roots cached for quick rejection"
);
pub static ref PRE_FINALIZATION_BLOCK_LOOKUP_COUNT: Result<IntGauge> =
try_create_int_gauge(
"beacon_pre_finalization_block_lookup_count",
"Number of block roots subject to single block lookups"
);
}
/// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot,
/// head state info, etc) and update the Prometheus `DEFAULT_REGISTRY`.
pub fn scrape_for_metrics<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) {
if let Ok(head) = beacon_chain.head() {
scrape_head_state::<T>(&head.beacon_state, head.beacon_state_root)
}
let _ = beacon_chain.with_head(|head| {
scrape_head_state(&head.beacon_state, head.beacon_state_root());
Ok::<_, BeaconChainError>(())
});
if let Some(slot) = beacon_chain.slot_clock.now() {
scrape_attestation_observation(slot, beacon_chain);
scrape_sync_committee_observation(slot, beacon_chain);
}
let attestation_stats = beacon_chain.op_pool.attestation_stats();
if let Some(snapshot_cache) = beacon_chain
.snapshot_cache
.try_write_for(SNAPSHOT_CACHE_TIMEOUT)
{
set_gauge(
&BLOCK_PROCESSING_SNAPSHOT_CACHE_SIZE,
snapshot_cache.len() as i64,
)
}
if let Some((size, num_lookups)) = beacon_chain.pre_finalization_block_cache.metrics() {
set_gauge_by_usize(&PRE_FINALIZATION_BLOCK_CACHE_SIZE, size);
set_gauge_by_usize(&PRE_FINALIZATION_BLOCK_LOOKUP_COUNT, num_lookups);
}
set_gauge_by_usize(
&OP_POOL_NUM_ATTESTATIONS,
beacon_chain.op_pool.num_attestations(),
attestation_stats.num_attestations,
);
set_gauge_by_usize(
&OP_POOL_NUM_ATTESTATION_DATA,
attestation_stats.num_attestation_data,
);
set_gauge_by_usize(
&OP_POOL_MAX_AGGREGATES_PER_DATA,
attestation_stats.max_aggregates_per_data,
);
set_gauge_by_usize(
&OP_POOL_NUM_ATTESTER_SLASHINGS,
@@ -357,67 +974,108 @@ pub fn scrape_for_metrics<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) {
&OP_POOL_NUM_VOLUNTARY_EXITS,
beacon_chain.op_pool.num_voluntary_exits(),
);
set_gauge_by_usize(
&OP_POOL_NUM_SYNC_CONTRIBUTIONS,
beacon_chain.op_pool.num_sync_contributions(),
);
beacon_chain
.validator_monitor
.read()
.scrape_metrics(&beacon_chain.slot_clock, &beacon_chain.spec);
}
/// Scrape the given `state` assuming it's the head state, updating the `DEFAULT_REGISTRY`.
fn scrape_head_state<T: BeaconChainTypes>(state: &BeaconState<T::EthSpec>, state_root: Hash256) {
set_gauge_by_slot(&HEAD_STATE_SLOT, state.slot);
fn scrape_head_state<T: EthSpec>(state: &BeaconState<T>, state_root: Hash256) {
set_gauge_by_slot(&HEAD_STATE_SLOT, state.slot());
set_gauge_by_slot(&HEAD_STATE_SLOT_INTEROP, state.slot());
set_gauge_by_hash(&HEAD_STATE_ROOT, state_root);
set_gauge_by_slot(
&HEAD_STATE_LATEST_BLOCK_SLOT,
state.latest_block_header.slot,
state.latest_block_header().slot,
);
set_gauge_by_hash(
&HEAD_STATE_CURRENT_JUSTIFIED_ROOT,
state.current_justified_checkpoint.root,
state.current_justified_checkpoint().root,
);
set_gauge_by_epoch(
&HEAD_STATE_CURRENT_JUSTIFIED_EPOCH,
state.current_justified_checkpoint.epoch,
state.current_justified_checkpoint().epoch,
);
set_gauge_by_epoch(
&HEAD_STATE_CURRENT_JUSTIFIED_EPOCH_INTEROP,
state.current_justified_checkpoint().epoch,
);
set_gauge_by_hash(
&HEAD_STATE_PREVIOUS_JUSTIFIED_ROOT,
state.previous_justified_checkpoint.root,
state.previous_justified_checkpoint().root,
);
set_gauge_by_epoch(
&HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH,
state.previous_justified_checkpoint.epoch,
state.previous_justified_checkpoint().epoch,
);
set_gauge_by_epoch(
&HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH_INTEROP,
state.previous_justified_checkpoint().epoch,
);
set_gauge_by_hash(
&HEAD_STATE_FINALIZED_ROOT,
state.finalized_checkpoint().root,
);
set_gauge_by_hash(&HEAD_STATE_FINALIZED_ROOT, state.finalized_checkpoint.root);
set_gauge_by_epoch(
&HEAD_STATE_FINALIZED_EPOCH,
state.finalized_checkpoint.epoch,
state.finalized_checkpoint().epoch,
);
set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators.len());
set_gauge_by_u64(&HEAD_STATE_VALIDATOR_BALANCES, state.balances.iter().sum());
set_gauge_by_usize(
&HEAD_STATE_ACTIVE_VALIDATORS,
state
.validators
.iter()
.filter(|v| v.is_active_at(state.current_epoch()))
.count(),
set_gauge_by_epoch(
&HEAD_STATE_FINALIZED_EPOCH_INTEROP,
state.finalized_checkpoint().epoch,
);
set_gauge_by_usize(
&HEAD_STATE_SLASHED_VALIDATORS,
state.validators.iter().filter(|v| v.slashed).count(),
set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators().len());
set_gauge_by_u64(
&HEAD_STATE_VALIDATOR_BALANCES,
state.balances().iter().sum(),
);
set_gauge_by_usize(
&HEAD_STATE_WITHDRAWN_VALIDATORS,
state
.validators
.iter()
.filter(|v| v.is_withdrawable_at(state.current_epoch()))
.count(),
set_gauge_by_u64(&HEAD_STATE_ETH1_DEPOSIT_INDEX, state.eth1_deposit_index());
set_gauge_by_u64(
&HEAD_STATE_ETH1_DEPOSITS_INTEROP,
state.eth1_data().deposit_count,
);
set_gauge_by_u64(&HEAD_STATE_ETH1_DEPOSIT_INDEX, state.eth1_deposit_index);
set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators().len());
set_gauge_by_u64(
&HEAD_STATE_VALIDATOR_BALANCES,
state.balances().iter().sum(),
);
let mut num_active: usize = 0;
let mut num_slashed: usize = 0;
let mut num_withdrawn: usize = 0;
for v in state.validators() {
if v.is_active_at(state.current_epoch()) {
num_active += 1;
}
if v.slashed {
num_slashed += 1;
}
if v.is_withdrawable_at(state.current_epoch()) {
num_withdrawn += 1;
}
}
set_gauge_by_usize(&HEAD_STATE_ACTIVE_VALIDATORS, num_active);
set_gauge_by_usize(&HEAD_STATE_ACTIVE_VALIDATORS_INTEROP, num_active);
set_gauge_by_usize(&HEAD_STATE_SLASHED_VALIDATORS, num_slashed);
set_gauge_by_usize(&HEAD_STATE_WITHDRAWN_VALIDATORS, num_withdrawn);
}
fn scrape_attestation_observation<T: BeaconChainTypes>(slot_now: Slot, chain: &BeaconChain<T>) {
let prev_epoch = slot_now.epoch(T::EthSpec::slots_per_epoch()) - 1;
if let Some(count) = chain
.observed_attesters
.observed_gossip_attesters
.read()
.observed_validator_count(prev_epoch)
{
set_gauge_by_usize(&ATTN_OBSERVATION_PREV_EPOCH_ATTESTERS, count);
@@ -425,12 +1083,41 @@ fn scrape_attestation_observation<T: BeaconChainTypes>(slot_now: Slot, chain: &B
if let Some(count) = chain
.observed_aggregators
.read()
.observed_validator_count(prev_epoch)
{
set_gauge_by_usize(&ATTN_OBSERVATION_PREV_EPOCH_AGGREGATORS, count);
}
}
fn scrape_sync_committee_observation<T: BeaconChainTypes>(slot_now: Slot, chain: &BeaconChain<T>) {
let prev_slot = slot_now - 1;
let contributors = chain.observed_sync_contributors.read();
let mut contributor_sum = 0;
for i in 0..SYNC_COMMITTEE_SUBNET_COUNT {
if let Some(count) =
contributors.observed_validator_count(SlotSubcommitteeIndex::new(prev_slot, i))
{
contributor_sum += count;
}
}
drop(contributors);
set_gauge_by_usize(&SYNC_COMM_OBSERVATION_PREV_SLOT_SIGNERS, contributor_sum);
let sync_aggregators = chain.observed_sync_aggregators.read();
let mut aggregator_sum = 0;
for i in 0..SYNC_COMMITTEE_SUBNET_COUNT {
if let Some(count) =
sync_aggregators.observed_validator_count(SlotSubcommitteeIndex::new(prev_slot, i))
{
aggregator_sum += count;
}
}
drop(sync_aggregators);
set_gauge_by_usize(&SYNC_COMM_OBSERVATION_PREV_SLOT_AGGREGATORS, aggregator_sum);
}
fn set_gauge_by_slot(gauge: &Result<IntGauge>, value: Slot) {
set_gauge(gauge, value.as_u64() as i64);
}

View File

@@ -1,76 +1,375 @@
use crate::beacon_chain::BEACON_CHAIN_DB_KEY;
use crate::errors::BeaconChainError;
use crate::head_tracker::HeadTracker;
use crate::head_tracker::{HeadTracker, SszHeadTracker};
use crate::persisted_beacon_chain::{PersistedBeaconChain, DUMMY_CANONICAL_HEAD_BLOCK_ROOT};
use parking_lot::Mutex;
use slog::{debug, warn, Logger};
use slog::{debug, error, info, warn, Logger};
use std::collections::{HashMap, HashSet};
use std::mem;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::{mpsc, Arc};
use std::thread;
use store::hot_cold_store::{process_finalization, HotColdDBError};
use store::iter::{ParentRootBlockIterator, RootsIterator};
use store::{Error, ItemStore, StoreOp};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use store::hot_cold_store::{migrate_database, HotColdDBError};
use store::iter::RootsIterator;
use store::{Error, ItemStore, StoreItem, StoreOp};
pub use store::{HotColdDB, MemoryStore};
use types::*;
use types::{BeaconState, EthSpec, Hash256, Slot};
use types::{
BeaconState, BeaconStateError, BeaconStateHash, Checkpoint, Epoch, EthSpec, Hash256,
SignedBeaconBlockHash, Slot,
};
/// Trait for migration processes that update the database upon finalization.
pub trait Migrate<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>:
Send + Sync + 'static
{
fn new(db: Arc<HotColdDB<E, Hot, Cold>>, log: Logger) -> Self;
/// Compact at least this frequently, finalization permitting (7 days).
const MAX_COMPACTION_PERIOD_SECONDS: u64 = 604800;
/// Compact at *most* this frequently, to prevent over-compaction during sync (2 hours).
const MIN_COMPACTION_PERIOD_SECONDS: u64 = 7200;
/// Compact after a large finality gap, if we respect `MIN_COMPACTION_PERIOD_SECONDS`.
const COMPACTION_FINALITY_DISTANCE: u64 = 1024;
fn process_finalization(
/// The background migrator runs a thread to perform pruning and migrate state from the hot
/// to the cold database.
pub struct BackgroundMigrator<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
db: Arc<HotColdDB<E, Hot, Cold>>,
#[allow(clippy::type_complexity)]
tx_thread: Option<Mutex<(mpsc::Sender<Notification>, thread::JoinHandle<()>)>>,
/// Genesis block root, for persisting the `PersistedBeaconChain`.
genesis_block_root: Hash256,
log: Logger,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct MigratorConfig {
pub blocking: bool,
}
impl MigratorConfig {
pub fn blocking(mut self) -> Self {
self.blocking = true;
self
}
}
/// Pruning can be successful, or in rare cases deferred to a later point.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PruningOutcome {
/// The pruning succeeded and updated the pruning checkpoint from `old_finalized_checkpoint`.
Successful {
old_finalized_checkpoint: Checkpoint,
},
DeferredConcurrentMutation,
}
/// Logic errors that can occur during pruning, none of these should ever happen.
#[derive(Debug)]
pub enum PruningError {
IncorrectFinalizedState {
state_slot: Slot,
new_finalized_slot: Slot,
},
MissingInfoForCanonicalChain {
slot: Slot,
},
UnexpectedEqualStateRoots,
UnexpectedUnequalStateRoots,
}
/// Message sent to the migration thread containing the information it needs to run.
pub enum Notification {
Finalization(FinalizationNotification),
Reconstruction,
}
pub struct FinalizationNotification {
finalized_state_root: BeaconStateHash,
finalized_checkpoint: Checkpoint,
head_tracker: Arc<HeadTracker>,
genesis_block_root: Hash256,
}
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Hot, Cold> {
/// Create a new `BackgroundMigrator` and spawn its thread if necessary.
pub fn new(
db: Arc<HotColdDB<E, Hot, Cold>>,
config: MigratorConfig,
genesis_block_root: Hash256,
log: Logger,
) -> Self {
let tx_thread = if config.blocking {
None
} else {
Some(Mutex::new(Self::spawn_thread(db.clone(), log.clone())))
};
Self {
db,
tx_thread,
genesis_block_root,
log,
}
}
/// Process a finalized checkpoint from the `BeaconChain`.
///
/// If successful, all forks descending from before the `finalized_checkpoint` will be
/// pruned, and the split point of the database will be advanced to the slot of the finalized
/// checkpoint.
pub fn process_finalization(
&self,
_state_root: Hash256,
_new_finalized_state: BeaconState<E>,
_max_finality_distance: u64,
_head_tracker: Arc<HeadTracker>,
_old_finalized_block_hash: SignedBeaconBlockHash,
_new_finalized_block_hash: SignedBeaconBlockHash,
finalized_state_root: BeaconStateHash,
finalized_checkpoint: Checkpoint,
head_tracker: Arc<HeadTracker>,
) -> Result<(), BeaconChainError> {
let notif = FinalizationNotification {
finalized_state_root,
finalized_checkpoint,
head_tracker,
genesis_block_root: self.genesis_block_root,
};
// Send to background thread if configured, otherwise run in foreground.
if let Some(Notification::Finalization(notif)) =
self.send_background_notification(Notification::Finalization(notif))
{
Self::run_migration(self.db.clone(), notif, &self.log);
}
Ok(())
}
pub fn process_reconstruction(&self) {
if let Some(Notification::Reconstruction) =
self.send_background_notification(Notification::Reconstruction)
{
Self::run_reconstruction(self.db.clone(), &self.log);
}
}
pub fn run_reconstruction(db: Arc<HotColdDB<E, Hot, Cold>>, log: &Logger) {
if let Err(e) = db.reconstruct_historic_states() {
error!(
log,
"State reconstruction failed";
"error" => ?e,
);
}
}
/// If configured to run in the background, send `notif` to the background thread.
///
/// Return `None` if the message was sent to the background thread, `Some(notif)` otherwise.
#[must_use = "Message is not processed when this function returns `Some`"]
fn send_background_notification(&self, notif: Notification) -> Option<Notification> {
// Async path, on the background thread.
if let Some(tx_thread) = &self.tx_thread {
let (ref mut tx, ref mut thread) = *tx_thread.lock();
// Restart the background thread if it has crashed.
if let Err(tx_err) = tx.send(notif) {
let (new_tx, new_thread) = Self::spawn_thread(self.db.clone(), self.log.clone());
*tx = new_tx;
let old_thread = mem::replace(thread, new_thread);
// Join the old thread, which will probably have panicked, or may have
// halted normally just now as a result of us dropping the old `mpsc::Sender`.
if let Err(thread_err) = old_thread.join() {
warn!(
self.log,
"Migration thread died, so it was restarted";
"reason" => format!("{:?}", thread_err)
);
}
// Retry at most once, we could recurse but that would risk overflowing the stack.
let _ = tx.send(tx_err.0);
}
None
// Synchronous path, on the current thread.
} else {
Some(notif)
}
}
/// Perform the actual work of `process_finalization`.
fn run_migration(
db: Arc<HotColdDB<E, Hot, Cold>>,
notif: FinalizationNotification,
log: &Logger,
) {
debug!(log, "Database consolidation started");
let finalized_state_root = notif.finalized_state_root;
let finalized_state = match db.get_state(&finalized_state_root.into(), None) {
Ok(Some(state)) => state,
other => {
error!(
log,
"Migrator failed to load state";
"state_root" => ?finalized_state_root,
"error" => ?other
);
return;
}
};
let old_finalized_checkpoint = match Self::prune_abandoned_forks(
db.clone(),
notif.head_tracker,
finalized_state_root,
&finalized_state,
notif.finalized_checkpoint,
notif.genesis_block_root,
log,
) {
Ok(PruningOutcome::Successful {
old_finalized_checkpoint,
}) => old_finalized_checkpoint,
Ok(PruningOutcome::DeferredConcurrentMutation) => {
warn!(
log,
"Pruning deferred because of a concurrent mutation";
"message" => "this is expected only very rarely!"
);
return;
}
Err(e) => {
warn!(log, "Block pruning failed"; "error" => format!("{:?}", e));
return;
}
};
match migrate_database(db.clone(), finalized_state_root.into(), &finalized_state) {
Ok(()) => {}
Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => {
debug!(
log,
"Database migration postponed, unaligned finalized block";
"slot" => slot.as_u64()
);
}
Err(e) => {
warn!(
log,
"Database migration failed";
"error" => format!("{:?}", e)
);
return;
}
};
// Finally, compact the database so that new free space is properly reclaimed.
if let Err(e) = Self::run_compaction(
db,
old_finalized_checkpoint.epoch,
notif.finalized_checkpoint.epoch,
log,
) {
warn!(log, "Database compaction failed"; "error" => format!("{:?}", e));
}
debug!(log, "Database consolidation complete");
}
/// Spawn a new child thread to run the migration process.
///
/// Return a channel handle for sending requests to the thread.
fn spawn_thread(
db: Arc<HotColdDB<E, Hot, Cold>>,
log: Logger,
) -> (mpsc::Sender<Notification>, thread::JoinHandle<()>) {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
while let Ok(notif) = rx.recv() {
// Read the rest of the messages in the channel, preferring any reconstruction
// notification, or the finalization notification with the greatest finalized epoch.
let notif =
rx.try_iter()
.fold(notif, |best, other: Notification| match (&best, &other) {
(Notification::Reconstruction, _)
| (_, Notification::Reconstruction) => Notification::Reconstruction,
(
Notification::Finalization(fin1),
Notification::Finalization(fin2),
) => {
if fin2.finalized_checkpoint.epoch > fin1.finalized_checkpoint.epoch
{
other
} else {
best
}
}
});
match notif {
Notification::Reconstruction => Self::run_reconstruction(db.clone(), &log),
Notification::Finalization(fin) => Self::run_migration(db.clone(), fin, &log),
}
}
});
(tx, thread)
}
/// Traverses live heads and prunes blocks and states of chains that we know can't be built
/// upon because finalization would prohibit it. This is an optimisation intended to save disk
/// upon because finalization would prohibit it. This is an optimisation intended to save disk
/// space.
///
/// Assumptions:
/// * It is called after every finalization.
#[allow(clippy::too_many_arguments)]
fn prune_abandoned_forks(
store: Arc<HotColdDB<E, Hot, Cold>>,
head_tracker: Arc<HeadTracker>,
old_finalized_block_hash: SignedBeaconBlockHash,
new_finalized_block_hash: SignedBeaconBlockHash,
new_finalized_slot: Slot,
) -> Result<(), BeaconChainError> {
// There will never be any blocks to prune if there is only a single head in the chain.
if head_tracker.heads().len() == 1 {
return Ok(());
new_finalized_state_hash: BeaconStateHash,
new_finalized_state: &BeaconState<E>,
new_finalized_checkpoint: Checkpoint,
genesis_block_root: Hash256,
log: &Logger,
) -> Result<PruningOutcome, BeaconChainError> {
let old_finalized_checkpoint =
store
.load_pruning_checkpoint()?
.unwrap_or_else(|| Checkpoint {
epoch: Epoch::new(0),
root: Hash256::zero(),
});
let old_finalized_slot = old_finalized_checkpoint
.epoch
.start_slot(E::slots_per_epoch());
let new_finalized_slot = new_finalized_checkpoint
.epoch
.start_slot(E::slots_per_epoch());
let new_finalized_block_hash = new_finalized_checkpoint.root.into();
// The finalized state must be for the epoch boundary slot, not the slot of the finalized
// block.
if new_finalized_state.slot() != new_finalized_slot {
return Err(PruningError::IncorrectFinalizedState {
state_slot: new_finalized_state.slot(),
new_finalized_slot,
}
.into());
}
let old_finalized_slot = store
.get_block(&old_finalized_block_hash.into())?
.ok_or_else(|| BeaconChainError::MissingBeaconBlock(old_finalized_block_hash.into()))?
.slot();
// Collect hashes from new_finalized_block back to old_finalized_block (inclusive)
let mut found_block = false; // hack for `take_until`
let newly_finalized_blocks: HashMap<SignedBeaconBlockHash, Slot> =
ParentRootBlockIterator::new(&*store, new_finalized_block_hash.into())
.take_while(|result| match result {
Ok((block_hash, _)) => {
if found_block {
false
} else {
found_block |= *block_hash == old_finalized_block_hash.into();
true
}
}
Err(_) => true,
debug!(
log,
"Starting database pruning";
"old_finalized_epoch" => old_finalized_checkpoint.epoch,
"new_finalized_epoch" => new_finalized_checkpoint.epoch,
);
// For each slot between the new finalized checkpoint and the old finalized checkpoint,
// collect the beacon block root and state root of the canonical chain.
let newly_finalized_chain: HashMap<Slot, (SignedBeaconBlockHash, BeaconStateHash)> =
std::iter::once(Ok((
new_finalized_slot,
(new_finalized_block_hash, new_finalized_state_hash),
)))
.chain(RootsIterator::new(&store, new_finalized_state).map(|res| {
res.map(|(block_root, state_root, slot)| {
(slot, (block_root.into(), state_root.into()))
})
.map(|result| result.map(|(block_hash, block)| (block_hash.into(), block.slot())))
.collect::<Result<_, _>>()?;
}))
.take_while(|res| {
res.as_ref()
.map_or(true, |(slot, _)| *slot >= old_finalized_slot)
})
.collect::<Result<_, _>>()?;
// We don't know which blocks are shared among abandoned chains, so we buffer and delete
// everything in one fell swoop.
@@ -78,284 +377,237 @@ pub trait Migrate<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>:
let mut abandoned_states: HashSet<(Slot, BeaconStateHash)> = HashSet::new();
let mut abandoned_heads: HashSet<Hash256> = HashSet::new();
for (head_hash, head_slot) in head_tracker.heads() {
let mut potentially_abandoned_head: Option<Hash256> = Some(head_hash);
let mut potentially_abandoned_blocks: Vec<(
Slot,
Option<SignedBeaconBlockHash>,
Option<BeaconStateHash>,
)> = Vec::new();
let heads = head_tracker.heads();
debug!(
log,
"Extra pruning information";
"old_finalized_root" => format!("{:?}", old_finalized_checkpoint.root),
"new_finalized_root" => format!("{:?}", new_finalized_checkpoint.root),
"head_count" => heads.len(),
);
let head_state_hash = store
.get_block(&head_hash)?
.ok_or_else(|| BeaconStateError::MissingBeaconBlock(head_hash.into()))?
.state_root();
let iter = std::iter::once(Ok((head_hash, head_state_hash, head_slot)))
.chain(RootsIterator::from_block(Arc::clone(&store), head_hash)?);
for maybe_tuple in iter {
let (block_hash, state_hash, slot) = maybe_tuple?;
if slot < old_finalized_slot {
// We must assume here any candidate chains include old_finalized_block_hash,
// i.e. there aren't any forks starting at a block that is a strict ancestor of
// old_finalized_block_hash.
break;
for (head_hash, head_slot) in heads {
// Load head block. If it fails with a decode error, it's likely a reverted block,
// so delete it from the head tracker but leave it and its states in the database
// This is suboptimal as it wastes disk space, but it's difficult to fix. A re-sync
// can be used to reclaim the space.
let head_state_root = match store.get_block(&head_hash) {
Ok(Some(block)) => block.state_root(),
Ok(None) => {
return Err(BeaconStateError::MissingBeaconBlock(head_hash.into()).into())
}
match newly_finalized_blocks.get(&block_hash.into()).copied() {
// Block is not finalized, mark it and its state for deletion
Err(Error::SszDecodeError(e)) => {
warn!(
log,
"Forgetting invalid head block";
"block_root" => ?head_hash,
"error" => ?e,
);
abandoned_heads.insert(head_hash);
continue;
}
Err(e) => return Err(e.into()),
};
let mut potentially_abandoned_head = Some(head_hash);
let mut potentially_abandoned_blocks = vec![];
// Iterate backwards from this head, staging blocks and states for deletion.
let iter = std::iter::once(Ok((head_hash, head_state_root, head_slot)))
.chain(RootsIterator::from_block(&store, head_hash)?);
for maybe_tuple in iter {
let (block_root, state_root, slot) = maybe_tuple?;
let block_root = SignedBeaconBlockHash::from(block_root);
let state_root = BeaconStateHash::from(state_root);
match newly_finalized_chain.get(&slot) {
// If there's no information about a slot on the finalized chain, then
// it should be because it's ahead of the new finalized slot. Stage
// the fork's block and state for possible deletion.
None => {
potentially_abandoned_blocks.push((
slot,
Some(block_hash.into()),
Some(state_hash.into()),
));
if slot > new_finalized_slot {
potentially_abandoned_blocks.push((
slot,
Some(block_root),
Some(state_root),
));
} else if slot >= old_finalized_slot {
return Err(PruningError::MissingInfoForCanonicalChain { slot }.into());
} else {
// We must assume here any candidate chains include the old finalized
// checkpoint, i.e. there aren't any forks starting at a block that is a
// strict ancestor of old_finalized_checkpoint.
warn!(
log,
"Found a chain that should already have been pruned";
"head_block_root" => format!("{:?}", head_hash),
"head_slot" => head_slot,
);
potentially_abandoned_head.take();
break;
}
}
Some(finalized_slot) => {
// Block root is finalized, and we have reached the slot it was finalized
// at: we've hit a shared part of the chain.
if finalized_slot == slot {
// The first finalized block of a candidate chain lies after (in terms
// of slots order) the newly finalized block. It's not a candidate for
// prunning.
if finalized_slot == new_finalized_slot {
Some((finalized_block_root, finalized_state_root)) => {
// This fork descends from a newly finalized block, we can stop.
if block_root == *finalized_block_root {
// Sanity check: if the slot and block root match, then the
// state roots should match too.
if state_root != *finalized_state_root {
return Err(PruningError::UnexpectedUnequalStateRoots.into());
}
// If the fork descends from the whole finalized chain,
// do not prune it. Otherwise continue to delete all
// of the blocks and states that have been staged for
// deletion so far.
if slot == new_finalized_slot {
potentially_abandoned_blocks.clear();
potentially_abandoned_head.take();
}
// If there are skipped slots on the fork to be pruned, then
// we will have just staged the common block for deletion.
// Unstage it.
else {
for (_, block_root, _) in
potentially_abandoned_blocks.iter_mut().rev()
{
if block_root.as_ref() == Some(finalized_block_root) {
*block_root = None;
} else {
break;
}
}
}
break;
}
// Block root is finalized, but we're at a skip slot: delete the state only.
else {
} else {
if state_root == *finalized_state_root {
return Err(PruningError::UnexpectedEqualStateRoots.into());
}
potentially_abandoned_blocks.push((
slot,
None,
Some(state_hash.into()),
Some(block_root),
Some(state_root),
));
}
}
}
}
abandoned_heads.extend(potentially_abandoned_head.into_iter());
if !potentially_abandoned_blocks.is_empty() {
if let Some(abandoned_head) = potentially_abandoned_head {
debug!(
log,
"Pruning head";
"head_block_root" => format!("{:?}", abandoned_head),
"head_slot" => head_slot,
);
abandoned_heads.insert(abandoned_head);
abandoned_blocks.extend(
potentially_abandoned_blocks
.iter()
.filter_map(|(_, maybe_block_hash, _)| *maybe_block_hash),
);
abandoned_states.extend(potentially_abandoned_blocks.iter().filter_map(
|(slot, _, maybe_state_hash)| match maybe_state_hash {
None => None,
Some(state_hash) => Some((*slot, *state_hash)),
},
|(slot, _, maybe_state_hash)| maybe_state_hash.map(|sr| (*slot, sr)),
));
}
}
// Update the head tracker before the database, so that we maintain the invariant
// that a block present in the head tracker is present in the database.
// See https://github.com/sigp/lighthouse/issues/1557
let mut head_tracker_lock = head_tracker.0.write();
// Check that all the heads to be deleted are still present. The absence of any
// head indicates a race, that will likely resolve itself, so we defer pruning until
// later.
for head_hash in &abandoned_heads {
if !head_tracker_lock.contains_key(head_hash) {
return Ok(PruningOutcome::DeferredConcurrentMutation);
}
}
// Then remove them for real.
for head_hash in abandoned_heads {
head_tracker_lock.remove(&head_hash);
}
let batch: Vec<StoreOp<E>> = abandoned_blocks
.into_iter()
.map(Into::into)
.map(StoreOp::DeleteBlock)
.chain(
abandoned_states
.into_iter()
.map(|(slot, state_hash)| StoreOp::DeleteState(state_hash, slot)),
.map(|(slot, state_hash)| StoreOp::DeleteState(state_hash.into(), Some(slot))),
)
.collect();
store.do_atomically(batch)?;
for head_hash in abandoned_heads.into_iter() {
head_tracker.remove_head(head_hash);
let mut kv_batch = store.convert_to_kv_batch(&batch)?;
// Persist the head in case the process is killed or crashes here. This prevents
// the head tracker reverting after our mutation above.
let persisted_head = PersistedBeaconChain {
_canonical_head_block_root: DUMMY_CANONICAL_HEAD_BLOCK_ROOT,
genesis_block_root,
ssz_head_tracker: SszHeadTracker::from_map(&*head_tracker_lock),
};
drop(head_tracker_lock);
kv_batch.push(persisted_head.as_kv_store_op(BEACON_CHAIN_DB_KEY));
// Persist the new finalized checkpoint as the pruning checkpoint.
kv_batch.push(store.pruning_checkpoint_store_op(new_finalized_checkpoint));
store.hot_db.do_atomically(kv_batch)?;
debug!(log, "Database pruning complete");
Ok(PruningOutcome::Successful {
old_finalized_checkpoint,
})
}
/// Compact the database if it has been more than `COMPACTION_PERIOD_SECONDS` since it
/// was last compacted.
pub fn run_compaction(
db: Arc<HotColdDB<E, Hot, Cold>>,
old_finalized_epoch: Epoch,
new_finalized_epoch: Epoch,
log: &Logger,
) -> Result<(), Error> {
if !db.compact_on_prune() {
return Ok(());
}
let last_compaction_timestamp = db
.load_compaction_timestamp()?
.unwrap_or_else(|| Duration::from_secs(0));
let start_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(last_compaction_timestamp);
let seconds_since_last_compaction = start_time
.checked_sub(last_compaction_timestamp)
.as_ref()
.map_or(0, Duration::as_secs);
if seconds_since_last_compaction > MAX_COMPACTION_PERIOD_SECONDS
|| (new_finalized_epoch - old_finalized_epoch > COMPACTION_FINALITY_DISTANCE
&& seconds_since_last_compaction > MIN_COMPACTION_PERIOD_SECONDS)
{
info!(
log,
"Starting database compaction";
"old_finalized_epoch" => old_finalized_epoch,
"new_finalized_epoch" => new_finalized_epoch,
);
db.compact()?;
let finish_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(start_time);
db.store_compaction_timestamp(finish_time)?;
info!(log, "Database compaction complete");
}
Ok(())
}
}
/// Migrator that does nothing, for stores that don't need migration.
pub struct NullMigrator;
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> Migrate<E, Hot, Cold> for NullMigrator {
fn new(_: Arc<HotColdDB<E, Hot, Cold>>, _: Logger) -> Self {
NullMigrator
}
}
/// Migrator that immediately calls the store's migration function, blocking the current execution.
///
/// Mostly useful for tests.
pub struct BlockingMigrator<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
db: Arc<HotColdDB<E, Hot, Cold>>,
}
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> Migrate<E, Hot, Cold>
for BlockingMigrator<E, Hot, Cold>
{
fn new(db: Arc<HotColdDB<E, Hot, Cold>>, _: Logger) -> Self {
BlockingMigrator { db }
}
fn process_finalization(
&self,
state_root: Hash256,
new_finalized_state: BeaconState<E>,
_max_finality_distance: u64,
head_tracker: Arc<HeadTracker>,
old_finalized_block_hash: SignedBeaconBlockHash,
new_finalized_block_hash: SignedBeaconBlockHash,
) {
if let Err(e) = process_finalization(self.db.clone(), state_root, &new_finalized_state) {
// This migrator is only used for testing, so we just log to stderr without a logger.
eprintln!("Migration error: {:?}", e);
}
if let Err(e) = Self::prune_abandoned_forks(
self.db.clone(),
head_tracker,
old_finalized_block_hash,
new_finalized_block_hash,
new_finalized_state.slot,
) {
eprintln!("Pruning error: {:?}", e);
}
}
}
type MpscSender<E> = mpsc::Sender<(
Hash256,
BeaconState<E>,
Arc<HeadTracker>,
SignedBeaconBlockHash,
SignedBeaconBlockHash,
Slot,
)>;
/// Migrator that runs a background thread to migrate state from the hot to the cold database.
pub struct BackgroundMigrator<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
db: Arc<HotColdDB<E, Hot, Cold>>,
tx_thread: Mutex<(MpscSender<E>, thread::JoinHandle<()>)>,
log: Logger,
}
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> Migrate<E, Hot, Cold>
for BackgroundMigrator<E, Hot, Cold>
{
fn new(db: Arc<HotColdDB<E, Hot, Cold>>, log: Logger) -> Self {
let tx_thread = Mutex::new(Self::spawn_thread(db.clone(), log.clone()));
Self { db, tx_thread, log }
}
/// Perform the freezing operation on the database,
fn process_finalization(
&self,
finalized_state_root: Hash256,
new_finalized_state: BeaconState<E>,
max_finality_distance: u64,
head_tracker: Arc<HeadTracker>,
old_finalized_block_hash: SignedBeaconBlockHash,
new_finalized_block_hash: SignedBeaconBlockHash,
) {
if !self.needs_migration(new_finalized_state.slot, max_finality_distance) {
return;
}
let (ref mut tx, ref mut thread) = *self.tx_thread.lock();
let new_finalized_slot = new_finalized_state.slot;
if let Err(tx_err) = tx.send((
finalized_state_root,
new_finalized_state,
head_tracker,
old_finalized_block_hash,
new_finalized_block_hash,
new_finalized_slot,
)) {
let (new_tx, new_thread) = Self::spawn_thread(self.db.clone(), self.log.clone());
drop(mem::replace(tx, new_tx));
let old_thread = mem::replace(thread, new_thread);
// Join the old thread, which will probably have panicked, or may have
// halted normally just now as a result of us dropping the old `mpsc::Sender`.
if let Err(thread_err) = old_thread.join() {
warn!(
self.log,
"Migration thread died, so it was restarted";
"reason" => format!("{:?}", thread_err)
);
}
// Retry at most once, we could recurse but that would risk overflowing the stack.
let _ = tx.send(tx_err.0);
}
}
}
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Hot, Cold> {
/// Return true if a migration needs to be performed, given a new `finalized_slot`.
fn needs_migration(&self, finalized_slot: Slot, max_finality_distance: u64) -> bool {
let finality_distance = finalized_slot - self.db.get_split_slot();
finality_distance > max_finality_distance
}
#[allow(clippy::type_complexity)]
/// Spawn a new child thread to run the migration process.
///
/// Return a channel handle for sending new finalized states to the thread.
fn spawn_thread(
db: Arc<HotColdDB<E, Hot, Cold>>,
log: Logger,
) -> (
mpsc::Sender<(
Hash256,
BeaconState<E>,
Arc<HeadTracker>,
SignedBeaconBlockHash,
SignedBeaconBlockHash,
Slot,
)>,
thread::JoinHandle<()>,
) {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
while let Ok((
state_root,
state,
head_tracker,
old_finalized_block_hash,
new_finalized_block_hash,
new_finalized_slot,
)) = rx.recv()
{
match process_finalization(db.clone(), state_root, &state) {
Ok(()) => {}
Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => {
debug!(
log,
"Database migration postponed, unaligned finalized block";
"slot" => slot.as_u64()
);
}
Err(e) => {
warn!(
log,
"Database migration failed";
"error" => format!("{:?}", e)
);
}
};
match Self::prune_abandoned_forks(
db.clone(),
head_tracker,
old_finalized_block_hash,
new_finalized_block_hash,
new_finalized_slot,
) {
Ok(()) => {}
Err(e) => warn!(log, "Block pruning failed: {:?}", e),
}
}
});
(tx, thread)
}
}

View File

@@ -1,11 +1,18 @@
use crate::metrics;
use std::collections::HashMap;
use types::{Attestation, AttestationData, EthSpec, Slot};
use tree_hash::TreeHash;
use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
use types::slot_data::SlotData;
use types::sync_committee_contribution::SyncContributionData;
use types::{Attestation, AttestationData, EthSpec, Hash256, Slot, SyncCommitteeContribution};
type AttestationDataRoot = Hash256;
type SyncDataRoot = Hash256;
/// The number of slots that will be stored in the pool.
///
/// For example, if `SLOTS_RETAINED == 3` and the pool is pruned at slot `6`, then all attestations
/// at slots less than `4` will be dropped and any future attestation with a slot less than `4`
/// For example, if `SLOTS_RETAINED == 3` and the pool is pruned at slot `6`, then all items
/// at slots less than `4` will be dropped and any future item with a slot less than `4`
/// will be refused.
const SLOTS_RETAINED: usize = 3;
@@ -14,51 +21,98 @@ const SLOTS_RETAINED: usize = 3;
/// This is a DoS protection measure.
const MAX_ATTESTATIONS_PER_SLOT: usize = 16_384;
/// Returned upon successfully inserting an attestation into the pool.
/// Returned upon successfully inserting an item into the pool.
#[derive(Debug, PartialEq)]
pub enum InsertOutcome {
/// The `attestation.data` had not been seen before and was added to the pool.
NewAttestationData { committee_index: usize },
/// A validator signature for the given `attestation.data` was already known. No changes were
/// The item had not been seen before and was added to the pool.
NewItemInserted { committee_index: usize },
/// A validator signature for the given item's `Data` was already known. No changes were
/// made.
SignatureAlreadyKnown { committee_index: usize },
/// The `attestation.data` was known, but a signature for the given validator was not yet
/// The item's `Data` was known, but a signature for the given validator was not yet
/// known. The signature was aggregated into the pool.
SignatureAggregated { committee_index: usize },
}
#[derive(Debug, PartialEq)]
pub enum Error {
/// The given `attestation.data.slot` was too low to be stored. No changes were made.
/// The given `data.slot` was too low to be stored. No changes were made.
SlotTooLow {
slot: Slot,
lowest_permissible_slot: Slot,
},
/// The given `attestation.aggregation_bits` field was empty.
/// The given `aggregation_bits` field was empty.
NoAggregationBitsSet,
/// The given `attestation.aggregation_bits` field had more than one signature. The number of
/// The given `aggregation_bits` field had more than one signature. The number of
/// signatures found is included.
MoreThanOneAggregationBitSet(usize),
/// We have reached the maximum number of unique `AttestationData` that can be stored in a
/// We have reached the maximum number of unique items that can be stored in a
/// slot. This is a DoS protection function.
ReachedMaxAttestationsPerSlot(usize),
/// The given `attestation.aggregation_bits` field had a different length to the one currently
ReachedMaxItemsPerSlot(usize),
/// The given `aggregation_bits` field had a different length to the one currently
/// stored. This indicates a fairly serious error somewhere in the code that called this
/// function.
InconsistentBitfieldLengths,
/// The given `attestation` was for the incorrect slot. This is an internal error.
IncorrectSlot { expected: Slot, attestation: Slot },
/// The given item was for the incorrect slot. This is an internal error.
IncorrectSlot { expected: Slot, actual: Slot },
}
/// Implemented for items in the `NaiveAggregationPool`. Requires that items implement `SlotData`,
/// which means they have an associated slot. This handles aggregation of items that are inserted.
pub trait AggregateMap {
/// `Key` should be a hash of `Data`.
type Key;
/// The item stored in the map
type Value: Clone + SlotData;
/// The unique fields of `Value`, hashed to create `Key`.
type Data: SlotData;
/// Create a new `AggregateMap` with capacity `initial_capacity`.
fn new(initial_capacity: usize) -> Self;
/// Insert a `Value` into `Self`, returning a result.
fn insert(&mut self, value: &Self::Value) -> Result<InsertOutcome, Error>;
/// Get a `Value` from `Self` based on `Data`.
fn get(&self, data: &Self::Data) -> Option<Self::Value>;
/// Get a reference to the inner `HashMap`.
fn get_map(&self) -> &HashMap<Self::Key, Self::Value>;
/// Get a `Value` from `Self` based on `Key`, which is a hash of `Data`.
fn get_by_root(&self, root: &Self::Key) -> Option<&Self::Value>;
/// The number of items store in `Self`.
fn len(&self) -> usize;
/// Start a timer observing inserts.
fn start_insert_timer() -> Option<metrics::HistogramTimer>;
/// Start a timer observing the time it takes to create a new map for a new slot.
fn start_create_map_timer() -> Option<metrics::HistogramTimer>;
/// Start a timer observing the time it takes to prune the pool.
fn start_prune_timer() -> Option<metrics::HistogramTimer>;
/// The default capacity of `Self`.
fn default_capacity() -> usize;
}
/// A collection of `Attestation` objects, keyed by their `attestation.data`. Enforces that all
/// `attestation` are from the same slot.
struct AggregatedAttestationMap<E: EthSpec> {
map: HashMap<AttestationData, Attestation<E>>,
pub struct AggregatedAttestationMap<E: EthSpec> {
map: HashMap<AttestationDataRoot, Attestation<E>>,
}
impl<E: EthSpec> AggregatedAttestationMap<E> {
impl<E: EthSpec> AggregateMap for AggregatedAttestationMap<E> {
type Key = AttestationDataRoot;
type Value = Attestation<E>;
type Data = AttestationData;
/// Create an empty collection with the given `initial_capacity`.
pub fn new(initial_capacity: usize) -> Self {
fn new(initial_capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(initial_capacity),
}
@@ -67,7 +121,7 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
/// Insert an attestation into `self`, aggregating it into the pool.
///
/// The given attestation (`a`) must only have one signature.
pub fn insert(&mut self, a: &Attestation<E>) -> Result<InsertOutcome, Error> {
fn insert(&mut self, a: &Self::Value) -> Result<InsertOutcome, Error> {
let _timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_CORE_INSERT);
let set_bits = a
@@ -81,13 +135,15 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
let committee_index = set_bits
.first()
.copied()
.ok_or_else(|| Error::NoAggregationBitsSet)?;
.ok_or(Error::NoAggregationBitsSet)?;
if set_bits.len() > 1 {
return Err(Error::MoreThanOneAggregationBitSet(set_bits.len()));
}
if let Some(existing_attestation) = self.map.get_mut(&a.data) {
let attestation_data_root = a.data.tree_hash_root();
if let Some(existing_attestation) = self.map.get_mut(&attestation_data_root) {
if existing_attestation
.aggregation_bits
.get(committee_index)
@@ -102,60 +158,190 @@ impl<E: EthSpec> AggregatedAttestationMap<E> {
}
} else {
if self.map.len() >= MAX_ATTESTATIONS_PER_SLOT {
return Err(Error::ReachedMaxAttestationsPerSlot(
MAX_ATTESTATIONS_PER_SLOT,
));
return Err(Error::ReachedMaxItemsPerSlot(MAX_ATTESTATIONS_PER_SLOT));
}
self.map.insert(a.data.clone(), a.clone());
Ok(InsertOutcome::NewAttestationData { committee_index })
self.map.insert(attestation_data_root, a.clone());
Ok(InsertOutcome::NewItemInserted { committee_index })
}
}
/// Returns an aggregated `Attestation` with the given `data`, if any.
///
/// The given `a.data.slot` must match the slot that `self` was initialized with.
pub fn get(&self, data: &AttestationData) -> Result<Option<Attestation<E>>, Error> {
Ok(self.map.get(data).cloned())
fn get(&self, data: &Self::Data) -> Option<Self::Value> {
self.map.get(&data.tree_hash_root()).cloned()
}
/// Iterate all attestations in `self`.
pub fn iter(&self) -> impl Iterator<Item = &Attestation<E>> {
self.map.iter().map(|(_key, attestation)| attestation)
fn get_map(&self) -> &HashMap<Self::Key, Self::Value> {
&self.map
}
pub fn len(&self) -> usize {
/// Returns an aggregated `Attestation` with the given `root`, if any.
fn get_by_root(&self, root: &Self::Key) -> Option<&Self::Value> {
self.map.get(root)
}
fn len(&self) -> usize {
self.map.len()
}
fn start_insert_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_INSERT)
}
fn start_create_map_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_CREATE_MAP)
}
fn start_prune_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_PRUNE)
}
/// Use the `TARGET_COMMITTEE_SIZE`.
///
/// Note: hard-coded until `TARGET_COMMITTEE_SIZE` is available via `EthSpec`.
fn default_capacity() -> usize {
128
}
}
/// A pool of `Attestation` that is specially designed to store "unaggregated" attestations from
/// the native aggregation scheme.
/// A collection of `SyncCommitteeContribution`, keyed by their `SyncContributionData`. Enforces that all
/// contributions are from the same slot.
pub struct SyncContributionAggregateMap<E: EthSpec> {
map: HashMap<SyncDataRoot, SyncCommitteeContribution<E>>,
}
impl<E: EthSpec> AggregateMap for SyncContributionAggregateMap<E> {
type Key = SyncDataRoot;
type Value = SyncCommitteeContribution<E>;
type Data = SyncContributionData;
/// Create an empty collection with the given `initial_capacity`.
fn new(initial_capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(initial_capacity),
}
}
/// Insert a sync committee contribution into `self`, aggregating it into the pool.
///
/// The given sync contribution must only have one signature.
fn insert(
&mut self,
contribution: &SyncCommitteeContribution<E>,
) -> Result<InsertOutcome, Error> {
let _timer =
metrics::start_timer(&metrics::SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_CORE_INSERT);
let set_bits = contribution
.aggregation_bits
.iter()
.enumerate()
.filter(|(_i, bit)| *bit)
.map(|(i, _bit)| i)
.collect::<Vec<_>>();
let committee_index = set_bits
.first()
.copied()
.ok_or(Error::NoAggregationBitsSet)?;
if set_bits.len() > 1 {
return Err(Error::MoreThanOneAggregationBitSet(set_bits.len()));
}
let sync_data_root = SyncContributionData::from_contribution(contribution).tree_hash_root();
if let Some(existing_contribution) = self.map.get_mut(&sync_data_root) {
if existing_contribution
.aggregation_bits
.get(committee_index)
.map_err(|_| Error::InconsistentBitfieldLengths)?
{
Ok(InsertOutcome::SignatureAlreadyKnown { committee_index })
} else {
let _timer = metrics::start_timer(
&metrics::SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_AGGREGATION,
);
existing_contribution.aggregate(contribution);
Ok(InsertOutcome::SignatureAggregated { committee_index })
}
} else {
if self.map.len() >= E::sync_committee_size() {
return Err(Error::ReachedMaxItemsPerSlot(E::sync_committee_size()));
}
self.map.insert(sync_data_root, contribution.clone());
Ok(InsertOutcome::NewItemInserted { committee_index })
}
}
/// Returns an aggregated `SyncCommitteeContribution` with the given `data`, if any.
///
/// The given `data.slot` must match the slot that `self` was initialized with.
fn get(&self, data: &SyncContributionData) -> Option<SyncCommitteeContribution<E>> {
self.map.get(&data.tree_hash_root()).cloned()
}
fn get_map(&self) -> &HashMap<SyncDataRoot, SyncCommitteeContribution<E>> {
&self.map
}
/// Returns an aggregated `SyncCommitteeContribution` with the given `root`, if any.
fn get_by_root(&self, root: &SyncDataRoot) -> Option<&SyncCommitteeContribution<E>> {
self.map.get(root)
}
fn len(&self) -> usize {
self.map.len()
}
fn start_insert_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_INSERT)
}
fn start_create_map_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_CREATE_MAP)
}
fn start_prune_timer() -> Option<metrics::HistogramTimer> {
metrics::start_timer(&metrics::SYNC_CONTRIBUTION_PROCESSING_AGG_POOL_PRUNE)
}
/// Default to `SYNC_COMMITTEE_SUBNET_COUNT`.
fn default_capacity() -> usize {
SYNC_COMMITTEE_SUBNET_COUNT as usize
}
}
/// A pool of `Attestation` or `SyncCommitteeContribution` that is specially designed to store
/// "unaggregated" messages from the native aggregation scheme.
///
/// **The `NaiveAggregationPool` does not do any signature or attestation verification. It assumes
/// that all `Attestation` objects provided are valid.**
/// **The `NaiveAggregationPool` does not do any verification. It assumes that all `Attestation`
/// or `SyncCommitteeContribution` objects provided are valid.**
///
/// ## Details
///
/// The pool sorts the `Attestation` by `attestation.data.slot`, then by `attestation.data`.
/// The pool sorts the items by `slot`, then by `Data`.
///
/// As each unaggregated attestation is added it is aggregated with any existing `attestation` with
/// the same `AttestationData`. Considering that the pool only accepts attestations with a single
/// As each item is added it is aggregated with any existing item with the same `Data`. Considering
/// that the pool only accepts attestations or sync contributions with a single
/// signature, there should only ever be a single aggregated `Attestation` for any given
/// `AttestationData`.
/// `AttestationData` or a single `SyncCommitteeContribution` for any given `SyncContributionData`.
///
/// The pool has a capacity for `SLOTS_RETAINED` slots, when a new `attestation.data.slot` is
/// The pool has a capacity for `SLOTS_RETAINED` slots, when a new `slot` is
/// provided, the oldest slot is dropped and replaced with the new slot. The pool can also be
/// pruned by supplying a `current_slot`; all existing attestations with a slot lower than
/// `current_slot - SLOTS_RETAINED` will be removed and any future attestation with a slot lower
/// than that will also be refused. Pruning is done automatically based upon the attestations it
/// pruned by supplying a `current_slot`; all existing items with a slot lower than
/// `current_slot - SLOTS_RETAINED` will be removed and any future item with a slot lower
/// than that will also be refused. Pruning is done automatically based upon the items it
/// receives and it can be triggered manually.
pub struct NaiveAggregationPool<E: EthSpec> {
pub struct NaiveAggregationPool<T: AggregateMap> {
lowest_permissible_slot: Slot,
maps: HashMap<Slot, AggregatedAttestationMap<E>>,
maps: HashMap<Slot, T>,
}
impl<E: EthSpec> Default for NaiveAggregationPool<E> {
impl<T: AggregateMap> Default for NaiveAggregationPool<T> {
fn default() -> Self {
Self {
lowest_permissible_slot: Slot::new(0),
@@ -164,20 +350,20 @@ impl<E: EthSpec> Default for NaiveAggregationPool<E> {
}
}
impl<E: EthSpec> NaiveAggregationPool<E> {
/// Insert an attestation into `self`, aggregating it into the pool.
impl<T: AggregateMap> NaiveAggregationPool<T> {
/// Insert an item into `self`, aggregating it into the pool.
///
/// The given attestation (`a`) must only have one signature and have an
/// `attestation.data.slot` that is not lower than `self.lowest_permissible_slot`.
/// The given item must only have one signature and have an
/// `slot` that is not lower than `self.lowest_permissible_slot`.
///
/// The pool may be pruned if the given `attestation.data` has a slot higher than any
/// The pool may be pruned if the given item has a slot higher than any
/// previously seen.
pub fn insert(&mut self, attestation: &Attestation<E>) -> Result<InsertOutcome, Error> {
let _timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_INSERT);
let slot = attestation.data.slot;
pub fn insert(&mut self, item: &T::Value) -> Result<InsertOutcome, Error> {
let _timer = T::start_insert_timer();
let slot = item.get_slot();
let lowest_permissible_slot = self.lowest_permissible_slot;
// Reject any attestations that are too old.
// Reject any items that are too old.
if slot < lowest_permissible_slot {
return Err(Error::SlotTooLow {
slot,
@@ -185,14 +371,10 @@ impl<E: EthSpec> NaiveAggregationPool<E> {
});
}
let lock_timer =
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_MAPS_WRITE_LOCK);
drop(lock_timer);
let outcome = if let Some(map) = self.maps.get_mut(&slot) {
map.insert(attestation)
map.insert(item)
} else {
let _timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_CREATE_MAP);
let _timer = T::start_create_map_timer();
// To avoid re-allocations, try and determine a rough initial capacity for the new item
// by obtaining the mean size of all items in earlier epoch.
let (count, sum) = self
@@ -204,12 +386,11 @@ impl<E: EthSpec> NaiveAggregationPool<E> {
.map(|(_slot, map)| map.len())
.fold((0, 0), |(count, sum), len| (count + 1, sum + len));
// Use the mainnet default committee size if we can't determine an average.
let initial_capacity = sum.checked_div(count).unwrap_or(128);
let initial_capacity = sum.checked_div(count).unwrap_or_else(T::default_capacity);
let mut item = AggregatedAttestationMap::new(initial_capacity);
let outcome = item.insert(attestation);
self.maps.insert(slot, item);
let mut aggregate_map = T::new(initial_capacity);
let outcome = aggregate_map.insert(item);
self.maps.insert(slot, aggregate_map);
outcome
};
@@ -219,27 +400,36 @@ impl<E: EthSpec> NaiveAggregationPool<E> {
outcome
}
/// Returns an aggregated `Attestation` with the given `data`, if any.
pub fn get(&self, data: &AttestationData) -> Result<Option<Attestation<E>>, Error> {
/// Returns the total number of items stored in `self`.
pub fn num_items(&self) -> usize {
self.maps.iter().map(|(_, map)| map.len()).sum()
}
/// Returns an aggregated `T::Value` with the given `T::Data`, if any.
pub fn get(&self, data: &T::Data) -> Option<T::Value> {
self.maps
.iter()
.find(|(slot, _map)| **slot == data.slot)
.map(|(_slot, map)| map.get(data))
.unwrap_or_else(|| Ok(None))
.get(&data.get_slot())
.and_then(|map| map.get(data))
}
/// Iterate all attestations in all slots of `self`.
pub fn iter(&self) -> impl Iterator<Item = &Attestation<E>> {
self.maps.iter().map(|(_slot, map)| map.iter()).flatten()
/// Returns an aggregated `T::Value` with the given `slot` and `root`, if any.
pub fn get_by_slot_and_root(&self, slot: Slot, root: &T::Key) -> Option<T::Value> {
self.maps
.get(&slot)
.and_then(|map| map.get_by_root(root).cloned())
}
/// Removes any attestations with a slot lower than `current_slot` and bars any future
/// attestations with a slot lower than `current_slot - SLOTS_RETAINED`.
/// Iterate all items in all slots of `self`.
pub fn iter(&self) -> impl Iterator<Item = &T::Value> {
self.maps.values().flat_map(|map| map.get_map().values())
}
/// Removes any items with a slot lower than `current_slot` and bars any future
/// items with a slot lower than `current_slot - SLOTS_RETAINED`.
pub fn prune(&mut self, current_slot: Slot) {
let _timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_AGG_POOL_PRUNE);
let _timer = T::start_prune_timer();
// Taking advantage of saturating subtraction on `Slot`.
let lowest_permissible_slot = current_slot - Slot::from(SLOTS_RETAINED);
let lowest_permissible_slot = current_slot.saturating_sub(Slot::from(SLOTS_RETAINED));
// No need to prune if the lowest permissible slot has not changed and the queue length is
// less than the maximum
@@ -280,9 +470,10 @@ impl<E: EthSpec> NaiveAggregationPool<E> {
mod tests {
use super::*;
use ssz_types::BitList;
use store::BitVector;
use types::{
test_utils::{generate_deterministic_keypair, test_random_instance},
Fork, Hash256,
Fork, Hash256, SyncCommitteeMessage,
};
type E = types::MainnetEthSpec;
@@ -294,7 +485,14 @@ mod tests {
a
}
fn sign(a: &mut Attestation<E>, i: usize, genesis_validators_root: Hash256) {
fn get_sync_contribution(slot: Slot) -> SyncCommitteeContribution<E> {
let mut a: SyncCommitteeContribution<E> = test_random_instance();
a.slot = slot;
a.aggregation_bits = BitVector::new();
a
}
fn sign_attestation(a: &mut Attestation<E>, i: usize, genesis_validators_root: Hash256) {
a.sign(
&generate_deterministic_keypair(i).sk,
i,
@@ -305,190 +503,294 @@ mod tests {
.expect("should sign attestation");
}
fn unset_bit(a: &mut Attestation<E>, i: usize) {
fn sign_sync_contribution(
a: &mut SyncCommitteeContribution<E>,
i: usize,
genesis_validators_root: Hash256,
) {
let sync_message = SyncCommitteeMessage::new::<E>(
a.slot,
a.beacon_block_root,
i as u64,
&generate_deterministic_keypair(i).sk,
&Fork::default(),
genesis_validators_root,
&E::default_spec(),
);
let signed_contribution: SyncCommitteeContribution<E> =
SyncCommitteeContribution::from_message(&sync_message, a.subcommittee_index, i)
.unwrap();
a.aggregate(&signed_contribution);
}
fn unset_attestation_bit(a: &mut Attestation<E>, i: usize) {
a.aggregation_bits
.set(i, false)
.expect("should unset aggregation bit")
}
#[test]
fn single_attestation() {
let mut a = get_attestation(Slot::new(0));
let mut pool = NaiveAggregationPool::default();
assert_eq!(
pool.insert(&a),
Err(Error::NoAggregationBitsSet),
"should not accept attestation without any signatures"
);
sign(&mut a, 0, Hash256::random());
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewAttestationData { committee_index: 0 }),
"should accept new attestation"
);
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::SignatureAlreadyKnown { committee_index: 0 }),
"should acknowledge duplicate signature"
);
let retrieved = pool
.get(&a.data)
.expect("should not error while getting attestation")
.expect("should get an attestation");
assert_eq!(
retrieved, a,
"retrieved attestation should equal the one inserted"
);
sign(&mut a, 1, Hash256::random());
assert_eq!(
pool.insert(&a),
Err(Error::MoreThanOneAggregationBitSet(2)),
"should not accept attestation with multiple signatures"
);
fn unset_sync_contribution_bit(a: &mut SyncCommitteeContribution<E>, i: usize) {
a.aggregation_bits
.set(i, false)
.expect("should unset aggregation bit")
}
#[test]
fn multiple_attestations() {
let mut a_0 = get_attestation(Slot::new(0));
let mut a_1 = a_0.clone();
let genesis_validators_root = Hash256::random();
sign(&mut a_0, 0, genesis_validators_root);
sign(&mut a_1, 1, genesis_validators_root);
let mut pool = NaiveAggregationPool::default();
assert_eq!(
pool.insert(&a_0),
Ok(InsertOutcome::NewAttestationData { committee_index: 0 }),
"should accept a_0"
);
assert_eq!(
pool.insert(&a_1),
Ok(InsertOutcome::SignatureAggregated { committee_index: 1 }),
"should accept a_1"
);
let retrieved = pool
.get(&a_0.data)
.expect("should not error while getting attestation")
.expect("should get an attestation");
let mut a_01 = a_0.clone();
a_01.aggregate(&a_1);
assert_eq!(
retrieved, a_01,
"retrieved attestation should be aggregated"
);
/*
* Throw a different attestation data in there and ensure it isn't aggregated
*/
let mut a_different = a_0.clone();
let different_root = Hash256::from_low_u64_be(1337);
unset_bit(&mut a_different, 0);
sign(&mut a_different, 2, genesis_validators_root);
assert_ne!(a_different.data.beacon_block_root, different_root);
a_different.data.beacon_block_root = different_root;
assert_eq!(
pool.insert(&a_different),
Ok(InsertOutcome::NewAttestationData { committee_index: 2 }),
"should accept a_different"
);
assert_eq!(
pool.get(&a_0.data)
.expect("should not error while getting attestation")
.expect("should get an attestation"),
retrieved,
"should not have aggregated different attestation data"
);
fn mutate_attestation_block_root(a: &mut Attestation<E>, block_root: Hash256) {
a.data.beacon_block_root = block_root
}
#[test]
fn auto_pruning() {
let mut base = get_attestation(Slot::new(0));
sign(&mut base, 0, Hash256::random());
fn mutate_attestation_slot(a: &mut Attestation<E>, slot: Slot) {
a.data.slot = slot
}
let mut pool = NaiveAggregationPool::default();
fn attestation_block_root_comparator(a: &Attestation<E>, block_root: Hash256) -> bool {
a.data.beacon_block_root == block_root
}
for i in 0..SLOTS_RETAINED * 2 {
let slot = Slot::from(i);
let mut a = base.clone();
a.data.slot = slot;
fn key_from_attestation(a: &Attestation<E>) -> AttestationData {
a.data.clone()
}
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewAttestationData { committee_index: 0 }),
"should accept new attestation"
);
fn mutate_sync_contribution_block_root(
a: &mut SyncCommitteeContribution<E>,
block_root: Hash256,
) {
a.beacon_block_root = block_root
}
if i < SLOTS_RETAINED {
let len = i + 1;
assert_eq!(pool.maps.len(), len, "the pool should have length {}", len);
} else {
assert_eq!(
pool.maps.len(),
SLOTS_RETAINED,
"the pool should have length SLOTS_RETAINED"
);
fn mutate_sync_contribution_slot(a: &mut SyncCommitteeContribution<E>, slot: Slot) {
a.slot = slot
}
let mut pool_slots = pool
.maps
.iter()
.map(|(slot, _map)| *slot)
.collect::<Vec<_>>();
fn sync_contribution_block_root_comparator(
a: &SyncCommitteeContribution<E>,
block_root: Hash256,
) -> bool {
a.beacon_block_root == block_root
}
pool_slots.sort_unstable();
fn key_from_sync_contribution(a: &SyncCommitteeContribution<E>) -> SyncContributionData {
SyncContributionData::from_contribution(a)
}
macro_rules! test_suite {
(
$mod_name: ident,
$get_method_name: ident,
$sign_method_name: ident,
$unset_method_name: ident,
$block_root_mutator: ident,
$slot_mutator: ident,
$block_root_comparator: ident,
$key_getter: ident,
$map_type: ident,
$item_limit: expr
) => {
#[cfg(test)]
mod $mod_name {
use super::*;
#[test]
fn single_item() {
let mut a = $get_method_name(Slot::new(0));
let mut pool: NaiveAggregationPool<$map_type<E>> =
NaiveAggregationPool::default();
for (j, pool_slot) in pool_slots.iter().enumerate() {
let expected_slot = slot - (SLOTS_RETAINED - 1 - j) as u64;
assert_eq!(
*pool_slot, expected_slot,
"the slot of the map should be {}",
expected_slot
)
pool.insert(&a),
Err(Error::NoAggregationBitsSet),
"should not accept item without any signatures"
);
$sign_method_name(&mut a, 0, Hash256::random());
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewItemInserted { committee_index: 0 }),
"should accept new item"
);
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::SignatureAlreadyKnown { committee_index: 0 }),
"should acknowledge duplicate signature"
);
let retrieved = pool
.get(&$key_getter(&a))
.expect("should not error while getting item");
assert_eq!(retrieved, a, "retrieved item should equal the one inserted");
$sign_method_name(&mut a, 1, Hash256::random());
assert_eq!(
pool.insert(&a),
Err(Error::MoreThanOneAggregationBitSet(2)),
"should not accept item with multiple signatures"
);
}
#[test]
fn multiple_items() {
let mut a_0 = $get_method_name(Slot::new(0));
let mut a_1 = a_0.clone();
let genesis_validators_root = Hash256::random();
$sign_method_name(&mut a_0, 0, genesis_validators_root);
$sign_method_name(&mut a_1, 1, genesis_validators_root);
let mut pool: NaiveAggregationPool<$map_type<E>> =
NaiveAggregationPool::default();
assert_eq!(
pool.insert(&a_0),
Ok(InsertOutcome::NewItemInserted { committee_index: 0 }),
"should accept a_0"
);
assert_eq!(
pool.insert(&a_1),
Ok(InsertOutcome::SignatureAggregated { committee_index: 1 }),
"should accept a_1"
);
let retrieved = pool
.get(&$key_getter(&a_0))
.expect("should not error while getting attestation");
let mut a_01 = a_0.clone();
a_01.aggregate(&a_1);
assert_eq!(retrieved, a_01, "retrieved item should be aggregated");
/*
* Throw different data in there and ensure it isn't aggregated
*/
let mut a_different = a_0.clone();
let different_root = Hash256::from_low_u64_be(1337);
$unset_method_name(&mut a_different, 0);
$sign_method_name(&mut a_different, 2, genesis_validators_root);
assert!(!$block_root_comparator(&a_different, different_root));
$block_root_mutator(&mut a_different, different_root);
assert_eq!(
pool.insert(&a_different),
Ok(InsertOutcome::NewItemInserted { committee_index: 2 }),
"should accept a_different"
);
assert_eq!(
pool.get(&$key_getter(&a_0))
.expect("should not error while getting item"),
retrieved,
"should not have aggregated different items with different data"
);
}
#[test]
fn auto_pruning_item() {
let mut base = $get_method_name(Slot::new(0));
$sign_method_name(&mut base, 0, Hash256::random());
let mut pool: NaiveAggregationPool<$map_type<E>> =
NaiveAggregationPool::default();
for i in 0..SLOTS_RETAINED * 2 {
let slot = Slot::from(i);
let mut a = base.clone();
$slot_mutator(&mut a, slot);
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewItemInserted { committee_index: 0 }),
"should accept new item"
);
if i < SLOTS_RETAINED {
let len = i + 1;
assert_eq!(pool.maps.len(), len, "the pool should have length {}", len);
} else {
assert_eq!(
pool.maps.len(),
SLOTS_RETAINED,
"the pool should have length SLOTS_RETAINED"
);
let mut pool_slots = pool
.maps
.iter()
.map(|(slot, _map)| *slot)
.collect::<Vec<_>>();
pool_slots.sort_unstable();
for (j, pool_slot) in pool_slots.iter().enumerate() {
let expected_slot = slot - (SLOTS_RETAINED - 1 - j) as u64;
assert_eq!(
*pool_slot, expected_slot,
"the slot of the map should be {}",
expected_slot
)
}
}
}
}
#[test]
fn max_items() {
let mut base = $get_method_name(Slot::new(0));
$sign_method_name(&mut base, 0, Hash256::random());
let mut pool: NaiveAggregationPool<$map_type<E>> =
NaiveAggregationPool::default();
for i in 0..=$item_limit {
let mut a = base.clone();
$block_root_mutator(&mut a, Hash256::from_low_u64_be(i as u64));
if i < $item_limit {
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewItemInserted { committee_index: 0 }),
"should accept item below limit"
);
} else {
assert_eq!(
pool.insert(&a),
Err(Error::ReachedMaxItemsPerSlot($item_limit)),
"should not accept item above limit"
);
}
}
}
}
}
};
}
#[test]
fn max_attestations() {
let mut base = get_attestation(Slot::new(0));
sign(&mut base, 0, Hash256::random());
test_suite! {
attestation_tests,
get_attestation,
sign_attestation,
unset_attestation_bit,
mutate_attestation_block_root,
mutate_attestation_slot,
attestation_block_root_comparator,
key_from_attestation,
AggregatedAttestationMap,
MAX_ATTESTATIONS_PER_SLOT
}
let mut pool = NaiveAggregationPool::default();
for i in 0..=MAX_ATTESTATIONS_PER_SLOT {
let mut a = base.clone();
a.data.beacon_block_root = Hash256::from_low_u64_be(i as u64);
if i < MAX_ATTESTATIONS_PER_SLOT {
assert_eq!(
pool.insert(&a),
Ok(InsertOutcome::NewAttestationData { committee_index: 0 }),
"should accept attestation below limit"
);
} else {
assert_eq!(
pool.insert(&a),
Err(Error::ReachedMaxAttestationsPerSlot(
MAX_ATTESTATIONS_PER_SLOT
)),
"should not accept attestation above limit"
);
}
}
test_suite! {
sync_contribution_tests,
get_sync_contribution,
sign_sync_contribution,
unset_sync_contribution_bit,
mutate_sync_contribution_block_root,
mutate_sync_contribution_slot,
sync_contribution_block_root_comparator,
key_from_sync_contribution,
SyncContributionAggregateMap,
E::sync_committee_size()
}
}

View File

@@ -0,0 +1,507 @@
//! Provides an `ObservedAggregates` struct which allows us to reject aggregated attestations or
//! sync committee contributions if we've already seen them.
use std::collections::HashSet;
use std::marker::PhantomData;
use tree_hash::TreeHash;
use types::consts::altair::{
SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE,
};
use types::slot_data::SlotData;
use types::{Attestation, EthSpec, Hash256, Slot, SyncCommitteeContribution};
pub type ObservedSyncContributions<E> = ObservedAggregates<SyncCommitteeContribution<E>, E>;
pub type ObservedAggregateAttestations<E> = ObservedAggregates<Attestation<E>, E>;
/// A trait use to associate capacity constants with the type being stored in `ObservedAggregates`.
pub trait Consts {
/// The default capacity of items stored per slot, in a single `SlotHashSet`.
const DEFAULT_PER_SLOT_CAPACITY: usize;
/// The maximum number of slots
fn max_slot_capacity() -> usize;
/// The maximum number of items stored per slot, in a single `SlotHashSet`.
fn max_per_slot_capacity() -> usize;
}
impl<T: EthSpec> Consts for Attestation<T> {
/// Use 128 as it's the target committee size for the mainnet spec. This is perhaps a little
/// wasteful for the minimal spec, but considering it's approx. 128 * 32 bytes we're not wasting
/// much.
const DEFAULT_PER_SLOT_CAPACITY: usize = 128;
/// We need to keep attestations for each slot of the current epoch.
fn max_slot_capacity() -> usize {
T::slots_per_epoch() as usize
}
/// As a DoS protection measure, the maximum number of distinct `Attestations` or
/// `SyncCommitteeContributions` that will be recorded for each slot.
///
/// Currently this is set to ~524k. If we say that each entry is 40 bytes (Hash256 (32 bytes) + an
/// 8 byte hash) then this comes to about 20mb per slot. If we're storing 34 of these slots, then
/// we're at 680mb. This is a lot of memory usage, but probably not a show-stopper for most
/// reasonable hardware.
///
/// Upstream conditions should strongly restrict the amount of attestations that can show up in
/// this pool. The maximum size with respect to upstream restrictions is more likely on the order
/// of the number of validators.
fn max_per_slot_capacity() -> usize {
1 << 19 // 524,288
}
}
impl<T: EthSpec> Consts for SyncCommitteeContribution<T> {
/// Set to `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE * SYNC_COMMITTEE_SUBNET_COUNT`. This is the
/// expected number of aggregators per slot across all subcommittees.
const DEFAULT_PER_SLOT_CAPACITY: usize =
(SYNC_COMMITTEE_SUBNET_COUNT * TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) as usize;
/// We only need to keep contributions related to the current slot.
fn max_slot_capacity() -> usize {
1
}
/// We should never receive more aggregates than there are sync committee participants.
fn max_per_slot_capacity() -> usize {
T::sync_committee_size()
}
}
#[derive(Debug, PartialEq)]
pub enum ObserveOutcome {
/// This item was already known.
AlreadyKnown,
/// This was the first time this item was observed.
New,
}
#[derive(Debug, PartialEq)]
pub enum Error {
SlotTooLow {
slot: Slot,
lowest_permissible_slot: Slot,
},
/// The function to obtain a set index failed, this is an internal error.
InvalidSetIndex(usize),
/// We have reached the maximum number of unique items that can be observed in a slot.
/// This is a DoS protection function.
ReachedMaxObservationsPerSlot(usize),
IncorrectSlot {
expected: Slot,
attestation: Slot,
},
}
/// A `HashSet` that contains entries related to some `Slot`.
struct SlotHashSet {
set: HashSet<Hash256>,
slot: Slot,
max_capacity: usize,
}
impl SlotHashSet {
pub fn new(slot: Slot, initial_capacity: usize, max_capacity: usize) -> Self {
Self {
slot,
set: HashSet::with_capacity(initial_capacity),
max_capacity,
}
}
/// Store the items in self so future observations recognise its existence.
pub fn observe_item<T: SlotData>(
&mut self,
item: &T,
root: Hash256,
) -> Result<ObserveOutcome, Error> {
if item.get_slot() != self.slot {
return Err(Error::IncorrectSlot {
expected: self.slot,
attestation: item.get_slot(),
});
}
if self.set.contains(&root) {
Ok(ObserveOutcome::AlreadyKnown)
} else {
// Here we check to see if this slot has reached the maximum observation count.
//
// The resulting behaviour is that we are no longer able to successfully observe new
// items, however we will continue to return `is_known` values. We could also
// disable `is_known`, however then we would stop forwarding items across the
// gossip network and I think that this is a worse case than sending some invalid ones.
// The underlying libp2p network is responsible for removing duplicate messages, so
// this doesn't risk a broadcast loop.
if self.set.len() >= self.max_capacity {
return Err(Error::ReachedMaxObservationsPerSlot(self.max_capacity));
}
self.set.insert(root);
Ok(ObserveOutcome::New)
}
}
/// Indicates if `item` has been observed before.
pub fn is_known<T: SlotData>(&self, item: &T, root: Hash256) -> Result<bool, Error> {
if item.get_slot() != self.slot {
return Err(Error::IncorrectSlot {
expected: self.slot,
attestation: item.get_slot(),
});
}
Ok(self.set.contains(&root))
}
/// The number of observed items in `self`.
pub fn len(&self) -> usize {
self.set.len()
}
}
/// Stores the roots of objects for some number of `Slots`, so we can determine if
/// these have previously been seen on the network.
pub struct ObservedAggregates<T: TreeHash + SlotData + Consts, E: EthSpec> {
lowest_permissible_slot: Slot,
sets: Vec<SlotHashSet>,
_phantom_spec: PhantomData<E>,
_phantom_tree_hash: PhantomData<T>,
}
impl<T: TreeHash + SlotData + Consts, E: EthSpec> Default for ObservedAggregates<T, E> {
fn default() -> Self {
Self {
lowest_permissible_slot: Slot::new(0),
sets: vec![],
_phantom_spec: PhantomData,
_phantom_tree_hash: PhantomData,
}
}
}
impl<T: TreeHash + SlotData + Consts, E: EthSpec> ObservedAggregates<T, E> {
/// Store the root of `item` in `self`.
///
/// `root` must equal `item.tree_hash_root()`.
pub fn observe_item(
&mut self,
item: &T,
root_opt: Option<Hash256>,
) -> Result<ObserveOutcome, Error> {
let index = self.get_set_index(item.get_slot())?;
let root = root_opt.unwrap_or_else(|| item.tree_hash_root());
self.sets
.get_mut(index)
.ok_or(Error::InvalidSetIndex(index))
.and_then(|set| set.observe_item(item, root))
}
/// Check to see if the `root` of `item` is in self.
///
/// `root` must equal `a.tree_hash_root()`.
pub fn is_known(&mut self, item: &T, root: Hash256) -> Result<bool, Error> {
let index = self.get_set_index(item.get_slot())?;
self.sets
.get(index)
.ok_or(Error::InvalidSetIndex(index))
.and_then(|set| set.is_known(item, root))
}
/// The maximum number of slots that items are stored for.
fn max_capacity(&self) -> u64 {
// We add `2` in order to account for one slot either side of the range due to
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
(T::max_slot_capacity() + 2) as u64
}
/// Removes any items with a slot lower than `current_slot` and bars any future
/// item with a slot lower than `current_slot - SLOTS_RETAINED`.
pub fn prune(&mut self, current_slot: Slot) {
let lowest_permissible_slot = current_slot.saturating_sub(self.max_capacity() - 1);
self.sets.retain(|set| set.slot >= lowest_permissible_slot);
self.lowest_permissible_slot = lowest_permissible_slot;
}
/// Returns the index of `self.set` that matches `slot`.
///
/// If there is no existing set for this slot one will be created. If `self.sets.len() >=
/// Self::max_capacity()`, the set with the lowest slot will be replaced.
fn get_set_index(&mut self, slot: Slot) -> Result<usize, Error> {
let lowest_permissible_slot = self.lowest_permissible_slot;
if slot < lowest_permissible_slot {
return Err(Error::SlotTooLow {
slot,
lowest_permissible_slot,
});
}
// Prune the pool if this item indicates that the current slot has advanced.
if lowest_permissible_slot + self.max_capacity() < slot + 1 {
self.prune(slot)
}
if let Some(index) = self.sets.iter().position(|set| set.slot == slot) {
return Ok(index);
}
// To avoid re-allocations, try and determine a rough initial capacity for the new set
// by obtaining the mean size of all items in earlier epoch.
let (count, sum) = self
.sets
.iter()
// Only include slots that are less than the given slot in the average. This should
// generally avoid including recent slots that are still "filling up".
.filter(|set| set.slot < slot)
.map(|set| set.len())
.fold((0, 0), |(count, sum), len| (count + 1, sum + len));
// If we are unable to determine an average, just use the `self.default_per_slot_capacity`.
let initial_capacity = sum
.checked_div(count)
.unwrap_or(T::DEFAULT_PER_SLOT_CAPACITY);
if self.sets.len() < self.max_capacity() as usize || self.sets.is_empty() {
let index = self.sets.len();
self.sets.push(SlotHashSet::new(
slot,
initial_capacity,
T::max_per_slot_capacity(),
));
return Ok(index);
}
let index = self
.sets
.iter()
.enumerate()
.min_by_key(|(_i, set)| set.slot)
.map(|(i, _set)| i)
.expect("sets cannot be empty due to previous .is_empty() check");
self.sets[index] = SlotHashSet::new(slot, initial_capacity, T::max_per_slot_capacity());
Ok(index)
}
}
#[cfg(test)]
#[cfg(not(debug_assertions))]
mod tests {
use super::*;
use tree_hash::TreeHash;
use types::{test_utils::test_random_instance, Hash256};
type E = types::MainnetEthSpec;
fn get_attestation(slot: Slot, beacon_block_root: u64) -> Attestation<E> {
let mut a: Attestation<E> = test_random_instance();
a.data.slot = slot;
a.data.beacon_block_root = Hash256::from_low_u64_be(beacon_block_root);
a
}
fn get_sync_contribution(slot: Slot, beacon_block_root: u64) -> SyncCommitteeContribution<E> {
let mut a: SyncCommitteeContribution<E> = test_random_instance();
a.slot = slot;
a.beacon_block_root = Hash256::from_low_u64_be(beacon_block_root);
a
}
macro_rules! test_suite {
($mod_name: ident, $type: ident, $method_name: ident) => {
#[cfg(test)]
mod $mod_name {
use super::*;
const NUM_ELEMENTS: usize = 8;
fn single_slot_test(store: &mut $type<E>, slot: Slot) {
let items = (0..NUM_ELEMENTS as u64)
.map(|i| $method_name(slot, i))
.collect::<Vec<_>>();
for a in &items {
assert_eq!(
store.is_known(a, a.tree_hash_root()),
Ok(false),
"should indicate an unknown attestation is unknown"
);
assert_eq!(
store.observe_item(a, None),
Ok(ObserveOutcome::New),
"should observe new attestation"
);
}
for a in &items {
assert_eq!(
store.is_known(a, a.tree_hash_root()),
Ok(true),
"should indicate a known attestation is known"
);
assert_eq!(
store.observe_item(a, Some(a.tree_hash_root())),
Ok(ObserveOutcome::AlreadyKnown),
"should acknowledge an existing attestation"
);
}
}
#[test]
fn single_slot() {
let mut store = $type::default();
single_slot_test(&mut store, Slot::new(0));
assert_eq!(store.sets.len(), 1, "should have a single set stored");
assert_eq!(
store.sets[0].len(),
NUM_ELEMENTS,
"set should have NUM_ELEMENTS elements"
);
}
#[test]
fn mulitple_contiguous_slots() {
let mut store = $type::default();
let max_cap = store.max_capacity();
for i in 0..max_cap * 3 {
let slot = Slot::new(i);
single_slot_test(&mut store, slot);
/*
* Ensure that the number of sets is correct.
*/
if i < max_cap {
assert_eq!(
store.sets.len(),
i as usize + 1,
"should have a {} sets stored",
i + 1
);
} else {
assert_eq!(
store.sets.len(),
max_cap as usize,
"should have max_capacity sets stored"
);
}
/*
* Ensure that each set contains the correct number of elements.
*/
for set in &store.sets[..] {
assert_eq!(
set.len(),
NUM_ELEMENTS,
"each store should have NUM_ELEMENTS elements"
)
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_slots =
store.sets.iter().map(|set| set.slot).collect::<Vec<_>>();
assert!(
store_slots.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
store_slots.sort_unstable();
let expected_slots = (i.saturating_sub(max_cap - 1)..=i)
.map(Slot::new)
.collect::<Vec<_>>();
assert_eq!(expected_slots, store_slots, "should have expected slots");
}
}
#[test]
fn mulitple_non_contiguous_slots() {
let mut store = $type::default();
let max_cap = store.max_capacity();
let to_skip = vec![1_u64, 2, 3, 5, 6, 29, 30, 31, 32, 64];
let slots = (0..max_cap * 3)
.into_iter()
.filter(|i| !to_skip.contains(i))
.collect::<Vec<_>>();
for &i in &slots {
if to_skip.contains(&i) {
continue;
}
let slot = Slot::from(i);
single_slot_test(&mut store, slot);
/*
* Ensure that each set contains the correct number of elements.
*/
for set in &store.sets[..] {
assert_eq!(
set.len(),
NUM_ELEMENTS,
"each store should have NUM_ELEMENTS elements"
)
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_slots =
store.sets.iter().map(|set| set.slot).collect::<Vec<_>>();
store_slots.sort_unstable();
assert!(
store_slots.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
let lowest = store.lowest_permissible_slot.as_u64();
let highest = slot.as_u64();
let expected_slots = (lowest..=highest)
.filter(|i| !to_skip.contains(i))
.map(Slot::new)
.collect::<Vec<_>>();
assert_eq!(
expected_slots,
&store_slots[..],
"should have expected slots"
);
}
}
}
};
}
test_suite!(
observed_sync_aggregates,
ObservedSyncContributions,
get_sync_contribution
);
test_suite!(
observed_aggregate_attestations,
ObservedAggregateAttestations,
get_attestation
);
}

View File

@@ -1,443 +0,0 @@
//! Provides an `ObservedAttestations` struct which allows us to reject aggregated attestations if
//! we've already seen the aggregated attestation.
use parking_lot::RwLock;
use std::collections::HashSet;
use std::marker::PhantomData;
use tree_hash::TreeHash;
use types::{Attestation, EthSpec, Hash256, Slot};
/// As a DoS protection measure, the maximum number of distinct `Attestations` that will be
/// recorded for each slot.
///
/// Currently this is set to ~524k. If we say that each entry is 40 bytes (Hash256 (32 bytes) + an
/// 8 byte hash) then this comes to about 20mb per slot. If we're storing 34 of these slots, then
/// we're at 680mb. This is a lot of memory usage, but probably not a show-stopper for most
/// reasonable hardware.
///
/// Upstream conditions should strongly restrict the amount of attestations that can show up in
/// this pool. The maximum size with respect to upstream restrictions is more likely on the order
/// of the number of validators.
const MAX_OBSERVATIONS_PER_SLOT: usize = 1 << 19; // 524,288
#[derive(Debug, PartialEq)]
pub enum ObserveOutcome {
/// This attestation was already known.
AlreadyKnown,
/// This was the first time this attestation was observed.
New,
}
#[derive(Debug, PartialEq)]
pub enum Error {
SlotTooLow {
slot: Slot,
lowest_permissible_slot: Slot,
},
/// The function to obtain a set index failed, this is an internal error.
InvalidSetIndex(usize),
/// We have reached the maximum number of unique `Attestation` that can be observed in a slot.
/// This is a DoS protection function.
ReachedMaxObservationsPerSlot(usize),
IncorrectSlot {
expected: Slot,
attestation: Slot,
},
}
/// A `HashSet` that contains entries related to some `Slot`.
struct SlotHashSet {
set: HashSet<Hash256>,
slot: Slot,
}
impl SlotHashSet {
pub fn new(slot: Slot, initial_capacity: usize) -> Self {
Self {
slot,
set: HashSet::with_capacity(initial_capacity),
}
}
/// Store the attestation in self so future observations recognise its existence.
pub fn observe_attestation<E: EthSpec>(
&mut self,
a: &Attestation<E>,
root: Hash256,
) -> Result<ObserveOutcome, Error> {
if a.data.slot != self.slot {
return Err(Error::IncorrectSlot {
expected: self.slot,
attestation: a.data.slot,
});
}
if self.set.contains(&root) {
Ok(ObserveOutcome::AlreadyKnown)
} else {
// Here we check to see if this slot has reached the maximum observation count.
//
// The resulting behaviour is that we are no longer able to successfully observe new
// attestations, however we will continue to return `is_known` values. We could also
// disable `is_known`, however then we would stop forwarding attestations across the
// gossip network and I think that this is a worse case than sending some invalid ones.
// The underlying libp2p network is responsible for removing duplicate messages, so
// this doesn't risk a broadcast loop.
if self.set.len() >= MAX_OBSERVATIONS_PER_SLOT {
return Err(Error::ReachedMaxObservationsPerSlot(
MAX_OBSERVATIONS_PER_SLOT,
));
}
self.set.insert(root);
Ok(ObserveOutcome::New)
}
}
/// Indicates if `a` has been observed before.
pub fn is_known<E: EthSpec>(&self, a: &Attestation<E>, root: Hash256) -> Result<bool, Error> {
if a.data.slot != self.slot {
return Err(Error::IncorrectSlot {
expected: self.slot,
attestation: a.data.slot,
});
}
Ok(self.set.contains(&root))
}
/// The number of observed attestations in `self`.
pub fn len(&self) -> usize {
self.set.len()
}
}
/// Stores the roots of `Attestation` objects for some number of `Slots`, so we can determine if
/// these have previously been seen on the network.
pub struct ObservedAttestations<E: EthSpec> {
lowest_permissible_slot: RwLock<Slot>,
sets: RwLock<Vec<SlotHashSet>>,
_phantom: PhantomData<E>,
}
impl<E: EthSpec> Default for ObservedAttestations<E> {
fn default() -> Self {
Self {
lowest_permissible_slot: RwLock::new(Slot::new(0)),
sets: RwLock::new(vec![]),
_phantom: PhantomData,
}
}
}
impl<E: EthSpec> ObservedAttestations<E> {
/// Store the root of `a` in `self`.
///
/// `root` must equal `a.tree_hash_root()`.
pub fn observe_attestation(
&self,
a: &Attestation<E>,
root_opt: Option<Hash256>,
) -> Result<ObserveOutcome, Error> {
let index = self.get_set_index(a.data.slot)?;
let root = root_opt.unwrap_or_else(|| a.tree_hash_root());
self.sets
.write()
.get_mut(index)
.ok_or_else(|| Error::InvalidSetIndex(index))
.and_then(|set| set.observe_attestation(a, root))
}
/// Check to see if the `root` of `a` is in self.
///
/// `root` must equal `a.tree_hash_root()`.
pub fn is_known(&self, a: &Attestation<E>, root: Hash256) -> Result<bool, Error> {
let index = self.get_set_index(a.data.slot)?;
self.sets
.read()
.get(index)
.ok_or_else(|| Error::InvalidSetIndex(index))
.and_then(|set| set.is_known(a, root))
}
/// The maximum number of slots that attestations are stored for.
fn max_capacity(&self) -> u64 {
// We add `2` in order to account for one slot either side of the range due to
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
E::slots_per_epoch() + 2
}
/// Removes any attestations with a slot lower than `current_slot` and bars any future
/// attestations with a slot lower than `current_slot - SLOTS_RETAINED`.
pub fn prune(&self, current_slot: Slot) {
// Taking advantage of saturating subtraction on `Slot`.
let lowest_permissible_slot = current_slot - (self.max_capacity() - 1);
self.sets
.write()
.retain(|set| set.slot >= lowest_permissible_slot);
*self.lowest_permissible_slot.write() = lowest_permissible_slot;
}
/// Returns the index of `self.set` that matches `slot`.
///
/// If there is no existing set for this slot one will be created. If `self.sets.len() >=
/// Self::max_capacity()`, the set with the lowest slot will be replaced.
fn get_set_index(&self, slot: Slot) -> Result<usize, Error> {
let lowest_permissible_slot: Slot = *self.lowest_permissible_slot.read();
if slot < lowest_permissible_slot {
return Err(Error::SlotTooLow {
slot,
lowest_permissible_slot,
});
}
// Prune the pool if this attestation indicates that the current slot has advanced.
if lowest_permissible_slot + self.max_capacity() < slot + 1 {
self.prune(slot)
}
let mut sets = self.sets.write();
if let Some(index) = sets.iter().position(|set| set.slot == slot) {
return Ok(index);
}
// To avoid re-allocations, try and determine a rough initial capacity for the new set
// by obtaining the mean size of all items in earlier epoch.
let (count, sum) = sets
.iter()
// Only include slots that are less than the given slot in the average. This should
// generally avoid including recent slots that are still "filling up".
.filter(|set| set.slot < slot)
.map(|set| set.len())
.fold((0, 0), |(count, sum), len| (count + 1, sum + len));
// If we are unable to determine an average, just use 128 as it's the target committee
// size for the mainnet spec. This is perhaps a little wasteful for the minimal spec,
// but considering it's approx. 128 * 32 bytes we're not wasting much.
let initial_capacity = sum.checked_div(count).unwrap_or(128);
if sets.len() < self.max_capacity() as usize || sets.is_empty() {
let index = sets.len();
sets.push(SlotHashSet::new(slot, initial_capacity));
return Ok(index);
}
let index = sets
.iter()
.enumerate()
.min_by_key(|(_i, set)| set.slot)
.map(|(i, _set)| i)
.expect("sets cannot be empty due to previous .is_empty() check");
sets[index] = SlotHashSet::new(slot, initial_capacity);
Ok(index)
}
}
#[cfg(test)]
#[cfg(not(debug_assertions))]
mod tests {
use super::*;
use tree_hash::TreeHash;
use types::{test_utils::test_random_instance, Hash256};
type E = types::MainnetEthSpec;
const NUM_ELEMENTS: usize = 8;
fn get_attestation(slot: Slot, beacon_block_root: u64) -> Attestation<E> {
let mut a: Attestation<E> = test_random_instance();
a.data.slot = slot;
a.data.beacon_block_root = Hash256::from_low_u64_be(beacon_block_root);
a
}
fn single_slot_test(store: &ObservedAttestations<E>, slot: Slot) {
let attestations = (0..NUM_ELEMENTS as u64)
.map(|i| get_attestation(slot, i))
.collect::<Vec<_>>();
for a in &attestations {
assert_eq!(
store.is_known(a, a.tree_hash_root()),
Ok(false),
"should indicate an unknown attestation is unknown"
);
assert_eq!(
store.observe_attestation(a, None),
Ok(ObserveOutcome::New),
"should observe new attestation"
);
}
for a in &attestations {
assert_eq!(
store.is_known(a, a.tree_hash_root()),
Ok(true),
"should indicate a known attestation is known"
);
assert_eq!(
store.observe_attestation(a, Some(a.tree_hash_root())),
Ok(ObserveOutcome::AlreadyKnown),
"should acknowledge an existing attestation"
);
}
}
#[test]
fn single_slot() {
let store = ObservedAttestations::default();
single_slot_test(&store, Slot::new(0));
assert_eq!(
store.sets.read().len(),
1,
"should have a single set stored"
);
assert_eq!(
store.sets.read()[0].len(),
NUM_ELEMENTS,
"set should have NUM_ELEMENTS elements"
);
}
#[test]
fn mulitple_contiguous_slots() {
let store = ObservedAttestations::default();
let max_cap = store.max_capacity();
for i in 0..max_cap * 3 {
let slot = Slot::new(i);
single_slot_test(&store, slot);
/*
* Ensure that the number of sets is correct.
*/
if i < max_cap {
assert_eq!(
store.sets.read().len(),
i as usize + 1,
"should have a {} sets stored",
i + 1
);
} else {
assert_eq!(
store.sets.read().len(),
max_cap as usize,
"should have max_capacity sets stored"
);
}
/*
* Ensure that each set contains the correct number of elements.
*/
for set in &store.sets.read()[..] {
assert_eq!(
set.len(),
NUM_ELEMENTS,
"each store should have NUM_ELEMENTS elements"
)
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_slots = store
.sets
.read()
.iter()
.map(|set| set.slot)
.collect::<Vec<_>>();
assert!(
store_slots.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
store_slots.sort_unstable();
let expected_slots = (i.saturating_sub(max_cap - 1)..=i)
.map(Slot::new)
.collect::<Vec<_>>();
assert_eq!(expected_slots, store_slots, "should have expected slots");
}
}
#[test]
fn mulitple_non_contiguous_slots() {
let store = ObservedAttestations::default();
let max_cap = store.max_capacity();
let to_skip = vec![1_u64, 2, 3, 5, 6, 29, 30, 31, 32, 64];
let slots = (0..max_cap * 3)
.into_iter()
.filter(|i| !to_skip.contains(i))
.collect::<Vec<_>>();
for &i in &slots {
if to_skip.contains(&i) {
continue;
}
let slot = Slot::from(i);
single_slot_test(&store, slot);
/*
* Ensure that each set contains the correct number of elements.
*/
for set in &store.sets.read()[..] {
assert_eq!(
set.len(),
NUM_ELEMENTS,
"each store should have NUM_ELEMENTS elements"
)
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_slots = store
.sets
.read()
.iter()
.map(|set| set.slot)
.collect::<Vec<_>>();
store_slots.sort_unstable();
assert!(
store_slots.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
let lowest = store.lowest_permissible_slot.read().as_u64();
let highest = slot.as_u64();
let expected_slots = (lowest..=highest)
.filter(|i| !to_skip.contains(i))
.map(Slot::new)
.collect::<Vec<_>>();
assert_eq!(
expected_slots,
&store_slots[..],
"should have expected slots"
);
}
}
}

View File

@@ -5,15 +5,44 @@
//! the same epoch.
//! - `ObservedAggregators`: allows filtering aggregated attestations from the same aggregators in
//! the same epoch
//!
//! Provides an additional two structs that help us filter out sync committee message and
//! contribution gossip from validators that have already published messages this slot:
//!
//! - `ObservedSyncContributors`: allows filtering sync committee messages from the same validator in
//! the same slot.
//! - `ObservedSyncAggregators`: allows filtering sync committee contributions from the same aggregators in
//! the same slot and in the same subcommittee.
use crate::types::consts::altair::TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE;
use bitvec::vec::BitVec;
use parking_lot::RwLock;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::marker::PhantomData;
use types::{Attestation, Epoch, EthSpec, Unsigned};
use types::slot_data::SlotData;
use types::{Epoch, EthSpec, Slot, Unsigned};
pub type ObservedAttesters<E> = AutoPruningContainer<EpochBitfield, E>;
pub type ObservedAggregators<E> = AutoPruningContainer<EpochHashSet, E>;
/// The maximum capacity of the `AutoPruningEpochContainer`.
///
/// Fits the next, current and previous epochs. We require the next epoch due to the
/// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`. We require the previous epoch since the specification
/// declares:
///
/// ```ignore
/// aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE
/// >= current_slot >= aggregate.data.slot
/// ```
///
/// This means that during the current epoch we will always accept an attestation
/// from at least one slot in the previous epoch.
pub const MAX_CACHED_EPOCHS: u64 = 3;
pub type ObservedAttesters<E> = AutoPruningEpochContainer<EpochBitfield, E>;
pub type ObservedSyncContributors<E> =
AutoPruningSlotContainer<SlotSubcommitteeIndex, SyncContributorSlotHashSet<E>, E>;
pub type ObservedAggregators<E> = AutoPruningEpochContainer<EpochHashSet, E>;
pub type ObservedSyncAggregators<E> =
AutoPruningSlotContainer<SlotSubcommitteeIndex, SyncAggregatorSlotHashSet, E>;
#[derive(Debug, PartialEq)]
pub enum Error {
@@ -21,7 +50,11 @@ pub enum Error {
epoch: Epoch,
lowest_permissible_epoch: Epoch,
},
/// We have reached the maximum number of unique `Attestation` that can be observed in a slot.
SlotTooLow {
slot: Slot,
lowest_permissible_slot: Slot,
},
/// We have reached the maximum number of unique items that can be observed in a slot.
/// This is a DoS protection function.
ReachedMaxObservationsPerSlot(usize),
/// The function to obtain a set index failed, this is an internal error.
@@ -49,7 +82,8 @@ pub trait Item {
fn contains(&self, validator_index: usize) -> bool;
}
/// Stores a `BitVec` that represents which validator indices have attested during an epoch.
/// Stores a `BitVec` that represents which validator indices have attested or sent sync committee
/// signatures during an epoch.
pub struct EpochBitfield {
bitfield: BitVec,
}
@@ -100,7 +134,7 @@ impl Item for EpochBitfield {
}
}
/// Stores a `HashSet` of which validator indices have created an aggregate attestation during an
/// Stores a `HashSet` of which validator indices have created an aggregate during an
/// epoch.
pub struct EpochHashSet {
set: HashSet<usize>,
@@ -139,6 +173,84 @@ impl Item for EpochHashSet {
}
}
/// Stores a `HashSet` of which validator indices have created a sync aggregate during a
/// slot.
pub struct SyncContributorSlotHashSet<E> {
set: HashSet<usize>,
phantom: PhantomData<E>,
}
impl<E: EthSpec> Item for SyncContributorSlotHashSet<E> {
fn with_capacity(capacity: usize) -> Self {
Self {
set: HashSet::with_capacity(capacity),
phantom: PhantomData,
}
}
/// Defaults to the `SYNC_SUBCOMMITTEE_SIZE`.
fn default_capacity() -> usize {
E::sync_subcommittee_size()
}
fn len(&self) -> usize {
self.set.len()
}
fn validator_count(&self) -> usize {
self.set.len()
}
/// Inserts the `validator_index` in the set. Returns `true` if the `validator_index` was
/// already in the set.
fn insert(&mut self, validator_index: usize) -> bool {
!self.set.insert(validator_index)
}
/// Returns `true` if the `validator_index` is in the set.
fn contains(&self, validator_index: usize) -> bool {
self.set.contains(&validator_index)
}
}
/// Stores a `HashSet` of which validator indices have created a sync aggregate during a
/// slot.
pub struct SyncAggregatorSlotHashSet {
set: HashSet<usize>,
}
impl Item for SyncAggregatorSlotHashSet {
fn with_capacity(capacity: usize) -> Self {
Self {
set: HashSet::with_capacity(capacity),
}
}
/// Defaults to the `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE`.
fn default_capacity() -> usize {
TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE as usize
}
fn len(&self) -> usize {
self.set.len()
}
fn validator_count(&self) -> usize {
self.set.len()
}
/// Inserts the `validator_index` in the set. Returns `true` if the `validator_index` was
/// already in the set.
fn insert(&mut self, validator_index: usize) -> bool {
!self.set.insert(validator_index)
}
/// Returns `true` if the `validator_index` is in the set.
fn contains(&self, validator_index: usize) -> bool {
self.set.contains(&validator_index)
}
}
/// A container that stores some number of `T` items.
///
/// This container is "auto-pruning" since it gets an idea of the current slot by which
@@ -147,49 +259,46 @@ impl Item for EpochHashSet {
/// attestations with an epoch prior to `a.data.target.epoch - 32` will be cleared from the cache.
///
/// `T` should be set to a `EpochBitfield` or `EpochHashSet`.
pub struct AutoPruningContainer<T, E: EthSpec> {
lowest_permissible_epoch: RwLock<Epoch>,
items: RwLock<HashMap<Epoch, T>>,
pub struct AutoPruningEpochContainer<T, E: EthSpec> {
lowest_permissible_epoch: Epoch,
items: HashMap<Epoch, T>,
_phantom: PhantomData<E>,
}
impl<T, E: EthSpec> Default for AutoPruningContainer<T, E> {
impl<T, E: EthSpec> Default for AutoPruningEpochContainer<T, E> {
fn default() -> Self {
Self {
lowest_permissible_epoch: RwLock::new(Epoch::new(0)),
items: RwLock::new(HashMap::new()),
lowest_permissible_epoch: Epoch::new(0),
items: HashMap::new(),
_phantom: PhantomData,
}
}
}
impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
impl<T: Item, E: EthSpec> AutoPruningEpochContainer<T, E> {
/// Observe that `validator_index` has produced attestation `a`. Returns `Ok(true)` if `a` has
/// previously been observed for `validator_index`.
///
/// ## Errors
///
/// - `validator_index` is higher than `VALIDATOR_REGISTRY_LIMIT`.
/// - `a.data.target.slot` is earlier than `self.earliest_permissible_slot`.
/// - `a.data.target.slot` is earlier than `self.lowest_permissible_slot`.
pub fn observe_validator(
&self,
a: &Attestation<E>,
&mut self,
epoch: Epoch,
validator_index: usize,
) -> Result<bool, Error> {
self.sanitize_request(a, validator_index)?;
let epoch = a.data.target.epoch;
self.sanitize_request(epoch, validator_index)?;
self.prune(epoch);
let mut items = self.items.write();
if let Some(item) = items.get_mut(&epoch) {
if let Some(item) = self.items.get_mut(&epoch) {
Ok(item.insert(validator_index))
} else {
// To avoid re-allocations, try and determine a rough initial capacity for the new item
// by obtaining the mean size of all items in earlier epoch.
let (count, sum) = items
let (count, sum) = self
.items
.iter()
// Only include epochs that are less than the given slot in the average. This should
// generally avoid including recent epochs that are still "filling up".
@@ -201,7 +310,7 @@ impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
let mut item = T::with_capacity(initial_capacity);
item.insert(validator_index);
items.insert(epoch, item);
self.items.insert(epoch, item);
Ok(false)
}
@@ -213,18 +322,17 @@ impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
/// ## Errors
///
/// - `validator_index` is higher than `VALIDATOR_REGISTRY_LIMIT`.
/// - `a.data.target.slot` is earlier than `self.earliest_permissible_slot`.
/// - `a.data.target.slot` is earlier than `self.lowest_permissible_slot`.
pub fn validator_has_been_observed(
&self,
a: &Attestation<E>,
epoch: Epoch,
validator_index: usize,
) -> Result<bool, Error> {
self.sanitize_request(a, validator_index)?;
self.sanitize_request(epoch, validator_index)?;
let exists = self
.items
.read()
.get(&a.data.target.epoch)
.get(&epoch)
.map_or(false, |item| item.contains(validator_index));
Ok(exists)
@@ -233,19 +341,15 @@ impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
/// Returns the number of validators that have been observed at the given `epoch`. Returns
/// `None` if `self` does not have a cache for that epoch.
pub fn observed_validator_count(&self, epoch: Epoch) -> Option<usize> {
self.items
.read()
.get(&epoch)
.map(|item| item.validator_count())
self.items.get(&epoch).map(|item| item.validator_count())
}
fn sanitize_request(&self, a: &Attestation<E>, validator_index: usize) -> Result<(), Error> {
fn sanitize_request(&self, epoch: Epoch, validator_index: usize) -> Result<(), Error> {
if validator_index > E::ValidatorRegistryLimit::to_usize() {
return Err(Error::ValidatorIndexTooHigh(validator_index));
}
let epoch = a.data.target.epoch;
let lowest_permissible_epoch: Epoch = *self.lowest_permissible_epoch.read();
let lowest_permissible_epoch = self.lowest_permissible_epoch;
if epoch < lowest_permissible_epoch {
return Err(Error::EpochTooLow {
epoch,
@@ -258,11 +362,7 @@ impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
/// The maximum number of epochs stored in `self`.
fn max_capacity(&self) -> u64 {
// The current epoch and the previous epoch. This is sufficient whilst
// GOSSIP_CLOCK_DISPARITY is 1/2 a slot or less:
//
// https://github.com/ethereum/eth2.0-specs/pull/1706#issuecomment-610151808
2
MAX_CACHED_EPOCHS
}
/// Updates `self` with the current epoch, removing all attestations that become expired
@@ -270,90 +370,251 @@ impl<T: Item, E: EthSpec> AutoPruningContainer<T, E> {
///
/// Also sets `self.lowest_permissible_epoch` with relation to `current_epoch` and
/// `Self::max_capacity`.
pub fn prune(&self, current_epoch: Epoch) {
// Taking advantage of saturating subtraction on `Slot`.
let lowest_permissible_epoch = current_epoch - (self.max_capacity().saturating_sub(1));
pub fn prune(&mut self, current_epoch: Epoch) {
let lowest_permissible_epoch =
current_epoch.saturating_sub(self.max_capacity().saturating_sub(1));
*self.lowest_permissible_epoch.write() = lowest_permissible_epoch;
self.lowest_permissible_epoch = lowest_permissible_epoch;
self.items
.write()
.retain(|epoch, _item| *epoch >= lowest_permissible_epoch);
}
#[allow(dead_code)]
/// Returns the `lowest_permissible_epoch`. Used in tests.
pub(crate) fn get_lowest_permissible(&self) -> Epoch {
self.lowest_permissible_epoch
}
/// Returns `true` if the given `index` has been stored in `self` at `epoch`.
///
/// This is useful for doppelganger detection.
pub fn index_seen_at_epoch(&self, index: usize, epoch: Epoch) -> bool {
self.items
.get(&epoch)
.map(|item| item.contains(index))
.unwrap_or(false)
}
}
/// A container that stores some number of `V` items.
///
/// This container is "auto-pruning" since it gets an idea of the current slot by which
/// sync contributions are provided to it and prunes old entries based upon that. For example, if
/// `Self::max_capacity == 3` and an attestation with `data.slot` is supplied, then all
/// sync contributions with an epoch prior to `data.slot - 3` will be cleared from the cache.
///
/// `V` should be set to a `SyncAggregatorSlotHashSet` or a `SyncContributorSlotHashSet`.
pub struct AutoPruningSlotContainer<K: SlotData + Eq + Hash, V, E: EthSpec> {
lowest_permissible_slot: Slot,
items: HashMap<K, V>,
_phantom: PhantomData<E>,
}
impl<K: SlotData + Eq + Hash, V, E: EthSpec> Default for AutoPruningSlotContainer<K, V, E> {
fn default() -> Self {
Self {
lowest_permissible_slot: Slot::new(0),
items: HashMap::new(),
_phantom: PhantomData,
}
}
}
impl<K: SlotData + Eq + Hash, V: Item, E: EthSpec> AutoPruningSlotContainer<K, V, E> {
/// Observe that `validator_index` has produced a sync committee message. Returns `Ok(true)` if
/// the sync committee message has previously been observed for `validator_index`.
///
/// ## Errors
///
/// - `validator_index` is higher than `VALIDATOR_REGISTRY_LIMIT`.
/// - `key.slot` is earlier than `self.lowest_permissible_slot`.
pub fn observe_validator(&mut self, key: K, validator_index: usize) -> Result<bool, Error> {
let slot = key.get_slot();
self.sanitize_request(slot, validator_index)?;
self.prune(slot);
if let Some(item) = self.items.get_mut(&key) {
Ok(item.insert(validator_index))
} else {
// To avoid re-allocations, try and determine a rough initial capacity for the new item
// by obtaining the mean size of all items in earlier slot.
let (count, sum) = self
.items
.iter()
// Only include slots that are less than the given slot in the average. This should
// generally avoid including recent slots that are still "filling up".
.filter(|(item_key, _item)| item_key.get_slot() < slot)
.map(|(_, item)| item.len())
.fold((0, 0), |(count, sum), len| (count + 1, sum + len));
let initial_capacity = sum.checked_div(count).unwrap_or_else(V::default_capacity);
let mut item = V::with_capacity(initial_capacity);
item.insert(validator_index);
self.items.insert(key, item);
Ok(false)
}
}
/// Returns `Ok(true)` if the `validator_index` has already produced a conflicting sync committee message.
///
/// ## Errors
///
/// - `validator_index` is higher than `VALIDATOR_REGISTRY_LIMIT`.
/// - `key.slot` is earlier than `self.lowest_permissible_slot`.
pub fn validator_has_been_observed(
&self,
key: K,
validator_index: usize,
) -> Result<bool, Error> {
self.sanitize_request(key.get_slot(), validator_index)?;
let exists = self
.items
.get(&key)
.map_or(false, |item| item.contains(validator_index));
Ok(exists)
}
/// Returns the number of validators that have been observed at the given `slot`. Returns
/// `None` if `self` does not have a cache for that slot.
pub fn observed_validator_count(&self, key: K) -> Option<usize> {
self.items.get(&key).map(|item| item.validator_count())
}
fn sanitize_request(&self, slot: Slot, validator_index: usize) -> Result<(), Error> {
if validator_index > E::ValidatorRegistryLimit::to_usize() {
return Err(Error::ValidatorIndexTooHigh(validator_index));
}
let lowest_permissible_slot = self.lowest_permissible_slot;
if slot < lowest_permissible_slot {
return Err(Error::SlotTooLow {
slot,
lowest_permissible_slot,
});
}
Ok(())
}
/// The maximum number of slots stored in `self`.
fn max_capacity(&self) -> u64 {
// The next, current and previous slots. We require the next slot due to the
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
3
}
/// Updates `self` with the current slot, removing all sync committee messages that become expired
/// relative to `Self::max_capacity`.
///
/// Also sets `self.lowest_permissible_slot` with relation to `current_slot` and
/// `Self::max_capacity`.
pub fn prune(&mut self, current_slot: Slot) {
let lowest_permissible_slot =
current_slot.saturating_sub(self.max_capacity().saturating_sub(1));
self.lowest_permissible_slot = lowest_permissible_slot;
self.items
.retain(|key, _item| key.get_slot() >= lowest_permissible_slot);
}
#[allow(dead_code)]
/// Returns the `lowest_permissible_slot`. Used in tests.
pub(crate) fn get_lowest_permissible(&self) -> Slot {
self.lowest_permissible_slot
}
}
/// This is used to key information about sync committee aggregators. We require the
/// `subcommittee_index` because it is possible that a validator can aggregate for multiple
/// subcommittees in the same slot.
#[derive(Eq, PartialEq, Hash, Clone, Copy, PartialOrd, Ord, Debug)]
pub struct SlotSubcommitteeIndex {
slot: Slot,
subcommittee_index: u64,
}
impl SlotData for SlotSubcommitteeIndex {
fn get_slot(&self) -> Slot {
self.slot
}
}
impl SlotSubcommitteeIndex {
pub fn new(slot: Slot, subcommittee_index: u64) -> Self {
Self {
slot,
subcommittee_index,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_suite {
type E = types::MainnetEthSpec;
macro_rules! test_suite_epoch {
($mod_name: ident, $type: ident) => {
#[cfg(test)]
mod $mod_name {
use super::*;
use types::test_utils::test_random_instance;
type E = types::MainnetEthSpec;
fn single_period_test(store: &mut $type<E>, period: Epoch) {
let validator_indices = [0, 1, 2, 3, 5, 6, 7, 18, 22];
fn get_attestation(epoch: Epoch) -> Attestation<E> {
let mut a: Attestation<E> = test_random_instance();
a.data.target.epoch = epoch;
a
}
fn single_epoch_test(store: &$type<E>, epoch: Epoch) {
let attesters = [0, 1, 2, 3, 5, 6, 7, 18, 22];
let a = &get_attestation(epoch);
for &i in &attesters {
for &i in &validator_indices {
assert_eq!(
store.validator_has_been_observed(a, i),
store.validator_has_been_observed(period, i),
Ok(false),
"should indicate an unknown attestation is unknown"
"should indicate an unknown item is unknown"
);
assert_eq!(
store.observe_validator(a, i),
store.observe_validator(period, i),
Ok(false),
"should observe new attestation"
"should observe new item"
);
}
for &i in &attesters {
for &i in &validator_indices {
assert_eq!(
store.validator_has_been_observed(a, i),
store.validator_has_been_observed(period, i),
Ok(true),
"should indicate a known attestation is known"
"should indicate a known item is known"
);
assert_eq!(
store.observe_validator(a, i),
store.observe_validator(period, i),
Ok(true),
"should acknowledge an existing attestation"
"should acknowledge an existing item"
);
}
}
#[test]
fn single_epoch() {
let store = $type::default();
fn single_period() {
let mut store = $type::default();
single_epoch_test(&store, Epoch::new(0));
single_period_test(&mut store, Epoch::new(0));
assert_eq!(
store.items.read().len(),
1,
"should have a single bitfield stored"
);
assert_eq!(store.items.len(), 1, "should have a single bitfield stored");
}
#[test]
fn mulitple_contiguous_epochs() {
let store = $type::default();
fn mulitple_contiguous_periods() {
let mut store = $type::default();
let max_cap = store.max_capacity();
for i in 0..max_cap * 3 {
let epoch = Epoch::new(i);
let period = Epoch::new(i);
single_epoch_test(&store, epoch);
single_period_test(&mut store, period);
/*
* Ensure that the number of sets is correct.
@@ -361,14 +622,14 @@ mod tests {
if i < max_cap {
assert_eq!(
store.items.read().len(),
store.items.len(),
i as usize + 1,
"should have a {} items stored",
i + 1
);
} else {
assert_eq!(
store.items.read().len(),
store.items.len(),
max_cap as usize,
"should have max_capacity items stored"
);
@@ -378,76 +639,77 @@ mod tests {
* Ensure that all the sets have the expected slots
*/
let mut store_epochs = store
let mut store_periods = store
.items
.read()
.iter()
.map(|(epoch, _set)| *epoch)
.map(|(period, _set)| *period)
.collect::<Vec<_>>();
assert!(
store_epochs.len() <= store.max_capacity() as usize,
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
store_epochs.sort_unstable();
store_periods.sort_unstable();
let expected_epochs = (i.saturating_sub(max_cap - 1)..=i)
let expected_periods = (i.saturating_sub(max_cap - 1)..=i)
.map(Epoch::new)
.collect::<Vec<_>>();
assert_eq!(expected_epochs, store_epochs, "should have expected slots");
assert_eq!(
expected_periods, store_periods,
"should have expected slots"
);
}
}
#[test]
fn mulitple_non_contiguous_epochs() {
let store = $type::default();
fn mulitple_non_contiguous_periods() {
let mut store = $type::default();
let max_cap = store.max_capacity();
let to_skip = vec![1_u64, 3, 4, 5];
let epochs = (0..max_cap * 3)
let periods = (0..max_cap * 3)
.into_iter()
.filter(|i| !to_skip.contains(i))
.collect::<Vec<_>>();
for &i in &epochs {
for &i in &periods {
if to_skip.contains(&i) {
continue;
}
let epoch = Epoch::from(i);
let period = Epoch::from(i);
single_epoch_test(&store, epoch);
single_period_test(&mut store, period);
/*
* Ensure that all the sets have the expected slots
*/
let mut store_epochs = store
let mut store_periods = store
.items
.read()
.iter()
.map(|(epoch, _)| *epoch)
.map(|(period, _)| *period)
.collect::<Vec<_>>();
store_epochs.sort_unstable();
store_periods.sort_unstable();
assert!(
store_epochs.len() <= store.max_capacity() as usize,
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
let lowest = store.lowest_permissible_epoch.read().as_u64();
let highest = epoch.as_u64();
let expected_epochs = (lowest..=highest)
let lowest = store.get_lowest_permissible().as_u64();
let highest = period.as_u64();
let expected_periods = (lowest..=highest)
.filter(|i| !to_skip.contains(i))
.map(Epoch::new)
.collect::<Vec<_>>();
assert_eq!(
expected_epochs,
&store_epochs[..],
expected_periods,
&store_periods[..],
"should have expected epochs"
);
}
@@ -456,6 +718,285 @@ mod tests {
};
}
test_suite!(observed_attesters, ObservedAttesters);
test_suite!(observed_aggregators, ObservedAggregators);
test_suite_epoch!(observed_attesters, ObservedAttesters);
test_suite_epoch!(observed_aggregators, ObservedAggregators);
macro_rules! test_suite_slot {
($mod_name: ident, $type: ident) => {
#[cfg(test)]
mod $mod_name {
use super::*;
fn single_period_test(store: &mut $type<E>, key: SlotSubcommitteeIndex) {
let validator_indices = [0, 1, 2, 3, 5, 6, 7, 18, 22];
for &i in &validator_indices {
assert_eq!(
store.validator_has_been_observed(key, i),
Ok(false),
"should indicate an unknown item is unknown"
);
assert_eq!(
store.observe_validator(key, i),
Ok(false),
"should observe new item"
);
}
for &i in &validator_indices {
assert_eq!(
store.validator_has_been_observed(key, i),
Ok(true),
"should indicate a known item is known"
);
assert_eq!(
store.observe_validator(key, i),
Ok(true),
"should acknowledge an existing item"
);
}
}
#[test]
fn single_period() {
let mut store = $type::default();
single_period_test(&mut store, SlotSubcommitteeIndex::new(Slot::new(0), 0));
assert_eq!(store.items.len(), 1, "should have a single bitfield stored");
}
#[test]
fn single_period_multiple_subcommittees() {
let mut store = $type::default();
single_period_test(&mut store, SlotSubcommitteeIndex::new(Slot::new(0), 0));
single_period_test(&mut store, SlotSubcommitteeIndex::new(Slot::new(0), 1));
single_period_test(&mut store, SlotSubcommitteeIndex::new(Slot::new(0), 2));
assert_eq!(store.items.len(), 3, "should have three hash sets stored");
}
#[test]
fn mulitple_contiguous_periods_same_subcommittee() {
let mut store = $type::default();
let max_cap = store.max_capacity();
for i in 0..max_cap * 3 {
let period = SlotSubcommitteeIndex::new(Slot::new(i), 0);
single_period_test(&mut store, period);
/*
* Ensure that the number of sets is correct.
*/
if i < max_cap {
assert_eq!(
store.items.len(),
i as usize + 1,
"should have a {} items stored",
i + 1
);
} else {
assert_eq!(
store.items.len(),
max_cap as usize,
"should have max_capacity items stored"
);
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_periods = store
.items
.iter()
.map(|(period, _set)| *period)
.collect::<Vec<_>>();
assert!(
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
store_periods.sort_unstable();
let expected_periods = (i.saturating_sub(max_cap - 1)..=i)
.map(|i| SlotSubcommitteeIndex::new(Slot::new(i), 0))
.collect::<Vec<_>>();
assert_eq!(
expected_periods, store_periods,
"should have expected slots"
);
}
}
#[test]
fn mulitple_non_contiguous_periods_same_subcommitte() {
let mut store = $type::default();
let max_cap = store.max_capacity();
let to_skip = vec![1_u64, 3, 4, 5];
let periods = (0..max_cap * 3)
.into_iter()
.filter(|i| !to_skip.contains(i))
.collect::<Vec<_>>();
for &i in &periods {
if to_skip.contains(&i) {
continue;
}
let period = SlotSubcommitteeIndex::new(Slot::from(i), 0);
single_period_test(&mut store, period);
/*
* Ensure that all the sets have the expected slots
*/
let mut store_periods = store
.items
.iter()
.map(|(period, _)| *period)
.collect::<Vec<_>>();
store_periods.sort_unstable();
assert!(
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
let lowest = store.get_lowest_permissible().as_u64();
let highest = period.slot.as_u64();
let expected_periods = (lowest..=highest)
.filter(|i| !to_skip.contains(i))
.map(|i| SlotSubcommitteeIndex::new(Slot::new(i), 0))
.collect::<Vec<_>>();
assert_eq!(
expected_periods,
&store_periods[..],
"should have expected epochs"
);
}
}
#[test]
fn mulitple_contiguous_periods_different_subcommittee() {
let mut store = $type::default();
let max_cap = store.max_capacity();
for i in 0..max_cap * 3 {
let period = SlotSubcommitteeIndex::new(Slot::new(i), i);
single_period_test(&mut store, period);
/*
* Ensure that the number of sets is correct.
*/
if i < max_cap {
assert_eq!(
store.items.len(),
i as usize + 1,
"should have a {} items stored",
i + 1
);
} else {
assert_eq!(
store.items.len(),
max_cap as usize,
"should have max_capacity items stored"
);
}
/*
* Ensure that all the sets have the expected slots
*/
let mut store_periods = store
.items
.iter()
.map(|(period, _set)| *period)
.collect::<Vec<_>>();
assert!(
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
store_periods.sort_unstable();
let expected_periods = (i.saturating_sub(max_cap - 1)..=i)
.map(|i| SlotSubcommitteeIndex::new(Slot::new(i), i))
.collect::<Vec<_>>();
assert_eq!(
expected_periods, store_periods,
"should have expected slots"
);
}
}
#[test]
fn mulitple_non_contiguous_periods_different_subcommitte() {
let mut store = $type::default();
let max_cap = store.max_capacity();
let to_skip = vec![1_u64, 3, 4, 5];
let periods = (0..max_cap * 3)
.into_iter()
.filter(|i| !to_skip.contains(i))
.collect::<Vec<_>>();
for &i in &periods {
if to_skip.contains(&i) {
continue;
}
let period = SlotSubcommitteeIndex::new(Slot::from(i), i);
single_period_test(&mut store, period);
/*
* Ensure that all the sets have the expected slots
*/
let mut store_periods = store
.items
.iter()
.map(|(period, _)| *period)
.collect::<Vec<_>>();
store_periods.sort_unstable();
assert!(
store_periods.len() <= store.max_capacity() as usize,
"store size should not exceed max"
);
let lowest = store.get_lowest_permissible().as_u64();
let highest = period.slot.as_u64();
let expected_periods = (lowest..=highest)
.filter(|i| !to_skip.contains(i))
.map(|i| SlotSubcommitteeIndex::new(Slot::new(i), i))
.collect::<Vec<_>>();
assert_eq!(
expected_periods,
&store_periods[..],
"should have expected epochs"
);
}
}
}
};
}
test_suite_slot!(observed_sync_contributors, ObservedSyncContributors);
test_suite_slot!(observed_sync_aggregators, ObservedSyncAggregators);
}

View File

@@ -1,10 +1,9 @@
//! Provides the `ObservedBlockProducers` struct which allows for rejecting gossip blocks from
//! validators that have already produced a block.
use parking_lot::RwLock;
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
use types::{BeaconBlock, EthSpec, Slot, Unsigned};
use types::{BeaconBlockRef, Epoch, EthSpec, Slot, Unsigned};
#[derive(Debug, PartialEq)]
pub enum Error {
@@ -27,8 +26,8 @@ pub enum Error {
/// active_validator_count`, however in reality that is more like `slots_since_finality *
/// known_distinct_shufflings` which is much smaller.
pub struct ObservedBlockProducers<E: EthSpec> {
finalized_slot: RwLock<Slot>,
items: RwLock<HashMap<Slot, HashSet<u64>>>,
finalized_slot: Slot,
items: HashMap<Slot, HashSet<u64>>,
_phantom: PhantomData<E>,
}
@@ -36,8 +35,8 @@ impl<E: EthSpec> Default for ObservedBlockProducers<E> {
/// Instantiates `Self` with `finalized_slot == 0`.
fn default() -> Self {
Self {
finalized_slot: RwLock::new(Slot::new(0)),
items: RwLock::new(HashMap::new()),
finalized_slot: Slot::new(0),
items: HashMap::new(),
_phantom: PhantomData,
}
}
@@ -53,15 +52,14 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
///
/// - `block.proposer_index` is greater than `VALIDATOR_REGISTRY_LIMIT`.
/// - `block.slot` is equal to or less than the latest pruned `finalized_slot`.
pub fn observe_proposer(&self, block: &BeaconBlock<E>) -> Result<bool, Error> {
pub fn observe_proposer(&mut self, block: BeaconBlockRef<'_, E>) -> Result<bool, Error> {
self.sanitize_block(block)?;
let did_not_exist = self
.items
.write()
.entry(block.slot)
.entry(block.slot())
.or_insert_with(|| HashSet::with_capacity(E::SlotsPerEpoch::to_usize()))
.insert(block.proposer_index);
.insert(block.proposer_index());
Ok(!did_not_exist)
}
@@ -74,28 +72,27 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
///
/// - `block.proposer_index` is greater than `VALIDATOR_REGISTRY_LIMIT`.
/// - `block.slot` is equal to or less than the latest pruned `finalized_slot`.
pub fn proposer_has_been_observed(&self, block: &BeaconBlock<E>) -> Result<bool, Error> {
pub fn proposer_has_been_observed(&self, block: BeaconBlockRef<'_, E>) -> Result<bool, Error> {
self.sanitize_block(block)?;
let exists = self
.items
.read()
.get(&block.slot)
.map_or(false, |set| set.contains(&block.proposer_index));
.get(&block.slot())
.map_or(false, |set| set.contains(&block.proposer_index()));
Ok(exists)
}
/// Returns `Ok(())` if the given `block` is sane.
fn sanitize_block(&self, block: &BeaconBlock<E>) -> Result<(), Error> {
if block.proposer_index > E::ValidatorRegistryLimit::to_u64() {
return Err(Error::ValidatorIndexTooHigh(block.proposer_index));
fn sanitize_block(&self, block: BeaconBlockRef<'_, E>) -> Result<(), Error> {
if block.proposer_index() >= E::ValidatorRegistryLimit::to_u64() {
return Err(Error::ValidatorIndexTooHigh(block.proposer_index()));
}
let finalized_slot = *self.finalized_slot.read();
if finalized_slot > 0 && block.slot <= finalized_slot {
let finalized_slot = self.finalized_slot;
if finalized_slot > 0 && block.slot() <= finalized_slot {
return Err(Error::FinalizedBlock {
slot: block.slot,
slot: block.slot(),
finalized_slot,
});
}
@@ -109,44 +106,51 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
/// equal to or less than `finalized_slot`.
///
/// No-op if `finalized_slot == 0`.
pub fn prune(&self, finalized_slot: Slot) {
pub fn prune(&mut self, finalized_slot: Slot) {
if finalized_slot == 0 {
return;
}
*self.finalized_slot.write() = finalized_slot;
self.items
.write()
.retain(|slot, _set| *slot > finalized_slot);
self.finalized_slot = finalized_slot;
self.items.retain(|slot, _set| *slot > finalized_slot);
}
/// Returns `true` if the given `validator_index` has been stored in `self` at `epoch`.
///
/// This is useful for doppelganger detection.
pub fn index_seen_at_epoch(&self, validator_index: u64, epoch: Epoch) -> bool {
self.items.iter().any(|(slot, producers)| {
slot.epoch(E::slots_per_epoch()) == epoch && producers.contains(&validator_index)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use types::MainnetEthSpec;
use types::{BeaconBlock, MainnetEthSpec};
type E = MainnetEthSpec;
fn get_block(slot: u64, proposer: u64) -> BeaconBlock<E> {
let mut block = BeaconBlock::empty(&E::default_spec());
block.slot = slot.into();
block.proposer_index = proposer;
*block.slot_mut() = slot.into();
*block.proposer_index_mut() = proposer;
block
}
#[test]
fn pruning() {
let cache = ObservedBlockProducers::default();
let mut cache = ObservedBlockProducers::default();
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(cache.items.read().len(), 0, "no slots should be present");
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 0, "no slots should be present");
// Slot 0, proposer 0
let block_a = &get_block(0, 0);
let block_a = get_block(0, 0);
assert_eq!(
cache.observe_proposer(block_a),
cache.observe_proposer(block_a.to_ref()),
Ok(false),
"can observe proposer, indicates proposer unobserved"
);
@@ -155,16 +159,11 @@ mod tests {
* Preconditions.
*/
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(
cache.items.read().len(),
1,
"only one slot should be present"
);
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 1, "only one slot should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(0))
.expect("slot zero should be present")
.len(),
@@ -178,16 +177,11 @@ mod tests {
cache.prune(Slot::new(0));
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(
cache.items.read().len(),
1,
"only one slot should be present"
);
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 1, "only one slot should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(0))
.expect("slot zero should be present")
.len(),
@@ -201,21 +195,21 @@ mod tests {
cache.prune(E::slots_per_epoch().into());
assert_eq!(
*cache.finalized_slot.read(),
cache.finalized_slot,
Slot::from(E::slots_per_epoch()),
"finalized slot is updated"
);
assert_eq!(cache.items.read().len(), 0, "no items left");
assert_eq!(cache.items.len(), 0, "no items left");
/*
* Check that we can't insert a finalized block
*/
// First slot of finalized epoch, proposer 0
let block_b = &get_block(E::slots_per_epoch(), 0);
let block_b = get_block(E::slots_per_epoch(), 0);
assert_eq!(
cache.observe_proposer(block_b),
cache.observe_proposer(block_b.to_ref()),
Err(Error::FinalizedBlock {
slot: E::slots_per_epoch().into(),
finalized_slot: E::slots_per_epoch().into(),
@@ -223,7 +217,7 @@ mod tests {
"cant insert finalized block"
);
assert_eq!(cache.items.read().len(), 0, "block was not added");
assert_eq!(cache.items.len(), 0, "block was not added");
/*
* Check that we _can_ insert a non-finalized block
@@ -232,23 +226,18 @@ mod tests {
let three_epochs = E::slots_per_epoch() * 3;
// First slot of finalized epoch, proposer 0
let block_b = &get_block(three_epochs, 0);
let block_b = get_block(three_epochs, 0);
assert_eq!(
cache.observe_proposer(block_b),
cache.observe_proposer(block_b.to_ref()),
Ok(false),
"can insert non-finalized block"
);
assert_eq!(
cache.items.read().len(),
1,
"only one slot should be present"
);
assert_eq!(cache.items.len(), 1, "only one slot should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(three_epochs))
.expect("the three epochs slot should be present")
.len(),
@@ -264,20 +253,15 @@ mod tests {
cache.prune(two_epochs.into());
assert_eq!(
*cache.finalized_slot.read(),
cache.finalized_slot,
Slot::from(two_epochs),
"finalized slot is updated"
);
assert_eq!(
cache.items.read().len(),
1,
"only one slot should be present"
);
assert_eq!(cache.items.len(), 1, "only one slot should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(three_epochs))
.expect("the three epochs slot should be present")
.len(),
@@ -288,42 +272,37 @@ mod tests {
#[test]
fn simple_observations() {
let cache = ObservedBlockProducers::default();
let mut cache = ObservedBlockProducers::default();
// Slot 0, proposer 0
let block_a = &get_block(0, 0);
let block_a = get_block(0, 0);
assert_eq!(
cache.proposer_has_been_observed(block_a),
cache.proposer_has_been_observed(block_a.to_ref()),
Ok(false),
"no observation in empty cache"
);
assert_eq!(
cache.observe_proposer(block_a),
cache.observe_proposer(block_a.to_ref()),
Ok(false),
"can observe proposer, indicates proposer unobserved"
);
assert_eq!(
cache.proposer_has_been_observed(block_a),
cache.proposer_has_been_observed(block_a.to_ref()),
Ok(true),
"observed block is indicated as true"
);
assert_eq!(
cache.observe_proposer(block_a),
cache.observe_proposer(block_a.to_ref()),
Ok(true),
"observing again indicates true"
);
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(
cache.items.read().len(),
1,
"only one slot should be present"
);
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 1, "only one slot should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(0))
.expect("slot zero should be present")
.len(),
@@ -332,35 +311,34 @@ mod tests {
);
// Slot 1, proposer 0
let block_b = &get_block(1, 0);
let block_b = get_block(1, 0);
assert_eq!(
cache.proposer_has_been_observed(block_b),
cache.proposer_has_been_observed(block_b.to_ref()),
Ok(false),
"no observation for new slot"
);
assert_eq!(
cache.observe_proposer(block_b),
cache.observe_proposer(block_b.to_ref()),
Ok(false),
"can observe proposer for new slot, indicates proposer unobserved"
);
assert_eq!(
cache.proposer_has_been_observed(block_b),
cache.proposer_has_been_observed(block_b.to_ref()),
Ok(true),
"observed block in slot 1 is indicated as true"
);
assert_eq!(
cache.observe_proposer(block_b),
cache.observe_proposer(block_b.to_ref()),
Ok(true),
"observing slot 1 again indicates true"
);
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(cache.items.read().len(), 2, "two slots should be present");
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 2, "two slots should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(0))
.expect("slot zero should be present")
.len(),
@@ -370,7 +348,6 @@ mod tests {
assert_eq!(
cache
.items
.read()
.get(&Slot::new(1))
.expect("slot zero should be present")
.len(),
@@ -379,35 +356,34 @@ mod tests {
);
// Slot 0, proposer 1
let block_c = &get_block(0, 1);
let block_c = get_block(0, 1);
assert_eq!(
cache.proposer_has_been_observed(block_c),
cache.proposer_has_been_observed(block_c.to_ref()),
Ok(false),
"no observation for new proposer"
);
assert_eq!(
cache.observe_proposer(block_c),
cache.observe_proposer(block_c.to_ref()),
Ok(false),
"can observe new proposer, indicates proposer unobserved"
);
assert_eq!(
cache.proposer_has_been_observed(block_c),
cache.proposer_has_been_observed(block_c.to_ref()),
Ok(true),
"observed new proposer block is indicated as true"
);
assert_eq!(
cache.observe_proposer(block_c),
cache.observe_proposer(block_c.to_ref()),
Ok(true),
"observing new proposer again indicates true"
);
assert_eq!(*cache.finalized_slot.read(), 0, "finalized slot is zero");
assert_eq!(cache.items.read().len(), 2, "two slots should be present");
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
assert_eq!(cache.items.len(), 2, "two slots should be present");
assert_eq!(
cache
.items
.read()
.get(&Slot::new(0))
.expect("slot zero should be present")
.len(),
@@ -417,7 +393,6 @@ mod tests {
assert_eq!(
cache
.items
.read()
.get(&Slot::new(1))
.expect("slot zero should be present")
.len(),

View File

@@ -1,9 +1,7 @@
use derivative::Derivative;
use parking_lot::Mutex;
use smallvec::SmallVec;
use state_processing::{SigVerifiedOp, VerifyOperation};
use std::collections::HashSet;
use std::iter::FromIterator;
use std::marker::PhantomData;
use types::{
AttesterSlashing, BeaconState, ChainSpec, EthSpec, ProposerSlashing, SignedVoluntaryExit,
@@ -25,7 +23,7 @@ pub struct ObservedOperations<T: ObservableOperation<E>, E: EthSpec> {
/// For attester slashings, this is the set of all validators who would be slashed by
/// previously seen attester slashings, i.e. those validators in the intersection of
/// `attestation_1.attester_indices` and `attestation_2.attester_indices`.
observed_validator_indices: Mutex<HashSet<u64>>,
observed_validator_indices: HashSet<u64>,
_phantom: PhantomData<(T, E)>,
}
@@ -58,10 +56,18 @@ impl<E: EthSpec> ObservableOperation<E> for ProposerSlashing {
impl<E: EthSpec> ObservableOperation<E> for AttesterSlashing<E> {
fn observed_validators(&self) -> SmallVec<[u64; SMALL_VEC_SIZE]> {
let attestation_1_indices =
HashSet::<u64>::from_iter(self.attestation_1.attesting_indices.iter().copied());
let attestation_2_indices =
HashSet::<u64>::from_iter(self.attestation_2.attesting_indices.iter().copied());
let attestation_1_indices = self
.attestation_1
.attesting_indices
.iter()
.copied()
.collect::<HashSet<u64>>();
let attestation_2_indices = self
.attestation_2
.attesting_indices
.iter()
.copied()
.collect::<HashSet<u64>>();
attestation_1_indices
.intersection(&attestation_2_indices)
.copied()
@@ -71,12 +77,12 @@ impl<E: EthSpec> ObservableOperation<E> for AttesterSlashing<E> {
impl<T: ObservableOperation<E>, E: EthSpec> ObservedOperations<T, E> {
pub fn verify_and_observe(
&self,
&mut self,
op: T,
head_state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<ObservationOutcome<T>, T::Error> {
let mut observed_validator_indices = self.observed_validator_indices.lock();
let observed_validator_indices = &mut self.observed_validator_indices;
let new_validator_indices = op.observed_validators();
// If all of the new validator indices have been previously observed, short-circuit

View File

@@ -4,9 +4,19 @@ use ssz_derive::{Decode, Encode};
use store::{DBColumn, Error as StoreError, StoreItem};
use types::Hash256;
/// Dummy value to use for the canonical head block root, see below.
pub const DUMMY_CANONICAL_HEAD_BLOCK_ROOT: Hash256 = Hash256::repeat_byte(0xff);
#[derive(Clone, Encode, Decode)]
pub struct PersistedBeaconChain {
pub canonical_head_block_root: Hash256,
/// This value is ignored to resolve the issue described here:
///
/// https://github.com/sigp/lighthouse/pull/1639
///
/// Its removal is tracked here:
///
/// https://github.com/sigp/lighthouse/issues/1784
pub _canonical_head_block_root: Hash256,
pub genesis_block_root: Hash256,
pub ssz_head_tracker: SszHeadTracker,
}

View File

@@ -1,25 +1,47 @@
use crate::beacon_fork_choice_store::PersistedForkChoiceStore as ForkChoiceStore;
use fork_choice::PersistedForkChoice as ForkChoice;
use crate::beacon_fork_choice_store::{
PersistedForkChoiceStoreV1, PersistedForkChoiceStoreV7, PersistedForkChoiceStoreV8,
};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use store::{DBColumn, Error, StoreItem};
use superstruct::superstruct;
#[derive(Encode, Decode)]
// If adding a new version you should update this type alias and fix the breakages.
pub type PersistedForkChoice = PersistedForkChoiceV8;
#[superstruct(
variants(V1, V7, V8),
variant_attributes(derive(Encode, Decode)),
no_enum
)]
pub struct PersistedForkChoice {
pub fork_choice: ForkChoice,
pub fork_choice_store: ForkChoiceStore,
pub fork_choice: fork_choice::PersistedForkChoice,
#[superstruct(only(V1))]
pub fork_choice_store: PersistedForkChoiceStoreV1,
#[superstruct(only(V7))]
pub fork_choice_store: PersistedForkChoiceStoreV7,
#[superstruct(only(V8))]
pub fork_choice_store: PersistedForkChoiceStoreV8,
}
impl StoreItem for PersistedForkChoice {
fn db_column() -> DBColumn {
DBColumn::ForkChoice
}
macro_rules! impl_store_item {
($type:ty) => {
impl StoreItem for $type {
fn db_column() -> DBColumn {
DBColumn::ForkChoice
}
fn as_store_bytes(&self) -> Vec<u8> {
self.as_ssz_bytes()
}
fn as_store_bytes(&self) -> Vec<u8> {
self.as_ssz_bytes()
}
fn from_store_bytes(bytes: &[u8]) -> std::result::Result<Self, Error> {
Self::from_ssz_bytes(bytes).map_err(Into::into)
}
fn from_store_bytes(bytes: &[u8]) -> std::result::Result<Self, Error> {
Self::from_ssz_bytes(bytes).map_err(Into::into)
}
}
};
}
impl_store_item!(PersistedForkChoiceV1);
impl_store_item!(PersistedForkChoiceV7);
impl_store_item!(PersistedForkChoiceV8);

View File

@@ -0,0 +1,119 @@
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use itertools::process_results;
use lru::LruCache;
use parking_lot::Mutex;
use slog::debug;
use std::time::Duration;
use types::Hash256;
const BLOCK_ROOT_CACHE_LIMIT: usize = 512;
const LOOKUP_LIMIT: usize = 8;
const METRICS_TIMEOUT: Duration = Duration::from_millis(100);
/// Cache for rejecting attestations to blocks from before finalization.
///
/// It stores a collection of block roots that are pre-finalization and therefore not known to fork
/// choice in `verify_head_block_is_known` during attestation processing.
#[derive(Default)]
pub struct PreFinalizationBlockCache {
cache: Mutex<Cache>,
}
struct Cache {
/// Set of block roots that are known to be pre-finalization.
block_roots: LruCache<Hash256, ()>,
/// Set of block roots that are the subject of single block lookups.
in_progress_lookups: LruCache<Hash256, ()>,
}
impl Default for Cache {
fn default() -> Self {
Cache {
block_roots: LruCache::new(BLOCK_ROOT_CACHE_LIMIT),
in_progress_lookups: LruCache::new(LOOKUP_LIMIT),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Check whether the block with `block_root` is known to be pre-finalization.
///
/// The provided `block_root` is assumed to be unknown to fork choice. I.e., it
/// is not known to be a descendant of the finalized block.
///
/// Return `true` if the attestation to this block should be rejected outright,
/// return `false` if more information is needed from a single-block-lookup.
pub fn is_pre_finalization_block(&self, block_root: Hash256) -> Result<bool, BeaconChainError> {
let mut cache = self.pre_finalization_block_cache.cache.lock();
// Check the cache to see if we already know this pre-finalization block root.
if cache.block_roots.contains(&block_root) {
return Ok(true);
}
// Avoid repeating the disk lookup for blocks that are already subject to a network lookup.
// Sync will take care of de-duplicating the single block lookups.
if cache.in_progress_lookups.contains(&block_root) {
return Ok(false);
}
// 1. Check memory for a recent pre-finalization block.
let is_recent_finalized_block = self.with_head(|head| {
process_results(
head.beacon_state.rev_iter_block_roots(&self.spec),
|mut iter| iter.any(|(_, root)| root == block_root),
)
.map_err(BeaconChainError::BeaconStateError)
})?;
if is_recent_finalized_block {
cache.block_roots.put(block_root, ());
return Ok(true);
}
// 2. Check on disk.
if self.store.get_block(&block_root)?.is_some() {
cache.block_roots.put(block_root, ());
return Ok(true);
}
// 3. Check the network with a single block lookup.
cache.in_progress_lookups.put(block_root, ());
if cache.in_progress_lookups.len() == LOOKUP_LIMIT {
// NOTE: we expect this to occur sometimes if a lot of blocks that we look up fail to be
// imported for reasons other than being pre-finalization. The cache will eventually
// self-repair in this case by replacing old entries with new ones until all the failed
// blocks have been flushed out. Solving this issue isn't as simple as hooking the
// beacon processor's functions that handle failed blocks because we need the block root
// and it has been erased from the `BlockError` by that point.
debug!(
self.log,
"Pre-finalization lookup cache is full";
);
}
Ok(false)
}
pub fn pre_finalization_block_rejected(&self, block_root: Hash256) {
// Future requests can know that this block is invalid without having to look it up again.
let mut cache = self.pre_finalization_block_cache.cache.lock();
cache.in_progress_lookups.pop(&block_root);
cache.block_roots.put(block_root, ());
}
}
impl PreFinalizationBlockCache {
pub fn block_processed(&self, block_root: Hash256) {
// Future requests will find this block in fork choice, so no need to cache it in the
// ongoing lookup cache any longer.
self.cache.lock().in_progress_lookups.pop(&block_root);
}
pub fn contains(&self, block_root: Hash256) -> bool {
self.cache.lock().block_roots.contains(&block_root)
}
pub fn metrics(&self) -> Option<(usize, usize)> {
let cache = self.cache.try_lock_for(METRICS_TIMEOUT)?;
Some((cache.block_roots.len(), cache.in_progress_lookups.len()))
}
}

View File

@@ -0,0 +1,74 @@
use crate::{BeaconChain, BeaconChainTypes};
use slog::{debug, error};
use slot_clock::SlotClock;
use std::sync::Arc;
use task_executor::TaskExecutor;
use tokio::time::sleep;
/// At 12s slot times, the means that the payload preparation routine will run 4s before the start
/// of each slot (`12 / 3 = 4`).
pub const PAYLOAD_PREPARATION_LOOKAHEAD_FACTOR: u32 = 3;
/// Spawns a routine which ensures the EL is provided advance notice of any block producers.
///
/// This routine will run once per slot, at `slot_duration / PAYLOAD_PREPARATION_LOOKAHEAD_FACTOR`
/// before the start of each slot.
///
/// The service will not be started if there is no `execution_layer` on the `chain`.
pub fn start_proposer_prep_service<T: BeaconChainTypes>(
executor: TaskExecutor,
chain: Arc<BeaconChain<T>>,
) {
// Avoid spawning the service if there's no EL, it'll just error anyway.
if chain.execution_layer.is_some() {
executor.clone().spawn(
async move { proposer_prep_service(executor, chain).await },
"proposer_prep_service",
);
}
}
/// Loop indefinitely, calling `BeaconChain::prepare_beacon_proposer_async` at an interval.
async fn proposer_prep_service<T: BeaconChainTypes>(
executor: TaskExecutor,
chain: Arc<BeaconChain<T>>,
) {
let slot_duration = chain.slot_clock.slot_duration();
loop {
match chain.slot_clock.duration_to_next_slot() {
Some(duration) => {
let additional_delay = slot_duration
- chain.slot_clock.slot_duration() / PAYLOAD_PREPARATION_LOOKAHEAD_FACTOR;
sleep(duration + additional_delay).await;
debug!(
chain.log,
"Proposer prepare routine firing";
);
let inner_chain = chain.clone();
executor.spawn(
async move {
if let Err(e) = inner_chain.prepare_beacon_proposer_async().await {
error!(
inner_chain.log,
"Proposer prepare routine failed";
"error" => ?e
);
}
},
"proposer_prep_update",
);
continue;
}
None => {
error!(chain.log, "Failed to read slot clock");
// If we can't read the slot clock, just wait another slot.
sleep(slot_duration).await;
continue;
}
};
}
}

View File

@@ -0,0 +1,212 @@
//! Utilities for managing database schema changes.
mod migration_schema_v6;
mod migration_schema_v7;
mod migration_schema_v8;
mod types;
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY};
use crate::persisted_fork_choice::{PersistedForkChoiceV1, PersistedForkChoiceV7};
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
use operation_pool::{PersistedOperationPool, PersistedOperationPoolBase};
use slog::{warn, Logger};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::fs;
use std::path::Path;
use std::sync::Arc;
use store::config::OnDiskStoreConfig;
use store::hot_cold_store::{HotColdDB, HotColdDBError};
use store::metadata::{SchemaVersion, CONFIG_KEY, CURRENT_SCHEMA_VERSION};
use store::{DBColumn, Error as StoreError, ItemStore, StoreItem};
const PUBKEY_CACHE_FILENAME: &str = "pubkey_cache.ssz";
/// Migrate the database from one schema version to another, applying all requisite mutations.
pub fn migrate_schema<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
datadir: &Path,
from: SchemaVersion,
to: SchemaVersion,
log: Logger,
) -> Result<(), StoreError> {
match (from, to) {
// Migrating from the current schema version to iself is always OK, a no-op.
(_, _) if from == to && to == CURRENT_SCHEMA_VERSION => Ok(()),
// Migrate across multiple versions by recursively migrating one step at a time.
(_, _) if from.as_u64() + 1 < to.as_u64() => {
let next = SchemaVersion(from.as_u64() + 1);
migrate_schema::<T>(db.clone(), datadir, from, next, log.clone())?;
migrate_schema::<T>(db, datadir, next, to, log)
}
// Migration from v0.3.0 to v0.3.x, adding the temporary states column.
// Nothing actually needs to be done, but once a DB uses v2 it shouldn't go back.
(SchemaVersion(1), SchemaVersion(2)) => {
db.store_schema_version(to)?;
Ok(())
}
// Migration for removing the pubkey cache.
(SchemaVersion(2), SchemaVersion(3)) => {
let pk_cache_path = datadir.join(PUBKEY_CACHE_FILENAME);
// Load from file, store to DB.
ValidatorPubkeyCache::<T>::load_from_file(&pk_cache_path)
.and_then(|cache| ValidatorPubkeyCache::convert(cache, db.clone()))
.map_err(|e| StoreError::SchemaMigrationError(format!("{:?}", e)))?;
db.store_schema_version(to)?;
// Delete cache file now that keys are stored in the DB.
fs::remove_file(&pk_cache_path).map_err(|e| {
StoreError::SchemaMigrationError(format!(
"unable to delete {}: {:?}",
pk_cache_path.display(),
e
))
})?;
Ok(())
}
// Migration for adding sync committee contributions to the persisted op pool.
(SchemaVersion(3), SchemaVersion(4)) => {
// Deserialize from what exists in the database using the `PersistedOperationPoolBase`
// variant and convert it to the Altair variant.
let pool_opt = db
.get_item::<PersistedOperationPoolBase<T::EthSpec>>(&OP_POOL_DB_KEY)?
.map(PersistedOperationPool::Base)
.map(PersistedOperationPool::base_to_altair);
if let Some(pool) = pool_opt {
// Store the converted pool under the same key.
db.put_item::<PersistedOperationPool<T::EthSpec>>(&OP_POOL_DB_KEY, &pool)?;
}
db.store_schema_version(to)?;
Ok(())
}
// Migration for weak subjectivity sync support and clean up of `OnDiskStoreConfig` (#1784).
(SchemaVersion(4), SchemaVersion(5)) => {
if let Some(OnDiskStoreConfigV4 {
slots_per_restore_point,
..
}) = db.hot_db.get(&CONFIG_KEY)?
{
let new_config = OnDiskStoreConfig {
slots_per_restore_point,
};
db.hot_db.put(&CONFIG_KEY, &new_config)?;
}
db.store_schema_version(to)?;
Ok(())
}
// Migration for adding `execution_status` field to the fork choice store.
(SchemaVersion(5), SchemaVersion(6)) => {
// Database operations to be done atomically
let mut ops = vec![];
// The top-level `PersistedForkChoice` struct is still V1 but will have its internal
// bytes for the fork choice updated to V6.
let fork_choice_opt = db.get_item::<PersistedForkChoiceV1>(&FORK_CHOICE_DB_KEY)?;
if let Some(mut persisted_fork_choice) = fork_choice_opt {
migration_schema_v6::update_execution_statuses::<T>(&mut persisted_fork_choice)
.map_err(StoreError::SchemaMigrationError)?;
// Store the converted fork choice store under the same key.
ops.push(persisted_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY));
}
db.store_schema_version_atomically(to, ops)?;
Ok(())
}
// 1. Add `proposer_boost_root`.
// 2. Update `justified_epoch` to `justified_checkpoint` and `finalized_epoch` to
// `finalized_checkpoint`.
// 3. This migration also includes a potential update to the justified
// checkpoint in case the fork choice store's justified checkpoint and finalized checkpoint
// combination does not actually exist for any blocks in fork choice. This was possible in
// the consensus spec prior to v1.1.6.
//
// Relevant issues:
//
// https://github.com/sigp/lighthouse/issues/2741
// https://github.com/ethereum/consensus-specs/pull/2727
// https://github.com/ethereum/consensus-specs/pull/2730
(SchemaVersion(6), SchemaVersion(7)) => {
// Database operations to be done atomically
let mut ops = vec![];
let fork_choice_opt = db.get_item::<PersistedForkChoiceV1>(&FORK_CHOICE_DB_KEY)?;
if let Some(persisted_fork_choice_v1) = fork_choice_opt {
// This migrates the `PersistedForkChoiceStore`, adding the `proposer_boost_root` field.
let mut persisted_fork_choice_v7 = persisted_fork_choice_v1.into();
let result = migration_schema_v7::update_fork_choice::<T>(
&mut persisted_fork_choice_v7,
db.clone(),
);
// Fall back to re-initializing fork choice from an anchor state if necessary.
if let Err(e) = result {
warn!(log, "Unable to migrate to database schema 7, re-initializing fork choice"; "error" => ?e);
migration_schema_v7::update_with_reinitialized_fork_choice::<T>(
&mut persisted_fork_choice_v7,
db.clone(),
)
.map_err(StoreError::SchemaMigrationError)?;
}
// Store the converted fork choice store under the same key.
ops.push(persisted_fork_choice_v7.as_kv_store_op(FORK_CHOICE_DB_KEY));
}
db.store_schema_version_atomically(to, ops)?;
Ok(())
}
// Migration to add an `epoch` key to the fork choice's balances cache.
(SchemaVersion(7), SchemaVersion(8)) => {
let mut ops = vec![];
let fork_choice_opt = db.get_item::<PersistedForkChoiceV7>(&FORK_CHOICE_DB_KEY)?;
if let Some(fork_choice) = fork_choice_opt {
let updated_fork_choice =
migration_schema_v8::update_fork_choice::<T>(fork_choice, db.clone())?;
ops.push(updated_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY));
}
db.store_schema_version_atomically(to, ops)?;
Ok(())
}
// Anything else is an error.
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
target_version: to,
current_version: from,
}
.into()),
}
}
// Store config used in v4 schema and earlier.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct OnDiskStoreConfigV4 {
pub slots_per_restore_point: u64,
pub _block_cache_size: usize,
}
impl StoreItem for OnDiskStoreConfigV4 {
fn db_column() -> DBColumn {
DBColumn::BeaconMeta
}
fn as_store_bytes(&self) -> Vec<u8> {
self.as_ssz_bytes()
}
fn from_store_bytes(bytes: &[u8]) -> Result<Self, StoreError> {
Ok(Self::from_ssz_bytes(bytes)?)
}
}

View File

@@ -0,0 +1,74 @@
Database Schema Migrations
====
This document is an attempt to record some best practices and design conventions for applying
database schema migrations within Lighthouse.
## General Structure
If you make a breaking change to an on-disk data structure you need to increment the
`SCHEMA_VERSION` in `beacon_node/store/src/metadata.rs` and add a migration from the previous
version to the new version.
The entry-point for database migrations is in `schema_change.rs`, _not_ `migrate.rs` (which deals
with finalization). Supporting code for a specific migration may be added in
`schema_change/migration_schema_vX.rs`, where `X` is the version being migrated _to_.
## Combining Schema Changes
Schema changes may be combined if they are part of the same pull request to
`unstable`. Once a schema version is defined in `unstable` we should not apply changes to it
without incrementing the version. This prevents conflicts between versions that appear to be the
same. This allows us to deploy `unstable` to nodes without having to worry about needing to resync
because of a sneaky schema change.
Changing the on-disk structure for a version _before_ it is merged to `unstable` is OK. You will
just have to handle manually resyncing any test nodes (use checkpoint sync).
## Naming Conventions
Prefer to name versions of structs by _the version at which the change was introduced_. For example
if you add a field to `Foo` in v9, call the previous version `FooV1` (assuming this is `Foo`'s first
migration) and write a schema change that migrates from `FooV1` to `FooV9`.
Prefer to use explicit version names in `schema_change.rs` and the `schema_change` module. To
interface with the outside either:
1. Define a type alias to the latest version, e.g. `pub type Foo = FooV9`, or
2. Define a mapping from the latest version to the version used elsewhere, e.g.
```rust
impl From<FooV9> for Foo {}
```
Avoid names like:
* `LegacyFoo`
* `OldFoo`
* `FooWithoutX`
## First-version vs Last-version
Previously the schema migration code would name types by the _last_ version at which they were
valid. For example if `Foo` changed in `V9` then we would name the two variants `FooV8` and `FooV9`.
The problem with this scheme is that if `Foo` changes again in the future at say v12 then `FooV9` would
need to be renamed to `FooV11`, which is annoying. Using the _first_ valid version as described
above does not have this issue.
## Using SuperStruct
If possible, consider using [`superstruct`](https://crates.io/crates/superstruct) to handle data
structure changes between versions.
* Use `superstruct(no_enum)` to avoid generating an unnecessary top-level enum.
## Example
A field is added to `Foo` in v9, and there are two variants: `FooV1` and `FooV9`. There is a
migration from `FooV1` to `FooV9`. `Foo` is aliased to `FooV9`.
Some time later another field is added to `Foo` in v12. A new `FooV12` is created, along with a
migration from `FooV9` to `FooV12`. The primary `Foo` type gets re-aliased to `FooV12`. The previous
migration from V1 to V9 shouldn't break because the schema migration refers to `FooV9` explicitly
rather than `Foo`. Due to the re-aliasing (or re-mapping) the compiler will check every usage
of `Foo` to make sure that it still makes sense with `FooV12`.

View File

@@ -0,0 +1,28 @@
///! These functions and structs are only relevant to the database migration from schema 5 to 6.
use crate::persisted_fork_choice::PersistedForkChoiceV1;
use crate::schema_change::types::{SszContainerV1, SszContainerV6};
use crate::BeaconChainTypes;
use ssz::four_byte_option_impl;
use ssz::{Decode, Encode};
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
// selector.
four_byte_option_impl!(four_byte_option_usize, usize);
pub(crate) fn update_execution_statuses<T: BeaconChainTypes>(
persisted_fork_choice: &mut PersistedForkChoiceV1,
) -> Result<(), String> {
let ssz_container_v1 =
SszContainerV1::from_ssz_bytes(&persisted_fork_choice.fork_choice.proto_array_bytes)
.map_err(|e| {
format!(
"Failed to decode ProtoArrayForkChoice during schema migration: {:?}",
e
)
})?;
let ssz_container_v6: SszContainerV6 = ssz_container_v1.into();
persisted_fork_choice.fork_choice.proto_array_bytes = ssz_container_v6.as_ssz_bytes();
Ok(())
}

View File

@@ -0,0 +1,327 @@
///! These functions and structs are only relevant to the database migration from schema 6 to 7.
use crate::beacon_chain::BeaconChainTypes;
use crate::beacon_fork_choice_store::{PersistedForkChoiceStoreV1, PersistedForkChoiceStoreV7};
use crate::persisted_fork_choice::{PersistedForkChoiceV1, PersistedForkChoiceV7};
use crate::schema_change::types::{ProtoNodeV6, SszContainerV6, SszContainerV7};
use crate::types::{Checkpoint, Epoch, Hash256};
use crate::types::{EthSpec, Slot};
use crate::{BeaconForkChoiceStore, BeaconSnapshot};
use fork_choice::ForkChoice;
use proto_array::{core::ProtoNode, core::SszContainer, ProtoArrayForkChoice};
use ssz::four_byte_option_impl;
use ssz::{Decode, Encode};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use store::hot_cold_store::HotColdDB;
use store::iter::BlockRootsIterator;
use store::Error as StoreError;
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
// selector.
four_byte_option_impl!(four_byte_option_usize, usize);
/// This method is used to re-initialize fork choice from the finalized state in case we hit an
/// error during this migration.
pub(crate) fn update_with_reinitialized_fork_choice<T: BeaconChainTypes>(
persisted_fork_choice: &mut PersistedForkChoiceV7,
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
) -> Result<(), String> {
let anchor_block_root = persisted_fork_choice
.fork_choice_store
.finalized_checkpoint
.root;
let anchor_block = db
.get_block(&anchor_block_root)
.map_err(|e| format!("{:?}", e))?
.ok_or_else(|| "Missing anchor beacon block".to_string())?;
let anchor_state = db
.get_state(&anchor_block.state_root(), Some(anchor_block.slot()))
.map_err(|e| format!("{:?}", e))?
.ok_or_else(|| "Missing anchor beacon state".to_string())?;
let snapshot = BeaconSnapshot {
beacon_block: anchor_block,
beacon_block_root: anchor_block_root,
beacon_state: anchor_state,
};
let store = BeaconForkChoiceStore::get_forkchoice_store(db, &snapshot);
let fork_choice = ForkChoice::from_anchor(
store,
anchor_block_root,
&snapshot.beacon_block,
&snapshot.beacon_state,
)
.map_err(|e| format!("{:?}", e))?;
persisted_fork_choice.fork_choice = fork_choice.to_persisted();
Ok(())
}
pub(crate) fn update_fork_choice<T: BeaconChainTypes>(
persisted_fork_choice: &mut PersistedForkChoiceV7,
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
) -> Result<(), StoreError> {
// `PersistedForkChoice` stores the `ProtoArray` as a `Vec<u8>`. Deserialize these
// bytes assuming the legacy struct, and transform them to the new struct before
// re-serializing.
let ssz_container_v6 =
SszContainerV6::from_ssz_bytes(&persisted_fork_choice.fork_choice.proto_array_bytes)
.map_err(|e| {
StoreError::SchemaMigrationError(format!(
"Failed to decode ProtoArrayForkChoice during schema migration: {:?}",
e
))
})?;
// Clone the V6 proto nodes in order to maintain information about `node.justified_epoch`
// and `node.finalized_epoch`.
let nodes_v6 = ssz_container_v6.nodes.clone();
let justified_checkpoint = persisted_fork_choice.fork_choice_store.justified_checkpoint;
let finalized_checkpoint = persisted_fork_choice.fork_choice_store.finalized_checkpoint;
// These transformations instantiate `node.justified_checkpoint` and `node.finalized_checkpoint`
// to `None`.
let ssz_container_v7: SszContainerV7 =
ssz_container_v6.into_ssz_container_v7(justified_checkpoint, finalized_checkpoint);
let ssz_container: SszContainer = ssz_container_v7.into();
let mut fork_choice: ProtoArrayForkChoice = ssz_container.into();
update_checkpoints::<T>(finalized_checkpoint.root, &nodes_v6, &mut fork_choice, db)
.map_err(StoreError::SchemaMigrationError)?;
// Update the justified checkpoint in the store in case we have a discrepancy
// between the store and the proto array nodes.
update_store_justified_checkpoint(persisted_fork_choice, &mut fork_choice)
.map_err(StoreError::SchemaMigrationError)?;
Ok(())
}
struct HeadInfo {
index: usize,
root: Hash256,
slot: Slot,
}
fn update_checkpoints<T: BeaconChainTypes>(
finalized_root: Hash256,
nodes_v6: &[ProtoNodeV6],
fork_choice: &mut ProtoArrayForkChoice,
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
) -> Result<(), String> {
let heads = find_finalized_descendant_heads(finalized_root, fork_choice);
// For each head, first gather all epochs we will need to find justified or finalized roots for.
for head in heads {
// `relevant_epochs` are epochs for which we will need to find the root at the start slot.
// We don't need to worry about whether the are finalized or justified epochs.
let mut relevant_epochs = HashSet::new();
let relevant_epoch_finder = |index, _: &mut ProtoNode| {
let (justified_epoch, finalized_epoch) = nodes_v6
.get(index)
.map(|node: &ProtoNodeV6| (node.justified_epoch, node.finalized_epoch))
.ok_or_else(|| "Index not found in legacy proto nodes".to_string())?;
relevant_epochs.insert(justified_epoch);
relevant_epochs.insert(finalized_epoch);
Ok(())
};
apply_to_chain_of_ancestors(
finalized_root,
head.index,
fork_choice,
relevant_epoch_finder,
)?;
// find the block roots associated with each relevant epoch.
let roots_by_epoch =
map_relevant_epochs_to_roots::<T>(head.root, head.slot, relevant_epochs, db.clone())?;
// Apply this mutator to the chain of descendants from this head, adding justified
// and finalized checkpoints for each.
let node_mutator = |index, node: &mut ProtoNode| {
let (justified_epoch, finalized_epoch) = nodes_v6
.get(index)
.map(|node: &ProtoNodeV6| (node.justified_epoch, node.finalized_epoch))
.ok_or_else(|| "Index not found in legacy proto nodes".to_string())?;
// Update the checkpoints only if they haven't already been populated.
if node.justified_checkpoint.is_none() {
let justified_checkpoint =
roots_by_epoch
.get(&justified_epoch)
.map(|&root| Checkpoint {
epoch: justified_epoch,
root,
});
node.justified_checkpoint = justified_checkpoint;
}
if node.finalized_checkpoint.is_none() {
let finalized_checkpoint =
roots_by_epoch
.get(&finalized_epoch)
.map(|&root| Checkpoint {
epoch: finalized_epoch,
root,
});
node.finalized_checkpoint = finalized_checkpoint;
}
Ok(())
};
apply_to_chain_of_ancestors(finalized_root, head.index, fork_choice, node_mutator)?;
}
Ok(())
}
/// Coverts the given `HashSet<Epoch>` to a `Vec<Epoch>` then reverse sorts by `Epoch`. Next, a
/// single `BlockRootsIterator` is created which is used to iterate backwards from the given
/// `head_root` and `head_slot`, finding the block root at the start slot of each epoch.
fn map_relevant_epochs_to_roots<T: BeaconChainTypes>(
head_root: Hash256,
head_slot: Slot,
epochs: HashSet<Epoch>,
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
) -> Result<HashMap<Epoch, Hash256>, String> {
// Convert the `HashSet` to a `Vec` and reverse sort the epochs.
let mut relevant_epochs = epochs.into_iter().collect::<Vec<_>>();
relevant_epochs.sort_unstable_by(|a, b| b.cmp(a));
// Iterate backwards from the given `head_root` and `head_slot` and find the block root at each epoch.
let mut iter = std::iter::once(Ok((head_root, head_slot)))
.chain(BlockRootsIterator::from_block(&db, head_root).map_err(|e| format!("{:?}", e))?);
let mut roots_by_epoch = HashMap::new();
for epoch in relevant_epochs {
let start_slot = epoch.start_slot(T::EthSpec::slots_per_epoch());
let root = iter
.find_map(|next| match next {
Ok((root, slot)) => (slot == start_slot).then(|| Ok(root)),
Err(e) => Some(Err(format!("{:?}", e))),
})
.transpose()?
.ok_or_else(|| "Justified root not found".to_string())?;
roots_by_epoch.insert(epoch, root);
}
Ok(roots_by_epoch)
}
/// Applies a mutator to every node in a chain, starting from the node at the given
/// `head_index` and iterating through ancestors until the `finalized_root` is reached.
fn apply_to_chain_of_ancestors<F>(
finalized_root: Hash256,
head_index: usize,
fork_choice: &mut ProtoArrayForkChoice,
mut node_mutator: F,
) -> Result<(), String>
where
F: FnMut(usize, &mut ProtoNode) -> Result<(), String>,
{
let head = fork_choice
.core_proto_array_mut()
.nodes
.get_mut(head_index)
.ok_or_else(|| "Head index not found in proto nodes".to_string())?;
node_mutator(head_index, head)?;
let mut parent_index_opt = head.parent;
let mut parent_opt =
parent_index_opt.and_then(|index| fork_choice.core_proto_array_mut().nodes.get_mut(index));
// Iterate backwards through all parents until there is no reference to a parent or we reach
// the `finalized_root` node.
while let (Some(parent), Some(parent_index)) = (parent_opt, parent_index_opt) {
node_mutator(parent_index, parent)?;
// Break out of this while loop *after* the `node_mutator` has been applied to the finalized
// node.
if parent.root == finalized_root {
break;
}
// Update parent values
parent_index_opt = parent.parent;
parent_opt = parent_index_opt
.and_then(|index| fork_choice.core_proto_array_mut().nodes.get_mut(index));
}
Ok(())
}
/// Finds all heads by finding all nodes in the proto array that are not referenced as parents. Then
/// checks that these nodes are descendants of the finalized root in order to determine if they are
/// relevant.
fn find_finalized_descendant_heads(
finalized_root: Hash256,
fork_choice: &ProtoArrayForkChoice,
) -> Vec<HeadInfo> {
let nodes_referenced_as_parents: HashSet<usize> = fork_choice
.core_proto_array()
.nodes
.iter()
.filter_map(|node| node.parent)
.collect::<HashSet<_>>();
fork_choice
.core_proto_array()
.nodes
.iter()
.enumerate()
.filter_map(|(index, node)| {
(!nodes_referenced_as_parents.contains(&index)
&& fork_choice.is_descendant(finalized_root, node.root))
.then(|| HeadInfo {
index,
root: node.root,
slot: node.slot,
})
})
.collect::<Vec<_>>()
}
fn update_store_justified_checkpoint(
persisted_fork_choice: &mut PersistedForkChoiceV7,
fork_choice: &mut ProtoArrayForkChoice,
) -> Result<(), String> {
let justified_checkpoint = fork_choice
.core_proto_array()
.nodes
.iter()
.filter_map(|node| {
(node.finalized_checkpoint
== Some(persisted_fork_choice.fork_choice_store.finalized_checkpoint))
.then(|| node.justified_checkpoint)
.flatten()
})
.max_by_key(|justified_checkpoint| justified_checkpoint.epoch)
.ok_or("Proto node with current finalized checkpoint not found")?;
fork_choice.core_proto_array_mut().justified_checkpoint = justified_checkpoint;
persisted_fork_choice.fork_choice.proto_array_bytes = fork_choice.as_bytes();
persisted_fork_choice.fork_choice_store.justified_checkpoint = justified_checkpoint;
Ok(())
}
// Add a zero `proposer_boost_root` when migrating from V1-6 to V7.
impl From<PersistedForkChoiceStoreV1> for PersistedForkChoiceStoreV7 {
fn from(other: PersistedForkChoiceStoreV1) -> Self {
Self {
balances_cache: other.balances_cache,
time: other.time,
finalized_checkpoint: other.finalized_checkpoint,
justified_checkpoint: other.justified_checkpoint,
justified_balances: other.justified_balances,
best_justified_checkpoint: other.best_justified_checkpoint,
proposer_boost_root: Hash256::zero(),
}
}
}
impl From<PersistedForkChoiceV1> for PersistedForkChoiceV7 {
fn from(other: PersistedForkChoiceV1) -> Self {
Self {
fork_choice: other.fork_choice,
fork_choice_store: other.fork_choice_store.into(),
}
}
}

View File

@@ -0,0 +1,50 @@
use crate::beacon_chain::BeaconChainTypes;
use crate::beacon_fork_choice_store::{
BalancesCacheV8, CacheItemV8, PersistedForkChoiceStoreV7, PersistedForkChoiceStoreV8,
};
use crate::persisted_fork_choice::{PersistedForkChoiceV7, PersistedForkChoiceV8};
use std::sync::Arc;
use store::{Error as StoreError, HotColdDB};
use types::EthSpec;
pub fn update_fork_choice<T: BeaconChainTypes>(
fork_choice: PersistedForkChoiceV7,
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
) -> Result<PersistedForkChoiceV8, StoreError> {
let PersistedForkChoiceStoreV7 {
balances_cache,
time,
finalized_checkpoint,
justified_checkpoint,
justified_balances,
best_justified_checkpoint,
proposer_boost_root,
} = fork_choice.fork_choice_store;
let mut fork_choice_store = PersistedForkChoiceStoreV8 {
balances_cache: BalancesCacheV8::default(),
time,
finalized_checkpoint,
justified_checkpoint,
justified_balances,
best_justified_checkpoint,
proposer_boost_root,
};
// Add epochs to the balances cache. It's safe to just use the block's epoch because
// before schema v8 the cache would always miss on skipped slots.
for item in balances_cache.items {
// Drop any blocks that aren't found, they're presumably too old and this is only a cache.
if let Some(block) = db.get_block(&item.block_root)? {
fork_choice_store.balances_cache.items.push(CacheItemV8 {
block_root: item.block_root,
epoch: block.slot().epoch(T::EthSpec::slots_per_epoch()),
balances: item.balances,
});
}
}
Ok(PersistedForkChoiceV8 {
fork_choice: fork_choice.fork_choice,
fork_choice_store,
})
}

View File

@@ -0,0 +1,192 @@
use crate::types::{AttestationShufflingId, Checkpoint, Epoch, Hash256, Slot};
use proto_array::core::{ProposerBoost, ProtoNode, SszContainer, VoteTracker};
use proto_array::ExecutionStatus;
use ssz::four_byte_option_impl;
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use superstruct::superstruct;
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
// selector.
four_byte_option_impl!(four_byte_option_usize, usize);
four_byte_option_impl!(four_byte_option_checkpoint, Checkpoint);
#[superstruct(
variants(V1, V6, V7),
variant_attributes(derive(Clone, PartialEq, Debug, Encode, Decode)),
no_enum
)]
pub struct ProtoNode {
pub slot: Slot,
pub state_root: Hash256,
pub target_root: Hash256,
pub current_epoch_shuffling_id: AttestationShufflingId,
pub next_epoch_shuffling_id: AttestationShufflingId,
pub root: Hash256,
#[ssz(with = "four_byte_option_usize")]
pub parent: Option<usize>,
#[superstruct(only(V1, V6))]
pub justified_epoch: Epoch,
#[superstruct(only(V1, V6))]
pub finalized_epoch: Epoch,
#[ssz(with = "four_byte_option_checkpoint")]
#[superstruct(only(V7))]
pub justified_checkpoint: Option<Checkpoint>,
#[ssz(with = "four_byte_option_checkpoint")]
#[superstruct(only(V7))]
pub finalized_checkpoint: Option<Checkpoint>,
pub weight: u64,
#[ssz(with = "four_byte_option_usize")]
pub best_child: Option<usize>,
#[ssz(with = "four_byte_option_usize")]
pub best_descendant: Option<usize>,
#[superstruct(only(V6, V7))]
pub execution_status: ExecutionStatus,
}
impl Into<ProtoNodeV6> for ProtoNodeV1 {
fn into(self) -> ProtoNodeV6 {
ProtoNodeV6 {
slot: self.slot,
state_root: self.state_root,
target_root: self.target_root,
current_epoch_shuffling_id: self.current_epoch_shuffling_id,
next_epoch_shuffling_id: self.next_epoch_shuffling_id,
root: self.root,
parent: self.parent,
justified_epoch: self.justified_epoch,
finalized_epoch: self.finalized_epoch,
weight: self.weight,
best_child: self.best_child,
best_descendant: self.best_descendant,
// We set the following execution value as if the block is a pre-merge-fork block. This
// is safe as long as we never import a merge block with the old version of proto-array.
// This will be safe since we can't actually process merge blocks until we've made this
// change to fork choice.
execution_status: ExecutionStatus::irrelevant(),
}
}
}
impl Into<ProtoNodeV7> for ProtoNodeV6 {
fn into(self) -> ProtoNodeV7 {
ProtoNodeV7 {
slot: self.slot,
state_root: self.state_root,
target_root: self.target_root,
current_epoch_shuffling_id: self.current_epoch_shuffling_id,
next_epoch_shuffling_id: self.next_epoch_shuffling_id,
root: self.root,
parent: self.parent,
justified_checkpoint: None,
finalized_checkpoint: None,
weight: self.weight,
best_child: self.best_child,
best_descendant: self.best_descendant,
execution_status: self.execution_status,
}
}
}
impl Into<ProtoNode> for ProtoNodeV7 {
fn into(self) -> ProtoNode {
ProtoNode {
slot: self.slot,
state_root: self.state_root,
target_root: self.target_root,
current_epoch_shuffling_id: self.current_epoch_shuffling_id,
next_epoch_shuffling_id: self.next_epoch_shuffling_id,
root: self.root,
parent: self.parent,
justified_checkpoint: self.justified_checkpoint,
finalized_checkpoint: self.finalized_checkpoint,
weight: self.weight,
best_child: self.best_child,
best_descendant: self.best_descendant,
execution_status: self.execution_status,
}
}
}
#[superstruct(
variants(V1, V6, V7),
variant_attributes(derive(Encode, Decode)),
no_enum
)]
#[derive(Encode, Decode)]
pub struct SszContainer {
pub votes: Vec<VoteTracker>,
pub balances: Vec<u64>,
pub prune_threshold: usize,
#[superstruct(only(V1, V6))]
pub justified_epoch: Epoch,
#[superstruct(only(V1, V6))]
pub finalized_epoch: Epoch,
#[superstruct(only(V7))]
pub justified_checkpoint: Checkpoint,
#[superstruct(only(V7))]
pub finalized_checkpoint: Checkpoint,
#[superstruct(only(V1))]
pub nodes: Vec<ProtoNodeV1>,
#[superstruct(only(V6))]
pub nodes: Vec<ProtoNodeV6>,
#[superstruct(only(V7))]
pub nodes: Vec<ProtoNodeV7>,
pub indices: Vec<(Hash256, usize)>,
#[superstruct(only(V7))]
pub previous_proposer_boost: ProposerBoost,
}
impl Into<SszContainerV6> for SszContainerV1 {
fn into(self) -> SszContainerV6 {
let nodes = self.nodes.into_iter().map(Into::into).collect();
SszContainerV6 {
votes: self.votes,
balances: self.balances,
prune_threshold: self.prune_threshold,
justified_epoch: self.justified_epoch,
finalized_epoch: self.finalized_epoch,
nodes,
indices: self.indices,
}
}
}
impl SszContainerV6 {
pub(crate) fn into_ssz_container_v7(
self,
justified_checkpoint: Checkpoint,
finalized_checkpoint: Checkpoint,
) -> SszContainerV7 {
let nodes = self.nodes.into_iter().map(Into::into).collect();
SszContainerV7 {
votes: self.votes,
balances: self.balances,
prune_threshold: self.prune_threshold,
justified_checkpoint,
finalized_checkpoint,
nodes,
indices: self.indices,
previous_proposer_boost: ProposerBoost::default(),
}
}
}
impl Into<SszContainer> for SszContainerV7 {
fn into(self) -> SszContainer {
let nodes = self.nodes.into_iter().map(Into::into).collect();
SszContainer {
votes: self.votes,
balances: self.balances,
prune_threshold: self.prune_threshold,
justified_checkpoint: self.justified_checkpoint,
finalized_checkpoint: self.finalized_checkpoint,
nodes,
indices: self.indices,
previous_proposer_boost: self.previous_proposer_boost,
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::metrics;
use lru::LruCache;
use types::{beacon_state::CommitteeCache, Epoch, Hash256};
use types::{beacon_state::CommitteeCache, AttestationShufflingId, Epoch, Hash256};
/// The size of the LRU cache that stores committee caches for quicker verification.
///
@@ -14,7 +14,7 @@ const CACHE_SIZE: usize = 16;
/// It has been named `ShufflingCache` because `CommitteeCacheCache` is a bit weird and looks like
/// a find/replace error.
pub struct ShufflingCache {
cache: LruCache<(Epoch, Hash256), CommitteeCache>,
cache: LruCache<AttestationShufflingId, CommitteeCache>,
}
impl ShufflingCache {
@@ -24,8 +24,8 @@ impl ShufflingCache {
}
}
pub fn get(&mut self, epoch: Epoch, root: Hash256) -> Option<&CommitteeCache> {
let opt = self.cache.get(&(epoch, root));
pub fn get(&mut self, key: &AttestationShufflingId) -> Option<&CommitteeCache> {
let opt = self.cache.get(key);
if opt.is_some() {
metrics::inc_counter(&metrics::SHUFFLING_CACHE_HITS);
@@ -36,11 +36,40 @@ impl ShufflingCache {
opt
}
pub fn insert(&mut self, epoch: Epoch, root: Hash256, committee_cache: &CommitteeCache) {
let key = (epoch, root);
pub fn contains(&self, key: &AttestationShufflingId) -> bool {
self.cache.contains(key)
}
pub fn insert(&mut self, key: AttestationShufflingId, committee_cache: &CommitteeCache) {
if !self.cache.contains(&key) {
self.cache.put(key, committee_cache.clone());
}
}
}
/// Contains the shuffling IDs for a beacon block.
pub struct BlockShufflingIds {
pub current: AttestationShufflingId,
pub next: AttestationShufflingId,
pub block_root: Hash256,
}
impl BlockShufflingIds {
/// Returns the shuffling ID for the given epoch.
///
/// Returns `None` if `epoch` is prior to `self.current.shuffling_epoch`.
pub fn id_for_epoch(&self, epoch: Epoch) -> Option<AttestationShufflingId> {
if epoch == self.current.shuffling_epoch {
Some(self.current.clone())
} else if epoch == self.next.shuffling_epoch {
Some(self.next.clone())
} else if epoch > self.next.shuffling_epoch {
Some(AttestationShufflingId::from_components(
epoch,
self.block_root,
))
} else {
None
}
}
}

View File

@@ -1,10 +1,140 @@
use crate::BeaconSnapshot;
use itertools::process_results;
use std::cmp;
use types::{Epoch, EthSpec, Hash256};
use std::time::Duration;
use types::{
beacon_state::CloneConfig, BeaconState, ChainSpec, Epoch, EthSpec, Hash256, SignedBeaconBlock,
Slot,
};
/// The default size of the cache.
pub const DEFAULT_SNAPSHOT_CACHE_SIZE: usize = 4;
/// The minimum block delay to clone the state in the cache instead of removing it.
/// This helps keep block processing fast during re-orgs from late blocks.
const MINIMUM_BLOCK_DELAY_FOR_CLONE: Duration = Duration::from_secs(6);
/// This snapshot is to be used for verifying a child of `self.beacon_block`.
#[derive(Debug)]
pub struct PreProcessingSnapshot<T: EthSpec> {
/// This state is equivalent to the `self.beacon_block.state_root()` state that has been
/// advanced forward one slot using `per_slot_processing`. This state is "primed and ready" for
/// the application of another block.
pub pre_state: BeaconState<T>,
/// This value is only set to `Some` if the `pre_state` was *not* advanced forward.
pub beacon_state_root: Option<Hash256>,
pub beacon_block: SignedBeaconBlock<T>,
pub beacon_block_root: Hash256,
}
impl<T: EthSpec> From<BeaconSnapshot<T>> for PreProcessingSnapshot<T> {
fn from(snapshot: BeaconSnapshot<T>) -> Self {
let beacon_state_root = Some(snapshot.beacon_state_root());
Self {
pre_state: snapshot.beacon_state,
beacon_state_root,
beacon_block: snapshot.beacon_block,
beacon_block_root: snapshot.beacon_block_root,
}
}
}
impl<T: EthSpec> CacheItem<T> {
pub fn new_without_pre_state(snapshot: BeaconSnapshot<T>) -> Self {
Self {
beacon_block: snapshot.beacon_block,
beacon_block_root: snapshot.beacon_block_root,
beacon_state: snapshot.beacon_state,
pre_state: None,
}
}
fn clone_to_snapshot_with(&self, clone_config: CloneConfig) -> BeaconSnapshot<T> {
BeaconSnapshot {
beacon_state: self.beacon_state.clone_with(clone_config),
beacon_block: self.beacon_block.clone(),
beacon_block_root: self.beacon_block_root,
}
}
pub fn into_pre_state(self) -> PreProcessingSnapshot<T> {
// Do not include the beacon state root if the state has been advanced.
let beacon_state_root =
Some(self.beacon_block.state_root()).filter(|_| self.pre_state.is_none());
PreProcessingSnapshot {
beacon_block: self.beacon_block,
beacon_block_root: self.beacon_block_root,
pre_state: self.pre_state.unwrap_or(self.beacon_state),
beacon_state_root,
}
}
pub fn clone_as_pre_state(&self) -> PreProcessingSnapshot<T> {
// Do not include the beacon state root if the state has been advanced.
let beacon_state_root =
Some(self.beacon_block.state_root()).filter(|_| self.pre_state.is_none());
PreProcessingSnapshot {
beacon_block: self.beacon_block.clone(),
beacon_block_root: self.beacon_block_root,
pre_state: self
.pre_state
.as_ref()
.map_or_else(|| self.beacon_state.clone(), |pre_state| pre_state.clone()),
beacon_state_root,
}
}
}
/// The information required for block production.
pub struct BlockProductionPreState<T: EthSpec> {
/// This state may or may not have been advanced forward a single slot.
///
/// See the documentation in the `crate::state_advance_timer` module for more information.
pub pre_state: BeaconState<T>,
/// This value will only be `Some` if `self.pre_state` was **not** advanced forward a single
/// slot.
///
/// This value can be used to avoid tree-hashing the state during the first call to
/// `per_slot_processing`.
pub state_root: Option<Hash256>,
}
pub enum StateAdvance<T: EthSpec> {
/// The cache does not contain the supplied block root.
BlockNotFound,
/// The cache contains the supplied block root but the state has already been advanced.
AlreadyAdvanced,
/// The cache contains the supplied block root and the state has not yet been advanced.
State {
state: Box<BeaconState<T>>,
state_root: Hash256,
block_slot: Slot,
},
}
/// The item stored in the `SnapshotCache`.
pub struct CacheItem<T: EthSpec> {
beacon_block: SignedBeaconBlock<T>,
beacon_block_root: Hash256,
/// This state is equivalent to `self.beacon_block.state_root()`.
beacon_state: BeaconState<T>,
/// This state is equivalent to `self.beacon_state` that has had `per_slot_processing` applied
/// to it. This state assists in optimizing block processing.
pre_state: Option<BeaconState<T>>,
}
impl<T: EthSpec> Into<BeaconSnapshot<T>> for CacheItem<T> {
fn into(self) -> BeaconSnapshot<T> {
BeaconSnapshot {
beacon_state: self.beacon_state,
beacon_block: self.beacon_block,
beacon_block_root: self.beacon_block_root,
}
}
}
/// Provides a cache of `BeaconSnapshot` that is intended primarily for block processing.
///
/// ## Cache Queuing
@@ -20,7 +150,7 @@ pub const DEFAULT_SNAPSHOT_CACHE_SIZE: usize = 4;
pub struct SnapshotCache<T: EthSpec> {
max_len: usize,
head_block_root: Hash256,
snapshots: Vec<BeaconSnapshot<T>>,
snapshots: Vec<CacheItem<T>>,
}
impl<T: EthSpec> SnapshotCache<T> {
@@ -31,15 +161,57 @@ impl<T: EthSpec> SnapshotCache<T> {
Self {
max_len: cmp::max(max_len, 1),
head_block_root: head.beacon_block_root,
snapshots: vec![head],
snapshots: vec![CacheItem::new_without_pre_state(head)],
}
}
/// The block roots of all snapshots contained in `self`.
pub fn beacon_block_roots(&self) -> Vec<Hash256> {
self.snapshots.iter().map(|s| s.beacon_block_root).collect()
}
/// The number of snapshots contained in `self`.
pub fn len(&self) -> usize {
self.snapshots.len()
}
/// Insert a snapshot, potentially removing an existing snapshot if `self` is at capacity (see
/// struct-level documentation for more info).
pub fn insert(&mut self, snapshot: BeaconSnapshot<T>) {
pub fn insert(
&mut self,
snapshot: BeaconSnapshot<T>,
pre_state: Option<BeaconState<T>>,
spec: &ChainSpec,
) {
let parent_root = snapshot.beacon_block.message().parent_root();
let item = CacheItem {
beacon_block: snapshot.beacon_block,
beacon_block_root: snapshot.beacon_block_root,
beacon_state: snapshot.beacon_state,
pre_state,
};
// Remove the grandparent of the block that was just inserted.
//
// Assuming it's unlikely to see re-orgs deeper than one block, this method helps keep the
// cache small by removing any states that already have more than one descendant.
//
// Remove the grandparent first to free up room in the cache.
let grandparent_result =
process_results(item.beacon_state.rev_iter_block_roots(spec), |iter| {
iter.map(|(_slot, root)| root)
.find(|root| *root != item.beacon_block_root && *root != parent_root)
});
if let Ok(Some(grandparent_root)) = grandparent_result {
let head_block_root = self.head_block_root;
self.snapshots.retain(|snapshot| {
let root = snapshot.beacon_block_root;
root == head_block_root || root != grandparent_root
});
}
if self.snapshots.len() < self.max_len {
self.snapshots.push(snapshot);
self.snapshots.push(item);
} else {
let insert_at = self
.snapshots
@@ -47,7 +219,7 @@ impl<T: EthSpec> SnapshotCache<T> {
.enumerate()
.filter_map(|(i, snapshot)| {
if snapshot.beacon_block_root != self.head_block_root {
Some((i, snapshot.beacon_state.slot))
Some((i, snapshot.beacon_state.slot()))
} else {
None
}
@@ -56,32 +228,125 @@ impl<T: EthSpec> SnapshotCache<T> {
.map(|(i, _slot)| i);
if let Some(i) = insert_at {
self.snapshots[i] = snapshot;
self.snapshots[i] = item;
}
}
}
/// If there is a snapshot with `block_root`, remove and return it.
pub fn try_remove(&mut self, block_root: Hash256) -> Option<BeaconSnapshot<T>> {
/// If available, returns a `CacheItem` that should be used for importing/processing a block.
/// The method will remove the block from `self`, carrying across any caches that may or may not
/// be built.
///
/// In the event the block being processed was observed late, clone the cache instead of
/// moving it. This allows us to process the next block quickly in the case of a re-org.
/// Additionally, if the slot was skipped, clone the cache. This ensures blocks that are
/// later than 1 slot still have access to the cache and can be processed quickly.
pub fn get_state_for_block_processing(
&mut self,
block_root: Hash256,
block_slot: Slot,
block_delay: Option<Duration>,
spec: &ChainSpec,
) -> Option<(PreProcessingSnapshot<T>, bool)> {
self.snapshots
.iter()
.position(|snapshot| snapshot.beacon_block_root == block_root)
.map(|i| self.snapshots.remove(i))
.map(|i| {
if let Some(cache) = self.snapshots.get(i) {
if block_slot > cache.beacon_block.slot() + 1 {
return (cache.clone_as_pre_state(), true);
}
if let Some(delay) = block_delay {
if delay >= MINIMUM_BLOCK_DELAY_FOR_CLONE
&& delay <= Duration::from_secs(spec.seconds_per_slot) * 4
{
return (cache.clone_as_pre_state(), true);
}
}
}
(self.snapshots.remove(i).into_pre_state(), false)
})
}
/// If there is a snapshot with `block_root`, clone it (with only the committee caches) and
/// return the clone.
pub fn get_cloned(&self, block_root: Hash256) -> Option<BeaconSnapshot<T>> {
/// If available, obtains a clone of a `BeaconState` that should be used for block production.
/// The clone will use `CloneConfig:all()`, ensuring any tree-hash cache is cloned too.
///
/// ## Note
///
/// This method clones the `BeaconState` (instead of removing it) since we assume that any block
/// we produce will soon be pushed to the `BeaconChain` for importing/processing. Keeping a copy
/// of that `BeaconState` in `self` will greatly help with import times.
pub fn get_state_for_block_production(
&self,
block_root: Hash256,
) -> Option<BlockProductionPreState<T>> {
self.snapshots
.iter()
.find(|snapshot| snapshot.beacon_block_root == block_root)
.map(|snapshot| snapshot.clone_with_only_committee_caches())
.map(|snapshot| {
if let Some(pre_state) = &snapshot.pre_state {
BlockProductionPreState {
pre_state: pre_state.clone_with(CloneConfig::all()),
state_root: None,
}
} else {
BlockProductionPreState {
pre_state: snapshot.beacon_state.clone_with(CloneConfig::all()),
state_root: Some(snapshot.beacon_block.state_root()),
}
}
})
}
/// If there is a snapshot with `block_root`, clone it and return the clone.
pub fn get_cloned(
&self,
block_root: Hash256,
clone_config: CloneConfig,
) -> Option<BeaconSnapshot<T>> {
self.snapshots
.iter()
.find(|snapshot| snapshot.beacon_block_root == block_root)
.map(|snapshot| snapshot.clone_to_snapshot_with(clone_config))
}
pub fn get_for_state_advance(&mut self, block_root: Hash256) -> StateAdvance<T> {
if let Some(snapshot) = self
.snapshots
.iter_mut()
.find(|snapshot| snapshot.beacon_block_root == block_root)
{
if snapshot.pre_state.is_some() {
StateAdvance::AlreadyAdvanced
} else {
let cloned = snapshot
.beacon_state
.clone_with(CloneConfig::committee_caches_only());
StateAdvance::State {
state: Box::new(std::mem::replace(&mut snapshot.beacon_state, cloned)),
state_root: snapshot.beacon_block.state_root(),
block_slot: snapshot.beacon_block.slot(),
}
}
} else {
StateAdvance::BlockNotFound
}
}
pub fn update_pre_state(&mut self, block_root: Hash256, state: BeaconState<T>) -> Option<()> {
self.snapshots
.iter_mut()
.find(|snapshot| snapshot.beacon_block_root == block_root)
.map(|snapshot| {
snapshot.pre_state = Some(state);
})
}
/// Removes all snapshots from the queue that are less than or equal to the finalized epoch.
pub fn prune(&mut self, finalized_epoch: Epoch) {
self.snapshots.retain(|snapshot| {
snapshot.beacon_state.slot > finalized_epoch.start_slot(T::slots_per_epoch())
snapshot.beacon_state.slot() > finalized_epoch.start_slot(T::slots_per_epoch())
})
}
@@ -97,34 +362,48 @@ impl<T: EthSpec> SnapshotCache<T> {
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType};
use types::{
test_utils::{generate_deterministic_keypair, TestingBeaconStateBuilder},
BeaconBlock, Epoch, MainnetEthSpec, SignedBeaconBlock, Slot,
test_utils::generate_deterministic_keypair, BeaconBlock, Epoch, MainnetEthSpec,
SignedBeaconBlock, Slot,
};
fn get_harness() -> BeaconChainHarness<EphemeralHarnessType<MainnetEthSpec>> {
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.default_spec()
.deterministic_keypairs(1)
.fresh_ephemeral_store()
.build();
harness.advance_slot();
harness
}
const CACHE_SIZE: usize = 4;
fn get_snapshot(i: u64) -> BeaconSnapshot<MainnetEthSpec> {
let spec = MainnetEthSpec::default_spec();
let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(1, &spec);
let (beacon_state, _keypairs) = state_builder.build();
let beacon_state = get_harness().chain.head_beacon_state().unwrap();
let signed_beacon_block = SignedBeaconBlock::from_block(
BeaconBlock::empty(&spec),
generate_deterministic_keypair(0)
.sk
.sign(Hash256::from_low_u64_be(42)),
);
BeaconSnapshot {
beacon_state,
beacon_state_root: Hash256::from_low_u64_be(i),
beacon_block: SignedBeaconBlock {
message: BeaconBlock::empty(&spec),
signature: generate_deterministic_keypair(0)
.sk
.sign(Hash256::from_low_u64_be(42)),
},
beacon_block: signed_beacon_block,
beacon_block_root: Hash256::from_low_u64_be(i),
}
}
#[test]
fn insert_get_prune_update() {
let spec = MainnetEthSpec::default_spec();
let mut cache = SnapshotCache::new(CACHE_SIZE, get_snapshot(0));
// Insert a bunch of entries in the cache. It should look like this:
@@ -138,9 +417,10 @@ mod test {
let mut snapshot = get_snapshot(i);
// Each snapshot should be one slot into an epoch, with each snapshot one epoch apart.
snapshot.beacon_state.slot = Slot::from(i * MainnetEthSpec::slots_per_epoch() + 1);
*snapshot.beacon_state.slot_mut() =
Slot::from(i * MainnetEthSpec::slots_per_epoch() + 1);
cache.insert(snapshot);
cache.insert(snapshot, None, &spec);
assert_eq!(
cache.snapshots.len(),
@@ -158,36 +438,51 @@ mod test {
// 2 2
// 3 3
assert_eq!(cache.snapshots.len(), CACHE_SIZE);
cache.insert(get_snapshot(42));
cache.insert(get_snapshot(42), None, &spec);
assert_eq!(cache.snapshots.len(), CACHE_SIZE);
assert!(
cache.try_remove(Hash256::from_low_u64_be(1)).is_none(),
cache
.get_state_for_block_processing(
Hash256::from_low_u64_be(1),
Slot::new(0),
None,
&spec
)
.is_none(),
"the snapshot with the lowest slot should have been removed during the insert function"
);
assert!(cache.get_cloned(Hash256::from_low_u64_be(1)).is_none());
assert!(cache
.get_cloned(Hash256::from_low_u64_be(1), CloneConfig::none())
.is_none());
assert!(
assert_eq!(
cache
.get_cloned(Hash256::from_low_u64_be(0))
.get_cloned(Hash256::from_low_u64_be(0), CloneConfig::none())
.expect("the head should still be in the cache")
.beacon_block_root
== Hash256::from_low_u64_be(0),
.beacon_block_root,
Hash256::from_low_u64_be(0),
"get_cloned should get the correct snapshot"
);
assert!(
assert_eq!(
cache
.try_remove(Hash256::from_low_u64_be(0))
.get_state_for_block_processing(
Hash256::from_low_u64_be(0),
Slot::new(0),
None,
&spec
)
.expect("the head should still be in the cache")
.beacon_block_root
== Hash256::from_low_u64_be(0),
"try_remove should get the correct snapshot"
.0
.beacon_block_root,
Hash256::from_low_u64_be(0),
"get_state_for_block_processing should get the correct snapshot"
);
assert_eq!(
cache.snapshots.len(),
CACHE_SIZE - 1,
"try_remove should shorten the cache"
"get_state_for_block_processing should shorten the cache"
);
// Prune the cache. Afterwards it should look like:
@@ -203,17 +498,23 @@ mod test {
// Over-fill the cache so it needs to eject some old values on insert.
for i in 0..CACHE_SIZE as u64 {
cache.insert(get_snapshot(u64::max_value() - i));
cache.insert(get_snapshot(u64::max_value() - i), None, &spec);
}
// Ensure that the new head value was not removed from the cache.
assert!(
assert_eq!(
cache
.try_remove(Hash256::from_low_u64_be(2))
.get_state_for_block_processing(
Hash256::from_low_u64_be(2),
Slot::new(0),
None,
&spec
)
.expect("the new head should still be in the cache")
.beacon_block_root
== Hash256::from_low_u64_be(2),
"try_remove should get the correct snapshot"
.0
.beacon_block_root,
Hash256::from_low_u64_be(2),
"get_state_for_block_processing should get the correct snapshot"
);
}
}

View File

@@ -0,0 +1,394 @@
//! Provides a timer which runs in the tail-end of each slot and maybe advances the state of the
//! head block forward a single slot.
//!
//! This provides an optimization with the following benefits:
//!
//! 1. Removes the burden of a single, mandatory `per_slot_processing` call from the leading-edge of
//! block processing. This helps import blocks faster.
//! 2. Allows the node to learn of the shuffling for the next epoch, before the first block from
//! that epoch has arrived. This helps reduce gossip block propagation times.
//!
//! The downsides to this optimization are:
//!
//! 1. We are required to store an additional `BeaconState` for the head block. This consumes
//! memory.
//! 2. There's a possibility that the head block is never built upon, causing wasted CPU cycles.
use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS;
use crate::{
beacon_chain::{ATTESTATION_CACHE_LOCK_TIMEOUT, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT},
snapshot_cache::StateAdvance,
BeaconChain, BeaconChainError, BeaconChainTypes,
};
use slog::{debug, error, warn, Logger};
use slot_clock::SlotClock;
use state_processing::per_slot_processing;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use task_executor::TaskExecutor;
use tokio::time::sleep;
use types::{AttestationShufflingId, EthSpec, Hash256, RelativeEpoch, Slot};
/// If the head slot is more than `MAX_ADVANCE_DISTANCE` from the current slot, then don't perform
/// the state advancement.
///
/// This avoids doing unnecessary work whilst the node is syncing or has perhaps been put to sleep
/// for some period of time.
const MAX_ADVANCE_DISTANCE: u64 = 4;
#[derive(Debug)]
enum Error {
BeaconChain(BeaconChainError),
HeadMissingFromSnapshotCache(Hash256),
MaxDistanceExceeded {
current_slot: Slot,
head_slot: Slot,
},
StateAlreadyAdvanced {
block_root: Hash256,
},
BadStateSlot {
_state_slot: Slot,
_block_slot: Slot,
},
}
impl From<BeaconChainError> for Error {
fn from(e: BeaconChainError) -> Self {
Self::BeaconChain(e)
}
}
/// Provides a simple thread-safe lock to be used for task co-ordination. Practically equivalent to
/// `Mutex<()>`.
#[derive(Clone)]
struct Lock(Arc<AtomicBool>);
impl Lock {
/// Instantiate an unlocked self.
pub fn new() -> Self {
Self(Arc::new(AtomicBool::new(false)))
}
/// Lock self, returning `true` if the lock was already set.
pub fn lock(&self) -> bool {
self.0.fetch_or(true, Ordering::SeqCst)
}
/// Unlock self.
pub fn unlock(&self) {
self.0.store(false, Ordering::SeqCst);
}
}
/// Spawns the timer described in the module-level documentation.
pub fn spawn_state_advance_timer<T: BeaconChainTypes>(
executor: TaskExecutor,
beacon_chain: Arc<BeaconChain<T>>,
log: Logger,
) {
executor.spawn(
state_advance_timer(executor.clone(), beacon_chain, log),
"state_advance_timer",
);
}
/// Provides the timer described in the module-level documentation.
async fn state_advance_timer<T: BeaconChainTypes>(
executor: TaskExecutor,
beacon_chain: Arc<BeaconChain<T>>,
log: Logger,
) {
let is_running = Lock::new();
let slot_clock = &beacon_chain.slot_clock;
let slot_duration = slot_clock.slot_duration();
loop {
match beacon_chain.slot_clock.duration_to_next_slot() {
Some(duration) => sleep(duration + (slot_duration / 4) * 3).await,
None => {
error!(log, "Failed to read slot clock");
// If we can't read the slot clock, just wait another slot.
sleep(slot_duration).await;
continue;
}
};
// Only start spawn the state advance task if the lock was previously free.
if !is_running.lock() {
let log = log.clone();
let beacon_chain = beacon_chain.clone();
let is_running = is_running.clone();
executor.spawn_blocking(
move || {
match advance_head(&beacon_chain, &log) {
Ok(()) => (),
Err(Error::BeaconChain(e)) => error!(
log,
"Failed to advance head state";
"error" => ?e
),
Err(Error::StateAlreadyAdvanced { block_root }) => debug!(
log,
"State already advanced on slot";
"block_root" => ?block_root
),
Err(Error::MaxDistanceExceeded {
current_slot,
head_slot,
}) => debug!(
log,
"Refused to advance head state";
"head_slot" => head_slot,
"current_slot" => current_slot,
),
other => warn!(
log,
"Did not advance head state";
"reason" => ?other
),
};
// Permit this blocking task to spawn again, next time the timer fires.
is_running.unlock();
},
"state_advance_blocking",
);
} else {
warn!(
log,
"State advance routine overloaded";
"msg" => "system resources may be overloaded"
)
}
}
}
/// Reads the `snapshot_cache` from the `beacon_chain` and attempts to take a clone of the
/// `BeaconState` of the head block. If it obtains this clone, the state will be advanced a single
/// slot then placed back in the `snapshot_cache` to be used for block verification.
///
/// See the module-level documentation for rationale.
fn advance_head<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
log: &Logger,
) -> Result<(), Error> {
let current_slot = beacon_chain.slot()?;
// These brackets ensure that the `head_slot` value is dropped before we run fork choice and
// potentially invalidate it.
//
// Fork-choice is not run *before* this function to avoid unnecessary calls whilst syncing.
{
let head_slot = beacon_chain.head_info()?.slot;
// Don't run this when syncing or if lagging too far behind.
if head_slot + MAX_ADVANCE_DISTANCE < current_slot {
return Err(Error::MaxDistanceExceeded {
current_slot,
head_slot,
});
}
}
// Run fork choice so we get the latest view of the head.
//
// This is useful since it's quite likely that the last time we ran fork choice was shortly
// after receiving the latest gossip block, but not necessarily after we've received the
// majority of attestations.
beacon_chain.fork_choice()?;
let head_root = beacon_chain.head_info()?.block_root;
let (head_slot, head_state_root, mut state) = match beacon_chain
.snapshot_cache
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::SnapshotCacheLockTimeout)?
.get_for_state_advance(head_root)
{
StateAdvance::AlreadyAdvanced => {
return Err(Error::StateAlreadyAdvanced {
block_root: head_root,
})
}
StateAdvance::BlockNotFound => return Err(Error::HeadMissingFromSnapshotCache(head_root)),
StateAdvance::State {
state,
state_root,
block_slot,
} => (block_slot, state_root, *state),
};
let initial_slot = state.slot();
let initial_epoch = state.current_epoch();
let state_root = if state.slot() == head_slot {
Some(head_state_root)
} else {
// Protect against advancing a state more than a single slot.
//
// Advancing more than one slot without storing the intermediate state would corrupt the
// database. Future works might store temporary, intermediate states inside this function.
return Err(Error::BadStateSlot {
_block_slot: head_slot,
_state_slot: state.slot(),
});
};
// Advance the state a single slot.
if let Some(summary) = per_slot_processing(&mut state, state_root, &beacon_chain.spec)
.map_err(BeaconChainError::from)?
{
// Expose Prometheus metrics.
if let Err(e) = summary.observe_metrics() {
error!(
log,
"Failed to observe epoch summary metrics";
"src" => "state_advance_timer",
"error" => ?e
);
}
// Only notify the validator monitor for recent blocks.
if state.current_epoch() + VALIDATOR_MONITOR_HISTORIC_EPOCHS as u64
>= current_slot.epoch(T::EthSpec::slots_per_epoch())
{
// Potentially create logs/metrics for locally monitored validators.
if let Err(e) = beacon_chain
.validator_monitor
.read()
.process_validator_statuses(state.current_epoch(), &summary, &beacon_chain.spec)
{
error!(
log,
"Unable to process validator statuses";
"error" => ?e
);
}
}
}
debug!(
log,
"Advanced head state one slot";
"head_root" => ?head_root,
"state_slot" => state.slot(),
"current_slot" => current_slot,
);
// Build the current epoch cache, to prepare to compute proposer duties.
state
.build_committee_cache(RelativeEpoch::Current, &beacon_chain.spec)
.map_err(BeaconChainError::from)?;
// Build the next epoch cache, to prepare to compute attester duties.
state
.build_committee_cache(RelativeEpoch::Next, &beacon_chain.spec)
.map_err(BeaconChainError::from)?;
// If the `pre_state` is in a later epoch than `state`, pre-emptively add the proposer shuffling
// for the state's current epoch and the committee cache for the state's next epoch.
if initial_epoch < state.current_epoch() {
// Update the proposer cache.
//
// We supply the `head_root` as the decision block since the prior `if` statement guarantees
// the head root is the latest block from the prior epoch.
beacon_chain
.beacon_proposer_cache
.lock()
.insert(
state.current_epoch(),
head_root,
state
.get_beacon_proposer_indices(&beacon_chain.spec)
.map_err(BeaconChainError::from)?,
state.fork(),
)
.map_err(BeaconChainError::from)?;
// Update the attester cache.
let shuffling_id = AttestationShufflingId::new(head_root, &state, RelativeEpoch::Next)
.map_err(BeaconChainError::from)?;
let committee_cache = state
.committee_cache(RelativeEpoch::Next)
.map_err(BeaconChainError::from)?;
beacon_chain
.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::AttestationCacheLockTimeout)?
.insert(shuffling_id.clone(), committee_cache);
debug!(
log,
"Primed proposer and attester caches";
"head_root" => ?head_root,
"next_epoch_shuffling_root" => ?shuffling_id.shuffling_decision_block,
"state_epoch" => state.current_epoch(),
"current_epoch" => current_slot.epoch(T::EthSpec::slots_per_epoch()),
);
}
// Apply the state to the attester cache, if the cache deems it interesting.
beacon_chain
.attester_cache
.maybe_cache_state(&state, head_root, &beacon_chain.spec)
.map_err(BeaconChainError::from)?;
let final_slot = state.slot();
// Insert the advanced state back into the snapshot cache.
beacon_chain
.snapshot_cache
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::SnapshotCacheLockTimeout)?
.update_pre_state(head_root, state)
.ok_or(Error::HeadMissingFromSnapshotCache(head_root))?;
// If we have moved into the next slot whilst processing the state then this function is going
// to become ineffective and likely become a hindrance as we're stealing the tree hash cache
// from the snapshot cache (which may force the next block to rebuild a new one).
//
// If this warning occurs very frequently on well-resourced machines then we should consider
// starting it earlier in the slot. Otherwise, it's a good indication that the machine is too
// slow/overloaded and will be useful information for the user.
let starting_slot = current_slot;
let current_slot = beacon_chain.slot()?;
if starting_slot < current_slot {
warn!(
log,
"State advance too slow";
"head_root" => %head_root,
"advanced_slot" => final_slot,
"current_slot" => current_slot,
"starting_slot" => starting_slot,
"msg" => "system resources may be overloaded",
);
}
debug!(
log,
"Completed state advance";
"head_root" => ?head_root,
"advanced_slot" => final_slot,
"initial_slot" => initial_slot,
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lock() {
let lock = Lock::new();
assert!(!lock.lock());
assert!(lock.lock());
assert!(lock.lock());
lock.unlock();
assert!(!lock.lock());
assert!(lock.lock());
}
}

View File

@@ -0,0 +1,665 @@
//! Provides verification for the following sync committee messages:
//!
//! - "Unaggregated" `SyncCommitteeMessage` received from either gossip or the HTTP API.
//! - "Aggregated" `SignedContributionAndProof` received from gossip or the HTTP API.
//!
//! For clarity, we define:
//!
//! - Unaggregated: a `SyncCommitteeMessage` object.
//! - Aggregated: a `SignedContributionAndProof` which has zero or more signatures.
//! - Note: "zero or more" may soon change to "one or more".
//!
//! Similar to the `crate::block_verification` module, we try to avoid doing duplicate verification
//! work as a sync committee message passes through different stages of verification. We represent these
//! different stages of verification with wrapper types. These wrapper-types flow in a particular
//! pattern:
//!
//! ```ignore
//! types::SyncCommitteeMessage types::SignedContributionAndProof
//! | |
//! ▼ ▼
//! VerifiedSyncCommitteeMessage VerifiedSyncContribution
//! | |
//! -------------------------------------
//! |
//! ▼
//! impl SignatureVerifiedSyncContribution
//! ```
use crate::observed_attesters::SlotSubcommitteeIndex;
use crate::{
beacon_chain::{MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT},
metrics,
observed_aggregates::ObserveOutcome,
BeaconChain, BeaconChainError, BeaconChainTypes,
};
use bls::{verify_signature_sets, PublicKeyBytes};
use derivative::Derivative;
use safe_arith::ArithError;
use slot_clock::SlotClock;
use state_processing::per_block_processing::errors::SyncCommitteeMessageValidationError;
use state_processing::signature_sets::{
signed_sync_aggregate_selection_proof_signature_set, signed_sync_aggregate_signature_set,
sync_committee_contribution_signature_set_from_pubkeys,
sync_committee_message_set_from_pubkeys,
};
use std::borrow::Cow;
use std::collections::HashMap;
use strum::AsRefStr;
use tree_hash::TreeHash;
use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
use types::slot_data::SlotData;
use types::sync_committee::Error as SyncCommitteeError;
use types::{
sync_committee_contribution::Error as ContributionError, AggregateSignature, BeaconStateError,
EthSpec, Hash256, SignedContributionAndProof, Slot, SyncCommitteeContribution,
SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId,
};
/// Returned when a sync committee contribution was not successfully verified. It might not have been verified for
/// two reasons:
///
/// - The sync committee message is malformed or inappropriate for the context (indicated by all variants
/// other than `BeaconChainError`).
/// - The application encountered an internal error whilst attempting to determine validity
/// (the `BeaconChainError` variant)
#[derive(Debug, AsRefStr)]
pub enum Error {
/// The sync committee message is from a slot that is later than the current slot (with respect to the
/// gossip clock disparity).
///
/// ## Peer scoring
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
FutureSlot {
message_slot: Slot,
latest_permissible_slot: Slot,
},
/// The sync committee message is from a slot that is prior to the earliest permissible slot (with
/// respect to the gossip clock disparity).
///
/// ## Peer scoring
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
PastSlot {
message_slot: Slot,
earliest_permissible_slot: Slot,
},
/// The sync committee message's aggregation bits were empty when they shouldn't be.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
EmptyAggregationBitfield,
/// The `selection_proof` on the sync contribution does not elect it as an aggregator.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
InvalidSelectionProof { aggregator_index: u64 },
/// The `selection_proof` on the sync committee contribution selects it as a validator, however the
/// aggregator index is not in the committee for that sync contribution.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
AggregatorNotInCommittee { aggregator_index: u64 },
/// The aggregator index refers to a validator index that we have not seen.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
AggregatorPubkeyUnknown(u64),
/// The sync contribution has been seen before; either in a block, on the gossip network or from a
/// local validator.
///
/// ## Peer scoring
///
/// It's unclear if this sync contribution is valid, however we have already observed it and do not
/// need to observe it again.
SyncContributionAlreadyKnown(Hash256),
/// There has already been an aggregation observed for this validator, we refuse to process a
/// second.
///
/// ## Peer scoring
///
/// It's unclear if this sync committee message is valid, however we have already observed an aggregate
/// sync committee message from this validator for this epoch and should not observe another.
AggregatorAlreadyKnown(u64),
/// The aggregator index is higher than the maximum possible validator count.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
UnknownValidatorIndex(usize),
/// The public key of the validator has not been seen locally.
///
/// ## Peer scoring
///
/// It's unclear if this sync committee message is valid, however we have already observed an aggregate
/// sync committee message from this validator for this epoch and should not observe another.
UnknownValidatorPubkey(PublicKeyBytes),
/// A signature on the sync committee message is invalid.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
InvalidSignature,
/// We have already observed a signature for the `validator_index` and refuse to process
/// another.
///
/// ## Peer scoring
///
/// It's unclear if this sync message is valid, however we have already observed a
/// signature from this validator for this slot and should not observe
/// another.
PriorSyncCommitteeMessageKnown { validator_index: u64, slot: Slot },
/// The sync committee message was received on an invalid sync committee message subnet.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
InvalidSubnetId {
received: SyncSubnetId,
expected: Vec<SyncSubnetId>,
},
/// The sync message failed the `state_processing` verification stage.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
Invalid(SyncCommitteeMessageValidationError),
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
BeaconChainError(BeaconChainError),
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
BeaconStateError(BeaconStateError),
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
InvalidSubcommittee {
subcommittee_index: u64,
subcommittee_size: u64,
},
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
ArithError(ArithError),
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
ContributionError(ContributionError),
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
SyncCommitteeError(SyncCommitteeError),
}
impl From<BeaconChainError> for Error {
fn from(e: BeaconChainError) -> Self {
Error::BeaconChainError(e)
}
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Self {
Error::BeaconStateError(e)
}
}
impl From<SyncCommitteeError> for Error {
fn from(e: SyncCommitteeError) -> Self {
Error::SyncCommitteeError(e)
}
}
impl From<ArithError> for Error {
fn from(e: ArithError) -> Self {
Error::ArithError(e)
}
}
impl From<ContributionError> for Error {
fn from(e: ContributionError) -> Self {
Error::ContributionError(e)
}
}
/// Wraps a `SignedContributionAndProof` that has been verified for propagation on the gossip network.\
#[derive(Derivative)]
#[derivative(Clone(bound = "T: BeaconChainTypes"))]
pub struct VerifiedSyncContribution<T: BeaconChainTypes> {
signed_aggregate: SignedContributionAndProof<T::EthSpec>,
participant_pubkeys: Vec<PublicKeyBytes>,
}
/// Wraps a `SyncCommitteeMessage` that has been verified for propagation on the gossip network.
#[derive(Clone)]
pub struct VerifiedSyncCommitteeMessage {
sync_message: SyncCommitteeMessage,
subnet_positions: HashMap<SyncSubnetId, Vec<usize>>,
}
impl<T: BeaconChainTypes> VerifiedSyncContribution<T> {
/// Returns `Ok(Self)` if the `signed_aggregate` is valid to be (re)published on the gossip
/// network.
pub fn verify(
signed_aggregate: SignedContributionAndProof<T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<Self, Error> {
let aggregator_index = signed_aggregate.message.aggregator_index;
let contribution = &signed_aggregate.message.contribution;
let subcommittee_index = contribution.subcommittee_index as usize;
// Ensure sync committee contribution is within the MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance.
verify_propagation_slot_range(&chain.slot_clock, contribution)?;
// Validate subcommittee index.
if contribution.subcommittee_index >= SYNC_COMMITTEE_SUBNET_COUNT {
return Err(Error::InvalidSubcommittee {
subcommittee_index: contribution.subcommittee_index,
subcommittee_size: SYNC_COMMITTEE_SUBNET_COUNT,
});
}
// Ensure that the sync committee message has participants.
if contribution.aggregation_bits.is_zero() {
return Err(Error::EmptyAggregationBitfield);
}
// Ensure the aggregator's pubkey is in the declared subcommittee of the current sync committee
let pubkey_bytes = chain
.validator_pubkey_bytes(aggregator_index as usize)?
.ok_or(Error::UnknownValidatorIndex(aggregator_index as usize))?;
let sync_subcommittee_pubkeys = chain
.sync_committee_at_next_slot(contribution.get_slot())?
.get_subcommittee_pubkeys(subcommittee_index)?;
if !sync_subcommittee_pubkeys.contains(&pubkey_bytes) {
return Err(Error::AggregatorNotInCommittee { aggregator_index });
};
// Ensure the valid sync contribution has not already been seen locally.
let contribution_root = contribution.tree_hash_root();
if chain
.observed_sync_contributions
.write()
.is_known(contribution, contribution_root)
.map_err(|e| Error::BeaconChainError(e.into()))?
{
return Err(Error::SyncContributionAlreadyKnown(contribution_root));
}
// Ensure there has been no other observed aggregate for the given `aggregator_index`.
//
// Note: do not observe yet, only observe once the sync contribution has been verified.
let observed_key =
SlotSubcommitteeIndex::new(contribution.slot, contribution.subcommittee_index);
match chain
.observed_sync_aggregators
.read()
.validator_has_been_observed(observed_key, aggregator_index as usize)
{
Ok(true) => Err(Error::AggregatorAlreadyKnown(aggregator_index)),
Ok(false) => Ok(()),
Err(e) => Err(BeaconChainError::from(e).into()),
}?;
// Note: this clones the signature which is known to be a relatively slow operation.
//
// Future optimizations should remove this clone.
let selection_proof =
SyncSelectionProof::from(signed_aggregate.message.selection_proof.clone());
if !selection_proof
.is_aggregator::<T::EthSpec>()
.map_err(|e| Error::BeaconChainError(e.into()))?
{
return Err(Error::InvalidSelectionProof { aggregator_index });
}
// Gather all validator pubkeys that signed this contribution.
let participant_pubkeys = sync_subcommittee_pubkeys
.into_iter()
.zip(contribution.aggregation_bits.iter())
.filter_map(|(pubkey, bit)| bit.then(|| pubkey))
.collect::<Vec<_>>();
// Ensure that all signatures are valid.
if !verify_signed_aggregate_signatures(
chain,
&signed_aggregate,
participant_pubkeys.as_slice(),
)? {
return Err(Error::InvalidSignature);
}
let contribution = &signed_aggregate.message.contribution;
let aggregator_index = signed_aggregate.message.aggregator_index;
// Observe the valid sync contribution so we do not re-process it.
//
// It's important to double check that the contribution is not already known, otherwise two
// contribution processed at the same time could be published.
if let ObserveOutcome::AlreadyKnown = chain
.observed_sync_contributions
.write()
.observe_item(contribution, Some(contribution_root))
.map_err(|e| Error::BeaconChainError(e.into()))?
{
return Err(Error::SyncContributionAlreadyKnown(contribution_root));
}
// Observe the aggregator so we don't process another aggregate from them.
//
// It's important to double check that the sync committee message is not already known, otherwise two
// sync committee messages processed at the same time could be published.
if chain
.observed_sync_aggregators
.write()
.observe_validator(observed_key, aggregator_index as usize)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorSyncCommitteeMessageKnown {
validator_index: aggregator_index,
slot: contribution.slot,
});
}
Ok(VerifiedSyncContribution {
signed_aggregate,
participant_pubkeys,
})
}
/// A helper function to add this aggregate to `beacon_chain.op_pool`.
pub fn add_to_pool(self, chain: &BeaconChain<T>) -> Result<(), Error> {
chain.add_contribution_to_block_inclusion_pool(self)
}
/// Returns the underlying `contribution` for the `signed_aggregate`.
pub fn contribution(self) -> SyncCommitteeContribution<T::EthSpec> {
self.signed_aggregate.message.contribution
}
/// Returns the underlying `signed_aggregate`.
pub fn aggregate(&self) -> &SignedContributionAndProof<T::EthSpec> {
&self.signed_aggregate
}
/// Returns the pubkeys of all validators that are included in the aggregate.
pub fn participant_pubkeys(&self) -> &[PublicKeyBytes] {
&self.participant_pubkeys
}
}
impl VerifiedSyncCommitteeMessage {
/// Returns `Ok(Self)` if the `sync_message` is valid to be (re)published on the gossip
/// network.
///
/// `subnet_id` is the subnet from which we received this sync message. This function will
/// verify that it was received on the correct subnet.
pub fn verify<T: BeaconChainTypes>(
sync_message: SyncCommitteeMessage,
subnet_id: SyncSubnetId,
chain: &BeaconChain<T>,
) -> Result<Self, Error> {
// Ensure sync committee message is for the current slot (within a
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
//
// We do not queue future sync committee messages for later processing.
verify_propagation_slot_range(&chain.slot_clock, &sync_message)?;
// Ensure the `subnet_id` is valid for the given validator.
let pubkey = chain
.validator_pubkey_bytes(sync_message.validator_index as usize)?
.ok_or(Error::UnknownValidatorIndex(
sync_message.validator_index as usize,
))?;
let sync_committee = chain.sync_committee_at_next_slot(sync_message.get_slot())?;
let subnet_positions = sync_committee.subcommittee_positions_for_public_key(&pubkey)?;
if !subnet_positions.contains_key(&subnet_id) {
return Err(Error::InvalidSubnetId {
received: subnet_id,
expected: subnet_positions.keys().cloned().collect::<Vec<_>>(),
});
}
// The sync committee message is the first valid message received for the participating validator
// for the slot, sync_message.slot.
let validator_index = sync_message.validator_index;
if chain
.observed_sync_contributors
.read()
.validator_has_been_observed(
SlotSubcommitteeIndex::new(sync_message.slot, subnet_id.into()),
validator_index as usize,
)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorSyncCommitteeMessageKnown {
validator_index,
slot: sync_message.slot,
});
}
// The aggregate signature of the sync committee message is valid.
verify_sync_committee_message(chain, &sync_message, &pubkey)?;
// Now that the sync committee message has been fully verified, store that we have received a valid
// sync committee message from this validator.
//
// It's important to double check that the sync committee message still hasn't been observed, since
// there can be a race-condition if we receive two sync committee messages at the same time and
// process them in different threads.
if chain
.observed_sync_contributors
.write()
.observe_validator(
SlotSubcommitteeIndex::new(sync_message.slot, subnet_id.into()),
validator_index as usize,
)
.map_err(BeaconChainError::from)?
{
return Err(Error::PriorSyncCommitteeMessageKnown {
validator_index,
slot: sync_message.slot,
});
}
Ok(Self {
sync_message,
subnet_positions,
})
}
/// A helper function to add this sync committee message to `beacon_chain.naive_sync_aggregation_pool`.
pub fn add_to_pool<T: BeaconChainTypes>(self, chain: &BeaconChain<T>) -> Result<Self, Error> {
chain.add_to_naive_sync_aggregation_pool(self)
}
/// Returns the subcommittee positions for the sync message, keyed on the `SyncSubnetId` for
/// the subnets the signature should be sent on.
pub fn subnet_positions(&self) -> &HashMap<SyncSubnetId, Vec<usize>> {
&self.subnet_positions
}
/// Returns the wrapped `SyncCommitteeMessage`.
pub fn sync_message(&self) -> &SyncCommitteeMessage {
&self.sync_message
}
}
/// Verify that the `sync_contribution` is within the acceptable gossip propagation range, with reference
/// to the current slot of the `chain`.
///
/// Accounts for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
pub fn verify_propagation_slot_range<S: SlotClock, U: SlotData>(
slot_clock: &S,
sync_contribution: &U,
) -> Result<(), Error> {
let message_slot = sync_contribution.get_slot();
let latest_permissible_slot = slot_clock
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
.ok_or(BeaconChainError::UnableToReadSlot)?;
if message_slot > latest_permissible_slot {
return Err(Error::FutureSlot {
message_slot,
latest_permissible_slot,
});
}
let earliest_permissible_slot = slot_clock
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
.ok_or(BeaconChainError::UnableToReadSlot)?;
if message_slot < earliest_permissible_slot {
return Err(Error::PastSlot {
message_slot,
earliest_permissible_slot,
});
}
Ok(())
}
/// Verifies all the signatures in a `SignedContributionAndProof` using BLS batch verification. This
/// includes three signatures:
///
/// - `signed_aggregate.signature`
/// - `signed_aggregate.message.selection_proof`
/// - `signed_aggregate.message.aggregate.signature`
///
/// # Returns
///
/// - `Ok(true)`: if all signatures are valid.
/// - `Ok(false)`: if one or more signatures are invalid.
/// - `Err(e)`: if there was an error preventing signature verification.
pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
signed_aggregate: &SignedContributionAndProof<T::EthSpec>,
participant_pubkeys: &[PublicKeyBytes],
) -> Result<bool, Error> {
let pubkey_cache = chain
.validator_pubkey_cache
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
let aggregator_index = signed_aggregate.message.aggregator_index;
if aggregator_index >= pubkey_cache.len() as u64 {
return Err(Error::AggregatorPubkeyUnknown(aggregator_index));
}
let next_slot_epoch =
(signed_aggregate.message.contribution.slot + 1).epoch(T::EthSpec::slots_per_epoch());
let fork = chain.spec.fork_at_epoch(next_slot_epoch);
let signature_sets = vec![
signed_sync_aggregate_selection_proof_signature_set(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
signed_aggregate,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
signed_sync_aggregate_signature_set(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
signed_aggregate,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
sync_committee_contribution_signature_set_from_pubkeys::<T::EthSpec, _>(
|validator_index| {
pubkey_cache
.get_pubkey_from_pubkey_bytes(validator_index)
.map(Cow::Borrowed)
},
participant_pubkeys,
&signed_aggregate.message.contribution.signature,
signed_aggregate
.message
.contribution
.slot
.epoch(T::EthSpec::slots_per_epoch()),
signed_aggregate.message.contribution.beacon_block_root,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?,
];
Ok(verify_signature_sets(signature_sets.iter()))
}
/// Verifies that the signature of the `sync_message` is valid.
pub fn verify_sync_committee_message<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
sync_message: &SyncCommitteeMessage,
pubkey_bytes: &PublicKeyBytes,
) -> Result<(), Error> {
let signature_setup_timer =
metrics::start_timer(&metrics::SYNC_MESSAGE_PROCESSING_SIGNATURE_SETUP_TIMES);
let pubkey_cache = chain
.validator_pubkey_cache
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
let pubkey = pubkey_cache
.get_pubkey_from_pubkey_bytes(pubkey_bytes)
.map(Cow::Borrowed)
.ok_or(Error::UnknownValidatorPubkey(*pubkey_bytes))?;
let next_slot_epoch = (sync_message.get_slot() + 1).epoch(T::EthSpec::slots_per_epoch());
let fork = chain.spec.fork_at_epoch(next_slot_epoch);
let agg_sig = AggregateSignature::from(&sync_message.signature);
let signature_set = sync_committee_message_set_from_pubkeys::<T::EthSpec>(
pubkey,
&agg_sig,
sync_message.slot.epoch(T::EthSpec::slots_per_epoch()),
sync_message.beacon_block_root,
&fork,
chain.genesis_validators_root,
&chain.spec,
)
.map_err(BeaconChainError::SignatureSetError)?;
metrics::stop_timer(signature_setup_timer);
let _signature_verification_timer =
metrics::start_timer(&metrics::SYNC_MESSAGE_PROCESSING_SIGNATURE_TIMES);
if signature_set.verify() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,48 @@
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
/// A simple wrapper around `parking_lot::RwLock` that only permits read/write access with a
/// time-out (i.e., no indefinitely-blocking operations).
///
/// Timeouts can be optionally be disabled at runtime for all instances of this type by calling
/// `TimeoutRwLock::disable_timeouts()`.
pub struct TimeoutRwLock<T>(RwLock<T>);
const TIMEOUT_LOCKS_ENABLED_DEFAULT: bool = true;
static TIMEOUT_LOCKS_ENABLED: AtomicBool = AtomicBool::new(TIMEOUT_LOCKS_ENABLED_DEFAULT);
impl TimeoutRwLock<()> {
pub fn disable_timeouts() {
// Use the strongest `SeqCst` ordering for the write, as it should only happen once.
TIMEOUT_LOCKS_ENABLED.store(false, Ordering::SeqCst);
}
}
impl<T> TimeoutRwLock<T> {
pub fn new(inner: T) -> Self {
Self(RwLock::new(inner))
}
fn timeouts_enabled() -> bool {
// Use relaxed ordering as it's OK for a few locks to run with timeouts "accidentally",
// and we want the atomic check to be as fast as possible.
TIMEOUT_LOCKS_ENABLED.load(Ordering::Relaxed)
}
pub fn try_read_for(&self, timeout: Duration) -> Option<RwLockReadGuard<T>> {
self.0.try_read_for(timeout)
if Self::timeouts_enabled() {
self.0.try_read_for(timeout)
} else {
Some(self.0.read())
}
}
pub fn try_write_for(&self, timeout: Duration) -> Option<RwLockWriteGuard<T>> {
self.0.try_write_for(timeout)
if Self::timeouts_enabled() {
self.0.try_write_for(timeout)
} else {
Some(self.0.write())
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
use crate::errors::BeaconChainError;
use crate::{BeaconChainTypes, BeaconStore};
use ssz::{Decode, DecodeError, Encode};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use types::{BeaconState, EthSpec, PublicKey, PublicKeyBytes, Validator};
use store::{DBColumn, Error as StoreError, StoreItem};
use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes};
/// Provides a mapping of `validator_index -> validator_publickey`.
///
@@ -16,40 +18,37 @@ use types::{BeaconState, EthSpec, PublicKey, PublicKeyBytes, Validator};
/// keys in compressed form and they are needed in decompressed form for signature verification.
/// Decompression is expensive when many keys are involved.
///
/// The cache has a `persistence_file` that it uses to maintain a persistent, on-disk
/// The cache has a `backing` that it uses to maintain a persistent, on-disk
/// copy of itself. This allows it to be restored between process invocations.
pub struct ValidatorPubkeyCache {
pub struct ValidatorPubkeyCache<T: BeaconChainTypes> {
pubkeys: Vec<PublicKey>,
indices: HashMap<PublicKeyBytes, usize>,
persitence_file: ValidatorPubkeyCacheFile,
pubkey_bytes: Vec<PublicKeyBytes>,
backing: PubkeyCacheBacking<T>,
}
impl ValidatorPubkeyCache {
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, BeaconChainError> {
ValidatorPubkeyCacheFile::open(&path)
.and_then(ValidatorPubkeyCacheFile::into_cache)
.map_err(Into::into)
}
/// Abstraction over on-disk backing.
///
/// `File` backing is legacy, `Database` is current.
enum PubkeyCacheBacking<T: BeaconChainTypes> {
File(ValidatorPubkeyCacheFile),
Database(BeaconStore<T>),
}
impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
/// Create a new public key cache using the keys in `state.validators`.
///
/// Also creates a new persistence file, returning an error if there is already a file at
/// `persistence_path`.
pub fn new<T: EthSpec, P: AsRef<Path>>(
state: &BeaconState<T>,
persistence_path: P,
pub fn new(
state: &BeaconState<T::EthSpec>,
store: BeaconStore<T>,
) -> Result<Self, BeaconChainError> {
if persistence_path.as_ref().exists() {
return Err(BeaconChainError::ValidatorPubkeyCacheFileError(format!(
"Persistence file already exists: {:?}",
persistence_path.as_ref()
)));
}
let mut cache = Self {
persitence_file: ValidatorPubkeyCacheFile::create(persistence_path)?,
pubkeys: vec![],
indices: HashMap::new(),
pubkey_bytes: vec![],
backing: PubkeyCacheBacking::Database(store),
};
cache.import_new_pubkeys(state)?;
@@ -57,33 +56,88 @@ impl ValidatorPubkeyCache {
Ok(cache)
}
/// Load the pubkey cache from the given on-disk database.
pub fn load_from_store(store: BeaconStore<T>) -> Result<Self, BeaconChainError> {
let mut pubkeys = vec![];
let mut indices = HashMap::new();
let mut pubkey_bytes = vec![];
for validator_index in 0.. {
if let Some(DatabasePubkey(pubkey)) =
store.get_item(&DatabasePubkey::key_for_index(validator_index))?
{
pubkeys.push((&pubkey).try_into().map_err(Error::PubkeyDecode)?);
pubkey_bytes.push(pubkey);
indices.insert(pubkey, validator_index);
} else {
break;
}
}
Ok(ValidatorPubkeyCache {
pubkeys,
indices,
pubkey_bytes,
backing: PubkeyCacheBacking::Database(store),
})
}
/// DEPRECATED: used only for migration
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, BeaconChainError> {
ValidatorPubkeyCacheFile::open(&path)
.and_then(ValidatorPubkeyCacheFile::into_cache)
.map_err(Into::into)
}
/// Convert a cache using `File` backing to one using `Database` backing.
///
/// This will write all of the keys from `existing_cache` to `store`.
pub fn convert(existing_cache: Self, store: BeaconStore<T>) -> Result<Self, BeaconChainError> {
let mut result = ValidatorPubkeyCache {
pubkeys: Vec::with_capacity(existing_cache.pubkeys.len()),
indices: HashMap::with_capacity(existing_cache.indices.len()),
pubkey_bytes: Vec::with_capacity(existing_cache.indices.len()),
backing: PubkeyCacheBacking::Database(store),
};
result.import(existing_cache.pubkeys.iter().map(PublicKeyBytes::from))?;
Ok(result)
}
/// Scan the given `state` and add any new validator public keys.
///
/// Does not delete any keys from `self` if they don't appear in `state`.
pub fn import_new_pubkeys<T: EthSpec>(
pub fn import_new_pubkeys(
&mut self,
state: &BeaconState<T>,
state: &BeaconState<T::EthSpec>,
) -> Result<(), BeaconChainError> {
if state.validators.len() > self.pubkeys.len() {
self.import(&state.validators[self.pubkeys.len()..])
if state.validators().len() > self.pubkeys.len() {
self.import(
state.validators()[self.pubkeys.len()..]
.iter()
.map(|v| v.pubkey),
)
} else {
Ok(())
}
}
/// Adds zero or more validators to `self`.
fn import(&mut self, validators: &[Validator]) -> Result<(), BeaconChainError> {
self.pubkeys.reserve(validators.len());
self.indices.reserve(validators.len());
fn import<I>(&mut self, validator_keys: I) -> Result<(), BeaconChainError>
where
I: Iterator<Item = PublicKeyBytes> + ExactSizeIterator,
{
self.pubkey_bytes.reserve(validator_keys.len());
self.pubkeys.reserve(validator_keys.len());
self.indices.reserve(validator_keys.len());
for v in validators.iter() {
for pubkey in validator_keys {
let i = self.pubkeys.len();
if self.indices.contains_key(&v.pubkey) {
if self.indices.contains_key(&pubkey) {
return Err(BeaconChainError::DuplicateValidatorPublicKey);
}
// The item is written to disk (the persistence file) _before_ it is written into
// The item is written to disk _before_ it is written into
// the local struct.
//
// This means that a pubkey cache read from disk will always be equivalent to or
@@ -92,15 +146,23 @@ impl ValidatorPubkeyCache {
// The motivation behind this ordering is that we do not want to have states that
// reference a pubkey that is not in our cache. However, it's fine to have pubkeys
// that are never referenced in a state.
self.persitence_file.append(i, &v.pubkey)?;
match &mut self.backing {
PubkeyCacheBacking::File(persistence_file) => {
persistence_file.append(i, &pubkey)?;
}
PubkeyCacheBacking::Database(store) => {
store.put_item(&DatabasePubkey::key_for_index(i), &DatabasePubkey(pubkey))?;
}
}
self.pubkeys.push(
(&v.pubkey)
(&pubkey)
.try_into()
.map_err(BeaconChainError::InvalidValidatorPubkeyBytes)?,
);
self.pubkey_bytes.push(pubkey);
self.indices.insert(v.pubkey.clone(), i);
self.indices.insert(pubkey, i);
}
Ok(())
@@ -111,6 +173,16 @@ impl ValidatorPubkeyCache {
self.pubkeys.get(i)
}
/// Get the `PublicKey` for a validator with `PublicKeyBytes`.
pub fn get_pubkey_from_pubkey_bytes(&self, pubkey: &PublicKeyBytes) -> Option<&PublicKey> {
self.get_index(pubkey).and_then(|index| self.get(index))
}
/// Get the public key (in bytes form) for a validator with index `i`.
pub fn get_pubkey_bytes(&self, i: usize) -> Option<&PublicKeyBytes> {
self.pubkey_bytes.get(i)
}
/// Get the index of a validator with `pubkey`.
pub fn get_index(&self, pubkey: &PublicKeyBytes) -> Option<usize> {
self.indices.get(pubkey).copied()
@@ -122,6 +194,31 @@ impl ValidatorPubkeyCache {
}
}
/// Wrapper for a public key stored in the database.
///
/// Keyed by the validator index as `Hash256::from_low_u64_be(index)`.
struct DatabasePubkey(PublicKeyBytes);
impl StoreItem for DatabasePubkey {
fn db_column() -> DBColumn {
DBColumn::PubkeyCache
}
fn as_store_bytes(&self) -> Vec<u8> {
self.0.as_ssz_bytes()
}
fn from_store_bytes(bytes: &[u8]) -> Result<Self, StoreError> {
Ok(Self(PublicKeyBytes::from_ssz_bytes(bytes)?))
}
}
impl DatabasePubkey {
fn key_for_index(index: usize) -> Hash256 {
Hash256::from_low_u64_be(index as u64)
}
}
/// Allows for maintaining an on-disk copy of the `ValidatorPubkeyCache`. The file is raw SSZ bytes
/// (not ASCII encoded).
///
@@ -144,8 +241,8 @@ enum Error {
/// The file read from disk does not have a contiguous list of validator public keys. The file
/// has become corrupted.
InconsistentIndex {
expected: Option<usize>,
found: usize,
_expected: Option<usize>,
_found: usize,
},
}
@@ -156,19 +253,9 @@ impl From<Error> for BeaconChainError {
}
impl ValidatorPubkeyCacheFile {
/// Creates a file for reading and writing.
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
OpenOptions::new()
.create_new(true)
.write(true)
.open(path)
.map(Self)
.map_err(Error::Io)
}
/// Opens an existing file for reading and writing.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
OpenOptions::new()
File::options()
.read(true)
.write(true)
.create(false)
@@ -187,7 +274,7 @@ impl ValidatorPubkeyCacheFile {
}
/// Creates a `ValidatorPubkeyCache` by reading and parsing the underlying file.
pub fn into_cache(mut self) -> Result<ValidatorPubkeyCache, Error> {
pub fn into_cache<T: BeaconChainTypes>(mut self) -> Result<ValidatorPubkeyCache<T>, Error> {
let mut bytes = vec![];
self.0.read_to_end(&mut bytes).map_err(Error::Io)?;
@@ -195,18 +282,20 @@ impl ValidatorPubkeyCacheFile {
let mut last = None;
let mut pubkeys = Vec::with_capacity(list.len());
let mut indices = HashMap::new();
let mut indices = HashMap::with_capacity(list.len());
let mut pubkey_bytes = Vec::with_capacity(list.len());
for (index, pubkey) in list {
let expected = last.map(|n| n + 1);
if expected.map_or(true, |expected| index == expected) {
last = Some(index);
pubkeys.push((&pubkey).try_into().map_err(Error::PubkeyDecode)?);
pubkey_bytes.push(pubkey);
indices.insert(pubkey, index);
} else {
return Err(Error::InconsistentIndex {
expected,
found: index,
_expected: expected,
_found: index,
});
}
}
@@ -214,7 +303,8 @@ impl ValidatorPubkeyCacheFile {
Ok(ValidatorPubkeyCache {
pubkeys,
indices,
persitence_file: self,
pubkey_bytes,
backing: PubkeyCacheBacking::File(self),
})
}
}
@@ -231,20 +321,38 @@ fn append_to_file(file: &mut File, index: usize, pubkey: &PublicKeyBytes) -> Res
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType};
use logging::test_logger;
use std::sync::Arc;
use store::HotColdDB;
use tempfile::tempdir;
use types::{
test_utils::{generate_deterministic_keypair, TestingBeaconStateBuilder},
BeaconState, EthSpec, Keypair, MainnetEthSpec,
test_utils::generate_deterministic_keypair, BeaconState, EthSpec, Keypair, MainnetEthSpec,
};
fn get_state(validator_count: usize) -> (BeaconState<MainnetEthSpec>, Vec<Keypair>) {
let spec = MainnetEthSpec::default_spec();
let builder =
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, &spec);
builder.build()
type E = MainnetEthSpec;
type T = EphemeralHarnessType<E>;
fn get_state(validator_count: usize) -> (BeaconState<E>, Vec<Keypair>) {
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.default_spec()
.deterministic_keypairs(validator_count)
.fresh_ephemeral_store()
.build();
harness.advance_slot();
(harness.get_current_state(), harness.validator_keypairs)
}
fn check_cache_get(cache: &ValidatorPubkeyCache, keypairs: &[Keypair]) {
fn get_store() -> BeaconStore<T> {
Arc::new(
HotColdDB::open_ephemeral(<_>::default(), E::default_spec(), test_logger()).unwrap(),
)
}
#[allow(clippy::needless_range_loop)]
fn check_cache_get(cache: &ValidatorPubkeyCache<T>, keypairs: &[Keypair]) {
let validator_count = keypairs.len();
for i in 0..validator_count + 1 {
@@ -275,10 +383,9 @@ mod test {
fn basic_operation() {
let (state, keypairs) = get_state(8);
let dir = tempdir().expect("should create tempdir");
let path = dir.path().join("cache.ssz");
let store = get_store();
let mut cache = ValidatorPubkeyCache::new(&state, path).expect("should create cache");
let mut cache = ValidatorPubkeyCache::new(&state, store).expect("should create cache");
check_cache_get(&cache, &keypairs[..]);
@@ -308,16 +415,16 @@ mod test {
fn persistence() {
let (state, keypairs) = get_state(8);
let dir = tempdir().expect("should create tempdir");
let path = dir.path().join("cache.ssz");
let store = get_store();
// Create a new cache.
let cache = ValidatorPubkeyCache::new(&state, &path).expect("should create cache");
let cache = ValidatorPubkeyCache::new(&state, store.clone()).expect("should create cache");
check_cache_get(&cache, &keypairs[..]);
drop(cache);
// Re-init the cache from the file.
let mut cache = ValidatorPubkeyCache::load_from_file(&path).expect("should open cache");
let mut cache =
ValidatorPubkeyCache::load_from_store(store.clone()).expect("should open cache");
check_cache_get(&cache, &keypairs[..]);
// Add some more keypairs.
@@ -329,7 +436,7 @@ mod test {
drop(cache);
// Re-init the cache from the file.
let cache = ValidatorPubkeyCache::load_from_file(&path).expect("should open cache");
let cache = ValidatorPubkeyCache::load_from_store(store).expect("should open cache");
check_cache_get(&cache, &keypairs[..]);
}
@@ -343,10 +450,10 @@ mod test {
append_to_file(&mut file, 0, &pubkey).expect("should write to file");
drop(file);
let cache = ValidatorPubkeyCache::load_from_file(&path).expect("should open cache");
let cache = ValidatorPubkeyCache::<T>::load_from_file(&path).expect("should open cache");
drop(cache);
let mut file = OpenOptions::new()
let mut file = File::options()
.write(true)
.append(true)
.open(&path)
@@ -356,7 +463,7 @@ mod test {
drop(file);
assert!(
ValidatorPubkeyCache::load_from_file(&path).is_err(),
ValidatorPubkeyCache::<T>::load_from_file(&path).is_err(),
"should not parse invalid file"
);
}

View File

@@ -1,13 +1,8 @@
#![cfg(not(debug_assertions))]
#[macro_use]
extern crate lazy_static;
use beacon_chain::{
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy},
StateSkipConfig,
};
use store::config::StoreConfig;
use beacon_chain::test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy};
use beacon_chain::{StateSkipConfig, WhenSlotSkipped};
use lazy_static::lazy_static;
use tree_hash::TreeHash;
use types::{AggregateSignature, EthSpec, Keypair, MainnetEthSpec, RelativeEpoch, Slot};
@@ -25,55 +20,51 @@ lazy_static! {
#[test]
fn produces_attestations() {
let num_blocks_produced = MainnetEthSpec::slots_per_epoch() * 4;
let additional_slots_tested = MainnetEthSpec::slots_per_epoch() * 3;
let harness = BeaconChainHarness::new(
MainnetEthSpec,
KEYPAIRS[..].to_vec(),
StoreConfig::default(),
);
// Skip past the genesis slot.
harness.advance_slot();
harness.extend_chain(
num_blocks_produced as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.default_spec()
.keypairs(KEYPAIRS[..].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let chain = &harness.chain;
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(state.slot, num_blocks_produced, "head should have updated");
assert_ne!(
state.finalized_checkpoint.epoch, 0,
"head should have updated"
);
let current_slot = chain.slot().expect("should get slot");
// Test all valid committee indices for all slots in the chain.
for slot in 0..=current_slot.as_u64() + MainnetEthSpec::slots_per_epoch() * 3 {
// for slot in 0..=current_slot.as_u64() + MainnetEthSpec::slots_per_epoch() * 3 {
for slot in 0..=num_blocks_produced + additional_slots_tested {
if slot > 0 && slot <= num_blocks_produced {
harness.advance_slot();
harness.extend_chain(
1,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
}
let slot = Slot::from(slot);
let state = chain
let mut state = chain
.state_at_slot(slot, StateSkipConfig::WithStateRoots)
.expect("should get state");
let block_slot = if slot > current_slot {
current_slot
} else {
let block_slot = if slot <= num_blocks_produced {
slot
} else {
Slot::from(num_blocks_produced)
};
let block = chain
.block_at_slot(block_slot)
.block_at_slot(block_slot, WhenSlotSkipped::Prev)
.expect("should get block")
.expect("block should not be skipped");
let block_root = block.message.tree_hash_root();
let block_root = block.message().tree_hash_root();
let epoch_boundary_slot = state
.current_epoch()
.start_slot(MainnetEthSpec::slots_per_epoch());
let target_root = if state.slot == epoch_boundary_slot {
let target_root = if state.slot() == epoch_boundary_slot {
block_root
} else {
*state
@@ -81,6 +72,9 @@ fn produces_attestations() {
.expect("should get target block root")
};
state
.build_committee_cache(RelativeEpoch::Current, &harness.chain.spec)
.unwrap();
let committee_cache = state
.committee_cache(RelativeEpoch::Current)
.expect("should get committee_cache");
@@ -118,15 +112,35 @@ fn produces_attestations() {
assert_eq!(data.slot, slot, "bad slot");
assert_eq!(data.beacon_block_root, block_root, "bad block root");
assert_eq!(
data.source, state.current_justified_checkpoint,
data.source,
state.current_justified_checkpoint(),
"bad source"
);
assert_eq!(
data.source, state.current_justified_checkpoint,
data.source,
state.current_justified_checkpoint(),
"bad source"
);
assert_eq!(data.target.epoch, state.current_epoch(), "bad target epoch");
assert_eq!(data.target.root, target_root, "bad target root");
let early_attestation = {
let proto_block = chain.fork_choice.read().get_block(&block_root).unwrap();
chain
.early_attester_cache
.add_head_block(block_root, block.clone(), proto_block, &state, &chain.spec)
.unwrap();
chain
.early_attester_cache
.try_attest(slot, index, &chain.spec)
.unwrap()
.unwrap()
};
assert_eq!(
attestation, early_attestation,
"early attester cache inconsistent"
);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
mod attestation_production;
mod attestation_verification;
mod block_verification;
mod merge;
mod op_verification;
mod payload_invalidation;
mod store_tests;
mod sync_committee_verification;
mod tests;

View File

@@ -0,0 +1,182 @@
#![cfg(not(debug_assertions))] // Tests run too slow in debug.
use beacon_chain::test_utils::BeaconChainHarness;
use execution_layer::test_utils::{generate_pow_block, DEFAULT_TERMINAL_BLOCK};
use types::*;
const VALIDATOR_COUNT: usize = 32;
type E = MainnetEthSpec;
fn verify_execution_payload_chain<T: EthSpec>(chain: &[ExecutionPayload<T>]) {
let mut prev_ep: Option<ExecutionPayload<T>> = None;
for ep in chain {
assert!(*ep != ExecutionPayload::default());
assert!(ep.block_hash != ExecutionBlockHash::zero());
// Check against previous `ExecutionPayload`.
if let Some(prev_ep) = prev_ep {
assert_eq!(prev_ep.block_hash, ep.parent_hash);
assert_eq!(prev_ep.block_number + 1, ep.block_number);
}
prev_ep = Some(ep.clone());
}
}
#[test]
// TODO(merge): This isn't working cause the non-zero values in `initialize_beacon_state_from_eth1`
// are causing failed lookups to the execution node. I need to come back to this.
#[should_panic]
fn merge_with_terminal_block_hash_override() {
let altair_fork_epoch = Epoch::new(0);
let bellatrix_fork_epoch = Epoch::new(0);
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(altair_fork_epoch);
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
let genesis_pow_block_hash = generate_pow_block(
spec.terminal_total_difficulty,
DEFAULT_TERMINAL_BLOCK,
0,
ExecutionBlockHash::zero(),
)
.unwrap()
.block_hash;
spec.terminal_block_hash = genesis_pow_block_hash;
let harness = BeaconChainHarness::builder(E::default())
.spec(spec)
.deterministic_keypairs(VALIDATOR_COUNT)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
assert_eq!(
harness
.execution_block_generator()
.latest_block()
.unwrap()
.block_hash(),
genesis_pow_block_hash,
"pre-condition"
);
assert!(
harness
.chain
.head()
.unwrap()
.beacon_block
.as_merge()
.is_ok(),
"genesis block should be a merge block"
);
let mut execution_payloads = vec![];
for i in 0..E::slots_per_epoch() * 3 {
harness.extend_slots(1);
let block = harness.chain.head().unwrap().beacon_block;
let execution_payload = block.message().body().execution_payload().unwrap().clone();
if i == 0 {
assert_eq!(execution_payload.block_hash, genesis_pow_block_hash);
}
execution_payloads.push(execution_payload);
}
verify_execution_payload_chain(&execution_payloads);
}
#[test]
fn base_altair_merge_with_terminal_block_after_fork() {
let altair_fork_epoch = Epoch::new(4);
let altair_fork_slot = altair_fork_epoch.start_slot(E::slots_per_epoch());
let bellatrix_fork_epoch = Epoch::new(8);
let merge_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch());
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(altair_fork_epoch);
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
let mut execution_payloads = vec![];
let harness = BeaconChainHarness::builder(E::default())
.spec(spec)
.deterministic_keypairs(VALIDATOR_COUNT)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
/*
* Start with the base fork.
*/
assert!(harness.chain.head().unwrap().beacon_block.as_base().is_ok());
/*
* Do the Altair fork.
*/
harness.extend_to_slot(altair_fork_slot);
let altair_head = harness.chain.head().unwrap().beacon_block;
assert!(altair_head.as_altair().is_ok());
assert_eq!(altair_head.slot(), altair_fork_slot);
/*
* Do the merge fork, without a terminal PoW block.
*/
harness.extend_to_slot(merge_fork_slot);
let merge_head = harness.chain.head().unwrap().beacon_block;
assert!(merge_head.as_merge().is_ok());
assert_eq!(merge_head.slot(), merge_fork_slot);
assert_eq!(
*merge_head.message().body().execution_payload().unwrap(),
ExecutionPayload::default()
);
/*
* Next merge block shouldn't include an exec payload.
*/
harness.extend_slots(1);
let one_after_merge_head = harness.chain.head().unwrap().beacon_block;
assert_eq!(
*one_after_merge_head
.message()
.body()
.execution_payload()
.unwrap(),
ExecutionPayload::default()
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 1);
/*
* Trigger the terminal PoW block.
*/
harness
.execution_block_generator()
.move_to_terminal_block()
.unwrap();
/*
* Next merge block should include an exec payload.
*/
for _ in 0..4 {
harness.extend_slots(1);
let block = harness.chain.head().unwrap().beacon_block;
execution_payloads.push(block.message().body().execution_payload().unwrap().clone());
}
verify_execution_payload_chain(&execution_payloads);
}

View File

@@ -2,21 +2,15 @@
#![cfg(not(debug_assertions))]
#[macro_use]
extern crate lazy_static;
use beacon_chain::observed_operations::ObservationOutcome;
use beacon_chain::test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
};
use lazy_static::lazy_static;
use sloggers::{null::NullLoggerBuilder, Build};
use std::sync::Arc;
use store::{LevelDB, StoreConfig};
use tempfile::{tempdir, TempDir};
use types::test_utils::{
AttesterSlashingTestTask, ProposerSlashingTestTask, TestingAttesterSlashingBuilder,
TestingProposerSlashingBuilder, TestingVoluntaryExitBuilder,
};
use types::*;
pub const VALIDATOR_COUNT: usize = 24;
@@ -32,23 +26,22 @@ type TestHarness = BeaconChainHarness<DiskHarnessType<E>>;
type HotColdDB = store::HotColdDB<E, LevelDB<E>, LevelDB<E>>;
fn get_store(db_path: &TempDir) -> Arc<HotColdDB> {
let spec = E::default_spec();
let spec = test_spec::<E>();
let hot_path = db_path.path().join("hot_db");
let cold_path = db_path.path().join("cold_db");
let config = StoreConfig::default();
let log = NullLoggerBuilder.build().expect("logger should build");
Arc::new(
HotColdDB::open(&hot_path, &cold_path, config, spec, log)
.expect("disk store should initialize"),
)
HotColdDB::open(&hot_path, &cold_path, |_, _, _| Ok(()), config, spec, log)
.expect("disk store should initialize")
}
fn get_harness(store: Arc<HotColdDB>, validator_count: usize) -> TestHarness {
let harness = BeaconChainHarness::new_with_disk_store(
MinimalEthSpec,
store,
KEYPAIRS[0..validator_count].to_vec(),
);
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_disk_store(store)
.mock_execution_layer()
.build();
harness.advance_slot();
harness
}
@@ -58,7 +51,7 @@ fn voluntary_exit() {
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
let spec = &harness.chain.spec;
let spec = &harness.chain.spec.clone();
harness.extend_chain(
(E::slots_per_epoch() * (spec.shard_committee_period + 1)) as usize,
@@ -66,21 +59,13 @@ fn voluntary_exit() {
AttestationStrategy::AllValidators,
);
let head_info = harness.chain.head_info().unwrap();
let make_exit = |validator_index: usize, exit_epoch: u64| {
TestingVoluntaryExitBuilder::new(Epoch::new(exit_epoch), validator_index as u64).build(
&KEYPAIRS[validator_index].sk,
&head_info.fork,
head_info.genesis_validators_root,
spec,
)
};
let validator_index1 = VALIDATOR_COUNT - 1;
let validator_index2 = VALIDATOR_COUNT - 2;
let exit1 = make_exit(validator_index1, spec.shard_committee_period);
let exit1 = harness.make_voluntary_exit(
validator_index1 as u64,
Epoch::new(spec.shard_committee_period),
);
// First verification should show it to be fresh.
assert!(matches!(
@@ -100,14 +85,20 @@ fn voluntary_exit() {
));
// A different exit for the same validator should also be detected as a duplicate.
let exit2 = make_exit(validator_index1, spec.shard_committee_period + 1);
let exit2 = harness.make_voluntary_exit(
validator_index1 as u64,
Epoch::new(spec.shard_committee_period + 1),
);
assert!(matches!(
harness.chain.verify_voluntary_exit_for_gossip(exit2),
Ok(ObservationOutcome::AlreadyKnown)
));
// Exit for a different validator should be fine.
let exit3 = make_exit(validator_index2, spec.shard_committee_period);
let exit3 = harness.make_voluntary_exit(
validator_index2 as u64,
Epoch::new(spec.shard_committee_period),
);
assert!(matches!(
harness
.chain
@@ -122,25 +113,11 @@ fn proposer_slashing() {
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
let spec = &harness.chain.spec;
let head_info = harness.chain.head_info().unwrap();
let validator_index1 = VALIDATOR_COUNT - 1;
let validator_index2 = VALIDATOR_COUNT - 2;
let make_slashing = |validator_index: usize| {
TestingProposerSlashingBuilder::double_vote::<E>(
ProposerSlashingTestTask::Valid,
validator_index as u64,
&KEYPAIRS[validator_index].sk,
&head_info.fork,
head_info.genesis_validators_root,
spec,
)
};
let slashing1 = make_slashing(validator_index1);
let slashing1 = harness.make_proposer_slashing(validator_index1 as u64);
// First slashing for this proposer should be allowed.
assert!(matches!(
@@ -173,7 +150,7 @@ fn proposer_slashing() {
));
// Proposer slashing for a different index should be accepted
let slashing3 = make_slashing(validator_index2);
let slashing3 = harness.make_proposer_slashing(validator_index2 as u64);
assert!(matches!(
harness
.chain
@@ -188,9 +165,6 @@ fn attester_slashing() {
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
let spec = &harness.chain.spec;
let head_info = harness.chain.head_info().unwrap();
// First third of the validators
let first_third = (0..VALIDATOR_COUNT as u64 / 3).collect::<Vec<_>>();
@@ -201,25 +175,8 @@ fn attester_slashing() {
// Last half of the validators
let second_half = (VALIDATOR_COUNT as u64 / 2..VALIDATOR_COUNT as u64).collect::<Vec<_>>();
let signer = |idx: u64, message: &[u8]| {
KEYPAIRS[idx as usize]
.sk
.sign(Hash256::from_slice(&message))
};
let make_slashing = |validators| {
TestingAttesterSlashingBuilder::double_vote::<_, E>(
AttesterSlashingTestTask::Valid,
validators,
signer,
&head_info.fork,
head_info.genesis_validators_root,
spec,
)
};
// Slashing for first third of validators should be accepted.
let slashing1 = make_slashing(&first_third);
let slashing1 = harness.make_attester_slashing(first_third);
assert!(matches!(
harness
.chain
@@ -229,7 +186,7 @@ fn attester_slashing() {
));
// Overlapping slashing for first half of validators should also be accepted.
let slashing2 = make_slashing(&first_half);
let slashing2 = harness.make_attester_slashing(first_half);
assert!(matches!(
harness
.chain
@@ -255,7 +212,7 @@ fn attester_slashing() {
));
// Slashing for last half of validators should be accepted (distinct from all existing)
let slashing3 = make_slashing(&second_half);
let slashing3 = harness.make_attester_slashing(second_half);
assert!(matches!(
harness
.chain
@@ -264,7 +221,7 @@ fn attester_slashing() {
ObservationOutcome::New(_)
));
// Slashing for last third (contained in last half) should be rejected.
let slashing4 = make_slashing(&last_third);
let slashing4 = harness.make_attester_slashing(last_third);
assert!(matches!(
harness
.chain

View File

@@ -0,0 +1,695 @@
#![cfg(not(debug_assertions))]
use beacon_chain::{
test_utils::{BeaconChainHarness, EphemeralHarnessType},
BeaconChainError, BlockError, ExecutionPayloadError, HeadInfo, StateSkipConfig,
WhenSlotSkipped, INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
};
use execution_layer::{
json_structures::JsonPayloadAttributesV1, ExecutionLayer, PayloadAttributes,
};
use proto_array::ExecutionStatus;
use slot_clock::SlotClock;
use task_executor::ShutdownReason;
use types::*;
const VALIDATOR_COUNT: usize = 32;
type E = MainnetEthSpec;
#[derive(PartialEq, Clone)]
enum Payload {
Valid,
Invalid {
latest_valid_hash: Option<ExecutionBlockHash>,
},
Syncing,
}
struct InvalidPayloadRig {
harness: BeaconChainHarness<EphemeralHarnessType<E>>,
enable_attestations: bool,
}
impl InvalidPayloadRig {
fn new() -> Self {
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec)
.deterministic_keypairs(VALIDATOR_COUNT)
.mock_execution_layer()
.fresh_ephemeral_store()
.build();
// Move to slot 1.
harness.advance_slot();
Self {
harness,
enable_attestations: false,
}
}
fn enable_attestations(mut self) -> Self {
self.enable_attestations = true;
self
}
fn execution_layer(&self) -> ExecutionLayer {
self.harness.chain.execution_layer.clone().unwrap()
}
fn block_hash(&self, block_root: Hash256) -> ExecutionBlockHash {
self.harness
.chain
.get_block(&block_root)
.unwrap()
.unwrap()
.message()
.body()
.execution_payload()
.unwrap()
.block_hash
}
fn execution_status(&self, block_root: Hash256) -> ExecutionStatus {
self.harness
.chain
.fork_choice
.read()
.get_block(&block_root)
.unwrap()
.execution_status
}
fn fork_choice(&self) {
self.harness.chain.fork_choice().unwrap();
}
fn head_info(&self) -> HeadInfo {
self.harness.chain.head_info().unwrap()
}
fn previous_payload_attributes(&self) -> PayloadAttributes {
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
let json = mock_execution_layer
.server
.take_previous_request()
.expect("no previous request");
let params = json.get("params").expect("no params");
let payload_param_json = params.get(1).expect("no payload param");
let attributes: JsonPayloadAttributesV1 =
serde_json::from_value(payload_param_json.clone()).unwrap();
attributes.into()
}
fn move_to_terminal_block(&self) {
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
mock_execution_layer
.server
.execution_block_generator()
.move_to_terminal_block()
.unwrap();
}
fn build_blocks(&mut self, num_blocks: u64, is_valid: Payload) -> Vec<Hash256> {
(0..num_blocks)
.map(|_| self.import_block(is_valid.clone()))
.collect()
}
fn move_to_first_justification(&mut self, is_valid: Payload) {
let slots_till_justification = E::slots_per_epoch() * 3;
self.build_blocks(slots_till_justification, is_valid);
let justified_checkpoint = self.head_info().current_justified_checkpoint;
assert_eq!(justified_checkpoint.epoch, 2);
}
fn import_block(&mut self, is_valid: Payload) -> Hash256 {
self.import_block_parametric(is_valid, |error| {
matches!(
error,
BlockError::ExecutionPayloadError(
ExecutionPayloadError::RejectedByExecutionEngine { .. }
)
)
})
}
fn block_root_at_slot(&self, slot: Slot) -> Option<Hash256> {
self.harness
.chain
.block_root_at_slot(slot, WhenSlotSkipped::None)
.unwrap()
}
fn import_block_parametric<F: Fn(&BlockError<E>) -> bool>(
&mut self,
is_valid: Payload,
evaluate_error: F,
) -> Hash256 {
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
let head = self.harness.chain.head().unwrap();
let state = head.beacon_state;
let slot = state.slot() + 1;
let (block, post_state) = self.harness.make_block(state, slot);
let block_root = block.canonical_root();
match is_valid {
Payload::Valid | Payload::Syncing => {
if is_valid == Payload::Syncing {
// Importing a payload whilst returning `SYNCING` simulates an EE that obtains
// the block via it's own means (e.g., devp2p).
let should_import_payload = true;
mock_execution_layer
.server
.all_payloads_syncing(should_import_payload);
} else {
mock_execution_layer.server.full_payload_verification();
}
let root = self.harness.process_block(slot, block.clone()).unwrap();
if self.enable_attestations {
let all_validators: Vec<usize> = (0..VALIDATOR_COUNT).collect();
self.harness.attest_block(
&post_state,
block.state_root(),
block_root.into(),
&block,
&all_validators,
);
}
let execution_status = self.execution_status(root.into());
match is_valid {
Payload::Syncing => assert!(execution_status.is_not_verified()),
Payload::Valid => assert!(execution_status.is_valid()),
Payload::Invalid { .. } => unreachable!(),
}
assert_eq!(
self.harness.chain.get_block(&block_root).unwrap().unwrap(),
block,
"block from db must match block imported"
);
}
Payload::Invalid { latest_valid_hash } => {
let latest_valid_hash = latest_valid_hash
.unwrap_or_else(|| self.block_hash(block.message().parent_root()));
mock_execution_layer
.server
.all_payloads_invalid(latest_valid_hash);
match self.harness.process_block(slot, block) {
Err(error) if evaluate_error(&error) => (),
Err(other) => {
panic!("evaluate_error returned false with {:?}", other)
}
Ok(_) => panic!("block with invalid payload was imported"),
};
assert!(
self.harness
.chain
.fork_choice
.read()
.get_block(&block_root)
.is_none(),
"invalid block must not exist in fork choice"
);
assert!(
self.harness.chain.get_block(&block_root).unwrap().is_none(),
"invalid block cannot be accessed via get_block"
);
}
}
block_root
}
}
/// Simple test of the different import types.
#[test]
fn valid_invalid_syncing() {
let mut rig = InvalidPayloadRig::new();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid);
rig.import_block(Payload::Invalid {
latest_valid_hash: None,
});
rig.import_block(Payload::Syncing);
}
/// Ensure that an invalid payload can invalidate its parent too (given the right
/// `latest_valid_hash`.
#[test]
fn invalid_payload_invalidates_parent() {
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid); // Import a valid transition block.
rig.move_to_first_justification(Payload::Syncing);
let roots = vec![
rig.import_block(Payload::Syncing),
rig.import_block(Payload::Syncing),
rig.import_block(Payload::Syncing),
];
let latest_valid_hash = rig.block_hash(roots[0]);
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(latest_valid_hash),
});
assert!(rig.execution_status(roots[0]).is_valid());
assert!(rig.execution_status(roots[1]).is_invalid());
assert!(rig.execution_status(roots[2]).is_invalid());
assert_eq!(rig.head_info().block_root, roots[0]);
}
/// Ensure the client tries to exit when the justified checkpoint is invalidated.
#[test]
fn justified_checkpoint_becomes_invalid() {
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid); // Import a valid transition block.
rig.move_to_first_justification(Payload::Syncing);
let justified_checkpoint = rig.head_info().current_justified_checkpoint;
let parent_root_of_justified = rig
.harness
.chain
.get_block(&justified_checkpoint.root)
.unwrap()
.unwrap()
.parent_root();
let parent_hash_of_justified = rig.block_hash(parent_root_of_justified);
// No service should have triggered a shutdown, yet.
assert!(rig.harness.shutdown_reasons().is_empty());
// Import a block that will invalidate the justified checkpoint.
rig.import_block_parametric(
Payload::Invalid {
latest_valid_hash: Some(parent_hash_of_justified),
},
|error| {
matches!(
error,
// The block import should fail since the beacon chain knows the justified payload
// is invalid.
BlockError::BeaconChainError(BeaconChainError::JustifiedPayloadInvalid { .. })
)
},
);
// The beacon chain should have triggered a shutdown.
assert_eq!(
rig.harness.shutdown_reasons(),
vec![ShutdownReason::Failure(
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON
)]
);
}
/// Ensure that a `latest_valid_hash` for a pre-finality block only reverts a single block.
#[test]
fn pre_finalized_latest_valid_hash() {
let num_blocks = E::slots_per_epoch() * 4;
let finalized_epoch = 2;
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
let mut blocks = vec![];
blocks.push(rig.import_block(Payload::Valid)); // Import a valid transition block.
blocks.extend(rig.build_blocks(num_blocks - 1, Payload::Syncing));
assert_eq!(rig.head_info().finalized_checkpoint.epoch, finalized_epoch);
let pre_finalized_block_root = rig.block_root_at_slot(Slot::new(1)).unwrap();
let pre_finalized_block_hash = rig.block_hash(pre_finalized_block_root);
// No service should have triggered a shutdown, yet.
assert!(rig.harness.shutdown_reasons().is_empty());
// Import a pre-finalized block.
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(pre_finalized_block_hash),
});
// The latest imported block should be the head.
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
// The beacon chain should *not* have triggered a shutdown.
assert_eq!(rig.harness.shutdown_reasons(), vec![]);
// All blocks should still be unverified.
for i in E::slots_per_epoch() * finalized_epoch..num_blocks {
let slot = Slot::new(i);
let root = rig.block_root_at_slot(slot).unwrap();
if slot == 1 {
assert!(rig.execution_status(root).is_valid());
} else {
assert!(rig.execution_status(root).is_not_verified());
}
}
}
/// Ensure that a `latest_valid_hash` will:
///
/// - Invalidate descendants of `latest_valid_root`.
/// - Validate `latest_valid_root` and its ancestors.
#[test]
fn latest_valid_hash_will_validate() {
const LATEST_VALID_SLOT: u64 = 3;
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
let mut blocks = vec![];
blocks.push(rig.import_block(Payload::Valid)); // Import a valid transition block.
blocks.extend(rig.build_blocks(4, Payload::Syncing));
let latest_valid_root = rig
.block_root_at_slot(Slot::new(LATEST_VALID_SLOT))
.unwrap();
let latest_valid_hash = rig.block_hash(latest_valid_root);
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(latest_valid_hash),
});
assert_eq!(rig.head_info().slot, LATEST_VALID_SLOT);
for slot in 0..=5 {
let slot = Slot::new(slot);
let root = if slot > 0 {
// If not the genesis slot, check the blocks we just produced.
blocks[slot.as_usize() - 1]
} else {
// Genesis slot
rig.block_root_at_slot(slot).unwrap()
};
let execution_status = rig.execution_status(root);
if slot > LATEST_VALID_SLOT {
assert!(execution_status.is_invalid())
} else if slot == 0 {
assert!(execution_status.is_irrelevant())
} else {
assert!(execution_status.is_valid())
}
}
}
/// Check behaviour when the `latest_valid_hash` is a junk value.
#[test]
fn latest_valid_hash_is_junk() {
let num_blocks = E::slots_per_epoch() * 5;
let finalized_epoch = 3;
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
let mut blocks = vec![];
blocks.push(rig.import_block(Payload::Valid)); // Import a valid transition block.
blocks.extend(rig.build_blocks(num_blocks, Payload::Syncing));
assert_eq!(rig.head_info().finalized_checkpoint.epoch, finalized_epoch);
// No service should have triggered a shutdown, yet.
assert!(rig.harness.shutdown_reasons().is_empty());
let junk_hash = ExecutionBlockHash::repeat_byte(42);
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(junk_hash),
});
// The latest imported block should be the head.
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
// The beacon chain should *not* have triggered a shutdown.
assert_eq!(rig.harness.shutdown_reasons(), vec![]);
// All blocks should still be unverified.
for i in E::slots_per_epoch() * finalized_epoch..num_blocks {
let slot = Slot::new(i);
let root = rig.block_root_at_slot(slot).unwrap();
if slot == 1 {
assert!(rig.execution_status(root).is_valid());
} else {
assert!(rig.execution_status(root).is_not_verified());
}
}
}
/// Check that descendants of invalid blocks are also invalidated.
#[test]
fn invalidates_all_descendants() {
let num_blocks = E::slots_per_epoch() * 4 + E::slots_per_epoch() / 2;
let finalized_epoch = 2;
let finalized_slot = E::slots_per_epoch() * 2;
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid); // Import a valid transition block.
let blocks = rig.build_blocks(num_blocks, Payload::Syncing);
assert_eq!(rig.head_info().finalized_checkpoint.epoch, finalized_epoch);
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
// Apply a block which conflicts with the canonical chain.
let fork_slot = Slot::new(4 * E::slots_per_epoch() + 3);
let fork_parent_slot = fork_slot - 1;
let fork_parent_state = rig
.harness
.chain
.state_at_slot(fork_parent_slot, StateSkipConfig::WithStateRoots)
.unwrap();
assert_eq!(fork_parent_state.slot(), fork_parent_slot);
let (fork_block, _fork_post_state) = rig.harness.make_block(fork_parent_state, fork_slot);
let fork_block_root = rig.harness.chain.process_block(fork_block).unwrap();
rig.fork_choice();
// The latest valid hash will be set to the grandparent of the fork block. This means that the
// parent of the fork block will become invalid.
let latest_valid_slot = fork_parent_slot - 1;
let latest_valid_root = rig
.harness
.chain
.block_root_at_slot(latest_valid_slot, WhenSlotSkipped::None)
.unwrap()
.unwrap();
assert!(blocks.contains(&latest_valid_root));
let latest_valid_hash = rig.block_hash(latest_valid_root);
// The new block should not become the head, the old head should remain.
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(latest_valid_hash),
});
// The block before the fork should become the head.
assert_eq!(rig.head_info().block_root, latest_valid_root);
// The fork block should be invalidated, even though it's not an ancestor of the block that
// triggered the INVALID response from the EL.
assert!(rig.execution_status(fork_block_root).is_invalid());
for root in blocks {
let slot = rig.harness.chain.get_block(&root).unwrap().unwrap().slot();
// Fork choice doesn't have info about pre-finalization, nothing to check here.
if slot < finalized_slot {
continue;
}
let execution_status = rig.execution_status(root);
if slot <= latest_valid_slot {
// Blocks prior to the latest valid hash are valid.
assert!(execution_status.is_valid());
} else {
// Blocks after the latest valid hash are invalid.
assert!(execution_status.is_invalid());
}
}
}
/// Check that the head will switch after the canonical branch is invalidated.
#[test]
fn switches_heads() {
let num_blocks = E::slots_per_epoch() * 4 + E::slots_per_epoch() / 2;
let finalized_epoch = 2;
let finalized_slot = E::slots_per_epoch() * 2;
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid); // Import a valid transition block.
let blocks = rig.build_blocks(num_blocks, Payload::Syncing);
assert_eq!(rig.head_info().finalized_checkpoint.epoch, finalized_epoch);
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
// Apply a block which conflicts with the canonical chain.
let fork_slot = Slot::new(4 * E::slots_per_epoch() + 3);
let fork_parent_slot = fork_slot - 1;
let fork_parent_state = rig
.harness
.chain
.state_at_slot(fork_parent_slot, StateSkipConfig::WithStateRoots)
.unwrap();
assert_eq!(fork_parent_state.slot(), fork_parent_slot);
let (fork_block, _fork_post_state) = rig.harness.make_block(fork_parent_state, fork_slot);
let fork_parent_root = fork_block.parent_root();
let fork_block_root = rig.harness.chain.process_block(fork_block).unwrap();
rig.fork_choice();
let latest_valid_slot = fork_parent_slot;
let latest_valid_hash = rig.block_hash(fork_parent_root);
// The new block should not become the head, the old head should remain.
assert_eq!(rig.head_info().block_root, *blocks.last().unwrap());
rig.import_block(Payload::Invalid {
latest_valid_hash: Some(latest_valid_hash),
});
// The fork block should become the head.
assert_eq!(rig.head_info().block_root, fork_block_root);
// The fork block has not yet been validated.
assert!(rig.execution_status(fork_block_root).is_not_verified());
for root in blocks {
let slot = rig.harness.chain.get_block(&root).unwrap().unwrap().slot();
// Fork choice doesn't have info about pre-finalization, nothing to check here.
if slot < finalized_slot {
continue;
}
let execution_status = rig.execution_status(root);
if slot <= latest_valid_slot {
// Blocks prior to the latest valid hash are valid.
assert!(execution_status.is_valid());
} else {
// Blocks after the latest valid hash are invalid.
assert!(execution_status.is_invalid());
}
}
}
#[test]
fn invalid_during_processing() {
let mut rig = InvalidPayloadRig::new();
rig.move_to_terminal_block();
let roots = &[
rig.import_block(Payload::Valid),
rig.import_block(Payload::Invalid {
latest_valid_hash: None,
}),
rig.import_block(Payload::Valid),
];
// 0 should be present in the chain.
assert!(rig.harness.chain.get_block(&roots[0]).unwrap().is_some());
// 1 should *not* be present in the chain.
assert_eq!(rig.harness.chain.get_block(&roots[1]).unwrap(), None);
// 2 should be the head.
let head = rig.harness.chain.head_info().unwrap();
assert_eq!(head.block_root, roots[2]);
}
#[test]
fn invalid_after_optimistic_sync() {
let mut rig = InvalidPayloadRig::new().enable_attestations();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid); // Import a valid transition block.
let mut roots = vec![
rig.import_block(Payload::Syncing),
rig.import_block(Payload::Syncing),
rig.import_block(Payload::Syncing),
];
for root in &roots {
assert!(rig.harness.chain.get_block(root).unwrap().is_some());
}
// 2 should be the head.
let head = rig.harness.chain.head_info().unwrap();
assert_eq!(head.block_root, roots[2]);
roots.push(rig.import_block(Payload::Invalid {
latest_valid_hash: Some(rig.block_hash(roots[1])),
}));
// Running fork choice is necessary since a block has been invalidated.
rig.fork_choice();
// 1 should be the head, since 2 was invalidated.
let head = rig.harness.chain.head_info().unwrap();
assert_eq!(head.block_root, roots[1]);
}
#[test]
fn payload_preparation() {
let mut rig = InvalidPayloadRig::new();
rig.move_to_terminal_block();
rig.import_block(Payload::Valid);
let el = rig.execution_layer();
let head = rig.harness.chain.head().unwrap();
let current_slot = rig.harness.chain.slot().unwrap();
assert_eq!(head.beacon_state.slot(), 1);
assert_eq!(current_slot, 1);
let next_slot = current_slot + 1;
let proposer = head
.beacon_state
.get_beacon_proposer_index(next_slot, &rig.harness.chain.spec)
.unwrap();
let fee_recipient = Address::repeat_byte(99);
// Provide preparation data to the EL for `proposer`.
el.update_proposer_preparation_blocking(
Epoch::new(1),
&[ProposerPreparationData {
validator_index: proposer as u64,
fee_recipient,
}],
)
.unwrap();
rig.harness
.chain
.prepare_beacon_proposer_blocking()
.unwrap();
let payload_attributes = PayloadAttributes {
timestamp: rig
.harness
.chain
.slot_clock
.start_of(next_slot)
.unwrap()
.as_secs(),
prev_randao: *head
.beacon_state
.get_randao_mix(head.beacon_state.current_epoch())
.unwrap(),
suggested_fee_recipient: fee_recipient,
};
assert_eq!(rig.previous_payload_attributes(), payload_attributes);
}

View File

@@ -1,160 +0,0 @@
#![cfg(not(debug_assertions))]
#[macro_use]
extern crate lazy_static;
use beacon_chain::{
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy},
BeaconChain, BeaconChainTypes,
};
use sloggers::{null::NullLoggerBuilder, Build};
use std::sync::Arc;
use store::{HotColdDB, LevelDB, StoreConfig};
use tempfile::{tempdir, TempDir};
use types::{EthSpec, Keypair, MinimalEthSpec};
type E = MinimalEthSpec;
// Should ideally be divisible by 3.
pub const VALIDATOR_COUNT: usize = 24;
lazy_static! {
/// A cached set of keys.
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
}
fn get_store(db_path: &TempDir) -> Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>> {
let spec = E::default_spec();
let hot_path = db_path.path().join("hot_db");
let cold_path = db_path.path().join("cold_db");
let config = StoreConfig::default();
let log = NullLoggerBuilder.build().expect("logger should build");
Arc::new(
HotColdDB::open(&hot_path, &cold_path, config, spec, log)
.expect("disk store should initialize"),
)
}
#[test]
fn finalizes_after_resuming_from_db() {
let validator_count = 16;
let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 8;
let first_half = num_blocks_produced / 2;
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let harness = BeaconChainHarness::new_with_disk_store(
MinimalEthSpec,
store.clone(),
KEYPAIRS[0..validator_count].to_vec(),
);
harness.advance_slot();
harness.extend_chain(
first_half as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
assert!(
harness
.chain
.head()
.expect("should read head")
.beacon_state
.finalized_checkpoint
.epoch
> 0,
"the chain should have already finalized"
);
let latest_slot = harness.chain.slot().expect("should have a slot");
harness
.chain
.persist_head_and_fork_choice()
.expect("should persist the head and fork choice");
harness
.chain
.persist_op_pool()
.expect("should persist the op pool");
harness
.chain
.persist_eth1_cache()
.expect("should persist the eth1 cache");
let data_dir = harness.data_dir;
let original_chain = harness.chain;
let resumed_harness = BeaconChainHarness::resume_from_disk_store(
MinimalEthSpec,
store,
KEYPAIRS[0..validator_count].to_vec(),
data_dir,
);
assert_chains_pretty_much_the_same(&original_chain, &resumed_harness.chain);
// Set the slot clock of the resumed harness to be in the slot following the previous harness.
//
// This allows us to produce the block at the next slot.
resumed_harness
.chain
.slot_clock
.set_slot(latest_slot.as_u64() + 1);
resumed_harness.extend_chain(
(num_blocks_produced - first_half) as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
let state = &resumed_harness
.chain
.head()
.expect("should read head")
.beacon_state;
assert_eq!(
state.slot, num_blocks_produced,
"head should be at the current slot"
);
assert_eq!(
state.current_epoch(),
num_blocks_produced / MinimalEthSpec::slots_per_epoch(),
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint.epoch,
state.current_epoch() - 1,
"the head should be justified one behind the current epoch"
);
assert_eq!(
state.finalized_checkpoint.epoch,
state.current_epoch() - 2,
"the head should be finalized two behind the current epoch"
);
}
/// Checks that two chains are the same, for the purpose of this tests.
///
/// Several fields that are hard/impossible to check are ignored (e.g., the store).
fn assert_chains_pretty_much_the_same<T: BeaconChainTypes>(a: &BeaconChain<T>, b: &BeaconChain<T>) {
assert_eq!(a.spec, b.spec, "spec should be equal");
assert_eq!(a.op_pool, b.op_pool, "op_pool should be equal");
assert_eq!(
a.head().unwrap(),
b.head().unwrap(),
"head() should be equal"
);
assert_eq!(a.heads(), b.heads(), "heads() should be equal");
assert_eq!(
a.genesis_block_root, b.genesis_block_root,
"genesis_block_root should be equal"
);
assert!(
*a.fork_choice.read() == *b.fork_choice.read(),
"fork_choice should be equal"
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
#![cfg(not(debug_assertions))]
use beacon_chain::sync_committee_verification::Error as SyncCommitteeError;
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType, RelativeSyncCommittee};
use int_to_bytes::int_to_bytes32;
use lazy_static::lazy_static;
use safe_arith::SafeArith;
use store::{SignedContributionAndProof, SyncCommitteeMessage};
use tree_hash::TreeHash;
use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
use types::{
AggregateSignature, Epoch, EthSpec, Hash256, Keypair, MainnetEthSpec, SecretKey, Slot,
SyncSelectionProof, SyncSubnetId, Unsigned,
};
pub type E = MainnetEthSpec;
pub const VALIDATOR_COUNT: usize = 256;
lazy_static! {
/// A cached set of keys.
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
}
/// Returns a beacon chain harness.
fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<E>> {
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(Epoch::new(0));
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec)
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
harness.advance_slot();
harness
}
/// Returns a sync message that is valid for some slot in the given `chain`.
///
/// Also returns some info about who created it.
fn get_valid_sync_committee_message(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
slot: Slot,
relative_sync_committee: RelativeSyncCommittee,
) -> (SyncCommitteeMessage, usize, SecretKey, SyncSubnetId) {
let head_state = harness
.chain
.head_beacon_state()
.expect("should get head state");
let head_block_root = harness
.chain
.head()
.expect("should get head state")
.beacon_block_root;
let (signature, _) = harness
.make_sync_committee_messages(&head_state, head_block_root, slot, relative_sync_committee)
.get(0)
.expect("sync messages should exist")
.get(0)
.expect("first sync message should exist")
.clone();
(
signature.clone(),
signature.validator_index as usize,
harness.validator_keypairs[signature.validator_index as usize]
.sk
.clone(),
SyncSubnetId::new(0),
)
}
fn get_valid_sync_contribution(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
relative_sync_committee: RelativeSyncCommittee,
) -> (SignedContributionAndProof<E>, usize, SecretKey) {
let head_state = harness
.chain
.head_beacon_state()
.expect("should get head state");
let head_block_root = harness
.chain
.head()
.expect("should get head state")
.beacon_block_root;
let sync_contributions = harness.make_sync_contributions(
&head_state,
head_block_root,
head_state.slot(),
relative_sync_committee,
);
let (_, contribution_opt) = sync_contributions
.get(0)
.expect("sync contributions should exist");
let contribution = contribution_opt
.as_ref()
.cloned()
.expect("signed contribution and proof should exist");
let aggregator_index = contribution.message.aggregator_index as usize;
(
contribution,
aggregator_index,
harness.validator_keypairs[aggregator_index].sk.clone(),
)
}
/// Returns a proof and index for a validator that is **not** an aggregator for the current sync period.
fn get_non_aggregator(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
slot: Slot,
) -> (usize, SecretKey) {
let state = &harness.chain.head().expect("should get head").beacon_state;
let sync_subcommittee_size = E::sync_committee_size()
.safe_div(SYNC_COMMITTEE_SUBNET_COUNT as usize)
.expect("should determine sync subcommittee size");
let sync_committee = state
.current_sync_committee()
.expect("should use altair state")
.clone();
let non_aggregator_index = sync_committee
.pubkeys
.chunks(sync_subcommittee_size)
.enumerate()
.find_map(|(subcommittee_index, subcommittee)| {
subcommittee.iter().find_map(|pubkey| {
let validator_index = harness
.chain
.validator_index(&pubkey)
.expect("should get validator index")
.expect("pubkey should exist in beacon chain");
let selection_proof = SyncSelectionProof::new::<E>(
slot,
subcommittee_index as u64,
&harness.validator_keypairs[validator_index].sk,
&state.fork(),
state.genesis_validators_root(),
&harness.spec,
);
if !selection_proof
.is_aggregator::<E>()
.expect("should determine aggregator")
{
Some(validator_index)
} else {
None
}
})
})
.expect("should find at least one non-aggregator");
let aggregator_sk = harness.validator_keypairs[non_aggregator_index].sk.clone();
(non_aggregator_index, aggregator_sk)
}
/// Tests verification of `SignedContributionAndProof` from the gossip network.
#[test]
fn aggregated_gossip_verification() {
let harness = get_harness(VALIDATOR_COUNT);
let state = harness.get_current_state();
harness.add_attested_blocks_at_slots(
state,
Hash256::zero(),
&[Slot::new(1), Slot::new(2)],
(0..VALIDATOR_COUNT).collect::<Vec<_>>().as_slice(),
);
let current_slot = harness.chain.slot().expect("should get slot");
let (valid_aggregate, aggregator_index, aggregator_sk) =
get_valid_sync_contribution(&harness, RelativeSyncCommittee::Current);
macro_rules! assert_invalid {
($desc: tt, $attn_getter: expr, $($error: pat_param) |+ $( if $guard: expr )?) => {
assert!(
matches!(
harness
.chain
.verify_sync_contribution_for_gossip($attn_getter)
.err()
.expect(&format!(
"{} should error during verify_sync_contribution_for_gossip",
$desc
)),
$( $error ) |+ $( if $guard )?
),
"case: {}",
$desc,
);
};
}
/*
* The following two tests ensure:
*
* The contribution's slot is for the current slot, i.e. contribution.slot == current_slot
* (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
*/
let future_slot = current_slot + 1;
assert_invalid!(
"aggregate from future slot",
{
let mut a = valid_aggregate.clone();
a.message.contribution.slot = future_slot;
a
},
SyncCommitteeError::FutureSlot { message_slot, latest_permissible_slot }
if message_slot == future_slot && latest_permissible_slot == current_slot
);
let early_slot = current_slot
.as_u64()
// Subtract an additional slot since the harness will be exactly on the start of the
// slot and the propagation tolerance will allow an extra slot.
.checked_sub(2)
.expect("chain is not sufficiently deep for test")
.into();
assert_invalid!(
"aggregate from past slot",
{
let mut a = valid_aggregate.clone();
a.message.contribution.slot = early_slot;
a
},
SyncCommitteeError::PastSlot {
message_slot,
earliest_permissible_slot
}
if message_slot == early_slot
&& earliest_permissible_slot == current_slot - 1
);
/*
* The following test ensures:
*
* The subcommittee index is in the allowed range,
* i.e. `contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT`.
*/
assert_invalid!(
"subcommittee index out of range",
{
let mut a = valid_aggregate.clone();
a.message.contribution.subcommittee_index = SYNC_COMMITTEE_SUBNET_COUNT;
a
},
SyncCommitteeError::InvalidSubcommittee {
subcommittee_index,
subcommittee_size,
}
if subcommittee_index == SYNC_COMMITTEE_SUBNET_COUNT && subcommittee_size == SYNC_COMMITTEE_SUBNET_COUNT
);
/*
* The following test ensures:
*
* The sync contribution has participants.
*/
assert_invalid!(
"aggregate with no participants",
{
let mut a = valid_aggregate.clone();
let aggregation_bits = &mut a.message.contribution.aggregation_bits;
aggregation_bits.difference_inplace(&aggregation_bits.clone());
assert!(aggregation_bits.is_zero());
a.message.contribution.signature = AggregateSignature::infinity();
a
},
SyncCommitteeError::EmptyAggregationBitfield
);
/*
* This test ensures:
*
* The aggregator signature, signed_contribution_and_proof.signature, is valid.
*/
assert_invalid!(
"aggregate with bad signature",
{
let mut a = valid_aggregate.clone();
a.signature = aggregator_sk.sign(Hash256::from_low_u64_be(42));
a
},
SyncCommitteeError::InvalidSignature
);
/*
* The following test ensures:
*
* The contribution_and_proof.selection_proof is a valid signature of the `SyncAggregatorSelectionData`
* derived from the contribution by the validator with index `contribution_and_proof.aggregator_index`.
*/
assert_invalid!(
"aggregate with bad selection proof signature",
{
let mut a = valid_aggregate.clone();
// Generate some random signature until happens to be a valid selection proof. We need
// this in order to reach the signature verification code.
//
// Could run for ever, but that seems _really_ improbable.
let mut i: u64 = 0;
a.message.selection_proof = loop {
i += 1;
let proof: SyncSelectionProof = aggregator_sk
.sign(Hash256::from_slice(&int_to_bytes32(i)))
.into();
if proof
.is_aggregator::<E>()
.expect("should determine aggregator")
{
break proof.into();
}
};
a
},
SyncCommitteeError::InvalidSignature
);
/*
* The following test ensures:
*
* The aggregate signature is valid for the message `beacon_block_root` and aggregate pubkey
* derived from the participation info in `aggregation_bits` for the subcommittee specified by
* the `contribution.subcommittee_index`.
*/
assert_invalid!(
"aggregate with bad aggregate signature",
{
let mut a = valid_aggregate.clone();
let mut agg_sig = AggregateSignature::infinity();
agg_sig.add_assign(&aggregator_sk.sign(Hash256::from_low_u64_be(42)));
a.message.contribution.signature = agg_sig;
a
},
SyncCommitteeError::InvalidSignature
);
let too_high_index = <E as EthSpec>::ValidatorRegistryLimit::to_u64() + 1;
assert_invalid!(
"aggregate with too-high aggregator index",
{
let mut a = valid_aggregate.clone();
a.message.aggregator_index = too_high_index;
a
},
SyncCommitteeError::UnknownValidatorIndex(index)
if index == too_high_index as usize
);
/*
* The following test ensures:
*
* The aggregator's validator index is in the declared subcommittee of the current sync
* committee -- i.e. state.validators[contribution_and_proof.aggregator_index].pubkey in
* get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index).
*/
assert_invalid!(
"aggregate with unknown aggregator index",
{
let mut a = valid_aggregate.clone();
a.message.contribution.subcommittee_index +=1;
a
},
SyncCommitteeError::AggregatorNotInCommittee {
aggregator_index
}
if aggregator_index == valid_aggregate.message.aggregator_index as u64
);
/*
* The following test ensures:
*
* `contribution_and_proof.selection_proof` selects the validator as an aggregator for the
* slot -- i.e. is_sync_committee_aggregator(contribution_and_proof.selection_proof) returns True.
*/
let (non_aggregator_index, non_aggregator_sk) = get_non_aggregator(&harness, current_slot);
assert_invalid!(
"aggregate from non-aggregator",
{
SignedContributionAndProof::from_aggregate(
non_aggregator_index as u64,
valid_aggregate.message.contribution.clone(),
None,
&non_aggregator_sk,
&harness.chain.head_info().expect("should get head info").fork,
harness.chain.genesis_validators_root,
&harness.chain.spec,
)
},
SyncCommitteeError::InvalidSelectionProof {
aggregator_index: index
}
if index == non_aggregator_index as u64
);
// NOTE: from here on, the tests are stateful, and rely on the valid sync contribution having been
// seen. A refactor to give each test case its own state might be nice at some point
harness
.chain
.verify_sync_contribution_for_gossip(valid_aggregate.clone())
.expect("should verify sync contribution");
/*
* The following test ensures:
*
* The sync committee contribution is the first valid contribution received for the aggregator
* with index contribution_and_proof.aggregator_index for the slot contribution.slot and
* subcommittee index contribution.subcommittee_index.
*/
assert_invalid!(
"aggregate that has already been seen",
valid_aggregate.clone(),
SyncCommitteeError::SyncContributionAlreadyKnown(hash)
if hash == valid_aggregate.message.contribution.tree_hash_root()
);
/*
* The following test ensures:
*
* The sync committee contribution is the first valid contribution received for the aggregator
* with index `contribution_and_proof.aggregator_index` for the slot `contribution.slot` and
* subcommittee index `contribution.subcommittee_index`.
*/
assert_invalid!(
"aggregate from aggregator and subcommittee that has already been seen",
{
let mut a = valid_aggregate;
a.message.contribution.beacon_block_root = Hash256::from_low_u64_le(42);
a
},
SyncCommitteeError::AggregatorAlreadyKnown(index)
if index == aggregator_index as u64
);
/*
* The following test ensures that:
*
* A sync committee contribution for the slot before the sync committee period boundary is verified
* using the `head_state.next_sync_committee`.
*/
// Advance to the slot before the 3rd sync committee period because `current_sync_committee = next_sync_committee`
// at genesis.
let state = harness.get_current_state();
let target_slot = Slot::new(
(2 * harness.spec.epochs_per_sync_committee_period.as_u64() * E::slots_per_epoch()) - 1,
);
harness
.add_attested_block_at_slot(target_slot, state, Hash256::zero(), &[])
.expect("should add block");
// **Incorrectly** create a sync contribution using the current sync committee
let (next_valid_contribution, _, _) =
get_valid_sync_contribution(&harness, RelativeSyncCommittee::Current);
assert_invalid!(
"sync contribution created with incorrect sync committee",
next_valid_contribution.clone(),
SyncCommitteeError::InvalidSignature | SyncCommitteeError::AggregatorNotInCommittee { .. }
);
}
/// Tests the verification conditions for sync committee messages on the gossip network.
#[test]
fn unaggregated_gossip_verification() {
let harness = get_harness(VALIDATOR_COUNT);
let state = harness.get_current_state();
harness.add_attested_blocks_at_slots(
state,
Hash256::zero(),
&[Slot::new(1), Slot::new(2)],
(0..VALIDATOR_COUNT).collect::<Vec<_>>().as_slice(),
);
let current_slot = harness.chain.slot().expect("should get slot");
let (valid_sync_committee_message, expected_validator_index, validator_sk, subnet_id) =
get_valid_sync_committee_message(&harness, current_slot, RelativeSyncCommittee::Current);
macro_rules! assert_invalid {
($desc: tt, $attn_getter: expr, $subnet_getter: expr, $($error: pat_param) |+ $( if $guard: expr )?) => {
assert!(
matches!(
harness
.chain
.verify_sync_committee_message_for_gossip($attn_getter, $subnet_getter)
.err()
.expect(&format!(
"{} should error during verify_sync_committee_message_for_gossip",
$desc
)),
$( $error ) |+ $( if $guard )?
),
"case: {}",
$desc,
);
};
}
/*
* The following test ensures:
*
* The subnet_id is valid for the given validator, i.e. subnet_id in
* compute_subnets_for_sync_committee(state, sync_committee_message.validator_index).
*/
let id: u64 = subnet_id.into();
let invalid_subnet_id = SyncSubnetId::new(id + 1);
assert_invalid!(
"invalid subnet id",
{
valid_sync_committee_message.clone()
},
invalid_subnet_id,
SyncCommitteeError::InvalidSubnetId {
received,
expected,
}
if received == invalid_subnet_id && expected.contains(&subnet_id)
);
/*
* The following two tests ensure:
*
* This signature is within a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance from the current slot.
*/
let future_slot = current_slot + 1;
assert_invalid!(
"sync message from future slot",
{
let mut signature = valid_sync_committee_message.clone();
signature.slot = future_slot;
signature
},
subnet_id,
SyncCommitteeError::FutureSlot {
message_slot,
latest_permissible_slot,
}
if message_slot == future_slot && latest_permissible_slot == current_slot
);
// Subtract an additional slot since the harness will be exactly on the start of the
// slot and the propagation tolerance will allow an extra slot.
let early_slot = current_slot
.as_u64()
.checked_sub(2)
.expect("chain is not sufficiently deep for test")
.into();
assert_invalid!(
"sync message from past slot",
{
let mut signature = valid_sync_committee_message.clone();
signature.slot = early_slot;
signature
},
subnet_id,
SyncCommitteeError::PastSlot {
message_slot,
earliest_permissible_slot,
}
if message_slot == early_slot && earliest_permissible_slot == current_slot - 1
);
/*
* The following test ensures that:
*
* The signature is valid for the message beacon_block_root for the validator referenced by
* validator_index.
*/
assert_invalid!(
"sync message with bad signature",
{
let mut sync_message = valid_sync_committee_message.clone();
sync_message.signature = validator_sk.sign(Hash256::from_low_u64_le(424242));
sync_message
},
subnet_id,
SyncCommitteeError::InvalidSignature
);
harness
.chain
.verify_sync_committee_message_for_gossip(valid_sync_committee_message.clone(), subnet_id)
.expect("valid sync message should be verified");
/*
* The following test ensures that:
*
* There has been no other valid sync committee message for the declared slot for the
* validator referenced by sync_committee_message.validator_index.
*/
assert_invalid!(
"sync message that has already been seen",
valid_sync_committee_message,
subnet_id,
SyncCommitteeError::PriorSyncCommitteeMessageKnown {
validator_index,
slot,
}
if validator_index == expected_validator_index as u64 && slot == current_slot
);
/*
* The following test ensures that:
*
* A sync committee message for the slot before the sync committee period boundary is verified
* using the `head_state.next_sync_committee`.
*/
// Advance to the slot before the 3rd sync committee period because `current_sync_committee = next_sync_committee`
// at genesis.
let state = harness.get_current_state();
let target_slot = Slot::new(
(2 * harness.spec.epochs_per_sync_committee_period.as_u64() * E::slots_per_epoch()) - 1,
);
harness
.add_attested_block_at_slot(target_slot, state, Hash256::zero(), &[])
.expect("should add block");
// **Incorrectly** create a sync message using the current sync committee
let (next_valid_sync_committee_message, _, _, next_subnet_id) =
get_valid_sync_committee_message(&harness, target_slot, RelativeSyncCommittee::Current);
assert_invalid!(
"sync message on incorrect subnet",
next_valid_sync_committee_message.clone(),
next_subnet_id,
SyncCommitteeError::InvalidSubnetId {
received,
expected,
}
if received == subnet_id && !expected.contains(&subnet_id)
);
}

View File

@@ -1,19 +1,18 @@
#![cfg(not(debug_assertions))]
#[macro_use]
extern crate lazy_static;
use beacon_chain::{
attestation_verification::Error as AttnError,
test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType, OP_POOL_DB_KEY,
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
OP_POOL_DB_KEY,
},
StateSkipConfig, WhenSlotSkipped,
};
use lazy_static::lazy_static;
use operation_pool::PersistedOperationPool;
use state_processing::{
per_slot_processing, per_slot_processing::Error as SlotProcessingError, EpochProcessingError,
};
use store::config::StoreConfig;
use types::{BeaconStateError, EthSpec, Hash256, Keypair, MinimalEthSpec, RelativeEpoch, Slot};
// Should ideally be divisible by 3.
@@ -24,12 +23,13 @@ lazy_static! {
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
}
fn get_harness(validator_count: usize) -> BeaconChainHarness<HarnessType<MinimalEthSpec>> {
let harness = BeaconChainHarness::new(
MinimalEthSpec,
KEYPAIRS[0..validator_count].to_vec(),
StoreConfig::default(),
);
fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<MinimalEthSpec>> {
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
harness.advance_slot();
@@ -39,7 +39,7 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness<HarnessType<Minimal
#[test]
fn massive_skips() {
let harness = get_harness(8);
let spec = &MinimalEthSpec::default_spec();
let spec = &harness.chain.spec;
let mut state = harness.chain.head().expect("should get head").beacon_state;
// Run per_slot_processing until it returns an error.
@@ -50,7 +50,7 @@ fn massive_skips() {
}
};
assert!(state.slot > 1, "the state should skip at least one slot");
assert!(state.slot() > 1, "the state should skip at least one slot");
assert_eq!(
error,
SlotProcessingError::EpochProcessingError(EpochProcessingError::BeaconStateError(
@@ -75,13 +75,13 @@ fn iterators() {
let block_roots: Vec<(Hash256, Slot)> = harness
.chain
.rev_iter_block_roots()
.forwards_iter_block_roots(Slot::new(0))
.expect("should get iter")
.map(Result::unwrap)
.collect();
let state_roots: Vec<(Hash256, Slot)> = harness
.chain
.rev_iter_state_roots()
.forwards_iter_state_roots(Slot::new(0))
.expect("should get iter")
.map(Result::unwrap)
.collect();
@@ -110,30 +110,95 @@ fn iterators() {
block_roots.windows(2).for_each(|x| {
assert_eq!(
x[1].1,
x[0].1 - 1,
"block root slots should be decreasing by one"
x[0].1 + 1,
"block root slots should be increasing by one"
)
});
state_roots.windows(2).for_each(|x| {
assert_eq!(
x[1].1,
x[0].1 - 1,
"state root slots should be decreasing by one"
x[0].1 + 1,
"state root slots should be increasing by one"
)
});
let head = &harness.chain.head().expect("should get head");
assert_eq!(
*block_roots.first().expect("should have some block roots"),
*block_roots.last().expect("should have some block roots"),
(head.beacon_block_root, head.beacon_block.slot()),
"first block root and slot should be for the head block"
"last block root and slot should be for the head block"
);
assert_eq!(
*state_roots.first().expect("should have some state roots"),
(head.beacon_state_root, head.beacon_state.slot),
"first state root and slot should be for the head state"
*state_roots.last().expect("should have some state roots"),
(head.beacon_state_root(), head.beacon_state.slot()),
"last state root and slot should be for the head state"
);
}
#[test]
fn find_reorgs() {
let num_blocks_produced = MinimalEthSpec::slots_per_historical_root() + 1;
let harness = get_harness(VALIDATOR_COUNT);
harness.extend_chain(
num_blocks_produced as usize,
BlockStrategy::OnCanonicalHead,
// No need to produce attestations for this test.
AttestationStrategy::SomeValidators(vec![]),
);
let head_state = harness.chain.head_beacon_state().unwrap();
let head_slot = head_state.slot();
let genesis_state = harness
.chain
.state_at_slot(Slot::new(0), StateSkipConfig::WithStateRoots)
.unwrap();
// because genesis is more than `SLOTS_PER_HISTORICAL_ROOT` away, this should return with the
// finalized slot.
assert_eq!(
harness
.chain
.find_reorg_slot(&genesis_state, harness.chain.genesis_block_root)
.unwrap(),
head_state
.finalized_checkpoint()
.epoch
.start_slot(MinimalEthSpec::slots_per_epoch())
);
// test head
assert_eq!(
harness
.chain
.find_reorg_slot(
&head_state,
harness.chain.head_beacon_block().unwrap().canonical_root()
)
.unwrap(),
head_slot
);
// Re-org back to the slot prior to the head.
let prev_slot = head_slot - Slot::new(1);
let prev_state = harness
.chain
.state_at_slot(prev_slot, StateSkipConfig::WithStateRoots)
.unwrap();
let prev_block_root = harness
.chain
.block_root_at_slot(prev_slot, WhenSlotSkipped::None)
.unwrap()
.unwrap();
assert_eq!(
harness
.chain
.find_reorg_slot(&prev_state, prev_block_root)
.unwrap(),
prev_slot
);
}
@@ -170,7 +235,7 @@ fn chooses_fork() {
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(
state.slot,
state.slot(),
Slot::from(initial_blocks + honest_fork_blocks),
"head should be at the current slot"
);
@@ -201,7 +266,8 @@ fn finalizes_with_full_participation() {
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(
state.slot, num_blocks_produced,
state.slot(),
num_blocks_produced,
"head should be at the current slot"
);
assert_eq!(
@@ -210,12 +276,12 @@ fn finalizes_with_full_participation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint.epoch,
state.current_justified_checkpoint().epoch,
state.current_epoch() - 1,
"the head should be justified one behind the current epoch"
);
assert_eq!(
state.finalized_checkpoint.epoch,
state.finalized_checkpoint().epoch,
state.current_epoch() - 2,
"the head should be finalized two behind the current epoch"
);
@@ -239,7 +305,8 @@ fn finalizes_with_two_thirds_participation() {
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(
state.slot, num_blocks_produced,
state.slot(),
num_blocks_produced,
"head should be at the current slot"
);
assert_eq!(
@@ -253,12 +320,12 @@ fn finalizes_with_two_thirds_participation() {
// included in blocks during that epoch.
assert_eq!(
state.current_justified_checkpoint.epoch,
state.current_justified_checkpoint().epoch,
state.current_epoch() - 2,
"the head should be justified two behind the current epoch"
);
assert_eq!(
state.finalized_checkpoint.epoch,
state.finalized_checkpoint().epoch,
state.current_epoch() - 4,
"the head should be finalized three behind the current epoch"
);
@@ -283,7 +350,8 @@ fn does_not_finalize_with_less_than_two_thirds_participation() {
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(
state.slot, num_blocks_produced,
state.slot(),
num_blocks_produced,
"head should be at the current slot"
);
assert_eq!(
@@ -292,11 +360,13 @@ fn does_not_finalize_with_less_than_two_thirds_participation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint.epoch, 0,
state.current_justified_checkpoint().epoch,
0,
"no epoch should have been justified"
);
assert_eq!(
state.finalized_checkpoint.epoch, 0,
state.finalized_checkpoint().epoch,
0,
"no epoch should have been finalized"
);
}
@@ -316,7 +386,8 @@ fn does_not_finalize_without_attestation() {
let state = &harness.chain.head().expect("should get head").beacon_state;
assert_eq!(
state.slot, num_blocks_produced,
state.slot(),
num_blocks_produced,
"head should be at the current slot"
);
assert_eq!(
@@ -325,11 +396,13 @@ fn does_not_finalize_without_attestation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint.epoch, 0,
state.current_justified_checkpoint().epoch,
0,
"no epoch should have been justified"
);
assert_eq!(
state.finalized_checkpoint.epoch, 0,
state.finalized_checkpoint().epoch,
0,
"no epoch should have been finalized"
);
}
@@ -354,14 +427,14 @@ fn roundtrip_operation_pool() {
.persist_op_pool()
.expect("should persist op pool");
let key = Hash256::from_slice(&OP_POOL_DB_KEY);
let restored_op_pool = harness
.chain
.store
.get_item::<PersistedOperationPool<MinimalEthSpec>>(&key)
.get_item::<PersistedOperationPool<MinimalEthSpec>>(&OP_POOL_DB_KEY)
.expect("should read db")
.expect("should find op pool")
.into_operation_pool();
.into_operation_pool()
.unwrap();
assert_eq!(harness.chain.op_pool, restored_op_pool);
}
@@ -436,23 +509,16 @@ fn attestations_with_increasing_slots() {
AttestationStrategy::SomeValidators(vec![]),
);
attestations.append(
&mut harness.get_unaggregated_attestations(
&AttestationStrategy::AllValidators,
&harness.chain.head().expect("should get head").beacon_state,
harness
.chain
.head()
.expect("should get head")
.beacon_block_root,
harness
.chain
.head()
.expect("should get head")
.beacon_block
.slot(),
),
);
let head = harness.chain.head().unwrap();
let head_state_root = head.beacon_state_root();
attestations.extend(harness.get_unaggregated_attestations(
&AttestationStrategy::AllValidators,
&head.beacon_state,
head_state_root,
head.beacon_block_root,
head.beacon_block.slot(),
));
harness.advance_slot();
}
@@ -460,7 +526,7 @@ fn attestations_with_increasing_slots() {
for (attestation, subnet_id) in attestations.into_iter().flatten() {
let res = harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id);
.verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id));
let current_slot = harness.chain.slot().expect("should get slot");
let expected_attestation_slot = attestation.data.slot;
@@ -616,3 +682,154 @@ fn produces_and_processes_with_genesis_skip_slots() {
run_skip_slot_test(i)
}
}
#[test]
fn block_roots_skip_slot_behaviour() {
let harness = get_harness(VALIDATOR_COUNT);
// Test should be longer than the block roots to ensure a DB lookup is triggered.
let chain_length = harness
.chain
.head()
.unwrap()
.beacon_state
.block_roots()
.len() as u64
* 3;
let skipped_slots = [1, 6, 7, 10, chain_length];
// Build a chain with some skip slots.
for i in 1..=chain_length {
if i > 1 {
harness.advance_slot();
}
let slot = harness.chain.slot().unwrap().as_u64();
if !skipped_slots.contains(&slot) {
harness.extend_chain(
1,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
}
}
let mut prev_unskipped_root = None;
for target_slot in 0..=chain_length {
if skipped_slots.contains(&target_slot) {
/*
* A skip slot
*/
assert!(
harness
.chain
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::None)
.unwrap()
.is_none(),
"WhenSlotSkipped::None should return None on a skip slot"
);
let skipped_root = harness
.chain
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
.unwrap()
.expect("WhenSlotSkipped::Prev should always return Some");
assert_eq!(
skipped_root,
prev_unskipped_root.expect("test is badly formed"),
"WhenSlotSkipped::Prev should accurately return the prior skipped block"
);
let expected_block = harness.chain.get_block(&skipped_root).unwrap().unwrap();
assert_eq!(
harness
.chain
.block_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
.unwrap()
.unwrap(),
expected_block,
);
assert!(
harness
.chain
.block_at_slot(target_slot.into(), WhenSlotSkipped::None)
.unwrap()
.is_none(),
"WhenSlotSkipped::None should return None on a skip slot"
);
} else {
/*
* Not a skip slot
*/
let skips_none = harness
.chain
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::None)
.unwrap()
.expect("WhenSlotSkipped::None should return Some for non-skipped block");
let skips_prev = harness
.chain
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
.unwrap()
.expect("WhenSlotSkipped::Prev should always return Some");
assert_eq!(
skips_none, skips_prev,
"WhenSlotSkipped::None and WhenSlotSkipped::Prev should be equal on non-skipped slot"
);
let expected_block = harness.chain.get_block(&skips_prev).unwrap().unwrap();
assert_eq!(
harness
.chain
.block_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
.unwrap()
.unwrap(),
expected_block
);
assert_eq!(
harness
.chain
.block_at_slot(target_slot.into(), WhenSlotSkipped::None)
.unwrap()
.unwrap(),
expected_block
);
prev_unskipped_root = Some(skips_prev);
}
}
/*
* A future, non-existent slot.
*/
let future_slot = harness.chain.slot().unwrap() + 1;
assert_eq!(
harness.chain.head().unwrap().beacon_block.slot(),
future_slot - 2,
"test precondition"
);
assert!(
harness
.chain
.block_root_at_slot(future_slot, WhenSlotSkipped::None)
.unwrap()
.is_none(),
"WhenSlotSkipped::None should return None on a future slot"
);
assert!(
harness
.chain
.block_root_at_slot(future_slot, WhenSlotSkipped::Prev)
.unwrap()
.is_none(),
"WhenSlotSkipped::Prev should return None on a future slot"
);
}

View File

@@ -2,10 +2,9 @@
name = "client"
version = "0.2.0"
authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2018"
edition = "2021"
[dev-dependencies]
sloggers = "1.0.0"
toml = "0.5.6"
[dependencies]
@@ -13,31 +12,30 @@ beacon_chain = { path = "../beacon_chain" }
store = { path = "../store" }
network = { path = "../network" }
timer = { path = "../timer" }
eth2_libp2p = { path = "../eth2_libp2p" }
rest_api = { path = "../rest_api" }
lighthouse_network = { path = "../lighthouse_network" }
parking_lot = "0.11.0"
websocket_server = { path = "../websocket_server" }
prometheus = "0.9.0"
types = { path = "../../consensus/types" }
tree_hash = "0.1.0"
eth2_config = { path = "../../common/eth2_config" }
slot_clock = { path = "../../common/slot_clock" }
serde = "1.0.110"
serde_derive = "1.0.110"
error-chain = "0.12.2"
serde_yaml = "0.8.11"
serde = "1.0.116"
serde_derive = "1.0.116"
error-chain = "0.12.4"
slog = { version = "2.5.2", features = ["max_level_trace"] }
slog-async = "2.5.0"
tokio = "0.2.21"
dirs = "2.0.2"
futures = "0.3.5"
reqwest = "0.10.4"
url = "2.1.1"
tokio = "1.14.0"
dirs = "3.0.1"
eth1 = { path = "../eth1" }
eth2 = { path = "../../common/eth2" }
sensitive_url = { path = "../../common/sensitive_url" }
genesis = { path = "../genesis" }
task_executor = { path = "../../common/task_executor" }
environment = { path = "../../lighthouse/environment" }
eth2_ssz = "0.1.2"
lazy_static = "1.4.0"
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
time = "0.2.16"
bus = "2.2.3"
time = "0.3.5"
directory = {path = "../../common/directory"}
http_api = { path = "../http_api" }
http_metrics = { path = "../http_metrics" }
slasher = { path = "../../slasher" }
slasher_service = { path = "../../slasher/service" }
monitoring_api = {path = "../../common/monitoring_api"}
execution_layer = { path = "../execution_layer" }

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,16 @@
use directory::DEFAULT_ROOT_DIR;
use network::NetworkConfig;
use sensitive_url::SensitiveUrl;
use serde_derive::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use types::Graffiti;
pub const DEFAULT_DATADIR: &str = ".lighthouse";
/// The number initial validators when starting the `Minimal`.
const TESTNET_SPEC_CONSTANTS: &str = "minimal";
use types::{Graffiti, PublicKeyBytes};
/// Default directory name for the freezer database under the top-level data dir.
const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db";
/// Defines how the client should initialize the `BeaconChain` and other components.
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClientGenesis {
/// Creates a genesis state as per the 2019 Canada interop specifications.
Interop {
@@ -30,6 +27,15 @@ pub enum ClientGenesis {
/// We include the bytes instead of the `BeaconState<E>` because the `EthSpec` type
/// parameter would be very annoying.
SszBytes { genesis_state_bytes: Vec<u8> },
WeakSubjSszBytes {
genesis_state_bytes: Vec<u8>,
anchor_state_bytes: Vec<u8>,
anchor_block_bytes: Vec<u8>,
},
CheckpointSyncUrl {
genesis_state_bytes: Vec<u8>,
url: SensitiveUrl,
},
}
impl Default for ClientGenesis {
@@ -47,103 +53,137 @@ pub struct Config {
/// Path where the freezer database will be located.
pub freezer_db_path: Option<PathBuf>,
pub log_file: PathBuf,
pub spec_constants: String,
/// If true, the node will use co-ordinated junk for eth1 values.
///
/// This is the method used for the 2019 client interop in Canada.
pub dummy_eth1_backend: bool,
pub sync_eth1_chain: bool,
/// A list of hard-coded forks that will be disabled.
pub disabled_forks: Vec<String>,
/// Graffiti to be inserted everytime we create a block.
pub graffiti: Graffiti,
/// When true, automatically monitor validators using the HTTP API.
pub validator_monitor_auto: bool,
/// A list of validator pubkeys to monitor.
pub validator_monitor_pubkeys: Vec<PublicKeyBytes>,
#[serde(skip)]
/// The `genesis` field is not serialized or deserialized by `serde` to ensure it is defined
/// via the CLI at runtime, instead of from a configuration file saved to disk.
pub genesis: ClientGenesis,
pub store: store::StoreConfig,
pub network: network::NetworkConfig,
pub rest_api: rest_api::Config,
pub websocket_server: websocket_server::Config,
pub chain: beacon_chain::ChainConfig,
pub eth1: eth1::Config,
pub execution_layer: Option<execution_layer::Config>,
pub http_api: http_api::Config,
pub http_metrics: http_metrics::Config,
pub monitoring_api: Option<monitoring_api::Config>,
pub slasher: Option<slasher::Config>,
}
impl Default for Config {
fn default() -> Self {
Self {
data_dir: PathBuf::from(DEFAULT_DATADIR),
data_dir: PathBuf::from(DEFAULT_ROOT_DIR),
db_name: "chain_db".to_string(),
freezer_db_path: None,
log_file: PathBuf::from(""),
genesis: <_>::default(),
store: <_>::default(),
network: NetworkConfig::default(),
rest_api: <_>::default(),
websocket_server: <_>::default(),
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
chain: <_>::default(),
dummy_eth1_backend: false,
sync_eth1_chain: false,
eth1: <_>::default(),
disabled_forks: Vec::new(),
execution_layer: None,
graffiti: Graffiti::default(),
http_api: <_>::default(),
http_metrics: <_>::default(),
monitoring_api: None,
slasher: None,
validator_monitor_auto: false,
validator_monitor_pubkeys: vec![],
}
}
}
impl Config {
/// Get the database path without initialising it.
pub fn get_db_path(&self) -> Option<PathBuf> {
self.get_data_dir()
.map(|data_dir| data_dir.join(&self.db_name))
pub fn get_db_path(&self) -> PathBuf {
self.get_data_dir().join(&self.db_name)
}
/// Get the database path, creating it if necessary.
pub fn create_db_path(&self) -> Result<PathBuf, String> {
let db_path = self
.get_db_path()
.ok_or_else(|| "Unable to locate user home directory")?;
ensure_dir_exists(db_path)
ensure_dir_exists(self.get_db_path())
}
/// Fetch default path to use for the freezer database.
fn default_freezer_db_path(&self) -> Option<PathBuf> {
self.get_data_dir()
.map(|data_dir| data_dir.join(DEFAULT_FREEZER_DB_DIR))
fn default_freezer_db_path(&self) -> PathBuf {
self.get_data_dir().join(DEFAULT_FREEZER_DB_DIR)
}
/// Returns the path to which the client may initialize the on-disk freezer database.
///
/// Will attempt to use the user-supplied path from e.g. the CLI, or will default
/// to a directory in the data_dir if no path is provided.
pub fn get_freezer_db_path(&self) -> Option<PathBuf> {
pub fn get_freezer_db_path(&self) -> PathBuf {
self.freezer_db_path
.clone()
.or_else(|| self.default_freezer_db_path())
.unwrap_or_else(|| self.default_freezer_db_path())
}
/// Get the freezer DB path, creating it if necessary.
pub fn create_freezer_db_path(&self) -> Result<PathBuf, String> {
let freezer_db_path = self
.get_freezer_db_path()
.ok_or_else(|| "Unable to locate user home directory")?;
ensure_dir_exists(freezer_db_path)
ensure_dir_exists(self.get_freezer_db_path())
}
/// Returns the "modern" path to the data_dir.
///
/// See `Self::get_data_dir` documentation for more info.
fn get_modern_data_dir(&self) -> PathBuf {
self.data_dir.clone()
}
/// Returns the "legacy" path to the data_dir.
///
/// See `Self::get_data_dir` documentation for more info.
pub fn get_existing_legacy_data_dir(&self) -> Option<PathBuf> {
dirs::home_dir()
.map(|home_dir| home_dir.join(&self.data_dir))
// Return `None` if the directory does not exists.
.filter(|dir| dir.exists())
// Return `None` if the legacy directory is identical to the modern.
.filter(|dir| *dir != self.get_modern_data_dir())
}
/// Returns the core path for the client.
///
/// Will not create any directories.
pub fn get_data_dir(&self) -> Option<PathBuf> {
dirs::home_dir().map(|home_dir| home_dir.join(&self.data_dir))
///
/// ## Legacy Info
///
/// Legacy versions of Lighthouse did not properly handle relative paths for `--datadir`.
///
/// For backwards compatibility, we still compute the legacy path and check if it exists. If
/// it does exist, we use that directory rather than the modern path.
///
/// For more information, see:
///
/// https://github.com/sigp/lighthouse/pull/2843
fn get_data_dir(&self) -> PathBuf {
let existing_legacy_dir = self.get_existing_legacy_data_dir();
if let Some(legacy_dir) = existing_legacy_dir {
legacy_dir
} else {
self.get_modern_data_dir()
}
}
/// Returns the core path for the client.
///
/// Creates the directory if it does not exist.
pub fn create_data_dir(&self) -> Result<PathBuf, String> {
let path = self
.get_data_dir()
.ok_or_else(|| "Unable to locate user home directory".to_string())?;
ensure_dir_exists(path)
ensure_dir_exists(self.get_data_dir())
}
}
@@ -156,7 +196,6 @@ fn ensure_dir_exists(path: PathBuf) -> Result<PathBuf, String> {
#[cfg(test)]
mod tests {
use super::*;
use toml;
#[test]
fn serde() {

Some files were not shown because too many files have changed in this diff Show More