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

[Feature] - Slashing Interchange Support #8024

Merged
merged 45 commits into from Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
65f5122
Change LowestSignedProposal to Also Return a Boolean for Slashing Pro…
rauljordan Dec 2, 2020
fc76731
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 2, 2020
13df6db
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 2, 2020
ae4ab15
Update `LowestSignedTargetEpoch` to include exists (#8004)
terencechain Dec 2, 2020
74c4c87
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 3, 2020
2ed69c1
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 3, 2020
42ce7e0
Export Attesting History for Slashing Interchange Standard (#8027)
rauljordan Dec 3, 2020
7d0dba1
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 3, 2020
091eaaf
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 3, 2020
82b864a
Validate Proposers Are Not Slashable With Regard to Data Within Slash…
rauljordan Dec 4, 2020
e120963
Export Slashing Protection History Via CLI (#8040)
rauljordan Dec 4, 2020
c64de91
Add the additional eip-3076 attestation checks (#7966)
terencechain Dec 5, 2020
2c863cc
Add EIP-3076 Invariants for Proposer Slashing Protection (#8067)
rauljordan Dec 7, 2020
354ed45
Add EIP-3076 Interchange JSON CLI command to validator (#7880)
0xKiwi Dec 8, 2020
776e1ee
fix conflicts
rauljordan Dec 9, 2020
cca701a
Filter Slashable Attester Public Keys in Slashing Interchange Import …
rauljordan Dec 9, 2020
c990f84
Save Slashable Keys to Disk in the Validator Client (#8082)
rauljordan Dec 9, 2020
13e2dde
Properly Handle Duplicate Public Key Entries in Slashing Interchange …
rauljordan Dec 10, 2020
aabb187
Prevent Blacklisted Public Keys from Slashing Protection Imports from…
rauljordan Dec 11, 2020
6af65f7
resolve conflicts
rauljordan Dec 11, 2020
0754f60
ensure tests pass
rauljordan Dec 11, 2020
2276a85
Check for Signing Root Mismatch When Submitting Proposals and Importi…
rauljordan Dec 11, 2020
50b8637
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 11, 2020
43a3e3a
Set Empty Epochs in Between Attestations as FAR_FUTURE_EPOCH in Attes…
rauljordan Dec 14, 2020
7de27a2
Merge branch 'develop' into feature/slashing-interchange
rauljordan Dec 15, 2020
5df44e2
Add Slashing Interchange, EIP-3076, Spec Tests to Prysm (#7858)
terencechain Dec 15, 2020
216f22c
Implement Migration for Unattested Epochs in Attesting History Databa…
rauljordan Dec 15, 2020
07c8103
Handle empty blocks and attestations in interchange json and sort int…
shayzluf Dec 16, 2020
10359e5
resolve conflicts
rauljordan Jan 13, 2021
f416121
builds
rauljordan Jan 13, 2021
bfe630f
more tests finally build
rauljordan Jan 13, 2021
1756b13
fix confs
rauljordan Jan 14, 2021
ab8aa34
merge
rauljordan Jan 19, 2021
965511d
Align Slashing Interchange With Optimized Slashing Protection (#8268)
rauljordan Jan 20, 2021
e416e30
merge from dev
rauljordan Jan 20, 2021
55c75b6
Merge branch 'develop' into feature/slashing-interchange
rauljordan Jan 20, 2021
86fac0d
Merge branch 'develop' into feature/slashing-interchange
rauljordan Jan 20, 2021
28a637f
Merge branch 'develop' into feature/slashing-interchange
rauljordan Jan 21, 2021
6404735
Batch Save Imported EIP-3076 Attestations (#8304)
rauljordan Jan 22, 2021
16cb5f7
Merge branch 'develop' into feature/slashing-interchange
rauljordan Jan 22, 2021
bba4e8c
Merge branch 'develop' into feature/slashing-interchange
rauljordan Jan 22, 2021
e751724
revert bad find replace
rauljordan Jan 22, 2021
15cb30a
add comment to db func
rauljordan Jan 22, 2021
a9f213a
Merge branch 'develop' into feature/slashing-interchange
terencechain Jan 22, 2021
3881dcd
merge
rauljordan Jan 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions WORKSPACE
Expand Up @@ -210,6 +210,21 @@ http_archive(
url = "https://github.com/kubernetes/repo-infra/archive/6537f2101fb432b679f3d103ee729dd8ac5d30a0.tar.gz",
)

http_archive(
name = "eip3076_spec_tests",
build_file_content = """
filegroup(
name = "test_data",
srcs = glob([
"**/*.json",
]),
visibility = ["//visibility:public"],
)
""",
sha256 = "91434d5fd5e1c6eb7b0174fed2afe25e09bddf00e1e4c431db931b2cee4e7773",
url = "https://github.com/eth2-clients/slashing-protection-interchange-tests/archive/b8413ca42dc92308019d0d4db52c87e9e125c4e9.tar.gz",
)

http_archive(
name = "eth2_spec_tests_general",
build_file_content = """
Expand Down
2 changes: 1 addition & 1 deletion shared/promptutil/prompt.go
Expand Up @@ -55,7 +55,7 @@ func DefaultPrompt(promptText, defaultValue string) (string, error) {
if defaultValue != "" {
fmt.Printf("%s %s:\n", promptText, fmt.Sprintf("(%s: %s)", au.BrightGreen("default"), defaultValue))
} else {
fmt.Printf("%s\n", promptText)
fmt.Printf("%s:\n", promptText)
}
scanner := bufio.NewScanner(os.Stdin)
if ok := scanner.Scan(); ok {
Expand Down
20 changes: 16 additions & 4 deletions shared/slashutil/BUILD.bazel
Expand Up @@ -3,15 +3,27 @@ load("@prysm//tools/go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["surround_votes.go"],
srcs = [
"double_votes.go",
"surround_votes.go",
],
importpath = "github.com/prysmaticlabs/prysm/shared/slashutil",
visibility = ["//visibility:public"],
deps = ["@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library"],
deps = [
"//shared/params:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["surround_votes_test.go"],
srcs = [
"double_votes_test.go",
"surround_votes_test.go",
],
embed = [":go_default_library"],
deps = ["@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library"],
deps = [
"//shared/params:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
],
)
16 changes: 16 additions & 0 deletions shared/slashutil/double_votes.go
@@ -0,0 +1,16 @@
package slashutil

import "github.com/prysmaticlabs/prysm/shared/params"

// SigningRootsDiffer verifies that an incoming vs. existing attestation has a different signing root.
// If the existing signing root is empty, then we consider an attestation as different always.
func SigningRootsDiffer(existingSigningRoot, incomingSigningRoot [32]byte) bool {
zeroHash := params.BeaconConfig().ZeroHash
// If the existing signing root is empty, we always consider the incoming
// attestation as a double vote to be safe.
if existingSigningRoot == zeroHash {
return true
}
// Otherwise, we consider any sort of inequality to be a double vote.
return existingSigningRoot != incomingSigningRoot
}
59 changes: 59 additions & 0 deletions shared/slashutil/double_votes_test.go
@@ -0,0 +1,59 @@
package slashutil

import (
"testing"

"github.com/prysmaticlabs/prysm/shared/params"
)

func TestSigningRootsDiffer(t *testing.T) {
type args struct {
existingSigningRoot [32]byte
incomingSigningRoot [32]byte
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Empty existing signing root is slashable",
args: args{
existingSigningRoot: params.BeaconConfig().ZeroHash,
incomingSigningRoot: [32]byte{1},
},
want: true,
},
{
name: "Non-empty, different existing signing root is slashable",
args: args{
existingSigningRoot: [32]byte{2},
incomingSigningRoot: [32]byte{1},
},
want: true,
},
{
name: "Non-empty, same existing signing root and incoming signing root is not slashable",
args: args{
existingSigningRoot: [32]byte{2},
incomingSigningRoot: [32]byte{2},
},
want: false,
},
{
name: "Both empty are considered slashable",
args: args{
existingSigningRoot: params.BeaconConfig().ZeroHash,
incomingSigningRoot: params.BeaconConfig().ZeroHash,
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := SigningRootsDiffer(tt.args.existingSigningRoot, tt.args.incomingSigningRoot); got != tt.want {
t.Errorf("SigningRootsDiffer() = %v, want %v", got, tt.want)
}
})
}
}
1 change: 1 addition & 0 deletions validator/BUILD.bazel
Expand Up @@ -28,6 +28,7 @@ go_library(
"//validator/db:go_default_library",
"//validator/flags:go_default_library",
"//validator/node:go_default_library",
"//validator/slashing-protection:go_default_library",
"@com_github_joonix_log//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
Expand Down
4 changes: 4 additions & 0 deletions validator/accounts/prompt/prompt.go
Expand Up @@ -18,6 +18,10 @@ import (
const (
// ImportKeysDirPromptText for the import keys cli function.
ImportKeysDirPromptText = "Enter the directory or filepath where your keystores to import are located"
// DataDirDirPromptText for the validator database directory.
DataDirDirPromptText = "Enter the directory of the validator database you would like to use"
// SlashingProtectionJSONPromptText for the EIP-3076 slashing protection JSON prompt.
SlashingProtectionJSONPromptText = "Enter the the filepath of your EIP-3076 Slashing Protection JSON from your previously used validator client"
// WalletDirPromptText for the wallet.
WalletDirPromptText = "Enter a wallet directory"
// SelectAccountsDeletePromptText --
Expand Down
10 changes: 9 additions & 1 deletion validator/client/BUILD.bazel
Expand Up @@ -35,6 +35,7 @@ go_library(
"//shared/mputil:go_default_library",
"//shared/params:go_default_library",
"//shared/rand:go_default_library",
"//shared/slashutil:go_default_library",
"//shared/slotutil:go_default_library",
"//shared/timeutils:go_default_library",
"//shared/traceutil:go_default_library",
Expand All @@ -44,7 +45,7 @@ go_library(
"//validator/graffiti:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/imported:go_default_library",
"//validator/slashing-protection:go_default_library",
"//validator/slashing-protection/iface:go_default_library",
"@com_github_dgraph_io_ristretto//:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
Expand Down Expand Up @@ -83,9 +84,13 @@ go_test(
"propose_test.go",
"runner_test.go",
"service_test.go",
"slashing_protection_interchange_test.go",
"validator_test.go",
"wait_for_activation_test.go",
],
data = [
"@eip3076_spec_tests//:test_data",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
Expand All @@ -96,6 +101,7 @@ go_test(
"//shared/bytesutil:go_default_library",
"//shared/event:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/fileutil:go_default_library",
"//shared/mock:go_default_library",
"//shared/params:go_default_library",
"//shared/slotutil:go_default_library",
Expand All @@ -107,6 +113,7 @@ go_test(
"//validator/db/testing:go_default_library",
"//validator/graffiti:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/slashing-protection/local/standard-protection-format:go_default_library",
"//validator/testing:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
Expand All @@ -119,6 +126,7 @@ go_test(
"@com_github_tyler_smith_go_bip39//:go_default_library",
"@com_github_wealdtech_go_eth2_util//:go_default_library",
"@in_gopkg_d4l3k_messagediff_v1//:go_default_library",
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
],
)
36 changes: 35 additions & 1 deletion validator/client/attest_protect.go
Expand Up @@ -2,11 +2,13 @@ package client

import (
"context"
"encoding/hex"
"fmt"

"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/slashutil"
"github.com/prysmaticlabs/prysm/validator/db/kv"
"go.opencensus.io/trace"
)
Expand All @@ -26,7 +28,39 @@ func (v *validator) slashableAttestationCheck(
ctx, span := trace.StartSpan(ctx, "validator.postAttSignUpdate")
defer span.End()

fmtKey := fmt.Sprintf("%#x", pubKey[:])
// Based on EIP3076, validator should refuse to sign any attestation with source epoch less
// than the minimum source epoch present in that signer’s attestations.
lowestSourceEpoch, exists, err := v.db.LowestSignedSourceEpoch(ctx, pubKey)
if err != nil {
return err
}
if exists && indexedAtt.Data.Source.Epoch < lowestSourceEpoch {
return fmt.Errorf(
"could not sign attestation lower than lowest source epoch in db, %d < %d",
indexedAtt.Data.Source.Epoch,
lowestSourceEpoch,
)
}
existingSigningRoot, err := v.db.SigningRootAtTargetEpoch(ctx, pubKey, indexedAtt.Data.Target.Epoch)
if err != nil {
return err
}
signingRootsDiffer := slashutil.SigningRootsDiffer(existingSigningRoot, signingRoot)

// Based on EIP3076, validator should refuse to sign any attestation with target epoch less
// than or equal to the minimum target epoch present in that signer’s attestations.
lowestTargetEpoch, exists, err := v.db.LowestSignedTargetEpoch(ctx, pubKey)
if err != nil {
return err
}
if signingRootsDiffer && exists && indexedAtt.Data.Target.Epoch <= lowestTargetEpoch {
return fmt.Errorf(
"could not sign attestation lower than or equal to lowest target epoch in db, %d <= %d",
indexedAtt.Data.Target.Epoch,
lowestTargetEpoch,
)
}
fmtKey := "0x" + hex.EncodeToString(pubKey[:])
slashingKind, err := v.db.CheckSlashableAttestation(ctx, pubKey, signingRoot, indexedAtt)
if err != nil {
if v.emitAccountMetrics {
Expand Down
54 changes: 15 additions & 39 deletions validator/client/attest_protect_test.go
Expand Up @@ -40,39 +40,10 @@ func Test_slashableAttestationCheck(t *testing.T) {
}
mockProtector := &mockSlasher.MockProtector{AllowAttestation: false}
validator.protector = mockProtector
err := validator.slashableAttestationCheck(context.Background(), att, pubKey, [32]byte{})
err := validator.slashableAttestationCheck(context.Background(), att, pubKey, [32]byte{1})
require.ErrorContains(t, failedPostAttSignExternalErr, err)
mockProtector.AllowAttestation = true
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, [32]byte{})
require.NoError(t, err, "Expected allowed attestation not to throw error")
}

func Test_slashableAttestationCheck_Allowed(t *testing.T) {
config := &featureconfig.Flags{
SlasherProtection: false,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, _, finish := setup(t)
defer finish()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: bytesutil.PadTo([]byte("great block"), 32),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: bytesutil.PadTo([]byte("good source"), 32),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: bytesutil.PadTo([]byte("good target"), 32),
},
},
}
fakePubkey := bytesutil.ToBytes48([]byte("test"))
err := validator.slashableAttestationCheck(context.Background(), att, fakePubkey, [32]byte{})
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, [32]byte{1})
require.NoError(t, err, "Expected allowed attestation not to throw error")
}

Expand Down Expand Up @@ -107,21 +78,24 @@ func Test_slashableAttestationCheck_UpdatesLowestSignedEpochs(t *testing.T) {
validator.protector = mockProtector
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch2
&ethpb.DomainRequest{Epoch: 10, Domain: []byte{1, 0, 0, 0}},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
_, sr, err := validator.getDomainAndSigningRoot(ctx, att.Data)
require.NoError(t, err)
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, sr)
require.ErrorContains(t, "rejected", err, "Expected error on post signature update is detected as slashable")
mockProtector.AllowAttestation = true
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, sr)
require.NoError(t, err, "Expected allowed attestation not to throw error")
require.NoError(t, err)
differentSigningRoot := [32]byte{2}
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, differentSigningRoot)
require.ErrorContains(t, "could not sign attestation", err)

e, err := validator.db.LowestSignedSourceEpoch(context.Background(), pubKey)
e, exists, err := validator.db.LowestSignedSourceEpoch(context.Background(), pubKey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, uint64(4), e)
e, err = validator.db.LowestSignedTargetEpoch(context.Background(), pubKey)
e, exists, err = validator.db.LowestSignedTargetEpoch(context.Background(), pubKey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, uint64(10), e)
}

Expand Down Expand Up @@ -184,10 +158,12 @@ func Test_slashableAttestationCheck_GenesisEpoch(t *testing.T) {
fakePubkey := bytesutil.ToBytes48([]byte("test"))
err := validator.slashableAttestationCheck(ctx, att, fakePubkey, [32]byte{})
require.NoError(t, err, "Expected allowed attestation not to throw error")
e, err := validator.db.LowestSignedSourceEpoch(context.Background(), fakePubkey)
e, exists, err := validator.db.LowestSignedSourceEpoch(context.Background(), fakePubkey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, uint64(0), e)
e, err = validator.db.LowestSignedTargetEpoch(context.Background(), fakePubkey)
e, exists, err = validator.db.LowestSignedTargetEpoch(context.Background(), fakePubkey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, uint64(0), e)
}
6 changes: 3 additions & 3 deletions validator/client/attest_test.go
Expand Up @@ -215,7 +215,7 @@ func TestAttestToBlockHead_BlocksDoubleAtt(t *testing.T) {

validator.SubmitAttestation(context.Background(), 30, pubKey)
validator.SubmitAttestation(context.Background(), 30, pubKey)
require.LogsContain(t, hook, failedAttLocalProtectionErr)
require.LogsContain(t, hook, "Failed attestation slashing protection")
}

func TestAttestToBlockHead_BlocksSurroundAtt(t *testing.T) {
Expand Down Expand Up @@ -267,7 +267,7 @@ func TestAttestToBlockHead_BlocksSurroundAtt(t *testing.T) {

validator.SubmitAttestation(context.Background(), 30, pubKey)
validator.SubmitAttestation(context.Background(), 30, pubKey)
require.LogsContain(t, hook, failedAttLocalProtectionErr)
require.LogsContain(t, hook, "Failed attestation slashing protection")
}

func TestAttestToBlockHead_BlocksSurroundedAtt(t *testing.T) {
Expand Down Expand Up @@ -322,7 +322,7 @@ func TestAttestToBlockHead_BlocksSurroundedAtt(t *testing.T) {
}, nil)

validator.SubmitAttestation(context.Background(), 30, pubKey)
require.LogsContain(t, hook, failedAttLocalProtectionErr)
require.LogsContain(t, hook, "Failed attestation slashing protection")
}

func TestAttestToBlockHead_DoesNotAttestBeforeDelay(t *testing.T) {
Expand Down