mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
Integrate New Slashing Protection DB Methods at Runtime (#8219)
* integrate at runtime and revamp tests * historical att * Update validator/db/iface/interface.go * deepsource * import * log the slashing kind * gaz * create a slashutils * integrate new slashutil * imports
This commit is contained in:
@@ -35,6 +35,7 @@ go_library(
|
||||
"//shared/hashutil:go_default_library",
|
||||
"//shared/mathutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/slashutil:go_default_library",
|
||||
"//shared/sliceutil:go_default_library",
|
||||
"//shared/trieutil:go_default_library",
|
||||
"@com_github_gogo_protobuf//proto:go_default_library",
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
v "github.com/prysmaticlabs/prysm/beacon-chain/core/validators"
|
||||
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/shared/attestationutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/slashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/sliceutil"
|
||||
)
|
||||
|
||||
@@ -120,7 +121,10 @@ func IsSlashableAttestationData(data1, data2 *ethpb.AttestationData) bool {
|
||||
return false
|
||||
}
|
||||
isDoubleVote := !attestationutil.AttDataIsEqual(data1, data2) && data1.Target.Epoch == data2.Target.Epoch
|
||||
isSurroundVote := data1.Source.Epoch < data2.Source.Epoch && data2.Target.Epoch < data1.Target.Epoch
|
||||
att1 := ðpb.IndexedAttestation{Data: data1}
|
||||
att2 := ðpb.IndexedAttestation{Data: data2}
|
||||
// Check if att1 is surrounding att2.
|
||||
isSurroundVote := slashutil.IsSurround(att1, att2)
|
||||
return isDoubleVote || isSurroundVote
|
||||
}
|
||||
|
||||
|
||||
17
shared/slashutil/BUILD.bazel
Normal file
17
shared/slashutil/BUILD.bazel
Normal file
@@ -0,0 +1,17 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["surround_votes.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/shared/slashutil",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["surround_votes_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library"],
|
||||
)
|
||||
16
shared/slashutil/surround_votes.go
Normal file
16
shared/slashutil/surround_votes.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package slashutil
|
||||
|
||||
import ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
|
||||
// IsSurround checks if an attestation, a, is surrounding
|
||||
// another one, b, based on the eth2 slashing conditions specified
|
||||
// by @protolambda https://github.com/protolambda/eth2-surround#definition.
|
||||
//
|
||||
// s: source
|
||||
// t: target
|
||||
//
|
||||
// a surrounds b if: s_a < s_b and t_b < t_a
|
||||
//
|
||||
func IsSurround(a, b *ethpb.IndexedAttestation) bool {
|
||||
return a.Data.Source.Epoch < b.Data.Source.Epoch && b.Data.Target.Epoch < a.Data.Target.Epoch
|
||||
}
|
||||
92
shared/slashutil/surround_votes_test.go
Normal file
92
shared/slashutil/surround_votes_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package slashutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
)
|
||||
|
||||
func TestIsSurround(t *testing.T) {
|
||||
type args struct {
|
||||
a *ethpb.IndexedAttestation
|
||||
b *ethpb.IndexedAttestation
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "0 values returns false",
|
||||
args: args{
|
||||
a: createAttestation(0, 0),
|
||||
b: createAttestation(0, 0),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "detects surrounding attestation",
|
||||
args: args{
|
||||
a: createAttestation(2, 5),
|
||||
b: createAttestation(3, 4),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target < old target",
|
||||
args: args{
|
||||
a: createAttestation(3, 5),
|
||||
b: createAttestation(3, 4),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source > old source, but new target == old target",
|
||||
args: args{
|
||||
a: createAttestation(3, 5),
|
||||
b: createAttestation(4, 5),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source and targets equal to old one",
|
||||
args: args{
|
||||
a: createAttestation(3, 5),
|
||||
b: createAttestation(3, 5),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target > old target",
|
||||
args: args{
|
||||
a: createAttestation(3, 5),
|
||||
b: createAttestation(3, 6),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source < old source, but new target == old target",
|
||||
args: args{
|
||||
a: createAttestation(3, 5),
|
||||
b: createAttestation(2, 5),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsSurround(tt.args.a, tt.args.b); got != tt.want {
|
||||
t.Errorf("IsSurrounding() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createAttestation(source, target uint64) *ethpb.IndexedAttestation {
|
||||
return ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{Epoch: source},
|
||||
Target: ðpb.Checkpoint{Epoch: target},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/event:go_default_library",
|
||||
"//shared/hashutil:go_default_library",
|
||||
"//shared/slashutil:go_default_library",
|
||||
"//shared/sliceutil:go_default_library",
|
||||
"//slasher/beaconclient:go_default_library",
|
||||
"//slasher/db:go_default_library",
|
||||
@@ -48,6 +49,7 @@ go_test(
|
||||
"//proto/slashing:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/event:go_default_library",
|
||||
"//shared/slashutil:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"//slasher/db/testing:go_default_library",
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/attestationutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/slashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/sliceutil"
|
||||
status "github.com/prysmaticlabs/prysm/slasher/db/types"
|
||||
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
|
||||
@@ -137,8 +138,8 @@ func (ds *Service) detectSurroundVotes(
|
||||
if att.Data == nil {
|
||||
continue
|
||||
}
|
||||
isSurround := isSurrounding(incomingAtt, att)
|
||||
isSurrounded := isSurrounding(att, incomingAtt)
|
||||
isSurround := slashutil.IsSurround(incomingAtt, att)
|
||||
isSurrounded := slashutil.IsSurround(att, incomingAtt)
|
||||
if !isSurround && !isSurrounded {
|
||||
continue
|
||||
}
|
||||
@@ -210,11 +211,6 @@ func isDoubleVote(incomingAtt, prevAtt *ethpb.IndexedAttestation) bool {
|
||||
return !attestationutil.AttDataIsEqual(incomingAtt.Data, prevAtt.Data) && incomingAtt.Data.Target.Epoch == prevAtt.Data.Target.Epoch
|
||||
}
|
||||
|
||||
func isSurrounding(incomingAtt, prevAtt *ethpb.IndexedAttestation) bool {
|
||||
return incomingAtt.Data.Source.Epoch < prevAtt.Data.Source.Epoch &&
|
||||
incomingAtt.Data.Target.Epoch > prevAtt.Data.Target.Epoch
|
||||
}
|
||||
|
||||
// UpdateHighestAttestation updates to the db the highest source and target attestations for a each validator.
|
||||
func (ds *Service) UpdateHighestAttestation(ctx context.Context, att *ethpb.IndexedAttestation) error {
|
||||
for _, idx := range att.AttestingIndices {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/slashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
testDB "github.com/prysmaticlabs/prysm/slasher/db/testing"
|
||||
@@ -189,7 +190,7 @@ func TestDetect_detectAttesterSlashings_Surround(t *testing.T) {
|
||||
for _, ss := range slashings {
|
||||
slashingAtt1 := ss.Attestation_1
|
||||
slashingAtt2 := ss.Attestation_2
|
||||
if !isSurrounding(slashingAtt1, slashingAtt2) {
|
||||
if !slashutil.IsSurround(slashingAtt1, slashingAtt2) {
|
||||
t.Fatalf(
|
||||
"Expected slashing to be valid, received atts %d->%d and %d->%d",
|
||||
slashingAtt2.Data.Source.Epoch,
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/validator/db/kv"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -30,40 +27,24 @@ func (v *validator) slashableAttestationCheck(
|
||||
defer span.End()
|
||||
|
||||
fmtKey := fmt.Sprintf("%#x", pubKey[:])
|
||||
attesterHistory, err := v.db.AttestationHistoryForPubKeyV2(ctx, pubKey)
|
||||
slashingKind, err := v.db.CheckSlashableAttestation(ctx, pubKey, signingRoot, indexedAtt)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get attester history")
|
||||
}
|
||||
slashable, err := isNewAttSlashable(
|
||||
ctx,
|
||||
attesterHistory,
|
||||
indexedAtt.Data.Source.Epoch,
|
||||
indexedAtt.Data.Target.Epoch,
|
||||
signingRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if attestation is slashable")
|
||||
}
|
||||
if slashable {
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return errors.New(failedAttLocalProtectionErr)
|
||||
switch slashingKind {
|
||||
case kv.DoubleVote:
|
||||
log.Warn("Attestation is slashable as it is a double vote")
|
||||
case kv.SurroundingVote:
|
||||
log.Warn("Attestation is slashable as it is surrounding a previous attestation")
|
||||
case kv.SurroundedVote:
|
||||
log.Warn("Attestation is slashable as it is surrounded by a previous attestation")
|
||||
}
|
||||
return errors.Wrap(err, failedAttLocalProtectionErr)
|
||||
}
|
||||
newHistory, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(
|
||||
ctx,
|
||||
attesterHistory,
|
||||
indexedAtt.Data.Target.Epoch,
|
||||
&kv.HistoryData{
|
||||
Source: indexedAtt.Data.Source.Epoch,
|
||||
SigningRoot: signingRoot[:],
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not mark epoch %d as attested", indexedAtt.Data.Target.Epoch)
|
||||
}
|
||||
if err := v.db.SaveAttestationHistoryForPubKeyV2(ctx, pubKey, newHistory); err != nil {
|
||||
return errors.Wrapf(err, "could not save attestation history for public key: %#x", pubKey)
|
||||
|
||||
if err := v.db.ApplyAttestationForPubKey(ctx, pubKey, signingRoot, indexedAtt); err != nil {
|
||||
return errors.Wrap(err, "could not save attestation history for validator public key")
|
||||
}
|
||||
|
||||
// TODO(#7813): Add back the saving of lowest target and lowest source epoch
|
||||
@@ -78,146 +59,3 @@ func (v *validator) slashableAttestationCheck(
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNewAttSlashable uses the attestation history to determine if an attestation of sourceEpoch
|
||||
// and targetEpoch would be slashable. It can detect double, surrounding, and surrounded votes.
|
||||
func isNewAttSlashable(
|
||||
ctx context.Context,
|
||||
history kv.EncHistoryData,
|
||||
sourceEpoch,
|
||||
targetEpoch uint64,
|
||||
signingRoot [32]byte,
|
||||
) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "isNewAttSlashable")
|
||||
defer span.End()
|
||||
|
||||
if history == nil {
|
||||
return false, nil
|
||||
}
|
||||
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
|
||||
// Previously pruned, we should return false.
|
||||
latestEpochWritten, err := history.GetLatestEpochWritten(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get latest epoch written from encapsulated data")
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Underflow protected older then weak subjectivity check.
|
||||
if latestEpochWritten >= wsPeriod && targetEpoch <= latestEpochWritten-wsPeriod {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if there has already been a vote for this target epoch.
|
||||
hd, err := history.GetTargetData(ctx, targetEpoch)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "could not get target data for epoch: %d", targetEpoch)
|
||||
}
|
||||
if !hd.IsEmpty() && !bytes.Equal(signingRoot[:], hd.SigningRoot) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"signingRoot": fmt.Sprintf("%#x", signingRoot),
|
||||
"targetEpoch": targetEpoch,
|
||||
"previouslyAttestedSigningRoot": fmt.Sprintf("%#x", hd.SigningRoot),
|
||||
}).Warn("Attempted to submit a double vote, but blocked by slashing protection")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isSurround, err := isSurroundVote(ctx, history, latestEpochWritten, sourceEpoch, targetEpoch)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not check if attestation is surround vote")
|
||||
}
|
||||
return isSurround, nil
|
||||
}
|
||||
|
||||
func isSurroundVote(
|
||||
ctx context.Context,
|
||||
history kv.EncHistoryData,
|
||||
latestEpochWritten,
|
||||
sourceEpoch,
|
||||
targetEpoch uint64,
|
||||
) (bool, error) {
|
||||
for i := sourceEpoch; i <= targetEpoch; i++ {
|
||||
historicalAtt, err := checkHistoryAtTargetEpoch(ctx, history, latestEpochWritten, i)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "could not check historical attestation at target epoch: %d", i)
|
||||
}
|
||||
if historicalAtt.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
prevTarget := i
|
||||
prevSource := historicalAtt.Source
|
||||
if surroundingPrevAttestation(prevSource, prevTarget, sourceEpoch, targetEpoch) {
|
||||
// Surrounding attestation caught.
|
||||
log.WithFields(logrus.Fields{
|
||||
"targetEpoch": targetEpoch,
|
||||
"sourceEpoch": sourceEpoch,
|
||||
"previouslyAttestedTargetEpoch": prevTarget,
|
||||
"previouslyAttestedSourceEpoch": prevSource,
|
||||
}).Warn("Attempted to submit a surrounding attestation, but blocked by slashing protection")
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the new attestation is being surrounded.
|
||||
for i := targetEpoch; i <= latestEpochWritten; i++ {
|
||||
historicalAtt, err := checkHistoryAtTargetEpoch(ctx, history, latestEpochWritten, i)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "could not check historical attestation at target epoch: %d", i)
|
||||
}
|
||||
if historicalAtt.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
prevTarget := i
|
||||
prevSource := historicalAtt.Source
|
||||
if surroundedByPrevAttestation(prevSource, prevTarget, sourceEpoch, targetEpoch) {
|
||||
// Surrounded attestation caught.
|
||||
log.WithFields(logrus.Fields{
|
||||
"targetEpoch": targetEpoch,
|
||||
"sourceEpoch": sourceEpoch,
|
||||
"previouslyAttestedTargetEpoch": prevTarget,
|
||||
"previouslyAttestedSourceEpoch": prevSource,
|
||||
}).Warn("Attempted to submit a surrounded attestation, but blocked by slashing protection")
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func surroundedByPrevAttestation(prevSource, prevTarget, newSource, newTarget uint64) bool {
|
||||
return prevSource < newSource && newTarget < prevTarget
|
||||
}
|
||||
|
||||
func surroundingPrevAttestation(prevSource, prevTarget, newSource, newTarget uint64) bool {
|
||||
return newSource < prevSource && prevTarget < newTarget
|
||||
}
|
||||
|
||||
// Checks that the difference between the latest epoch written and
|
||||
// target epoch is greater than or equal to the weak subjectivity period.
|
||||
func differenceOutsideWeakSubjectivityBounds(latestEpochWritten, targetEpoch uint64) bool {
|
||||
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
|
||||
return latestEpochWritten >= wsPeriod && targetEpoch <= latestEpochWritten-wsPeriod
|
||||
}
|
||||
|
||||
// safeTargetToSource makes sure the epoch accessed is within bounds, and if it's not it at
|
||||
// returns the "default" nil value.
|
||||
// Returns the actual attesting history at a specified target epoch.
|
||||
// The response is nil if there was no attesting history at that epoch.
|
||||
func checkHistoryAtTargetEpoch(
|
||||
ctx context.Context,
|
||||
history kv.EncHistoryData,
|
||||
latestEpochWritten,
|
||||
targetEpoch uint64,
|
||||
) (*kv.HistoryData, error) {
|
||||
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
|
||||
if differenceOutsideWeakSubjectivityBounds(latestEpochWritten, targetEpoch) {
|
||||
return nil, nil
|
||||
}
|
||||
// Ignore target epoch is > latest written.
|
||||
if targetEpoch > latestEpochWritten {
|
||||
return nil, nil
|
||||
}
|
||||
historicalAtt, err := history.GetTargetData(ctx, targetEpoch%wsPeriod)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get target data for target epoch: %d", targetEpoch)
|
||||
}
|
||||
return historicalAtt, nil
|
||||
}
|
||||
|
||||
@@ -2,16 +2,13 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
"github.com/prysmaticlabs/prysm/validator/db/kv"
|
||||
mockSlasher "github.com/prysmaticlabs/prysm/validator/testing"
|
||||
)
|
||||
|
||||
@@ -195,501 +192,3 @@ func Test_slashableAttestationCheck_GenesisEpoch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), e)
|
||||
}
|
||||
|
||||
func TestAttestationHistory_BlocksDoubleAttestation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
history := kv.NewAttestationHistoryArray(3)
|
||||
// Mark an attestation spanning epochs 0 to 3.
|
||||
newAttSource := uint64(0)
|
||||
newAttTarget := uint64(3)
|
||||
sr1 := [32]byte{1}
|
||||
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
|
||||
Source: newAttSource,
|
||||
SigningRoot: sr1[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
lew, err := history.GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newAttTarget, lew, "Unexpected latest epoch written")
|
||||
|
||||
// Try an attestation that should be slashable (double att) spanning epochs 1 to 3.
|
||||
sr2 := [32]byte{2}
|
||||
newAttSource = uint64(1)
|
||||
newAttTarget = uint64(3)
|
||||
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, sr2)
|
||||
require.NoError(t, err)
|
||||
if !slashable {
|
||||
t.Fatalf("Expected attestation of source %d and target %d to be considered slashable", newAttSource, newAttTarget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationHistory_Prunes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
|
||||
|
||||
signingRoot := [32]byte{1}
|
||||
signingRoot2 := [32]byte{2}
|
||||
signingRoot3 := [32]byte{3}
|
||||
signingRoot4 := [32]byte{4}
|
||||
history := kv.NewAttestationHistoryArray(0)
|
||||
|
||||
// Try an attestation on totally unmarked history, should not be slashable.
|
||||
slashable, err := isNewAttSlashable(ctx, history, 0, wsPeriod+5, signingRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, slashable, "Should not be slashable")
|
||||
|
||||
// Mark attestations spanning epochs 0 to 3 and 6 to 9.
|
||||
prunedNewAttSource := uint64(0)
|
||||
prunedNewAttTarget := uint64(3)
|
||||
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, prunedNewAttTarget, &kv.HistoryData{
|
||||
Source: prunedNewAttSource,
|
||||
SigningRoot: signingRoot[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
newAttSource := prunedNewAttSource + 6
|
||||
newAttTarget := prunedNewAttTarget + 6
|
||||
newHist, err = kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
|
||||
Source: newAttSource,
|
||||
SigningRoot: signingRoot2[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
lte, err := history.GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newAttTarget, lte, "Unexpected latest epoch")
|
||||
|
||||
// Mark an attestation spanning epochs 54000 to 54003.
|
||||
farNewAttSource := newAttSource + wsPeriod
|
||||
farNewAttTarget := newAttTarget + wsPeriod
|
||||
newHist, err = kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, farNewAttTarget, &kv.HistoryData{
|
||||
Source: farNewAttSource,
|
||||
SigningRoot: signingRoot3[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
lte, err = history.GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, farNewAttTarget, lte, "Unexpected latest epoch")
|
||||
|
||||
histAtt, err := checkHistoryAtTargetEpoch(ctx, history, lte, prunedNewAttTarget)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, (*kv.HistoryData)(nil), histAtt, "Unexpectedly marked attestation")
|
||||
histAtt, err = checkHistoryAtTargetEpoch(ctx, history, lte, farNewAttTarget)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, farNewAttSource, histAtt.Source, "Unexpectedly marked attestation")
|
||||
|
||||
// Try an attestation from existing source to outside prune, should slash.
|
||||
slashable, err = isNewAttSlashable(ctx, history, newAttSource, farNewAttTarget, signingRoot4)
|
||||
require.NoError(t, err)
|
||||
if !slashable {
|
||||
t.Fatalf("Expected attestation of source %d, target %d to be considered slashable", newAttSource, farNewAttTarget)
|
||||
}
|
||||
// Try an attestation from before existing target to outside prune, should slash.
|
||||
slashable, err = isNewAttSlashable(ctx, history, newAttTarget-1, farNewAttTarget, signingRoot4)
|
||||
require.NoError(t, err)
|
||||
if !slashable {
|
||||
t.Fatalf("Expected attestation of source %d, target %d to be considered slashable", newAttTarget-1, farNewAttTarget)
|
||||
}
|
||||
// Try an attestation larger than pruning amount, should slash.
|
||||
slashable, err = isNewAttSlashable(ctx, history, 0, farNewAttTarget+5, signingRoot4)
|
||||
require.NoError(t, err)
|
||||
if !slashable {
|
||||
t.Fatalf("Expected attestation of source 0, target %d to be considered slashable", farNewAttTarget+5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationHistory_BlocksSurroundedAttestation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
history := kv.NewAttestationHistoryArray(0)
|
||||
|
||||
// Mark an attestation spanning epochs 0 to 3.
|
||||
signingRoot := [32]byte{1}
|
||||
newAttSource := uint64(0)
|
||||
newAttTarget := uint64(3)
|
||||
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
|
||||
Source: newAttSource,
|
||||
SigningRoot: signingRoot[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
lte, err := history.GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newAttTarget, lte)
|
||||
|
||||
// Try an attestation that should be slashable (being surrounded) spanning epochs 1 to 2.
|
||||
newAttSource = uint64(1)
|
||||
newAttTarget = uint64(2)
|
||||
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, slashable, "Expected slashable attestation")
|
||||
}
|
||||
|
||||
func TestAttestationHistory_BlocksSurroundingAttestation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
history := kv.NewAttestationHistoryArray(0)
|
||||
signingRoot := [32]byte{1}
|
||||
|
||||
// Mark an attestation spanning epochs 1 to 2.
|
||||
newAttSource := uint64(1)
|
||||
newAttTarget := uint64(2)
|
||||
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
|
||||
Source: newAttSource,
|
||||
SigningRoot: signingRoot[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = newHist
|
||||
lte, err := history.GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newAttTarget, lte)
|
||||
ts, err := history.GetTargetData(ctx, newAttTarget)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newAttSource, ts.Source)
|
||||
|
||||
// Try an attestation that should be slashable (surrounding) spanning epochs 0 to 3.
|
||||
newAttSource = uint64(0)
|
||||
newAttTarget = uint64(3)
|
||||
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, slashable)
|
||||
}
|
||||
|
||||
func Test_isSurroundVote(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
source := uint64(1)
|
||||
target := uint64(4)
|
||||
history := kv.NewAttestationHistoryArray(0)
|
||||
signingRoot1 := bytesutil.PadTo([]byte{1}, 32)
|
||||
hist, err := history.SetTargetData(ctx, target, &kv.HistoryData{
|
||||
Source: source,
|
||||
SigningRoot: signingRoot1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = hist
|
||||
tests := []struct {
|
||||
name string
|
||||
history kv.EncHistoryData
|
||||
latestEpochWritten uint64
|
||||
sourceEpoch uint64
|
||||
targetEpoch uint64
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ignores attestations outside of weak subjectivity bounds",
|
||||
history: kv.NewAttestationHistoryArray(0),
|
||||
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
sourceEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "detects surrounding attestations",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target + 1,
|
||||
sourceEpoch: source - 1,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "detects surrounded attestations",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target - 1,
|
||||
sourceEpoch: source + 1,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target < old target",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target - 1,
|
||||
sourceEpoch: source,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source > old source, but new target == old target",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target,
|
||||
sourceEpoch: source + 1,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source and targets equal to old one",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target,
|
||||
sourceEpoch: source,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target > old target",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target + 1,
|
||||
sourceEpoch: source,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source < old source, but new target == old target",
|
||||
history: history,
|
||||
latestEpochWritten: target,
|
||||
targetEpoch: target,
|
||||
sourceEpoch: source - 1,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := isSurroundVote(ctx, tt.history, tt.latestEpochWritten, tt.sourceEpoch, tt.targetEpoch)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("isSurroundVote() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("isSurroundVote() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_surroundedByPrevAttestation(t *testing.T) {
|
||||
type args struct {
|
||||
oldSource uint64
|
||||
oldTarget uint64
|
||||
newSource uint64
|
||||
newTarget uint64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "0 values returns false",
|
||||
args: args{
|
||||
oldSource: 0,
|
||||
oldTarget: 0,
|
||||
newSource: 0,
|
||||
newTarget: 0,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation is surrounded by an old one",
|
||||
args: args{
|
||||
oldSource: 2,
|
||||
oldTarget: 6,
|
||||
newSource: 3,
|
||||
newTarget: 5,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "new attestation source and targets equal to old one",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 3,
|
||||
newTarget: 5,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target < old target",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 3,
|
||||
newTarget: 4,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source > old source, but new target == old target",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 4,
|
||||
newTarget: 5,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := surroundedByPrevAttestation(tt.args.oldSource, tt.args.oldTarget, tt.args.newSource, tt.args.newTarget); got != tt.want {
|
||||
t.Errorf("surroundedByPrevAttestation() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_surroundingPrevAttestation(t *testing.T) {
|
||||
type args struct {
|
||||
oldSource uint64
|
||||
oldTarget uint64
|
||||
newSource uint64
|
||||
newTarget uint64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "0 values returns false",
|
||||
args: args{
|
||||
oldSource: 0,
|
||||
oldTarget: 0,
|
||||
newSource: 0,
|
||||
newTarget: 0,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation is surrounding an old one",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 2,
|
||||
newTarget: 6,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "new attestation source and targets equal to old one",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 3,
|
||||
newTarget: 5,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source == old source, but new target > old target",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 3,
|
||||
newTarget: 6,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new attestation source < old source, but new target == old target",
|
||||
args: args{
|
||||
oldSource: 3,
|
||||
oldTarget: 5,
|
||||
newSource: 2,
|
||||
newTarget: 5,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := surroundingPrevAttestation(tt.args.oldSource, tt.args.oldTarget, tt.args.newSource, tt.args.newTarget); got != tt.want {
|
||||
t.Errorf("surroundingPrevAttestation() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkHistoryAtTargetEpoch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
history := kv.NewAttestationHistoryArray(0)
|
||||
signingRoot1 := bytesutil.PadTo([]byte{1}, 32)
|
||||
hist, err := history.SetTargetData(ctx, 1, &kv.HistoryData{
|
||||
Source: 0,
|
||||
SigningRoot: signingRoot1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history = hist
|
||||
tests := []struct {
|
||||
name string
|
||||
history kv.EncHistoryData
|
||||
latestEpochWritten uint64
|
||||
targetEpoch uint64
|
||||
want *kv.HistoryData
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ignores difference in epochs outside of weak subjectivity bounds",
|
||||
history: kv.NewAttestationHistoryArray(0),
|
||||
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ignores target epoch > latest written epoch",
|
||||
history: kv.NewAttestationHistoryArray(0),
|
||||
latestEpochWritten: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod + 1,
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "target epoch == latest written epoch should return correct results",
|
||||
history: history,
|
||||
latestEpochWritten: 1,
|
||||
targetEpoch: 1,
|
||||
want: &kv.HistoryData{
|
||||
Source: 0,
|
||||
SigningRoot: signingRoot1,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := checkHistoryAtTargetEpoch(ctx, tt.history, tt.latestEpochWritten, tt.targetEpoch)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkHistoryAtTargetEpoch() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("checkHistoryAtTargetEpoch() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_differenceOutsideWeakSubjectivityBounds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want bool
|
||||
latestEpochWritten uint64
|
||||
targetEpoch uint64
|
||||
}{
|
||||
{
|
||||
name: "difference of weak subjectivity period - 1 returns false",
|
||||
latestEpochWritten: (2 * params.BeaconConfig().WeakSubjectivityPeriod) - 1,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "difference of weak subjectivity period returns true",
|
||||
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "difference > weak subjectivity period returns true",
|
||||
latestEpochWritten: (2 * params.BeaconConfig().WeakSubjectivityPeriod) + 1,
|
||||
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := differenceOutsideWeakSubjectivityBounds(tt.latestEpochWritten, tt.targetEpoch); got != tt.want {
|
||||
t.Errorf("differenceOutsideWeakSubjectivityBounds() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ go_library(
|
||||
deps = [
|
||||
"//shared/backuputil:go_default_library",
|
||||
"//validator/db/kv:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/backuputil"
|
||||
"github.com/prysmaticlabs/prysm/validator/db/kv"
|
||||
)
|
||||
@@ -36,7 +37,11 @@ type ValidatorDB interface {
|
||||
LowestSignedSourceEpoch(ctx context.Context, publicKey [48]byte) (uint64, error)
|
||||
SaveLowestSignedTargetEpoch(ctx context.Context, publicKey [48]byte, epoch uint64) error
|
||||
SaveLowestSignedSourceEpoch(ctx context.Context, publicKey [48]byte, epoch uint64) error
|
||||
AttestationHistoryForPubKeyV2(ctx context.Context, publicKey [48]byte) (kv.EncHistoryData, error)
|
||||
SaveAttestationHistoryForPubKeyV2(ctx context.Context, publicKey [48]byte, history kv.EncHistoryData) error
|
||||
AttestedPublicKeys(ctx context.Context) ([][48]byte, error)
|
||||
CheckSlashableAttestation(
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
||||
) (kv.SlashingKind, error)
|
||||
ApplyAttestationForPubKey(
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
||||
) error
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ go_library(
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/featureconfig:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/progressutil:go_default_library",
|
||||
"//shared/slashutil:go_default_library",
|
||||
"@com_github_golang_snappy//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
|
||||
@@ -3,9 +3,7 @@ package kv
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
@@ -28,60 +26,6 @@ func (store *Store) AttestedPublicKeys(ctx context.Context) ([][48]byte, error)
|
||||
return attestedPublicKeys, err
|
||||
}
|
||||
|
||||
// AttestationHistoryForPubKeyV2 returns the corresponding attesting
|
||||
// history for a specified validator public key.
|
||||
func (store *Store) AttestationHistoryForPubKeyV2(ctx context.Context, publicKey [48]byte) (EncHistoryData, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "Validator.AttestationHistoryForPubKeyV2")
|
||||
defer span.End()
|
||||
if !featureconfig.Get().DisableAttestingHistoryDBCache {
|
||||
store.lock.RLock()
|
||||
if history, ok := store.attestingHistoriesByPubKey[publicKey]; ok {
|
||||
store.lock.RUnlock()
|
||||
return history, nil
|
||||
}
|
||||
store.lock.RUnlock()
|
||||
}
|
||||
var err error
|
||||
var attestationHistory EncHistoryData
|
||||
err = store.view(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(historicAttestationsBucket)
|
||||
enc := bucket.Get(publicKey[:])
|
||||
if len(enc) == 0 {
|
||||
attestationHistory = NewAttestationHistoryArray(0)
|
||||
} else {
|
||||
data, err := snappy.Decode(nil /*dst*/, enc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attestationHistory = data
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !featureconfig.Get().DisableAttestingHistoryDBCache {
|
||||
store.lock.Lock()
|
||||
store.attestingHistoriesByPubKey[publicKey] = attestationHistory
|
||||
store.lock.Unlock()
|
||||
}
|
||||
return attestationHistory, err
|
||||
}
|
||||
|
||||
// SaveAttestationHistoryForPubKeyV2 saves the attestation history for the requested validator public key.
|
||||
func (store *Store) SaveAttestationHistoryForPubKeyV2(ctx context.Context, pubKey [48]byte, history EncHistoryData) error {
|
||||
ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationHistoryForPubKeyV2")
|
||||
defer span.End()
|
||||
err := store.update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(historicAttestationsBucket)
|
||||
enc := snappy.Encode(nil /*dst*/, history)
|
||||
return bucket.Put(pubKey[:], enc)
|
||||
})
|
||||
if !featureconfig.Get().DisableAttestingHistoryDBCache {
|
||||
store.lock.Lock()
|
||||
store.attestingHistoriesByPubKey[pubKey] = history
|
||||
store.lock.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LowestSignedSourceEpoch returns the lowest signed source epoch for a validator public key.
|
||||
// If no data exists, returning 0 is a sensible default.
|
||||
func (store *Store) LowestSignedSourceEpoch(ctx context.Context, publicKey [48]byte) (uint64, error) {
|
||||
|
||||
@@ -4,59 +4,9 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
)
|
||||
|
||||
func TestAttestationHistoryForPubKey_OK(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
pubKey := [48]byte{30}
|
||||
db := setupDB(t, [][48]byte{pubKey})
|
||||
|
||||
_, err := db.AttestationHistoryForPubKeyV2(context.Background(), pubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
history := NewAttestationHistoryArray(53999)
|
||||
|
||||
history, err = history.SetTargetData(
|
||||
ctx,
|
||||
10,
|
||||
&HistoryData{
|
||||
Source: uint64(1),
|
||||
SigningRoot: []byte{1, 2, 3},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.SaveAttestationHistoryForPubKeyV2(context.Background(), pubKey, history)
|
||||
require.NoError(t, err)
|
||||
got, err := db.AttestationHistoryForPubKeyV2(context.Background(), pubKey)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, history, got, "Expected attestation history epoch bits to be empty")
|
||||
}
|
||||
|
||||
func TestStore_AttestedPublicKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
validatorDB, err := NewKVStore(ctx, t.TempDir(), nil)
|
||||
require.NoError(t, err, "Failed to instantiate DB")
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, validatorDB.Close(), "Failed to close database")
|
||||
require.NoError(t, validatorDB.ClearDB(), "Failed to clear database")
|
||||
})
|
||||
|
||||
keys, err := validatorDB.AttestedPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, make([][48]byte, 0), keys)
|
||||
|
||||
pubKey := [48]byte{1}
|
||||
err = validatorDB.SaveAttestationHistoryForPubKeyV2(ctx, pubKey, NewAttestationHistoryArray(0))
|
||||
require.NoError(t, err)
|
||||
|
||||
keys, err = validatorDB.AttestedPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, [][48]byte{pubKey}, keys)
|
||||
}
|
||||
|
||||
func TestLowestSignedSourceEpoch_SaveRetrieve(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
validatorDB, err := NewKVStore(ctx, t.TempDir(), nil)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/slashutil"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
// CheckSlashableAttestation verifies an incoming attestation is
|
||||
// not a double vote for a validator public key nor a surround vote.
|
||||
func (store *Store) CheckSlashableAttestation(
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.Attestation,
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
||||
) (SlashingKind, error) {
|
||||
var slashKind SlashingKind
|
||||
err := store.view(func(tx *bolt.Tx) error {
|
||||
@@ -58,8 +59,16 @@ func (store *Store) CheckSlashableAttestation(
|
||||
return sourceEpochsBucket.ForEach(func(sourceEpochBytes []byte, targetEpochBytes []byte) error {
|
||||
existingSourceEpoch := bytesutil.BytesToUint64BigEndian(sourceEpochBytes)
|
||||
existingTargetEpoch := bytesutil.BytesToUint64BigEndian(targetEpochBytes)
|
||||
surrounding := att.Data.Source.Epoch < existingSourceEpoch && att.Data.Target.Epoch > existingTargetEpoch
|
||||
surrounded := att.Data.Source.Epoch > existingSourceEpoch && att.Data.Target.Epoch < existingTargetEpoch
|
||||
existingAtt := ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{Epoch: existingSourceEpoch},
|
||||
Target: ðpb.Checkpoint{Epoch: existingTargetEpoch},
|
||||
},
|
||||
}
|
||||
// Checks if the incoming attestation is surrounding or
|
||||
// is surrounded by an existing one.
|
||||
surrounding := slashutil.IsSurround(att, existingAtt)
|
||||
surrounded := slashutil.IsSurround(existingAtt, att)
|
||||
if surrounding {
|
||||
slashKind = SurroundingVote
|
||||
return fmt.Errorf(
|
||||
@@ -90,7 +99,7 @@ func (store *Store) CheckSlashableAttestation(
|
||||
// key by storing its signing root under the appropriate bucket as well
|
||||
// as its source and target epochs for future slashing protection checks.
|
||||
func (store *Store) ApplyAttestationForPubKey(
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.Attestation,
|
||||
ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation,
|
||||
) error {
|
||||
return store.update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(pubKeysBucket)
|
||||
|
||||
@@ -20,9 +20,9 @@ func TestStore_CheckSlashableAttestation_DoubleVote(t *testing.T) {
|
||||
validatorDB := setupDB(t, pubKeys)
|
||||
tests := []struct {
|
||||
name string
|
||||
existingAttestation *ethpb.Attestation
|
||||
existingAttestation *ethpb.IndexedAttestation
|
||||
existingSigningRoot [32]byte
|
||||
incomingAttestation *ethpb.Attestation
|
||||
incomingAttestation *ethpb.IndexedAttestation
|
||||
incomingSigningRoot [32]byte
|
||||
want bool
|
||||
}{
|
||||
@@ -118,7 +118,7 @@ func TestStore_CheckSlashableAttestation_SurroundVote_54kEpochs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
signingRoot [32]byte
|
||||
attestation *ethpb.Attestation
|
||||
attestation *ethpb.IndexedAttestation
|
||||
want SlashingKind
|
||||
}{
|
||||
{
|
||||
@@ -211,7 +211,7 @@ func benchCheckSurroundVote(
|
||||
require.NoError(b, err)
|
||||
|
||||
// Will surround many attestations.
|
||||
var surroundingVote *ethpb.Attestation
|
||||
var surroundingVote *ethpb.IndexedAttestation
|
||||
if shouldSurround {
|
||||
surroundingVote = createAttestation(numEpochs/2, numEpochs)
|
||||
} else {
|
||||
@@ -231,8 +231,8 @@ func benchCheckSurroundVote(
|
||||
}
|
||||
}
|
||||
|
||||
func createAttestation(source, target uint64) *ethpb.Attestation {
|
||||
return ðpb.Attestation{
|
||||
func createAttestation(source, target uint64) *ethpb.IndexedAttestation {
|
||||
return ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: source,
|
||||
|
||||
@@ -5,12 +5,10 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
prombolt "github.com/prysmaticlabs/prombbolt"
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -24,7 +22,6 @@ var ProtectionDbFileName = "validator.db"
|
||||
type Store struct {
|
||||
db *bolt.DB
|
||||
databasePath string
|
||||
lock sync.RWMutex
|
||||
attestingHistoriesByPubKey map[[48]byte]EncHistoryData
|
||||
}
|
||||
|
||||
@@ -116,19 +113,6 @@ func NewKVStore(ctx context.Context, dirPath string, pubKeys [][48]byte) (*Store
|
||||
}
|
||||
}
|
||||
|
||||
// We then fetch the attestation histories for each public key
|
||||
// and store them in a map for usage at runtime.
|
||||
if !featureconfig.Get().DisableAttestingHistoryDBCache {
|
||||
// No need for a lock here as this function is only called once
|
||||
// to initialize the database and would lead to deadlocks otherwise.
|
||||
for _, pubKey := range pubKeys {
|
||||
history, err := kv.AttestationHistoryForPubKeyV2(ctx, pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kv.attestingHistoriesByPubKey[pubKey] = history
|
||||
}
|
||||
}
|
||||
if err := kv.PruneAttestationsOlderThanCurrentWeakSubjectivity(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "could not prune old attestations from DB")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
"//validator/db/kv:go_default_library",
|
||||
"@com_github_k0kubun_go_ansi//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
"@com_github_schollz_progressbar_v3//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
@@ -42,6 +43,7 @@ go_test(
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"//validator/db/kv:go_default_library",
|
||||
"//validator/db/testing:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -42,3 +42,9 @@ type SignedBlock struct {
|
||||
Slot string `json:"slot"`
|
||||
SigningRoot string `json:"signing_root,omitempty"`
|
||||
}
|
||||
|
||||
type historicalAttestation struct {
|
||||
sourceEpoch uint64
|
||||
targetEpoch uint64
|
||||
signingRoot [32]byte
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/db"
|
||||
@@ -49,7 +50,7 @@ func ImportStandardProtectionJSON(ctx context.Context, validatorDB db.Database,
|
||||
return errors.Wrap(err, "could not parse unique entries for attestations by public key")
|
||||
}
|
||||
|
||||
attestingHistoryByPubKey := make(map[[48]byte]kv.EncHistoryData)
|
||||
attestingHistoryByPubKey := make(map[[48]byte][]*historicalAttestation)
|
||||
proposalHistoryByPubKey := make(map[[48]byte]kv.ProposalHistoryForPubkey)
|
||||
for pubKey, signedBlocks := range signedBlocksByPubKey {
|
||||
// Transform the processed signed blocks data from the JSON
|
||||
@@ -64,11 +65,11 @@ func ImportStandardProtectionJSON(ctx context.Context, validatorDB db.Database,
|
||||
for pubKey, signedAtts := range signedAttsByPubKey {
|
||||
// Transform the processed signed attestation data from the JSON
|
||||
// file into the internal Prysm representation of attesting history.
|
||||
attestingHistory, err := transformSignedAttestations(ctx, signedAtts)
|
||||
historicalAtt, err := transformSignedAttestations(ctx, signedAtts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not parse signed attestations in JSON file for key %#x", pubKey)
|
||||
}
|
||||
attestingHistoryByPubKey[pubKey] = *attestingHistory
|
||||
attestingHistoryByPubKey[pubKey] = historicalAtt
|
||||
}
|
||||
|
||||
// We save the histories to disk as atomic operations, ensuring that this only occurs
|
||||
@@ -92,12 +93,24 @@ func ImportStandardProtectionJSON(ctx context.Context, validatorDB db.Database,
|
||||
len(attestingHistoryByPubKey),
|
||||
"Importing attesting history for validator public keys",
|
||||
)
|
||||
for pubKey, history := range attestingHistoryByPubKey {
|
||||
for pubKey, attestations := range attestingHistoryByPubKey {
|
||||
if err := bar.Add(1); err != nil {
|
||||
log.WithError(err).Debug("Could not increase progress bar")
|
||||
}
|
||||
if err := validatorDB.SaveAttestationHistoryForPubKeyV2(ctx, pubKey, history); err != nil {
|
||||
return errors.Wrap(err, "could not save attesting history from imported JSON to database")
|
||||
for _, att := range attestations {
|
||||
indexedAtt := ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: att.sourceEpoch,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: att.targetEpoch,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := validatorDB.ApplyAttestationForPubKey(ctx, pubKey, att.signingRoot, indexedAtt); err != nil {
|
||||
return errors.Wrap(err, "could not save attestation from imported JSON to database")
|
||||
}
|
||||
}
|
||||
}
|
||||
return saveLowestSourceTargetToDB(ctx, validatorDB, signedAttsByPubKey)
|
||||
@@ -222,19 +235,13 @@ func transformSignedBlocks(ctx context.Context, signedBlocks []*SignedBlock) (*k
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transformSignedAttestations(ctx context.Context, atts []*SignedAttestation) (*kv.EncHistoryData, error) {
|
||||
attestingHistory := kv.NewAttestationHistoryArray(0)
|
||||
highestEpochWritten := uint64(0)
|
||||
var err error
|
||||
func transformSignedAttestations(ctx context.Context, atts []*SignedAttestation) ([]*historicalAttestation, error) {
|
||||
historicalAtts := make([]*historicalAttestation, 0)
|
||||
for _, attestation := range atts {
|
||||
target, err := uint64FromString(attestation.TargetEpoch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%d is not a valid epoch: %v", target, err)
|
||||
}
|
||||
// Keep track of the highest epoch written from the imported JSON.
|
||||
if target > highestEpochWritten {
|
||||
highestEpochWritten = target
|
||||
}
|
||||
source, err := uint64FromString(attestation.SourceEpoch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%d is not a valid epoch: %v", source, err)
|
||||
@@ -247,18 +254,13 @@ func transformSignedAttestations(ctx context.Context, atts []*SignedAttestation)
|
||||
return nil, fmt.Errorf("%#x is not a valid root: %v", signingRoot, err)
|
||||
}
|
||||
}
|
||||
attestingHistory, err = attestingHistory.SetTargetData(
|
||||
ctx, target, &kv.HistoryData{Source: source, SigningRoot: signingRoot[:]},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not set target data for attesting history")
|
||||
}
|
||||
historicalAtts = append(historicalAtts, &historicalAttestation{
|
||||
sourceEpoch: source,
|
||||
targetEpoch: target,
|
||||
signingRoot: signingRoot,
|
||||
})
|
||||
}
|
||||
attestingHistory, err = attestingHistory.SetLatestEpochWritten(ctx, highestEpochWritten)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not set latest epoch written")
|
||||
}
|
||||
return &attestingHistory, nil
|
||||
return historicalAtts, nil
|
||||
}
|
||||
|
||||
// This saves the lowest source and target epoch from the individual validator to the DB.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
@@ -74,15 +75,22 @@ func TestStore_ImportInterchangeData_BadFormat_PreventsDBWrites(t *testing.T) {
|
||||
// sure writing is an atomic operation: either the import succeeds and saves the slashing protection
|
||||
// data to our DB, or it does not.
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
receivedAttestingHistory, err := validatorDB.AttestationHistoryForPubKeyV2(ctx, publicKeys[i])
|
||||
require.NoError(t, err)
|
||||
defaultAttestingHistory := kv.NewAttestationHistoryArray(0)
|
||||
require.DeepEqual(
|
||||
t,
|
||||
defaultAttestingHistory,
|
||||
receivedAttestingHistory,
|
||||
"Imported attestation protection history is different than the empty default",
|
||||
)
|
||||
for _, att := range attestingHistory[i] {
|
||||
indexedAtt := ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: att.sourceEpoch,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: att.targetEpoch,
|
||||
},
|
||||
},
|
||||
}
|
||||
slashingKind, err := validatorDB.CheckSlashableAttestation(ctx, publicKeys[i], [32]byte{}, indexedAtt)
|
||||
// We expect we do not have an attesting history for each attestation
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, kv.NotSlashable, slashingKind)
|
||||
}
|
||||
proposals := proposalHistory[i].Proposals
|
||||
for _, proposal := range proposals {
|
||||
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
|
||||
@@ -120,14 +128,25 @@ func TestStore_ImportInterchangeData_OK(t *testing.T) {
|
||||
// Next, we attempt to retrieve the attesting and proposals histories from our database and
|
||||
// verify those indeed match the originally generated mock histories.
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
receivedAttestingHistory, err := validatorDB.AttestationHistoryForPubKeyV2(ctx, publicKeys[i])
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(
|
||||
t,
|
||||
attestingHistory[i],
|
||||
receivedAttestingHistory,
|
||||
"We should have stored any attesting history",
|
||||
)
|
||||
for _, att := range attestingHistory[i] {
|
||||
indexedAtt := ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: att.sourceEpoch,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: att.targetEpoch,
|
||||
},
|
||||
},
|
||||
}
|
||||
slashingKind, err := validatorDB.CheckSlashableAttestation(ctx, publicKeys[i], [32]byte{}, indexedAtt)
|
||||
// We expect we have an attesting history for the attestation and when
|
||||
// attempting to verify the same att is slashable with a different signing root,
|
||||
// we expect to receive a double vote slashing kind.
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, kv.DoubleVote, slashingKind)
|
||||
}
|
||||
|
||||
proposals := proposalHistory[i].Proposals
|
||||
for _, proposal := range proposals {
|
||||
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
|
||||
@@ -772,63 +791,53 @@ func Test_saveLowestSourceTargetToDBt_Ok(t *testing.T) {
|
||||
func mockSlashingProtectionJSON(
|
||||
t *testing.T,
|
||||
publicKeys [][48]byte,
|
||||
attestingHistories []kv.EncHistoryData,
|
||||
attestingHistories [][]*historicalAttestation,
|
||||
proposalHistories []kv.ProposalHistoryForPubkey,
|
||||
) *EIPSlashingProtectionFormat {
|
||||
standardProtectionFormat := &EIPSlashingProtectionFormat{}
|
||||
standardProtectionFormat.Metadata.GenesisValidatorsRoot = fmt.Sprintf("%#x", bytesutil.PadTo([]byte{32}, 32))
|
||||
standardProtectionFormat.Metadata.InterchangeFormatVersion = INTERCHANGE_FORMAT_VERSION
|
||||
ctx := context.Background()
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
data := &ProtectionData{
|
||||
Pubkey: fmt.Sprintf("%#x", publicKeys[i]),
|
||||
}
|
||||
highestEpochWritten, err := attestingHistories[i].GetLatestEpochWritten(ctx)
|
||||
require.NoError(t, err)
|
||||
for target := uint64(0); target <= highestEpochWritten; target++ {
|
||||
hd, err := attestingHistories[i].GetTargetData(ctx, target)
|
||||
require.NoError(t, err)
|
||||
for _, att := range attestingHistories[i] {
|
||||
data.SignedAttestations = append(data.SignedAttestations, &SignedAttestation{
|
||||
TargetEpoch: fmt.Sprintf("%d", target),
|
||||
SourceEpoch: fmt.Sprintf("%d", hd.Source),
|
||||
SigningRoot: fmt.Sprintf("%#x", hd.SigningRoot),
|
||||
TargetEpoch: fmt.Sprintf("%d", att.targetEpoch),
|
||||
SourceEpoch: fmt.Sprintf("%d", att.sourceEpoch),
|
||||
SigningRoot: fmt.Sprintf("%#x", att.signingRoot),
|
||||
})
|
||||
}
|
||||
for target := uint64(0); target < highestEpochWritten; target++ {
|
||||
proposal := proposalHistories[i].Proposals[target]
|
||||
for _, proposal := range proposalHistories[i].Proposals {
|
||||
block := &SignedBlock{
|
||||
Slot: fmt.Sprintf("%d", proposal.Slot),
|
||||
SigningRoot: fmt.Sprintf("%#x", proposal.SigningRoot),
|
||||
}
|
||||
data.SignedBlocks = append(data.SignedBlocks, block)
|
||||
|
||||
}
|
||||
standardProtectionFormat.Data = append(standardProtectionFormat.Data, data)
|
||||
}
|
||||
return standardProtectionFormat
|
||||
}
|
||||
|
||||
func mockAttestingAndProposalHistories(t *testing.T, numValidators int) ([]kv.EncHistoryData, []kv.ProposalHistoryForPubkey) {
|
||||
func mockAttestingAndProposalHistories(t *testing.T, numValidators int) ([][]*historicalAttestation, []kv.ProposalHistoryForPubkey) {
|
||||
// deduplicate and transform them into our internal format.
|
||||
attData := make([]kv.EncHistoryData, numValidators)
|
||||
attData := make([][]*historicalAttestation, numValidators)
|
||||
proposalData := make([]kv.ProposalHistoryForPubkey, numValidators)
|
||||
gen := rand.NewGenerator()
|
||||
ctx := context.Background()
|
||||
for v := 0; v < numValidators; v++ {
|
||||
var err error
|
||||
latestTarget := gen.Intn(int(params.BeaconConfig().WeakSubjectivityPeriod) / 1000)
|
||||
hd := kv.NewAttestationHistoryArray(uint64(latestTarget))
|
||||
historicalAtts := make([]*historicalAttestation, 0)
|
||||
proposals := make([]kv.Proposal, 0)
|
||||
for i := 1; i < latestTarget; i++ {
|
||||
signingRoot := [32]byte{}
|
||||
signingRootStr := fmt.Sprintf("%d", i)
|
||||
copy(signingRoot[:], signingRootStr)
|
||||
historyData := &kv.HistoryData{
|
||||
Source: uint64(gen.Intn(100000)),
|
||||
SigningRoot: signingRoot[:],
|
||||
}
|
||||
hd, err = hd.SetTargetData(ctx, uint64(i), historyData)
|
||||
require.NoError(t, err)
|
||||
historicalAtts = append(historicalAtts, &historicalAttestation{
|
||||
sourceEpoch: uint64(gen.Intn(100000)),
|
||||
targetEpoch: uint64(i),
|
||||
signingRoot: signingRoot,
|
||||
})
|
||||
}
|
||||
for i := 1; i <= latestTarget; i++ {
|
||||
signingRoot := [32]byte{}
|
||||
@@ -840,9 +849,7 @@ func mockAttestingAndProposalHistories(t *testing.T, numValidators int) ([]kv.En
|
||||
})
|
||||
}
|
||||
proposalData[v] = kv.ProposalHistoryForPubkey{Proposals: proposals}
|
||||
hd, err = hd.SetLatestEpochWritten(ctx, uint64(latestTarget))
|
||||
require.NoError(t, err)
|
||||
attData[v] = hd
|
||||
attData[v] = historicalAtts
|
||||
}
|
||||
return attData, proposalData
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user