-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP-3076: Validator client interchange format (slashing protection) #3076
Changes from all commits
4ae6429
eb9c280
34aebec
c4cbcd7
99c1784
36b2fd7
919d7ae
2d4eb5d
77a1f12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,290 @@ | ||||||||
--- | ||||||||
eip: 3076 | ||||||||
title: Validator client interchange format (slashing protection) | ||||||||
author: Michael Sproul (@michaelsproul), Sacha Saint-Leger (@sachayves), Danny Ryan (@djrtwo) | ||||||||
discussions-to: https://ethereum-magicians.org/t/eip-3076-validator-client-interchange-format-slashing-protection/ | ||||||||
status: Draft | ||||||||
type: Standards Track | ||||||||
category: Core | ||||||||
created: 2020-10-27 | ||||||||
--- | ||||||||
|
||||||||
## Simple Summary | ||||||||
|
||||||||
A JSON interchange format for Serenity (eth2) that contains the necessary slashing protection information required to safely migrate keys between clients. | ||||||||
|
||||||||
## Abstract | ||||||||
|
||||||||
A standard format for transferring a key's signing history allows validators to easily switch between clients without the risk of signing conflicting messages. While a [common keystore format](https://eips.ethereum.org/EIPS/eip-2335) provides part of the solution, it does not contain any information about a key's signing history. For a validator moving their keys from client A to client B, this can lead to scenarios in which client B inadvertently signs a message that conflicts with an earlier message signed with client A. | ||||||||
|
||||||||
We propose a database interchange format that helps solve this problem. The format contains a record of all blocks and attestations signed by a set of validators. | ||||||||
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This content currently feels much more like motivation rather than an abstract. Ideally, the abstract is a human readable and terse version of the specification. A couple paragraphs that gives the reader the highlights of the specification without getting bogged down in the edge cases and minutia. Someone may read the abstract to try to "get the gist" of the specification and decide whether this is an EIP that they want to implement. |
||||||||
|
||||||||
## Motivation | ||||||||
|
||||||||
Eth2 penalises validators for voting in ways that could result in two different versions of the chain being finalised. These types of penalties are called slashings. | ||||||||
|
||||||||
> The two main ways a validator can be slashed are | ||||||||
> | ||||||||
> 1. **Double voting**: voting for two different blocks during the same epoch. | ||||||||
> 2. **Surround voting**: voting for one version of reality, and later voting for another version in a way that doesn't make it clear that they no longer believe in the first. | ||||||||
|
||||||||
For a validator following the protocol correctly, there is, in principle, no risk of being slashed. However, changing clients (from client A to client B, say) can result in a slashing risk if client B is unaware of the blocks and attestations that were signed with client A. | ||||||||
|
||||||||
Specifically, problems can occur if client A and client B do not agree on what the present time is (i.e if their clocks are not synchronised within a certain bound). | ||||||||
|
||||||||
For example, say client A's time is accidentally set to a day in the future (225 epochs), and a validator switches from client A to client B without giving B a record of the blocks and attestations signed with A. | ||||||||
|
||||||||
The validator in question now runs the risk of voting for two different blocks in the same epoch (a slashable offence) for the next 225 epochs (since they've already voted on these epochs with client A, and now stand to vote on them again with client B). | ||||||||
|
||||||||
The above situation may sound far-fetched, but it's actually very similar to the incident that occurred on the eth2 Medalla testnet on 2020-08-14, where a time skew that affected all Prysm clients spiraled into a series of [cascading failures](https://medium.com/prysmatic-labs/eth2-medalla-testnet-incident-f7fbc3cc934a). It resulted in the testnet experiencing several days without finality -- an outcome that could have resulted in tens of millions of dollars in collective penalties had it occurred on mainnet. | ||||||||
|
||||||||
|
||||||||
## Specification | ||||||||
|
||||||||
|
||||||||
### JSON Schema | ||||||||
|
||||||||
A valid interchange file is one that adheres to the following JSON schema: | ||||||||
|
||||||||
```json | ||||||||
{ | ||||||||
"title": "Signing history", | ||||||||
"description": "This schema provides a record of the blocks and attestations signed by a set of validators", | ||||||||
"type": "object", | ||||||||
"properties": { | ||||||||
"metadata": { | ||||||||
"type": "object", | ||||||||
"properties": { | ||||||||
"interchange_format_version": { | ||||||||
"type": "string", | ||||||||
"description": "The version of the interchange format that this document adheres to" | ||||||||
}, | ||||||||
"genesis_validators_root": { | ||||||||
"type": "string", | ||||||||
"description": "Calculated at Genesis time; serves to uniquely identify the chain" | ||||||||
} | ||||||||
}, | ||||||||
"required": [ | ||||||||
"interchange_format_version", | ||||||||
"genesis_validators_root" | ||||||||
] | ||||||||
}, | ||||||||
"data": { | ||||||||
"type": "array", | ||||||||
"items": [ | ||||||||
{ | ||||||||
"type": "object", | ||||||||
"properties": { | ||||||||
"pubkey": { | ||||||||
"type": "string", | ||||||||
"description": "The BLS public key of the validator (encoded as a 0x-prefixed hex string)" | ||||||||
}, | ||||||||
"signed_blocks": { | ||||||||
"type": "array", | ||||||||
"items": [ | ||||||||
{ | ||||||||
"type": "object", | ||||||||
"properties": { | ||||||||
"slot": { | ||||||||
"type": "string", | ||||||||
"description": "The slot number of the block that was signed" | ||||||||
}, | ||||||||
"signing_root": { | ||||||||
"type": "string", | ||||||||
"description": "The output of compute_signing_root(block, domain)" | ||||||||
} | ||||||||
}, | ||||||||
"required": [ | ||||||||
"slot" | ||||||||
] | ||||||||
} | ||||||||
] | ||||||||
}, | ||||||||
"signed_attestations": { | ||||||||
"type": "array", | ||||||||
"items": [ | ||||||||
{ | ||||||||
"type": "object", | ||||||||
"properties": { | ||||||||
"source_epoch": { | ||||||||
"type": "string", | ||||||||
"description": "The attestation.data.source.epoch of the signed attestation" | ||||||||
}, | ||||||||
"target_epoch": { | ||||||||
"type": "string", | ||||||||
"description": "The attestation.data.target.epoch of the signed attestation" | ||||||||
}, | ||||||||
"signing_root": { | ||||||||
"type": "string", | ||||||||
"description": "The output of compute_signing_root(attestation, domain)" | ||||||||
} | ||||||||
}, | ||||||||
"required": [ | ||||||||
"source_epoch", | ||||||||
"target_epoch" | ||||||||
] | ||||||||
} | ||||||||
] | ||||||||
} | ||||||||
}, | ||||||||
"required": [ | ||||||||
"pubkey", | ||||||||
"signed_blocks", | ||||||||
"signed_attestations" | ||||||||
] | ||||||||
} | ||||||||
] | ||||||||
} | ||||||||
}, | ||||||||
"required": [ | ||||||||
"metadata", | ||||||||
"data" | ||||||||
] | ||||||||
} | ||||||||
``` | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Style consistency. |
||||||||
### Example JSON Instance | ||||||||
|
||||||||
|
||||||||
Comment on lines
+146
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Style consistency. |
||||||||
|
||||||||
```json | ||||||||
{ | ||||||||
"metadata": { | ||||||||
"interchange_format_version": "5", | ||||||||
"genesis_validators_root": "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" | ||||||||
}, | ||||||||
"data": [ | ||||||||
{ | ||||||||
"pubkey": "0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed", | ||||||||
"signed_blocks": [ | ||||||||
{ | ||||||||
"slot": "81952", | ||||||||
"signing_root": "0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b" | ||||||||
}, | ||||||||
{ | ||||||||
"slot": "81951", | ||||||||
} | ||||||||
], | ||||||||
"signed_attestations": [ | ||||||||
{ | ||||||||
"source_epoch": "2290", | ||||||||
"target_epoch": "3007", | ||||||||
"signing_root": "0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d" | ||||||||
}, | ||||||||
{ | ||||||||
"source_epoch": "2290", | ||||||||
"target_epoch": "3008", | ||||||||
} | ||||||||
] | ||||||||
} | ||||||||
] | ||||||||
} | ||||||||
``` | ||||||||
|
||||||||
### Conditions | ||||||||
|
||||||||
After importing an interchange file with data field `data`, a signer must respect the following conditions: | ||||||||
|
||||||||
1. Refuse to sign any block that is slashable with respect to the blocks contained in `data.signed_blocks`. For details of what constitutes a slashable block, see [process_proposer_slashing][pps]. If the `signing_root` is absent from a block, a signer must assume that any new block with the same `slot` is slashable with respect to the imported block. | ||||||||
|
||||||||
2. Refuse to sign any block with `slot <= min(b.slot for b in data.signed_blocks if b.pubkey == proposer_pubkey)`, except if it is a repeat signing as determined by the `signing_root`. | ||||||||
|
||||||||
3. Refuse to sign any attestation that is slashable with respect to the attestations contained in `data.signed_attestations`. For details of what constitutes a slashable attestation, see [is_slashable_attestation_data][isad]. | ||||||||
4. Refuse to sign any attestation with source epoch less than the minimum source epoch present in that signer's attestations (as seen in `data.signed_attestations`). In pseudocode: | ||||||||
```python3 | ||||||||
source.epoch < | ||||||||
min(att.source_epoch | ||||||||
for att in data.signed_attestations | ||||||||
if att.pubkey == attester_pubkey) | ||||||||
``` | ||||||||
|
||||||||
5. Refuse to sign any attestation with target epoch less than or equal to the minimum target epoch present in that signer's attestations (as seen in `data.signed_attestations`). In pseudocode: | ||||||||
|
||||||||
```python3 | ||||||||
target_epoch <= | ||||||||
min(att.target_epoch | ||||||||
for att in data.signed_attestations | ||||||||
if att.pubkey == attester_pubkey) | ||||||||
``` | ||||||||
|
||||||||
### Additional Information | ||||||||
|
||||||||
- The `interchange_format_version` version is set to 5. | ||||||||
|
||||||||
- A signed block or attestation's `signing_root` refers to the message data (hash tree root) that gets signed with a BLS signature. It allows validators to re-sign and re-broadcast blocks or attestations if asked. | ||||||||
|
||||||||
- The `signed_blocks` `signing_root`s are calculated using [`compute_signing_root(block, domain)`][csr]: where `block` is the block (of type `BeaconBlock` or `BeaconBlockHeader`) that was signed, and `domain` is equal to [`compute_domain(DOMAIN_BEACON_PROPOSER, fork, metadata.genesis_validators_root)`][cd]. | ||||||||
|
||||||||
- The `signed_attestations` `signing_root`s are calculated using [`compute_signing_root(attestation, domain)`][csr]: where `attestation` is the attestation (of type `AttestationData`) that was signed, and `domain` is equal to [`compute_domain(DOMAIN_BEACON_ATTESTER, fork, metadata.genesis_validators_root)`][cd]. | ||||||||
|
||||||||
[pps]: https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/beacon-chain.md#proposer-slashings | ||||||||
[isad]: https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/beacon-chain.md#is_slashable_attestation_data | ||||||||
[csr]: https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/beacon-chain.md#compute_signing_root | ||||||||
[cd]: https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/beacon-chain.md#compute_domain | ||||||||
Comment on lines
+219
to
+222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these links likely to go stale? Normally we prefer to have everything inlined and avoid linking externally partially to avoid having to deal with dead links. Linking to the ETH2 specs repository seems a bit special, but I still have a concern over links breaking and EIP maintainers having to then deal with that down the road. This is especially true with the links being to Ideal situation would be that the contents at the other end of these links be inlined, but I don't know if that is reasonable or not in this context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The links use a GitHub tag rather than a branch name, so will only go stale if the tag gets deleted (which I hope is unlikely). Our intent here was to link to the latest version of the spec at time of writing, and then update the tag to |
||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Style consistency. |
||||||||
|
||||||||
## Rationale | ||||||||
|
||||||||
The specification is designed to be flexible enough to support the full variety of slashing protection strategies that clients may implement. | ||||||||
|
||||||||
An earlier design encompassed two separate formats to achieve the same goal: `complete` and `minimal` (for the complete changelog [see here](https://hackmd.io/@sproul/Bk0Y0qdGD)). | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is best to just explain the rationale for a decision and leave it at that. Linking out to previous implementations will almost certainly result in dead links and for future readers it can be confusing. In this case, something like this seems like it would be fine:
Suggested change
|
||||||||
|
||||||||
The `complete` format was essentially the same as the format outlined in this document (without [conditions](#conditions) 2, 4 and 5). Its goal was to mimic the structure of a slashing protection database that prevents slashing by recording every message signed. | ||||||||
|
||||||||
The purpose of the `minimal` format was to allow for a simpler slashing protection strategy, whereby the only thing that needed to be recorded was the `slot` number of the most recently signed `block`, along with the `source` and `target` `epoch` numbers of the most recently signed attestation (while refusing to sign messages prior to these). | ||||||||
|
||||||||
> The advantage of the `complete` format was that it prevented false positives (meaning that it only prevented a validator from signing if it is guaranteed to incur a slashing otherwise). `complete` also supported republishing of identical messages if the `signing_root` is saved. The advantage of the `minimal` format was simplicity of implementation and succinctness of representation. | ||||||||
|
||||||||
However, in the process of fleshing out this EIP, we realised that we could add a couple of [conditions](#conditions) to `complete` that would allow it to cover the `minimal` case entirely. | ||||||||
|
||||||||
Whilst the format outlined in this specification is most similar to the old `complete`, clients who wish to follow a simpler slashing protection strategy (i.e. the old `minimal`), can do so by simply inserting a "fake" `signed_attestation` (with the correct `source_epoch` and `target_epoch` numbers), and a "fake" `signed_block` (with the correct `slot` number). | ||||||||
|
||||||||
Most fields in the JSON schema are strings. For fields in which it is possible to encode the value as either a string or an integer, strings were chosen. This choice was made in order to avoid issues with different languages supporting different ranges of integers (specifically JavaScript, where the `number` type is a 64-bit float). If a validator is yet to sign a block or attestation, the relevant list is simply left empty. | ||||||||
|
||||||||
Of the existing clients at time of writing, all clients are implementing `v4` (`"interchange_format_version": "4"`) of the specification outlined above. | ||||||||
|
||||||||
The difference between `v4` and `v5` is simply cosmetic -- in `v5`, the `interchange_format` field has been removed from `metadata` because there is no longer a need to cater for two different formats. | ||||||||
|
||||||||
The `interchange_format_version` is set to 5 because the specification went through several breaking changes during its design, incorporating feedback from implementers. | ||||||||
|
||||||||
## Backwards Compatibility | ||||||||
|
||||||||
This specification is not backwards-compatible with versions 3 and 4 of the draft format that have been circulating amongst client teams the last few months. However, we expect clients will update to `v5` for mainnet launch. | ||||||||
|
||||||||
|
||||||||
## Test Cases | ||||||||
|
||||||||
The relevant test cases can be found in [this repository](https://github.com/eth2-clients/slashing-protection-interchange-tests). | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any chance we could just inline these into the EIP rather than having them be linked externally? Can we just copy them over to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! I'm happy for them to live within the EIP. We'll push them shortly. |
||||||||
|
||||||||
|
||||||||
## Implementation | ||||||||
|
||||||||
Implementations exist for the following clients: | ||||||||
- [Teku](https://github.com/ConsenSys/teku/pull/2820) | ||||||||
- [Lighthouse](https://github.com/sigp/lighthouse/pull/1544) | ||||||||
- [Prysm](https://github.com/prysmaticlabs/prysm/pull/7518) | ||||||||
- [Nimbus](https://github.com/status-im/nimbus-eth2/pull/1643) | ||||||||
Comment on lines
+259
to
+265
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is currently a bit of a hot topic among the editors at the moment, but this content feels like it would be much more appropriate for the hard fork coordination process than for the EIP. The EIP is a technical specification, and it doesn't really care if anyone has implemented this or everyone has implemented this. Also, when someone is reading this in 5-10 years after all of these clients are no longer maintained and the repositories/organizations have been renamed/deleted then these links will no longer be adding value. Note: As I mentioned, this is currently a bit of a hot topic so not a blocker for draft at the moment. |
||||||||
|
||||||||
## Security Considerations | ||||||||
|
||||||||
In order to minimise risk and complexity, the format has been designed to map cleanly onto the internal database formats used by implementers. Nevertheless, there are a few pitfalls worth illuminating. | ||||||||
|
||||||||
### Advice for Complete-Record Databases | ||||||||
|
||||||||
For implementers who use a complete record of signed messages to implement their slashing protection database, we make the following recommendations: | ||||||||
|
||||||||
* You MUST ensure that, in addition to importing all of the messages from an interchange, all the [conditions](#conditions) are enforced. In particular, conditions (2), (4) and (5) may not have been enforced by your implementation before adopting the interchange format. Our recommendation is to enforce these rules at all times, to keep the implementation clean and minimise the attack surface. For example: your slashing protection mechanism should not sign a block with a slot number less than, or equal to, the minimum slot number of a previously signed block, _irrespective_ of whether that minimum-slot block was imported from an interchange file, or inserted as part of your database's regular operation. | ||||||||
* If your database records the signing roots of messages in addition to their slot/epochs, you should ensure that imported messages without signing roots are assigned a suitable dummy signing root internally. We suggest using a value like `0x0` which is extremely unlikely to collide with any real signing root. | ||||||||
|
||||||||
### Advice for Succinct Databases | ||||||||
|
||||||||
For implementers who wish to implement their slashing protection database by storing only the latest block and attestation for each validator, we make the following recommendations: | ||||||||
|
||||||||
* During import, make sure you take the _maximum_ slot block and _maximum_ source and target attestations for each validator. Although the [conditions](#conditions) require the minimums to be enforced, taking the maximums from an interchange file and merging them with any existing values in the database is the recommended approach. For example, if the interchange file includes blocks for validator `V` at slots 4, 98 and 243, then the latest signed block for validator `V` should be updated to the one from slot 243. However, if the database has already included a block for this validator at a slot greater than 243, for example, slot 351, then the database's existing value should remain unchanged. | ||||||||
|
||||||||
### General Recommendations | ||||||||
|
||||||||
* To avoid exporting an outdated interchange file -- an action which creates a slashing risk -- your implementation should only allow the slashing protection database to be exported when the validator client or signer is _stopped_ -- in other words, when the client or signer is no longer adding new messages to the database. | ||||||||
* Similarly, your implementation should only allow an interchange file to be imported when the validator client is stopped. | ||||||||
|
||||||||
## Copyright | ||||||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that linking to a draft EIP creates a weak dependency here that may cause problems if you want to move this to final before 2335 becomes final. If the only reason for mentioning it is as comparison, I recommend just leaving it out as it isn't really meaningful information for developers 5-10 years in the future who are reading this EIP (target audience).