prysm-pulse/validator/client/slashing_protection_interchange_test.go
Raul Jordan 92932ae58e
[Feature] - Slashing Interchange Support (#8024)
* Change LowestSignedProposal to Also Return a Boolean for Slashing Protection (#8020)

* amend to use bools

* ineff assign

* comment

* Update `LowestSignedTargetEpoch` to include exists (#8004)

* Replace highest with lowerest

* Update validator/db/kv/attestation_history_v2.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/db/kv/attestation_history_v2.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Invert equality for saveLowestSourceTargetToDB

* Add eip checks to ensure epochs cant be lower than db ones

* Should be less than equal to

* Check if epoch exists in DB getters

* Revert run time checks

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>

* Export Attesting History for Slashing Interchange Standard (#8027)

* added in att history checks

* logic for export

* export return nil

* test for export atts

* round trip passes first try!

* rem println

* fix up tests

* pass test

* Validate Proposers Are Not Slashable With Regard to Data Within Slasher Interchange JSON (#8031)

* filter slashable blocks and atts in same json stub

* add filter blocks func

* add test for filtering out the bad public keys

* Export Slashing Protection History Via CLI (#8040)

* include cli entrypoint for history exports

* builds properly

* test to confirm we export the data as expected

* abstract helpers properly

* full test suite

* gaz

* better errors

* marshal ident

* Add the additional eip-3076 attestation checks (#7966)

* Replace highest with lowerest

* Update validator/db/kv/attestation_history_v2.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/db/kv/attestation_history_v2.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Invert equality for saveLowestSourceTargetToDB

* Add eip checks to ensure epochs cant be lower than db ones

* Should be less than equal to

* Check if epoch exists in DB getters

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Add EIP-3076 Invariants for Proposer Slashing Protection (#8067)

* add invariant for proposer protection

* write different test cases

* pass tests

* Add EIP-3076 Interchange JSON CLI command to validator (#7880)

* Import JSON CLI

* CLI impotr

* f

* Begin adding new commands in slashing protection

* Move testing helpers to separate packae

* Add command for importing slashing protection JSONs

* fix import cycle

* fix test

* Undo cleaning changes

* Improvements

* Add better prompts

* Fix prompt

* Fix

* Fix

* Fix

* Fix conflict

* Fix

* Fixes

* Fixes

* Fix exported func

* test func

* Fixes

* fix test

* simplify import and standardize with export

* add round trip test

* true integration test works

* fix up comments

* logrus

* better error

* fix build

* build fix

* Update validator/slashing-protection/cli_export.go

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* Update validator/slashing-protection/cli_import.go

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* fmt

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* Filter Slashable Attester Public Keys in Slashing Interchange Import (#8051)

* filter slashable attesters from the same JSON

* builds

* fix up initially broken test

* circular dep

* import fix

* giz

* added in attesting history package

* add test for filter slashable attester keys

* pass tests

* Save Slashable Keys to Disk in the Validator Client (#8082)

* begin db funcs

* add in test and bucket

* gaz

* rem changes to import

* ineff assign

* add godoc

* Properly Handle Duplicate Public Key Entries in Slashing Interchange Imports (#8089)

* Prevent Blacklisted Public Keys from Slashing Protection Imports from Having Duties at Runtime (#8084)

* tests on update duties

* ensure the slashable public keys are filtered out from update duties via test

* begin test

* attempt test

* rename for better context

* pass tests

* deep source

* ensure tests pass

* Check for Signing Root Mismatch When Submitting Proposals and Importing Proposals in Slashing Interchange (#8085)

* flexible signing root

* add test

* add tests

* fix test

* Preston's comments

* res tests

* ensure we consider the case for minimum proposals

* pass test

* tests passing

* rem unused code

* Set Empty Epochs in Between Attestations as FAR_FUTURE_EPOCH in Attesting History (#8113)

* set target data

* all tests passing

* ineff assign

* signing root

* Add Slashing Interchange, EIP-3076, Spec Tests to Prysm (#7858)

* Add interchange test framework

* add checks for attestations

* Import genesis root if necessary

* flexible signing root

* add test

* Sync

* fix up test build

* only 3 failing tests now

* two failing

* attempting to debug problems in conformity tests

* include latest changes

* protect test in validator/client passing

* pass tests

* imports

* spec tests passing with bazel

* gh archive link to spectests using tar.gz suffix

* rev

* rev more comment changes

* fix sha

* godoc

* add back save

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Implement Migration for Unattested Epochs in Attesting History Database (#8121)

* migrate attesting history backbone done

* begin migration logic

* implement migration logic

* migration test

* add test

* migration logic

* bazel

* migration to its own file

* Handle empty blocks and attestations in interchange json and sort interchange json by public key (#8132)

* Handle empty blocks and attestations in interchange json

* add test

* sort json

* easier empty arrays

* pass test

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* builds

* more tests finally build

* Align Slashing Interchange With Optimized Slashing Protection (#8268)

* attestation history should account for multiple targets per source

* attempt at some fixes

* attempt some test fixes

* experimenting with sorting

* only one more failing test

* tests now pass

* slash protect tests passing

* only few tests now failing

* only spec tests failing now

* spec tests passing

* all tests passing

* helper function for verifying double votes

* use helper

* gaz

* deep source

* tests fixed

* expect specific number of times for domain data calls

* final comments

* Batch Save Imported EIP-3076 Attestations (#8304)

* optimize save

* test added

* add test for sad path

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>

* revert bad find replace

* add comment to db func

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: Ivan Martinez <ivanthegreatdev@gmail.com>
Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
Co-authored-by: Shay Zluf <thezluf@gmail.com>
2021-01-22 17:12:22 -06:00

186 lines
6.4 KiB
Go

package client
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"strings"
"testing"
"github.com/bazelbuild/rules_go/go/tools/bazel"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
interchangeformat "github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format"
)
type eip3076TestCase struct {
Name string `json:"name"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
Steps []struct {
ShouldSucceed bool `json:"should_succeed"`
AllowPartialImport bool `json:"allow_partial_import"`
Interchange struct {
Metadata struct {
InterchangeFormatVersion string `json:"interchange_format_version"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
} `json:"metadata"`
Data []struct {
Pubkey string `json:"pubkey"`
SignedBlocks []struct {
Slot string `json:"slot"`
SigningRoot string `json:"signing_root"`
} `json:"signed_blocks"`
SignedAttestations []struct {
SourceEpoch string `json:"source_epoch"`
TargetEpoch string `json:"target_epoch"`
SigningRoot string `json:"signing_root"`
} `json:"signed_attestations"`
} `json:"data"`
} `json:"interchange"`
Blocks []struct {
Pubkey string `json:"pubkey"`
Slot string `json:"slot"`
SigningRoot string `json:"signing_root"`
ShouldSucceed bool `json:"should_succeed"`
} `json:"blocks"`
Attestations []struct {
Pubkey string `json:"pubkey"`
SourceEpoch string `json:"source_epoch"`
TargetEpoch string `json:"target_epoch"`
SigningRoot string `json:"signing_root"`
ShouldSucceed bool `json:"should_succeed"`
} `json:"attestations"`
} `json:"steps"`
}
func setupEIP3076SpecTests(t *testing.T) []*eip3076TestCase {
testFolders, err := bazel.ListRunfiles()
require.NoError(t, err)
testCases := make([]*eip3076TestCase, 0)
for _, ff := range testFolders {
if strings.Contains(ff.ShortPath, "eip3076_spec_tests") &&
strings.Contains(ff.ShortPath, "generated/") {
enc, err := fileutil.ReadFileAsBytes(ff.Path)
require.NoError(t, err)
testCase := &eip3076TestCase{}
require.NoError(t, json.Unmarshal(enc, testCase))
testCases = append(testCases, testCase)
}
}
return testCases
}
func TestEIP3076SpecTests(t *testing.T) {
config := &featureconfig.Flags{
SlasherProtection: true,
}
reset := featureconfig.InitWithReset(config)
defer reset()
testCases := setupEIP3076SpecTests(t)
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
if tt.Name == "" {
t.Skip("Skipping eip3076TestCase with empty name")
}
for _, step := range tt.Steps {
// Set up validator client, one new validator client per eip3076TestCase.
// This ensures we initialize a new (empty) slashing protection database.
validator, _, _, _ := setup(t)
if tt.GenesisValidatorsRoot != "" {
r, err := interchangeformat.RootFromHex(tt.GenesisValidatorsRoot)
require.NoError(t, validator.db.SaveGenesisValidatorsRoot(context.Background(), r[:]))
require.NoError(t, err)
}
// The eip3076TestCase config contains the interchange config in json.
// This loads the interchange data via ImportStandardProtectionJSON.
interchangeBytes, err := json.Marshal(step.Interchange)
if err != nil {
t.Fatal(err)
}
b := bytes.NewBuffer(interchangeBytes)
if err := interchangeformat.ImportStandardProtectionJSON(context.Background(), validator.db, b); err != nil {
if step.ShouldSucceed {
t.Fatal(err)
}
} else if !step.ShouldSucceed {
require.NotNil(t, err, "import standard protection json should have failed")
}
// This loops through a list of block signings to attempt after importing the interchange data above.
for _, sb := range step.Blocks {
bSlot, err := interchangeformat.Uint64FromString(sb.Slot)
require.NoError(t, err)
pk, err := interchangeformat.PubKeyFromHex(sb.Pubkey)
require.NoError(t, err)
b := testutil.NewBeaconBlock()
b.Block.Slot = bSlot
var signingRoot [32]byte
if sb.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sb.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
}
err = validator.preBlockSignValidations(context.Background(), pk, b.Block, signingRoot)
if sb.ShouldSucceed {
require.NoError(t, err)
} else {
require.NotEqual(t, nil, err, "pre validation should have failed for block")
}
// Only proceed post update if pre validation did not error.
if err == nil {
err = validator.postBlockSignUpdate(context.Background(), pk, b, signingRoot)
if sb.ShouldSucceed {
require.NoError(t, err)
} else {
require.NotEqual(t, nil, err, "post validation should have failed for block")
}
}
}
// This loops through a list of attestation signings to attempt after importing the interchange data above.
for _, sa := range step.Attestations {
target, err := interchangeformat.Uint64FromString(sa.TargetEpoch)
require.NoError(t, err)
source, err := interchangeformat.Uint64FromString(sa.SourceEpoch)
require.NoError(t, err)
pk, err := interchangeformat.PubKeyFromHex(sa.Pubkey)
require.NoError(t, err)
ia := &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
BeaconBlockRoot: make([]byte, 32),
Target: &ethpb.Checkpoint{Epoch: target, Root: make([]byte, 32)},
Source: &ethpb.Checkpoint{Epoch: source, Root: make([]byte, 32)},
},
Signature: make([]byte, 96),
}
var signingRoot [32]byte
if sa.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sa.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
}
err = validator.slashableAttestationCheck(context.Background(), ia, pk, signingRoot)
if sa.ShouldSucceed {
require.NoError(t, err)
} else {
require.NotNil(t, err, "pre validation should have failed for attestation")
}
}
require.NoError(t, err, validator.db.Close())
}
})
}
}