Skip to content

Commit

Permalink
Use new proposal protection format (#7518)
Browse files Browse the repository at this point in the history
* Use new proposal protection format

* Update comments

* Split and merge with new db

* fix tests

* fix test

* optimize domain

* fix validation

* fix validation

* check import error

* fix e2e

* fix old propose tests add ign block test

* constant secret key

* static test for signing

* test domain

* fix testsplit

* gaz

* gaz

* tidy

* raul feedback

* fix tests

* tidy

* added info log for the migration

* gaz

* Update validator/client/propose_protect.go

Co-authored-by: Nishant Das <nishdas93@gmail.com>

* nishant feedback

* import fix

* fix

* remove propose protection flag

* fix block sign test

Co-authored-by: Nishant Das <nishdas93@gmail.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 15, 2020
1 parent daf0b51 commit acf2014
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 64 deletions.
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6Ro
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
Expand Down Expand Up @@ -1354,6 +1356,7 @@ google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEG
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
16 changes: 8 additions & 8 deletions validator/client/propose.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
}

// Sign returned block from beacon node
sig, err := v.signBlock(ctx, pubKey, epoch, b)
sig, domain, err := v.signBlock(ctx, pubKey, epoch, b)
if err != nil {
log.WithError(err).Error("Failed to sign block")
if v.emitAccountMetrics {
Expand All @@ -85,7 +85,7 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
Signature: sig,
}

if err := v.postBlockSignUpdate(ctx, pubKey, blk); err != nil {
if err := v.postBlockSignUpdate(ctx, pubKey, blk, domain); err != nil {
log.WithField("slot", blk.Block.Slot).WithError(err).Error("Failed post block signing validations")
return
}
Expand Down Expand Up @@ -190,19 +190,19 @@ func (v *validator) signRandaoReveal(ctx context.Context, pubKey [48]byte, epoch
}

// Sign block with proposer domain and private key.
func (v *validator) signBlock(ctx context.Context, pubKey [48]byte, epoch uint64, b *ethpb.BeaconBlock) ([]byte, error) {
func (v *validator) signBlock(ctx context.Context, pubKey [48]byte, epoch uint64, b *ethpb.BeaconBlock) ([]byte, *ethpb.DomainResponse, error) {
domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBeaconProposer[:])
if err != nil {
return nil, errors.Wrap(err, domainDataErr)
return nil, nil, errors.Wrap(err, domainDataErr)
}
if domain == nil {
return nil, errors.New(domainDataErr)
return nil, nil, errors.New(domainDataErr)
}

var sig bls.Signature
blockRoot, err := helpers.ComputeSigningRoot(b, domain.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, signingRootErr)
return nil, nil, errors.Wrap(err, signingRootErr)
}
sig, err = v.keyManagerV2.Sign(ctx, &validatorpb.SignRequest{
PublicKey: pubKey[:],
Expand All @@ -211,9 +211,9 @@ func (v *validator) signBlock(ctx context.Context, pubKey [48]byte, epoch uint64
Object: &validatorpb.SignRequest_Block{Block: b},
})
if err != nil {
return nil, errors.Wrap(err, "could not sign block proposal")
return nil, nil, errors.Wrap(err, "could not sign block proposal")
}
return sig.Marshal(), nil
return sig.Marshal(), domain, nil
}

// Sign voluntary exit with proposer domain and private key.
Expand Down
17 changes: 7 additions & 10 deletions validator/client/propose_protect.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"bytes"
"context"
"fmt"

Expand All @@ -18,16 +19,15 @@ var failedPostBlockSignErr = "made a double proposal, considered slashable by re

func (v *validator) preBlockSignValidations(ctx context.Context, pubKey [48]byte, block *ethpb.BeaconBlock) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
epoch := helpers.SlotToEpoch(block.Slot)
slotBits, err := v.db.ProposalHistoryForEpoch(ctx, pubKey[:], epoch)
signingRoot, err := v.db.ProposalHistoryForSlot(ctx, pubKey[:], block.Slot)
if err != nil {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.Wrap(err, "failed to get proposal history")
}
// If the bit for the current slot is marked, do not propose.
if slotBits.BitAt(block.Slot % params.BeaconConfig().SlotsPerEpoch) {
if !bytes.Equal(signingRoot, params.BeaconConfig().ZeroHash[:]) {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
Expand All @@ -50,9 +50,8 @@ func (v *validator) preBlockSignValidations(ctx context.Context, pubKey [48]byte
return nil
}

func (v *validator) postBlockSignUpdate(ctx context.Context, pubKey [48]byte, block *ethpb.SignedBeaconBlock) error {
func (v *validator) postBlockSignUpdate(ctx context.Context, pubKey [48]byte, block *ethpb.SignedBeaconBlock, domain *ethpb.DomainResponse) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
epoch := helpers.SlotToEpoch(block.Block.Slot)
if featureconfig.Get().SlasherProtection && v.protector != nil {
sbh, err := blockutil.SignedBeaconBlockHeaderFromBlock(block)
if err != nil {
Expand All @@ -69,16 +68,14 @@ func (v *validator) postBlockSignUpdate(ctx context.Context, pubKey [48]byte, bl
return fmt.Errorf(failedPostBlockSignErr)
}
}

slotBits, err := v.db.ProposalHistoryForEpoch(ctx, pubKey[:], epoch)
signingRoot, err := helpers.ComputeSigningRoot(block.Block, domain.SignatureDomain)
if err != nil {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.Wrap(err, "failed to get proposal history")
return errors.Wrap(err, "failed to compute signing root for block")
}
slotBits.SetBitAt(block.Block.Slot%params.BeaconConfig().SlotsPerEpoch, true)
if err := v.db.SaveProposalHistoryForEpoch(ctx, pubKey[:], epoch, slotBits); err != nil {
if err := v.db.SaveProposalHistoryForSlot(ctx, pubKey[:], block.Block.Slot, signingRoot[:]); err != nil {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
Expand Down
40 changes: 31 additions & 9 deletions validator/client/propose_protect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,36 @@ import (

ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
mockSlasher "github.com/prysmaticlabs/prysm/validator/testing"
)

func TestPreBlockSignLocalValidation(t *testing.T) {
ctx := context.Background()
config := &featureconfig.Flags{
SlasherProtection: false,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, validatorKey, finish := setup(t)
defer finish()

block := &ethpb.BeaconBlock{
Slot: 10,
ProposerIndex: 0,
}
err := validator.db.SaveProposalHistoryForSlot(ctx, validatorKey.PublicKey().Marshal(), 10, []byte{1})
require.NoError(t, err)
pubKey := [48]byte{}
copy(pubKey[:], validatorKey.PublicKey().Marshal())
err = validator.preBlockSignValidations(context.Background(), pubKey, block)
require.ErrorContains(t, failedPreBlockSignLocalErr, err)
block.Slot = 9
err = validator.preBlockSignValidations(context.Background(), pubKey, block)
require.NoError(t, err, "Expected allowed attestation not to throw error")
}

func TestPreBlockSignValidation(t *testing.T) {
config := &featureconfig.Flags{
SlasherProtection: true,
Expand Down Expand Up @@ -44,18 +70,14 @@ func TestPostBlockSignUpdate(t *testing.T) {
defer finish()
pubKey := [48]byte{}
copy(pubKey[:], validatorKey.PublicKey().Marshal())

block := &ethpb.SignedBeaconBlock{
Block: &ethpb.BeaconBlock{
Slot: 10,
ProposerIndex: 0,
},
}
emptyBlock := testutil.NewBeaconBlock()
emptyBlock.Block.Slot = 10
emptyBlock.Block.ProposerIndex = 0
mockProtector := &mockSlasher.MockProtector{AllowBlock: false}
validator.protector = mockProtector
err := validator.postBlockSignUpdate(context.Background(), pubKey, block)
err := validator.postBlockSignUpdate(context.Background(), pubKey, emptyBlock, nil)
require.ErrorContains(t, failedPostBlockSignErr, err, "Expected error when post signature update is detected as slashable")
mockProtector.AllowBlock = true
err = validator.postBlockSignUpdate(context.Background(), pubKey, block)
err = validator.postBlockSignUpdate(context.Background(), pubKey, emptyBlock, &ethpb.DomainResponse{SignatureDomain: make([]byte, 32)})
require.NoError(t, err, "Expected allowed attestation not to throw error")
}
35 changes: 35 additions & 0 deletions validator/client/propose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client

import (
"context"
"encoding/hex"
"errors"
"testing"
"time"
Expand All @@ -13,6 +14,7 @@ import (
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/mock"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
Expand Down Expand Up @@ -591,3 +593,36 @@ func TestProposeExit_BroadcastsBlock(t *testing.T) {
validatorKey.PublicKey().Marshal(),
))
}

func TestSignBlock(t *testing.T) {
validator, m, _, finish := setup(t)
defer finish()

secretKey, err := bls.SecretKeyFromBytes(bytesutil.PadTo([]byte{1}, 32))
require.NoError(t, err, "Failed to generate key from bytes")
publicKey := secretKey.PublicKey()
proposerDomain := make([]byte, 32)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), gomock.Any()).
Return(&ethpb.DomainResponse{SignatureDomain: proposerDomain}, nil)
ctx := context.Background()
blk := testutil.NewBeaconBlock()
blk.Block.Slot = 1
blk.Block.ProposerIndex = 100
var pubKey [48]byte
copy(pubKey[:], publicKey.Marshal())
km := &mockKeymanager{
keysMap: map[[48]byte]bls.SecretKey{
pubKey: secretKey,
},
}
validator.keyManagerV2 = km
sig, domain, err := validator.signBlock(ctx, pubKey, 0, blk.Block)
require.NoError(t, err, "%x,%x,%v", sig, domain.SignatureDomain, err)
require.Equal(t, "a049e1dc723e5a8b5bd14f292973572dffd53785ddb337"+
"82f20bf762cbe10ee7b9b4f5ae1ad6ff2089d352403750bed402b94b58469c072536"+
"faa9a09a88beaff697404ca028b1c7052b0de37dbcff985dfa500459783370312bdd"+
"36d6e0f224", hex.EncodeToString(sig))
// proposer domain
require.DeepEqual(t, proposerDomain, domain.SignatureDomain)
}
1 change: 1 addition & 0 deletions validator/db/kv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ go_library(
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@io_etcd_go_bbolt//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
Expand Down
52 changes: 47 additions & 5 deletions validator/db/kv/attestation_history_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ func (store *Store) SaveAttestationHistoryNewForPubKeys(ctx context.Context, his
return err
}

// ImportOldAttestationFormat import old attestation format data into the new attestation format
func (store *Store) ImportOldAttestationFormat(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "Validator.ImportOldAttestationFormat")
// MigrateV2AttestationProtection import old attestation format data into the new attestation format
func (store *Store) MigrateV2AttestationProtection(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "Validator.MigrateV2AttestationProtection")
defer span.End()
var allKeys [][48]byte

Expand Down Expand Up @@ -183,18 +183,60 @@ func (store *Store) ImportOldAttestationFormat(ctx context.Context) error {
dataMap[key] = newAttestationHistoryArray(atts.LatestEpochWritten)
dataMap[key], err = dataMap[key].setLatestEpochWritten(ctx, atts.LatestEpochWritten)
if err != nil {
return err
return errors.Wrapf(err, "failed to set latest epoch while migrating attestations to v2")
}
for target, source := range atts.TargetToSource {
dataMap[key], err = dataMap[key].setTargetData(ctx, target, &HistoryData{
Source: source,
SigningRoot: []byte{1},
})
if err != nil {
return err
return errors.Wrapf(err, "failed to set target data while migrating attestations to v2")
}
}
}
err = store.SaveAttestationHistoryNewForPubKeys(ctx, dataMap)
return err
}

// MigrateV2AttestationProtectionDb exports old attestation protection data
// format to the new format and save the exported flag to database.
func (store *Store) MigrateV2AttestationProtectionDb(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "Validator.MigrateV2AttestationProtectionDb")
defer span.End()
importAttestations, err := store.shouldMigrateAttestations()
if err != nil {
return errors.Wrap(err, "failed to analyze whether attestations should be imported")
}
if !importAttestations {
return nil
}
err = store.MigrateV2AttestationProtection(ctx)
if err != nil {
return errors.Wrap(err, "filed to import attestations")
}
err = store.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicAttestationsBucket)
if bucket != nil {
if err := bucket.Put([]byte(attestationExported), []byte{1}); err != nil {
return errors.Wrap(err, "failed to set migrated attestations flag in db")
}
}
return nil
})
return err
}

func (store *Store) shouldMigrateAttestations() (bool, error) {
var importAttestations bool
err := store.db.View(func(tx *bolt.Tx) error {
attestationBucket := tx.Bucket(historicAttestationsBucket)
if attestationBucket != nil && attestationBucket.Stats().KeyN != 0 {
if exported := attestationBucket.Get([]byte(attestationExported)); exported == nil {
importAttestations = true
}
}
return nil
})
return importAttestations, err
}
51 changes: 49 additions & 2 deletions validator/db/kv/attestation_history_new_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func TestStore_ImportOldAttestationFormatBadSourceFormat(t *testing.T) {
return nil
})
require.NoError(t, err)
require.ErrorContains(t, "could not retrieve data for public keys", db.ImportOldAttestationFormat(ctx))
require.ErrorContains(t, "could not retrieve data for public keys", db.MigrateV2AttestationProtection(ctx))
}

func TestStore_ImportOldAttestationFormat(t *testing.T) {
Expand Down Expand Up @@ -221,7 +221,7 @@ func TestStore_ImportOldAttestationFormat(t *testing.T) {
attestationHistory[pubKeys[1]] = history2

require.NoError(t, db.SaveAttestationHistoryForPubKeys(context.Background(), attestationHistory), "Saving attestation history failed")
require.NoError(t, db.ImportOldAttestationFormat(ctx), "Import attestation history failed")
require.NoError(t, db.MigrateV2AttestationProtection(ctx), "Import attestation history failed")

attHis, err := db.AttestationHistoryNewForPubKeys(ctx, pubKeys)
require.NoError(t, err)
Expand All @@ -239,3 +239,50 @@ func TestStore_ImportOldAttestationFormat(t *testing.T) {
}
}
}

func TestShouldImportAttestations(t *testing.T) {
pubkey := [48]byte{3}
db := setupDB(t, [][48]byte{pubkey})
ctx := context.Background()

shouldImport, err := db.shouldMigrateAttestations()
require.NoError(t, err)
require.Equal(t, false, shouldImport, "Empty bucket should not be imported")
newMap := make(map[uint64]uint64)
newMap[2] = 1
history := &slashpb.AttestationHistory{
TargetToSource: newMap,
LatestEpochWritten: 2,
}
attestationHistory := make(map[[48]byte]*slashpb.AttestationHistory)
attestationHistory[pubkey] = history
err = db.SaveAttestationHistoryForPubKeys(ctx, attestationHistory)
require.NoError(t, err)
shouldImport, err = db.shouldMigrateAttestations()
require.NoError(t, err)
require.Equal(t, true, shouldImport, "Bucket with content should be imported")
}

func TestStore_UpdateAttestationProtectionDb(t *testing.T) {
pubkey := [48]byte{3}
db := setupDB(t, [][48]byte{pubkey})
ctx := context.Background()
newMap := make(map[uint64]uint64)
newMap[2] = 1
history := &slashpb.AttestationHistory{
TargetToSource: newMap,
LatestEpochWritten: 2,
}
attestationHistory := make(map[[48]byte]*slashpb.AttestationHistory)
attestationHistory[pubkey] = history
err := db.SaveAttestationHistoryForPubKeys(ctx, attestationHistory)
require.NoError(t, err)
shouldImport, err := db.shouldMigrateAttestations()
require.NoError(t, err)
require.Equal(t, true, shouldImport, "Bucket with content should be imported")
err = db.MigrateV2AttestationProtectionDb(ctx)
require.NoError(t, err)
shouldImport, err = db.shouldMigrateAttestations()
require.NoError(t, err)
require.Equal(t, false, shouldImport, "Proposals should not be re-imported")
}

0 comments on commit acf2014

Please sign in to comment.