Skip to content
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

Implement EIP-3076 minimal slashing protection, using a filesystem database #13360

Merged
merged 55 commits into from
Mar 5, 2024

Conversation

nalepae
Copy link
Contributor

@nalepae nalepae commented Dec 18, 2023

What type of PR is this?
Feature

What does this PR do? Why is it needed?
This pull request implements EIP-3076 minimal slashing protection, using a filesystem database.

Which issues(s) does this PR fix?

Fixes #12475

Other notes for review
This PR is designed to be read commit by commit.

This PR implements the flag --enable-minimal-slashing-protection in the validator client.
If this flag is used, then the former BoltDB validator.db database is not used any more.
Instead, a pure filesystem, YAML database is used, using a EIP-3076 minimal slashing protection database.

The filesystem structure is the following:

.
├── configuration.yaml
└── slashing-protection
    ├── 0x80f408a41341416f79e3be605c5f1b960169bea5753b9b8181f7466b4fa0b13addb250c9d068af263f820c113536f9f7.yaml
    ├── 0x84d7fa31925c559ef85531687b190d9299036625a73370153502bf1c086ac7d7e3006c15210c6f9bd4b518c1900e0f45.yaml
    └── 0xa17524bcf06d4fbf042e17dae68637368607f4fc9c28c104f49b12d1e0bed93fa3714f35ff05f4e518f789319c9b8f86.yaml

configuration.yaml contains data relative to:

  • genesis validator root,
  • proposer settings, and
  • graffiti

An example of configuration.yaml file is:

genesisValidatorsRoot: 0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb
proposerSettings:
    proposerconfig:
        0x80f408a41341416f79e3be605c5f1b960169bea5753b9b8181f7466b4fa0b13addb250c9d068af263f820c113536f9f7:
            feerecipient: 0xef01d47ed04b181a2715D4050208f7d6e381bF42
            builder:
                enabled: true
                gaslimit: 30000000
                relays: []
        0xa17524bcf06d4fbf042e17dae68637368607f4fc9c28c104f49b12d1e0bed93fa3714f35ff05f4e518f789319c9b8f86:
            feerecipient: 0x801f87A1e14398e2ec9031CaBd585A84D92BC606
            builder:
                enabled: true
                gaslimit: 30000000
                relays: []
    defaultconfig:
        feerecipient: 0x1268AD189526AC0b386faF06eFfC46779c340eE6
        builder:
            enabled: true
            gaslimit: 30000000
            relays: []
graffiti:
    orderedindex: 0
    filehash: "0x0000000000000000000000000000000000000000000000000000000000000000"

In the slashing-protection directory, each file contains slashing protection data relative a given public key.
The public key itself is the name of the file.

An example of a file in the slashing-protection directory is:

latestSignedBlockSlot: 224123
lastSignedAttestationSourceEpoch: 224990
lastSignedAttestationTargetEpoch: 224991

The content of each field is self-describing.

Regarding anti-slashing strategy:

  • For block proposals: The signature of an incoming block proposal will be refused if the slot of the incoming block is lower or equal to the latestSignedBlockSlot stored in the database.
  • For attestations: The signature of an incoming attestation will be refused if:
    • the source epoch of the incoming attestation is lower to the lastSignedAttestationSourceEpoch stored in the database, or if
    • the target epoch of the incoming attestation is lower or equal to the lastSignedAttestationSourceEpoch stored in the database.

Strategy at VC start:

  • If both minimal, filesystem AND complete, BoltDB databases are found, the VC refuses to start
  • If --enable-minimal-slashing-protection is set AND a complete, BoltDB database is found, then a warning message is displayed indicating to the user how to convert the DB, and the VC continues with the complete, BoltDB database.
  • If --enable-minimal-slashing-protection is NOT set AND a minimal, filesystem database is found, then the VC automatically convert the minimal, filesystem database into a complete, BoltDB one. (No data loss in this way)

To convert a complete database into a minimal one, the user can do:

validator db convert-complete-to-minimal [--datadir <datadir>]

😱 This PR has 10k+ new lines! 😱
Actualy, a lot of modified lines in this PR result in tests transformed from something like:

func test(t *testing.T) {
   ... := setup(t)
   // Content of the test
}

to:

func test(t *testing.T) {
   for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
      ... := setup(t, isSlashingProtectionMinimal)
      // Content of the test
}

Git will consider all wrapped lines as new lines, even if only a few spaces had been added on the left.
For this reason, it is strongly advised to check the Hide whitespace option during the code review:
image

Benchmarks:
We create 2000 public keys, and we call, in parallel, SaveAttestationForPubKey on both complete, BoltDB and minimal, filesystem DBs.

Benchmark results:

  • complete, BoltDB: ~130ms
  • minimal, filesystem: ~127ms

Comments:
For complete, BoltDB database, SaveAttestationForPubKey fills a queue which is dumped into the DB every 100 ms (or if the queue, containing up to 2048 items, is full). It explains why the time needed is >= 100ms. If we decide to save only 10 attestations instead of 2000, the benchmark result is still ~104ms. Obviously, it cannot go under 100ms.

@nalepae nalepae force-pushed the 11616-minimal branch 30 times, most recently from 0bb4184 to c11c02b Compare December 22, 2023 12:40
nalepae added 24 commits March 4, 2024 23:04
Options were:
- `--source` (for source data directory), and
- `--target` (for target data directory)

However, since this command deals with slashing protection, which has
source (epochs) and target (epochs), the initial option names may confuse
the user.

In this commit:
`--source` ==> `--source-data-dir`
`--target` ==> `--target-data-dir`
And delete `CheckSlashableAttestation` from iface.
==> `filesystem` does not depend anymore on `kv`.
==> `iface` does not depend anymore on `kv`.
==> `slashing-protection` does not depend anymore on `kv`.
This way, we can:
- Avoid any circular import for tests.
- Implement once for all `iface.ValidatorDB` implementations
  the `ValidateMetadata`function.
- Have tests (and coverage) of `ValidateMetadata`in
  its own package.

The ideal solution would have been to implement `ValidateMetadata` as
a method with the `iface.ValidatorDB`receiver.
Unfortunately, golang does not allow that.
The whole purpose of this commit is to avoid the `switch validatorDB.(type)`
in `ImportStandardProtectionJSON`.
Before, `Exists` was only able to detect if a file exists.
Now, this function takes an extra `File` or `Directory` argument.
It detects either if a file or a directory exists.

Before, if an error was returned by `os.Stat`, the the file was
considered as non existing.
Now, it is treated as a real error.
To be consistent with `slashing-protection`.
@nalepae nalepae added this pull request to the merge queue Mar 5, 2024
Merged via the queue into develop with commit ef21d3a Mar 5, 2024
17 checks passed
@nalepae nalepae deleted the 11616-minimal branch March 5, 2024 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants