diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index 552b67b82d..b775239eb4 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -36,6 +36,7 @@ go_library( "//beacon-chain/p2p:go_default_library", "//beacon-chain/powchain:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", "//shared/params:go_default_library", diff --git a/beacon-chain/blockchain/forkchoice/BUILD.bazel b/beacon-chain/blockchain/forkchoice/BUILD.bazel index df73b3be82..bd7bc43c53 100644 --- a/beacon-chain/blockchain/forkchoice/BUILD.bazel +++ b/beacon-chain/blockchain/forkchoice/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "//beacon-chain/db/filters:go_default_library", "//beacon-chain/flags:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", "//shared/params:go_default_library", diff --git a/beacon-chain/blockchain/forkchoice/process_attestation.go b/beacon-chain/blockchain/forkchoice/process_attestation.go index 56280ed8fd..19782a18d4 100644 --- a/beacon-chain/blockchain/forkchoice/process_attestation.go +++ b/beacon-chain/blockchain/forkchoice/process_attestation.go @@ -14,6 +14,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" "github.com/prysmaticlabs/prysm/shared/params" @@ -232,7 +233,7 @@ func (s *Store) verifyAttestation(ctx context.Context, baseState *pb.BeaconState if err != nil { return nil, err } - indexedAtt, err := blocks.ConvertToIndexed(ctx, a, committee) + indexedAtt, err := attestationutil.ConvertToIndexed(ctx, a, committee) if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation") } @@ -248,7 +249,7 @@ func (s *Store) verifyAttestation(ctx context.Context, baseState *pb.BeaconState if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation without cache") } - indexedAtt, err = blocks.ConvertToIndexed(ctx, a, committee) + indexedAtt, err = attestationutil.ConvertToIndexed(ctx, a, committee) if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation") } diff --git a/beacon-chain/blockchain/process_attestation_helpers.go b/beacon-chain/blockchain/process_attestation_helpers.go index be497cda15..be63cdef46 100644 --- a/beacon-chain/blockchain/process_attestation_helpers.go +++ b/beacon-chain/blockchain/process_attestation_helpers.go @@ -12,6 +12,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" "go.opencensus.io/trace" @@ -101,7 +102,7 @@ func (s *Service) verifyAttestation(ctx context.Context, baseState *pb.BeaconSta if err != nil { return nil, err } - indexedAtt, err := blocks.ConvertToIndexed(ctx, a, committee) + indexedAtt, err := attestationutil.ConvertToIndexed(ctx, a, committee) if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation") } @@ -117,7 +118,7 @@ func (s *Service) verifyAttestation(ctx context.Context, baseState *pb.BeaconSta if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation without cache") } - indexedAtt, err = blocks.ConvertToIndexed(ctx, a, committee) + indexedAtt, err = attestationutil.ConvertToIndexed(ctx, a, committee) if err != nil { return nil, errors.Wrap(err, "could not convert attestation to indexed attestation") } diff --git a/beacon-chain/blockchain/receive_block.go b/beacon-chain/blockchain/receive_block.go index 739259aef5..857d5d416f 100644 --- a/beacon-chain/blockchain/receive_block.go +++ b/beacon-chain/blockchain/receive_block.go @@ -14,6 +14,7 @@ import ( statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" "github.com/prysmaticlabs/prysm/shared/traceutil" @@ -140,7 +141,7 @@ func (s *Service) ReceiveBlockNoPubsub(ctx context.Context, block *ethpb.SignedB if err != nil { return err } - indices, err := helpers.AttestingIndices(a.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return err } @@ -248,7 +249,7 @@ func (s *Service) ReceiveBlockNoPubsubForkchoice(ctx context.Context, block *eth if err != nil { return err } - indices, err := helpers.AttestingIndices(a.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return err } @@ -324,7 +325,7 @@ func (s *Service) ReceiveBlockNoVerify(ctx context.Context, block *ethpb.SignedB if err != nil { return err } - indices, err := helpers.AttestingIndices(a.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return err } diff --git a/beacon-chain/core/blocks/BUILD.bazel b/beacon-chain/core/blocks/BUILD.bazel index 6b19c861b2..468c0c6a8e 100644 --- a/beacon-chain/core/blocks/BUILD.bazel +++ b/beacon-chain/core/blocks/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "//beacon-chain/core/state/stateutils:go_default_library", "//beacon-chain/core/validators:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", @@ -48,6 +49,7 @@ go_test( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/state/stateutils:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/params:go_default_library", "//shared/testutil:go_default_library", diff --git a/beacon-chain/core/blocks/block_operations.go b/beacon-chain/core/blocks/block_operations.go index f735c90512..cae579d66c 100644 --- a/beacon-chain/core/blocks/block_operations.go +++ b/beacon-chain/core/blocks/block_operations.go @@ -17,6 +17,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/state/stateutils" v "github.com/prysmaticlabs/prysm/beacon-chain/core/validators" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" @@ -650,44 +651,6 @@ func ProcessAttestationNoVerify(ctx context.Context, beaconState *pb.BeaconState return beaconState, nil } -// ConvertToIndexed converts attestation to (almost) indexed-verifiable form. -// -// Note about spec pseudocode definition. The state was used by get_attesting_indices to determine -// the attestation committee. Now that we provide this as an argument, we no longer need to provide -// a state. -// -// Spec pseudocode definition: -// def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: -// """ -// Return the indexed attestation corresponding to ``attestation``. -// """ -// attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) -// -// return IndexedAttestation( -// attesting_indices=sorted(attesting_indices), -// data=attestation.data, -// signature=attestation.signature, -// ) -func ConvertToIndexed(ctx context.Context, attestation *ethpb.Attestation, committee []uint64) (*ethpb.IndexedAttestation, error) { - ctx, span := trace.StartSpan(ctx, "core.ConvertToIndexed") - defer span.End() - - attIndices, err := helpers.AttestingIndices(attestation.AggregationBits, committee) - if err != nil { - return nil, errors.Wrap(err, "could not get attesting indices") - } - - sort.Slice(attIndices, func(i, j int) bool { - return attIndices[i] < attIndices[j] - }) - inAtt := ðpb.IndexedAttestation{ - Data: attestation.Data, - Signature: attestation.Signature, - AttestingIndices: attIndices, - } - return inAtt, nil -} - // VerifyIndexedAttestation determines the validity of an indexed attestation. // // Spec pseudocode definition: @@ -778,7 +741,7 @@ func VerifyAttestation(ctx context.Context, beaconState *pb.BeaconState, att *et if err != nil { return err } - indexedAtt, err := ConvertToIndexed(ctx, att, committee) + indexedAtt, err := attestationutil.ConvertToIndexed(ctx, att, committee) if err != nil { return errors.Wrap(err, "could not convert to indexed attestation") } diff --git a/beacon-chain/core/blocks/block_operations_test.go b/beacon-chain/core/blocks/block_operations_test.go index ec642e2cbd..e2b6e38a44 100644 --- a/beacon-chain/core/blocks/block_operations_test.go +++ b/beacon-chain/core/blocks/block_operations_test.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state/stateutils" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" @@ -909,7 +910,7 @@ func TestProcessAttestations_OK(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices, err := helpers.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { t.Error(err) } @@ -962,7 +963,7 @@ func TestProcessAggregatedAttestation_OverlappingBits(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices1, err := helpers.AttestingIndices(att1.AggregationBits, committee) + attestingIndices1, err := attestationutil.AttestingIndices(att1.AggregationBits, committee) if err != nil { t.Fatal(err) } @@ -990,7 +991,7 @@ func TestProcessAggregatedAttestation_OverlappingBits(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices2, err := helpers.AttestingIndices(att2.AggregationBits, committee) + attestingIndices2, err := attestationutil.AttestingIndices(att2.AggregationBits, committee) if err != nil { t.Fatal(err) } @@ -1033,7 +1034,7 @@ func TestProcessAggregatedAttestation_NoOverlappingBits(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices1, err := helpers.AttestingIndices(att1.AggregationBits, committee) + attestingIndices1, err := attestationutil.AttestingIndices(att1.AggregationBits, committee) if err != nil { t.Fatal(err) } @@ -1060,7 +1061,7 @@ func TestProcessAggregatedAttestation_NoOverlappingBits(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices2, err := helpers.AttestingIndices(att2.AggregationBits, committee) + attestingIndices2, err := attestationutil.AttestingIndices(att2.AggregationBits, committee) if err != nil { t.Fatal(err) } @@ -1185,7 +1186,7 @@ func TestConvertToIndexed_OK(t *testing.T) { if err != nil { t.Error(err) } - ia, err := blocks.ConvertToIndexed(context.Background(), attestation, committee) + ia, err := attestationutil.ConvertToIndexed(context.Background(), attestation, committee) if err != nil { t.Errorf("failed to convert attestation to indexed attestation: %v", err) } diff --git a/beacon-chain/core/epoch/BUILD.bazel b/beacon-chain/core/epoch/BUILD.bazel index 0e988b3f63..a02e3e477e 100644 --- a/beacon-chain/core/epoch/BUILD.bazel +++ b/beacon-chain/core/epoch/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/validators:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/mathutil:go_default_library", "//shared/params:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/beacon-chain/core/epoch/epoch_processing.go b/beacon-chain/core/epoch/epoch_processing.go index 11e96b0d2c..7cead40237 100644 --- a/beacon-chain/core/epoch/epoch_processing.go +++ b/beacon-chain/core/epoch/epoch_processing.go @@ -14,6 +14,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/validators" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/mathutil" "github.com/prysmaticlabs/prysm/shared/params" ) @@ -290,7 +291,7 @@ func unslashedAttestingIndices(state *pb.BeaconState, atts []*pb.PendingAttestat if err != nil { return nil, err } - attestingIndices, err := helpers.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { return nil, errors.Wrap(err, "could not get attester indices") } diff --git a/beacon-chain/core/epoch/precompute/BUILD.bazel b/beacon-chain/core/epoch/precompute/BUILD.bazel index 6bbef61d51..bac5fce7ca 100644 --- a/beacon-chain/core/epoch/precompute/BUILD.bazel +++ b/beacon-chain/core/epoch/precompute/BUILD.bazel @@ -15,6 +15,7 @@ go_library( deps = [ "//beacon-chain/core/helpers:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/mathutil:go_default_library", "//shared/params:go_default_library", "//shared/traceutil:go_default_library", @@ -38,6 +39,7 @@ go_test( "//beacon-chain/core/epoch:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/params:go_default_library", "//shared/testutil:go_default_library", "@com_github_gogo_protobuf//proto:go_default_library", diff --git a/beacon-chain/core/epoch/precompute/attestation.go b/beacon-chain/core/epoch/precompute/attestation.go index fdaf5e740f..be2b46f9f9 100644 --- a/beacon-chain/core/epoch/precompute/attestation.go +++ b/beacon-chain/core/epoch/precompute/attestation.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/traceutil" "go.opencensus.io/trace" ) @@ -44,7 +45,7 @@ func ProcessAttestations( if err != nil { return nil, nil, err } - indices, err := helpers.AttestingIndices(a.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return nil, nil, err } diff --git a/beacon-chain/core/epoch/precompute/attestation_test.go b/beacon-chain/core/epoch/precompute/attestation_test.go index f095504300..8966b77823 100644 --- a/beacon-chain/core/epoch/precompute/attestation_test.go +++ b/beacon-chain/core/epoch/precompute/attestation_test.go @@ -9,6 +9,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" ) @@ -204,7 +205,7 @@ func TestProcessAttestations(t *testing.T) { if err != nil { t.Error(err) } - indices, _ := helpers.AttestingIndices(att1.AggregationBits, committee) + indices, _ := attestationutil.AttestingIndices(att1.AggregationBits, committee) for _, i := range indices { if !vp[i].IsPrevEpochAttester { t.Error("Not a prev epoch attester") @@ -214,7 +215,7 @@ func TestProcessAttestations(t *testing.T) { if err != nil { t.Error(err) } - indices, _ = helpers.AttestingIndices(att2.AggregationBits, committee) + indices, _ = attestationutil.AttestingIndices(att2.AggregationBits, committee) for _, i := range indices { if !vp[i].IsPrevEpochAttester { t.Error("Not a prev epoch attester") diff --git a/beacon-chain/core/helpers/BUILD.bazel b/beacon-chain/core/helpers/BUILD.bazel index 1dc8ea5718..202c8c5668 100644 --- a/beacon-chain/core/helpers/BUILD.bazel +++ b/beacon-chain/core/helpers/BUILD.bazel @@ -56,6 +56,7 @@ go_test( shard_count = 2, deps = [ "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", diff --git a/beacon-chain/core/helpers/committee.go b/beacon-chain/core/helpers/committee.go index ec66acf4b8..a8394f3e4d 100644 --- a/beacon-chain/core/helpers/committee.go +++ b/beacon-chain/core/helpers/committee.go @@ -172,33 +172,6 @@ func ComputeCommittee( return shuffledIndices, nil } -// AttestingIndices returns the attesting participants indices from the attestation data. The -// committee is provided as an argument rather than a direct implementation from the spec definition. -// Having the committee as an argument allows for re-use of beacon committees when possible. -// -// Spec pseudocode definition: -// def get_attesting_indices(state: BeaconState, -// data: AttestationData, -// bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: -// """ -// Return the set of attesting indices corresponding to ``data`` and ``bits``. -// """ -// committee = get_beacon_committee(state, data.slot, data.index) -// return set(index for i, index in enumerate(committee) if bits[i]) -func AttestingIndices(bf bitfield.Bitfield, committee []uint64) ([]uint64, error) { - indices := make([]uint64, 0, len(committee)) - indicesSet := make(map[uint64]bool) - for i, idx := range committee { - if !indicesSet[idx] { - if bf.BitAt(uint64(i)) { - indices = append(indices, idx) - } - } - indicesSet[idx] = true - } - return indices, nil -} - // CommitteeAssignmentContainer represents a committee, index, and attester slot for a given epoch. type CommitteeAssignmentContainer struct { Committee []uint64 diff --git a/beacon-chain/core/helpers/committee_test.go b/beacon-chain/core/helpers/committee_test.go index 28a8bb7c01..f0aee33eca 100644 --- a/beacon-chain/core/helpers/committee_test.go +++ b/beacon-chain/core/helpers/committee_test.go @@ -10,6 +10,7 @@ import ( ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/go-bitfield" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" "github.com/prysmaticlabs/prysm/shared/hashutil" @@ -123,7 +124,7 @@ func TestAttestationParticipants_NoCommitteeCache(t *testing.T) { if err != nil { t.Error(err) } - result, err := AttestingIndices(tt.bitfield, committee) + result, err := attestationutil.AttestingIndices(tt.bitfield, committee) if err != nil { t.Errorf("Failed to get attestation participants: %v", err) } @@ -184,7 +185,7 @@ func TestAttestingIndicesWithBeaconCommitteeWithoutCache_Ok(t *testing.T) { if err != nil { t.Error(err) } - result, err := AttestingIndices(tt.bitfield, committee) + result, err := attestationutil.AttestingIndices(tt.bitfield, committee) if err != nil { t.Errorf("Failed to get attestation participants: %v", err) } @@ -217,7 +218,7 @@ func TestAttestationParticipants_EmptyBitfield(t *testing.T) { if err != nil { t.Fatal(err) } - indices, err := AttestingIndices(bitfield.NewBitlist(128), committee) + indices, err := attestationutil.AttestingIndices(bitfield.NewBitlist(128), committee) if err != nil { t.Fatalf("attesting indices failed: %v", err) } diff --git a/beacon-chain/core/helpers/validators_test.go b/beacon-chain/core/helpers/validators_test.go index fb34e5d744..ceab472ebe 100644 --- a/beacon-chain/core/helpers/validators_test.go +++ b/beacon-chain/core/helpers/validators_test.go @@ -428,11 +428,11 @@ func TestComputeProposerIndex(t *testing.T) { name: "all_active_indices", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{0, 1, 2, 3, 4}, seed: seed, @@ -443,11 +443,11 @@ func TestComputeProposerIndex(t *testing.T) { name: "1_active_index", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{3}, seed: seed, @@ -458,11 +458,11 @@ func TestComputeProposerIndex(t *testing.T) { name: "empty_active_indices", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{}, seed: seed, @@ -473,11 +473,11 @@ func TestComputeProposerIndex(t *testing.T) { name: "active_indices_out_of_range", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{100}, seed: seed, @@ -488,16 +488,16 @@ func TestComputeProposerIndex(t *testing.T) { name: "second_half_active", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{5, 6, 7, 8, 9}, seed: seed, @@ -508,11 +508,11 @@ func TestComputeProposerIndex(t *testing.T) { name: "nil_validator", args: args{ validators: []*ethpb.Validator{ - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, nil, // Should never happen, but would cause a panic when it does happen. - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, - ðpb.Validator{EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, + {EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}, }, indices: []uint64{0, 1, 2, 3, 4}, seed: seed, diff --git a/beacon-chain/core/state/BUILD.bazel b/beacon-chain/core/state/BUILD.bazel index 0cfb970ff5..2fec82137c 100644 --- a/beacon-chain/core/state/BUILD.bazel +++ b/beacon-chain/core/state/BUILD.bazel @@ -53,6 +53,7 @@ go_test( "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/benchutil:go_default_library", "//shared/bls:go_default_library", "//shared/featureconfig:go_default_library", diff --git a/beacon-chain/core/state/transition_test.go b/beacon-chain/core/state/transition_test.go index a58f104b48..c7dbcc4fd0 100644 --- a/beacon-chain/core/state/transition_test.go +++ b/beacon-chain/core/state/transition_test.go @@ -15,6 +15,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/hashutil" "github.com/prysmaticlabs/prysm/shared/params" @@ -394,7 +395,7 @@ func TestProcessBlock_PassesProcessingConditions(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices, err := helpers.AttestingIndices(blockAtt.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(blockAtt.AggregationBits, committee) if err != nil { t.Error(err) } @@ -686,7 +687,7 @@ func TestProcessBlk_AttsBasedOnValidatorCount(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices, err := helpers.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { t.Error(err) } diff --git a/beacon-chain/rpc/aggregator/BUILD.bazel b/beacon-chain/rpc/aggregator/BUILD.bazel index 1ef8724624..48569df9ab 100644 --- a/beacon-chain/rpc/aggregator/BUILD.bazel +++ b/beacon-chain/rpc/aggregator/BUILD.bazel @@ -34,6 +34,7 @@ go_test( "//beacon-chain/sync/initial-sync/testing:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//proto/beacon/rpc/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/params:go_default_library", "//shared/testutil:go_default_library", diff --git a/beacon-chain/rpc/aggregator/server_test.go b/beacon-chain/rpc/aggregator/server_test.go index 06b8df0fae..5a7ebbe450 100644 --- a/beacon-chain/rpc/aggregator/server_test.go +++ b/beacon-chain/rpc/aggregator/server_test.go @@ -18,6 +18,7 @@ import ( mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" @@ -220,7 +221,7 @@ func generateAtt(state *pbp2p.BeaconState, index uint64, privKeys []*bls.SecretK AggregationBits: aggBits, } committee, _ := helpers.BeaconCommitteeFromState(state, att.Data.Slot, att.Data.CommitteeIndex) - attestingIndices, _ := helpers.AttestingIndices(att.AggregationBits, committee) + attestingIndices, _ := attestationutil.AttestingIndices(att.AggregationBits, committee) domain := helpers.Domain(state.Fork, 0, params.BeaconConfig().DomainBeaconAttester) sigs := make([]*bls.Signature, len(attestingIndices)) diff --git a/beacon-chain/rpc/validator/BUILD.bazel b/beacon-chain/rpc/validator/BUILD.bazel index 794513f668..4d7a2478b1 100644 --- a/beacon-chain/rpc/validator/BUILD.bazel +++ b/beacon-chain/rpc/validator/BUILD.bazel @@ -80,6 +80,7 @@ go_test( "//beacon-chain/sync/initial-sync/testing:go_default_library", "//proto/beacon/db:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/event:go_default_library", "//shared/featureconfig:go_default_library", diff --git a/beacon-chain/rpc/validator/proposer_test.go b/beacon-chain/rpc/validator/proposer_test.go index 3dc49e2a68..1f59dff791 100644 --- a/beacon-chain/rpc/validator/proposer_test.go +++ b/beacon-chain/rpc/validator/proposer_test.go @@ -19,6 +19,7 @@ import ( mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing" dbpb "github.com/prysmaticlabs/prysm/proto/beacon/db" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/hashutil" "github.com/prysmaticlabs/prysm/shared/params" @@ -1059,7 +1060,7 @@ func TestFilterAttestation_OK(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices, err := helpers.AttestingIndices(atts[i].AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(atts[i].AggregationBits, committee) if err != nil { t.Error(err) } diff --git a/beacon-chain/sync/BUILD.bazel b/beacon-chain/sync/BUILD.bazel index 454fec51d6..2ba64652c1 100644 --- a/beacon-chain/sync/BUILD.bazel +++ b/beacon-chain/sync/BUILD.bazel @@ -48,6 +48,7 @@ go_library( "//beacon-chain/p2p/encoder:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//shared:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/messagehandler:go_default_library", @@ -112,6 +113,7 @@ go_test( "//beacon-chain/sync/initial-sync/testing:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//proto/testing:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/params:go_default_library", diff --git a/beacon-chain/sync/validate_aggregate_proof.go b/beacon-chain/sync/validate_aggregate_proof.go index 9b6eda8fc8..cf2c82ebd1 100644 --- a/beacon-chain/sync/validate_aggregate_proof.go +++ b/beacon-chain/sync/validate_aggregate_proof.go @@ -13,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" @@ -120,7 +121,7 @@ func validateIndexInCommittee(ctx context.Context, s *pb.BeaconState, a *ethpb.A if err != nil { return err } - attestingIndices, err := helpers.AttestingIndices(a.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return err } diff --git a/beacon-chain/sync/validate_aggregate_proof_test.go b/beacon-chain/sync/validate_aggregate_proof_test.go index 23baa542dd..c974ad85c5 100644 --- a/beacon-chain/sync/validate_aggregate_proof_test.go +++ b/beacon-chain/sync/validate_aggregate_proof_test.go @@ -20,6 +20,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/p2p" p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" @@ -43,7 +44,7 @@ func TestVerifyIndexInCommittee_CanVerify(t *testing.T) { if err != nil { t.Error(err) } - indices, err := helpers.AttestingIndices(att.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { t.Fatal(err) } @@ -313,7 +314,7 @@ func TestValidateAggregateAndProof_CanValidate(t *testing.T) { if err != nil { t.Error(err) } - attestingIndices, err := helpers.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { t.Error(err) } diff --git a/shared/attestationutil/BUILD.bazel b/shared/attestationutil/BUILD.bazel new file mode 100644 index 0000000000..d50b8b878f --- /dev/null +++ b/shared/attestationutil/BUILD.bazel @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["attestation_utils.go"], + importpath = "github.com/prysmaticlabs/prysm/shared/attestationutil", + visibility = ["//visibility:public"], + deps = [ + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@io_opencensus_go//trace:go_default_library", + ], +) diff --git a/shared/attestationutil/attestation_utils.go b/shared/attestationutil/attestation_utils.go new file mode 100644 index 0000000000..22fa4cc52a --- /dev/null +++ b/shared/attestationutil/attestation_utils.go @@ -0,0 +1,76 @@ +package attestationutil + +import ( + "context" + "sort" + + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/go-bitfield" + "go.opencensus.io/trace" +) + +// ConvertToIndexed converts attestation to (almost) indexed-verifiable form. +// +// Note about spec pseudocode definition. The state was used by get_attesting_indices to determine +// the attestation committee. Now that we provide this as an argument, we no longer need to provide +// a state. +// +// Spec pseudocode definition: +// def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: +// """ +// Return the indexed attestation corresponding to ``attestation``. +// """ +// attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) +// +// return IndexedAttestation( +// attesting_indices=sorted(attesting_indices), +// data=attestation.data, +// signature=attestation.signature, +// ) +func ConvertToIndexed(ctx context.Context, attestation *ethpb.Attestation, committee []uint64) (*ethpb.IndexedAttestation, error) { + ctx, span := trace.StartSpan(ctx, "core.ConvertToIndexed") + defer span.End() + + attIndices, err := AttestingIndices(attestation.AggregationBits, committee) + if err != nil { + return nil, errors.Wrap(err, "could not get attesting indices") + } + + sort.Slice(attIndices, func(i, j int) bool { + return attIndices[i] < attIndices[j] + }) + inAtt := ðpb.IndexedAttestation{ + Data: attestation.Data, + Signature: attestation.Signature, + AttestingIndices: attIndices, + } + return inAtt, nil +} + +// AttestingIndices returns the attesting participants indices from the attestation data. The +// committee is provided as an argument rather than a direct implementation from the spec definition. +// Having the committee as an argument allows for re-use of beacon committees when possible. +// +// Spec pseudocode definition: +// def get_attesting_indices(state: BeaconState, +// data: AttestationData, +// bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: +// """ +// Return the set of attesting indices corresponding to ``data`` and ``bits``. +// """ +// committee = get_beacon_committee(state, data.slot, data.index) +// return set(index for i, index in enumerate(committee) if bits[i]) +func AttestingIndices(bf bitfield.Bitfield, committee []uint64) ([]uint64, error) { + indices := make([]uint64, 0, len(committee)) + indicesSet := make(map[uint64]bool) + for i, idx := range committee { + if !indicesSet[idx] { + if bf.BitAt(uint64(i)) { + indices = append(indices, idx) + } + } + indicesSet[idx] = true + } + return indices, nil +} diff --git a/shared/mock/BUILD.bazel b/shared/mock/BUILD.bazel index 45a91b74af..ae0fd463cf 100644 --- a/shared/mock/BUILD.bazel +++ b/shared/mock/BUILD.bazel @@ -4,15 +4,13 @@ package(default_testonly = True) go_library( name = "go_default_library", - srcs = [ - "broadcaster_mock.go", - "feed_mock.go", - ], + srcs = ["beacon_chain_service_mock.go"], importpath = "github.com/prysmaticlabs/prysm/shared/mock", visibility = ["//visibility:public"], deps = [ - "//shared/event:go_default_library", - "@com_github_gogo_protobuf//proto:go_default_library", + "@com_github_gogo_protobuf//types:go_default_library", "@com_github_golang_mock//gomock:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@org_golang_google_grpc//:go_default_library", ], ) diff --git a/validator/internal/beacon_chain_service_mock.go b/shared/mock/beacon_chain_service_mock.go similarity index 99% rename from validator/internal/beacon_chain_service_mock.go rename to shared/mock/beacon_chain_service_mock.go index 87077a563a..7eae3a896b 100644 --- a/validator/internal/beacon_chain_service_mock.go +++ b/shared/mock/beacon_chain_service_mock.go @@ -2,7 +2,7 @@ // Source: github.com/prysmaticlabs/ethereumapis/eth/v1alpha1 (interfaces: BeaconChainClient) // Package internal is a generated GoMock package. -package internal +package mock import ( context "context" diff --git a/shared/mock/broadcaster_mock.go b/shared/mock/broadcaster_mock.go deleted file mode 100644 index 64495733a9..0000000000 --- a/shared/mock/broadcaster_mock.go +++ /dev/null @@ -1,45 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/prysmaticlabs/prysm/shared/deprecated-p2p (interfaces: Broadcaster) - -// Package mock_p2p is a generated GoMock package. -package mock_p2p - -import ( - reflect "reflect" - - proto "github.com/gogo/protobuf/proto" - gomock "github.com/golang/mock/gomock" -) - -// MockBroadcaster is a mock of Broadcaster interface -type MockBroadcaster struct { - ctrl *gomock.Controller - recorder *MockBroadcasterMockRecorder -} - -// MockBroadcasterMockRecorder is the mock recorder for MockBroadcaster -type MockBroadcasterMockRecorder struct { - mock *MockBroadcaster -} - -// NewMockBroadcaster creates a new mock instance -func NewMockBroadcaster(ctrl *gomock.Controller) *MockBroadcaster { - mock := &MockBroadcaster{ctrl: ctrl} - mock.recorder = &MockBroadcasterMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockBroadcaster) EXPECT() *MockBroadcasterMockRecorder { - return m.recorder -} - -// Broadcast mocks base method -func (m *MockBroadcaster) Broadcast(arg0 proto.Message) { - m.ctrl.Call(m, "Broadcast", arg0) -} - -// Broadcast indicates an expected call of Broadcast -func (mr *MockBroadcasterMockRecorder) Broadcast(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockBroadcaster)(nil).Broadcast), arg0) -} diff --git a/shared/mock/feed_mock.go b/shared/mock/feed_mock.go deleted file mode 100644 index c3bd321a75..0000000000 --- a/shared/mock/feed_mock.go +++ /dev/null @@ -1,59 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/prysmaticlabs/prysm/shared/deprecated-p2p (interfaces: Feed) - -// Package mock_p2p is a generated GoMock package. -package mock_p2p - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - event "github.com/prysmaticlabs/prysm/shared/event" -) - -// MockFeed is a mock of Feed interface -type MockFeed struct { - ctrl *gomock.Controller - recorder *MockFeedMockRecorder -} - -// MockFeedMockRecorder is the mock recorder for MockFeed -type MockFeedMockRecorder struct { - mock *MockFeed -} - -// NewMockFeed creates a new mock instance -func NewMockFeed(ctrl *gomock.Controller) *MockFeed { - mock := &MockFeed{ctrl: ctrl} - mock.recorder = &MockFeedMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockFeed) EXPECT() *MockFeedMockRecorder { - return m.recorder -} - -// Send mocks base method -func (m *MockFeed) Send(arg0 interface{}) int { - ret := m.ctrl.Call(m, "Send", arg0) - ret0, _ := ret[0].(int) - return ret0 -} - -// Send indicates an expected call of Send -func (mr *MockFeedMockRecorder) Send(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockFeed)(nil).Send), arg0) -} - -// Subscribe mocks base method -func (m *MockFeed) Subscribe(arg0 interface{}) event.Subscription { - ret := m.ctrl.Call(m, "Subscribe", arg0) - ret0, _ := ret[0].(event.Subscription) - return ret0 -} - -// Subscribe indicates an expected call of Subscribe -func (mr *MockFeedMockRecorder) Subscribe(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockFeed)(nil).Subscribe), arg0) -} diff --git a/shared/testutil/BUILD.bazel b/shared/testutil/BUILD.bazel index 3a559d8f35..d7b0aa322a 100644 --- a/shared/testutil/BUILD.bazel +++ b/shared/testutil/BUILD.bazel @@ -15,10 +15,10 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/shared/testutil", visibility = ["//visibility:public"], deps = [ - "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/state:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/hashutil:go_default_library", diff --git a/shared/testutil/block.go b/shared/testutil/block.go index dc1b98ae64..426cc2773a 100644 --- a/shared/testutil/block.go +++ b/shared/testutil/block.go @@ -12,10 +12,10 @@ import ( ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/go-bitfield" "github.com/prysmaticlabs/go-ssz" - "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/stateutil" @@ -264,11 +264,11 @@ func generateAttesterSlashings( sig = privs[valIndex].Sign(dataRoot[:], domain) att2.Signature = bls.AggregateSignatures([]*bls.Signature{sig}).Marshal() - indexedAtt1, err := blocks.ConvertToIndexed(context.Background(), att1, committee) + indexedAtt1, err := attestationutil.ConvertToIndexed(context.Background(), att1, committee) if err != nil { return nil, err } - indexedAtt2, err := blocks.ConvertToIndexed(context.Background(), att2, committee) + indexedAtt2, err := attestationutil.ConvertToIndexed(context.Background(), att2, committee) if err != nil { return nil, err } diff --git a/slasher/db/attester_slashings.go b/slasher/db/attester_slashings.go index 231e221f3a..f33128ac1c 100644 --- a/slasher/db/attester_slashings.go +++ b/slasher/db/attester_slashings.go @@ -7,6 +7,7 @@ import ( "github.com/gogo/protobuf/proto" "github.com/pkg/errors" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/hashutil" ) @@ -99,16 +100,59 @@ func (db *Store) SaveAttesterSlashing(status SlashingStatus, slashing *ethpb.Att } root := hashutil.Hash(enc) key := encodeTypeRoot(SlashingType(Attestation), root) - err = db.update(func(tx *bolt.Tx) error { + return db.update(func(tx *bolt.Tx) error { b := tx.Bucket(slashingBucket) e := b.Put(key, append([]byte{byte(status)}, enc...)) - if e != nil { + return e + }) +} + +// SaveAttesterSlashings accepts a slice of slashing proof and its status and writes it to disk. +func (db *Store) SaveAttesterSlashings(status SlashingStatus, slashings []*ethpb.AttesterSlashing) error { + enc := make([][]byte, len(slashings)) + key := make([][]byte, len(slashings)) + var err error + for i, slashing := range slashings { + enc[i], err = proto.Marshal(slashing) + if err != nil { + return errors.Wrap(err, "failed to marshal") + } + root := hashutil.Hash(enc[i]) + key[i] = encodeTypeRoot(SlashingType(Attestation), root) + } + return db.update(func(tx *bolt.Tx) error { + b := tx.Bucket(slashingBucket) + for i := 0; i < len(enc); i++ { + e := b.Put(key[i], append([]byte{byte(status)}, enc[i]...)) + if e != nil { + return e + } + } + return nil + }) +} + +// GetLatestEpochDetected returns the latest detected epoch from db. +func (db *Store) GetLatestEpochDetected() (uint64, error) { + var epoch uint64 + err := db.view(func(tx *bolt.Tx) error { + b := tx.Bucket(slashingBucket) + enc := b.Get([]byte(latestEpochKey)) + if enc == nil { + epoch = 0 return nil } + epoch = bytesutil.FromBytes8(enc) + return nil + }) + return epoch, err +} + +// SetLatestEpochDetected sets the latest slashing detected epoch in db. +func (db *Store) SetLatestEpochDetected(epoch uint64) error { + return db.update(func(tx *bolt.Tx) error { + b := tx.Bucket(slashingBucket) + err := b.Put([]byte(latestEpochKey), bytesutil.Bytes8(epoch)) return err }) - if err != nil { - return err - } - return err } diff --git a/slasher/db/attester_slashings_test.go b/slasher/db/attester_slashings_test.go index 276a1ea2fb..2d645b5913 100644 --- a/slasher/db/attester_slashings_test.go +++ b/slasher/db/attester_slashings_test.go @@ -3,6 +3,7 @@ package db import ( "flag" "reflect" + "sort" "testing" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" @@ -26,10 +27,10 @@ func TestStore_AttesterSlashingNilBucket(t *testing.T) { p, err := db.AttesterSlashings(SlashingStatus(Active)) if err != nil { - t.Fatalf("failed to get attester slashing: %v", err) + t.Fatalf("Failed to get attester slashing: %v", err) } if p == nil || len(p) != 0 { - t.Fatalf("get should return empty attester slashing array for a non existent key") + t.Fatalf("Get should return empty attester slashing array for a non existent key") } } @@ -75,6 +76,33 @@ func TestStore_SaveAttesterSlashing(t *testing.T) { } +func TestStore_SaveAttesterSlashings(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ctx := cli.NewContext(app, set, nil) + db := SetupSlasherDB(t, ctx) + defer TeardownSlasherDB(t, db) + as := []*ethpb.AttesterSlashing{ + {Attestation_1: ðpb.IndexedAttestation{Signature: []byte("1")}}, + {Attestation_1: ðpb.IndexedAttestation{Signature: []byte("2")}}, + {Attestation_1: ðpb.IndexedAttestation{Signature: []byte("3")}}, + } + err := db.SaveAttesterSlashings(Active, as) + if err != nil { + t.Fatalf("save attester slashing failed: %v", err) + } + attesterSlashings, err := db.AttesterSlashings(Active) + if err != nil { + t.Fatalf("failed to get attester slashings: %v", err) + } + sort.SliceStable(attesterSlashings, func(i, j int) bool { + return attesterSlashings[i].Attestation_1.Signature[0] < attesterSlashings[j].Attestation_1.Signature[0] + }) + if attesterSlashings == nil || !reflect.DeepEqual(attesterSlashings, as) { + t.Fatalf("Attester slashing: %v should be part of attester slashings response: %v", as, attesterSlashings) + } +} + func TestStore_UpdateAttesterSlashingStatus(t *testing.T) { app := cli.NewApp() set := flag.NewFlagSet("test", 0) @@ -109,27 +137,54 @@ func TestStore_UpdateAttesterSlashingStatus(t *testing.T) { for _, tt := range tests { has, st, err := db.HasAttesterSlashing(tt.as) if err != nil { - t.Fatalf("failed to get attester slashing: %v", err) + t.Fatalf("Failed to get attester slashing: %v", err) } if !has { - t.Fatalf("failed to find attester slashing: %v", tt.as) + t.Fatalf("Failed to find attester slashing: %v", tt.as) } if st != tt.ss { - t.Fatalf("failed to find attester slashing with the correct status: %v", tt.as) + t.Fatalf("Failed to find attester slashing with the correct status: %v", tt.as) } err = db.SaveAttesterSlashing(SlashingStatus(Included), tt.as) has, st, err = db.HasAttesterSlashing(tt.as) if err != nil { - t.Fatalf("failed to get attester slashing: %v", err) + t.Fatalf("Failed to get attester slashing: %v", err) } if !has { - t.Fatalf("failed to find attester slashing: %v", tt.as) + t.Fatalf("Failed to find attester slashing: %v", tt.as) } if st != Included { - t.Fatalf("failed to find attester slashing with the correct status: %v", tt.as) + t.Fatalf("Failed to find attester slashing with the correct status: %v", tt.as) } } } + +func TestStore_LatestEpochDetected(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ctx := cli.NewContext(app, set, nil) + db := SetupSlasherDB(t, ctx) + defer TeardownSlasherDB(t, db) + e, err := db.GetLatestEpochDetected() + if err != nil { + t.Fatalf("Get latest epoch detected failed: %v", err) + } + if e != 0 { + t.Fatalf("Latest epoch detected should have been 0 before setting got: %d", e) + } + epoch := uint64(1) + err = db.SetLatestEpochDetected(epoch) + if err != nil { + t.Fatalf("Set latest epoch detected failed: %v", err) + } + e, err = db.GetLatestEpochDetected() + if err != nil { + t.Fatalf("Get latest epoch detected failed: %v", err) + } + if e != epoch { + t.Fatalf("Latest epoch detected should have been: %d got: %d", epoch, e) + } +} diff --git a/slasher/db/indexed_attestations.go b/slasher/db/indexed_attestations.go index dd2c2a6502..9b63b60e52 100644 --- a/slasher/db/indexed_attestations.go +++ b/slasher/db/indexed_attestations.go @@ -2,6 +2,7 @@ package db import ( "bytes" + "fmt" "reflect" "sort" @@ -126,9 +127,15 @@ func (db *Store) DoubleVotes(targetEpoch uint64, validatorIdx uint64, dataRoot [ if err != nil { return nil, err } + if idxAttestations == nil || len(idxAttestations) == 0 { + return nil, fmt.Errorf("can't check nil indexed attestation for double vote") + } var slashIdxAtt []*ethpb.IndexedAttestation for _, at := range idxAttestations { root, err := hashutil.HashProto(at.Data) + if at.Data == nil { + continue + } if err != nil { return nil, err } diff --git a/slasher/db/proposer_slashings.go b/slasher/db/proposer_slashings.go index 962b3e3203..83332fcf9d 100644 --- a/slasher/db/proposer_slashings.go +++ b/slasher/db/proposer_slashings.go @@ -154,16 +154,34 @@ func (db *Store) SaveProposerSlashing(status SlashingStatus, slashing *ethpb.Pro } root := hashutil.Hash(enc) key := encodeTypeRoot(SlashingType(Proposal), root) - err = db.update(func(tx *bolt.Tx) error { + return db.update(func(tx *bolt.Tx) error { b := tx.Bucket(slashingBucket) e := b.Put(key, append([]byte{byte(status)}, enc...)) - if e != nil { - return nil - } - return err + return e + }) +} + +// SaveProposeerSlashings accepts a slice of slashing proof and its status and writes it to disk. +func (db *Store) SaveProposeerSlashings(status SlashingStatus, slashings []*ethpb.ProposerSlashing) error { + enc := make([][]byte, len(slashings)) + key := make([][]byte, len(slashings)) + var err error + for i, slashing := range slashings { + enc[i], err = proto.Marshal(slashing) + if err != nil { + return errors.Wrap(err, "failed to marshal") + } + root := hashutil.Hash(enc[i]) + key[i] = encodeTypeRoot(SlashingType(Proposal), root) + } + return db.update(func(tx *bolt.Tx) error { + b := tx.Bucket(slashingBucket) + for i := 0; i < len(enc); i++ { + e := b.Put(key[i], append([]byte{byte(status)}, enc[i]...)) + if e != nil { + return e + } + } + return nil }) - if err != nil { - return err - } - return err } diff --git a/slasher/db/proposer_slashings_test.go b/slasher/db/proposer_slashings_test.go index 7a2c215e0b..af2ab11c81 100644 --- a/slasher/db/proposer_slashings_test.go +++ b/slasher/db/proposer_slashings_test.go @@ -3,6 +3,7 @@ package db import ( "flag" "reflect" + "sort" "testing" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" @@ -26,10 +27,10 @@ func TestStore_ProposerSlashingNilBucket(t *testing.T) { p, err := db.ProposalSlashingsByStatus(SlashingStatus(Active)) if err != nil { - t.Fatalf("failed to get proposer slashing: %v", err) + t.Fatalf("Failed to get proposer slashing: %v", err) } if p == nil || len(p) != 0 { - t.Fatalf("get should return empty attester slashing array for a non existent key") + t.Fatalf("Get should return empty attester slashing array for a non existent key") } } @@ -60,16 +61,16 @@ func TestStore_SaveProposerSlashing(t *testing.T) { for _, tt := range tests { err := db.SaveProposerSlashing(tt.ss, tt.ps) if err != nil { - t.Fatalf("save proposer slashing failed: %v", err) + t.Fatalf("Save proposer slashing failed: %v", err) } proposerSlashings, err := db.ProposalSlashingsByStatus(tt.ss) if err != nil { - t.Fatalf("failed to get proposer slashings: %v", err) + t.Fatalf("Failed to get proposer slashings: %v", err) } if proposerSlashings == nil || !reflect.DeepEqual(proposerSlashings[0], tt.ps) { - t.Fatalf("proposer slashing: %v should be part of proposer slashings response: %v", tt.ps, proposerSlashings) + t.Fatalf("Proposer slashing: %v should be part of proposer slashings response: %v", tt.ps, proposerSlashings) } } @@ -102,34 +103,61 @@ func TestStore_UpdateProposerSlashingStatus(t *testing.T) { for _, tt := range tests { err := db.SaveProposerSlashing(tt.ss, tt.ps) if err != nil { - t.Fatalf("save proposer slashing failed: %v", err) + t.Fatalf("Save proposer slashing failed: %v", err) } } for _, tt := range tests { has, st, err := db.HasProposerSlashing(tt.ps) if err != nil { - t.Fatalf("failed to get proposer slashing: %v", err) + t.Fatalf("Failed to get proposer slashing: %v", err) } if !has { - t.Fatalf("failed to find proposer slashing: %v", tt.ps) + t.Fatalf("Failed to find proposer slashing: %v", tt.ps) } if st != tt.ss { - t.Fatalf("failed to find proposer slashing with the correct status: %v", tt.ps) + t.Fatalf("Failed to find proposer slashing with the correct status: %v", tt.ps) } err = db.SaveProposerSlashing(SlashingStatus(Included), tt.ps) has, st, err = db.HasProposerSlashing(tt.ps) if err != nil { - t.Fatalf("failed to get proposer slashing: %v", err) + t.Fatalf("Failed to get proposer slashing: %v", err) } if !has { - t.Fatalf("failed to find proposer slashing: %v", tt.ps) + t.Fatalf("Failed to find proposer slashing: %v", tt.ps) } if st != Included { - t.Fatalf("failed to find proposer slashing with the correct status: %v", tt.ps) + t.Fatalf("Failed to find proposer slashing with the correct status: %v", tt.ps) } } } + +func TestStore_SaveProposerSlashings(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ctx := cli.NewContext(app, set, nil) + db := SetupSlasherDB(t, ctx) + defer TeardownSlasherDB(t, db) + ps := []*ethpb.ProposerSlashing{ + {ProposerIndex: 1}, + {ProposerIndex: 2}, + {ProposerIndex: 3}, + } + err := db.SaveProposeerSlashings(Active, ps) + if err != nil { + t.Fatalf("Save proposer slashings failed: %v", err) + } + proposerSlashings, err := db.ProposalSlashingsByStatus(Active) + if err != nil { + t.Fatalf("Failed to get proposer slashings: %v", err) + } + sort.SliceStable(proposerSlashings, func(i, j int) bool { + return proposerSlashings[i].ProposerIndex < proposerSlashings[j].ProposerIndex + }) + if proposerSlashings == nil || !reflect.DeepEqual(proposerSlashings, ps) { + t.Fatalf("Proposer slashing: %v should be part of proposer slashings response: %v", ps, proposerSlashings) + } +} diff --git a/slasher/db/schema.go b/slasher/db/schema.go index 12cbad4151..4f390bd995 100644 --- a/slasher/db/schema.go +++ b/slasher/db/schema.go @@ -4,6 +4,8 @@ import ( "github.com/prysmaticlabs/prysm/shared/bytesutil" ) +const latestEpochKey = "LATEST_EPOCH_DETECTED" + var ( // Slasher historicIndexedAttestationsBucket = []byte("historic-indexed-attestations-bucket") diff --git a/slasher/main.go b/slasher/main.go index 9b3dcbaa96..eab2cb6555 100644 --- a/slasher/main.go +++ b/slasher/main.go @@ -60,6 +60,7 @@ var appFlags = []cli.Flag{ cmd.MonitoringPortFlag, cmd.LogFileName, cmd.LogFormat, + cmd.ClearDB, debug.PProfFlag, debug.PProfAddrFlag, debug.PProfPortFlag, diff --git a/slasher/rpc/server.go b/slasher/rpc/server.go index 9e846f4a65..9f2f61e1d7 100644 --- a/slasher/rpc/server.go +++ b/slasher/rpc/server.go @@ -29,6 +29,9 @@ type Server struct { // is a slashable vote. func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.AttesterSlashingResponse, error) { //TODO(#3133): add signature validation + if req.Data == nil { + return nil, fmt.Errorf("cant hash nil data in indexed attestation") + } if err := ss.SlasherDB.SaveIndexedAttestation(req); err != nil { return nil, err } @@ -48,7 +51,7 @@ func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Indexed return nil, fmt.Errorf("indexed attestation contains repeated or non sorted ids") } wg.Add(1) - go func(idx uint64) { + go func(idx uint64, root [32]byte, req *ethpb.IndexedAttestation) { atts, err := ss.SlasherDB.DoubleVotes(tEpoch, idx, root[:], req) if err != nil { er <- err @@ -69,13 +72,17 @@ func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Indexed } wg.Done() return - }(idx) + }(idx, root, req) } wg.Wait() close(er) close(at) for e := range er { - err = fmt.Errorf(err.Error() + " : " + e.Error()) + if err != nil { + err = fmt.Errorf(err.Error() + " : " + e.Error()) + continue + } + err = e } for atts := range at { atsSlashinngRes.AttesterSlashing = append(atsSlashinngRes.AttesterSlashing, atts...) @@ -197,6 +204,9 @@ func (ss *Server) DetectSurroundVotes(ctx context.Context, validatorIdx uint64, return nil, err } for _, ia := range attestations { + if ia.Data == nil { + continue + } if ia.Data.Source.Epoch > req.Data.Source.Epoch && ia.Data.Target.Epoch < req.Data.Target.Epoch { as = append(as, ðpb.AttesterSlashing{ Attestation_1: req, diff --git a/slasher/service/BUILD.bazel b/slasher/service/BUILD.bazel index 8f447ce856..68f06ec20c 100644 --- a/slasher/service/BUILD.bazel +++ b/slasher/service/BUILD.bazel @@ -10,6 +10,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//proto/slashing:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/cmd:go_default_library", "//shared/debug:go_default_library", "//shared/params:go_default_library", @@ -22,6 +23,7 @@ go_library( "@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library", "@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library", "@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_urfave_cli//:go_default_library", @@ -39,7 +41,10 @@ go_test( srcs = ["service_test.go"], embed = [":go_default_library"], deps = [ + "//shared/mock:go_default_library", "//shared/testutil:go_default_library", + "@com_github_golang_mock//gomock:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_urfave_cli//:go_default_library", diff --git a/slasher/service/data_update.go b/slasher/service/data_update.go index ebe7dd3fa3..9f47c9725c 100644 --- a/slasher/service/data_update.go +++ b/slasher/service/data_update.go @@ -1,10 +1,15 @@ package service import ( + "fmt" "time" ptypes "github.com/gogo/protobuf/types" + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/slasher/db" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -32,8 +37,167 @@ func (s *Service) finalisedChangeUpdater() error { } log.Error("No chain head was returned by beacon chain.") case <-s.context.Done(): - return status.Error(codes.Canceled, "Stream context canceled") + err := status.Error(codes.Canceled, "Stream context canceled") + log.WithError(err) + return err } } } + +// attestationFeeder feeds attestations that were received by archive endpoint. +func (s *Service) attestationFeeder() error { + as, err := s.beaconClient.StreamAttestations(s.context, &ptypes.Empty{}) + if err != nil { + log.WithError(err).Errorf("failed to retrieve attestation stream") + return err + } + for { + select { + default: + if as == nil { + err := fmt.Errorf("attestation stream is nil. please check your archiver node status") + log.WithError(err) + return err + } + at, err := as.Recv() + if err != nil { + log.WithError(err) + return err + } + bcs, err := s.beaconClient.ListBeaconCommittees(s.context, ðpb.ListCommitteesRequest{ + QueryFilter: ðpb.ListCommitteesRequest_Epoch{ + Epoch: at.Data.Target.Epoch, + }, + }) + if err != nil { + log.WithError(err).Errorf("Could not list beacon committees for epoch %d", at.Data.Target.Epoch) + return err + } + err = s.detectAttestation(at, bcs) + if err != nil { + continue + } + log.Info("detected attestation for target: %d", at.Data.Target) + case <-s.context.Done(): + err := status.Error(codes.Canceled, "Stream context canceled") + log.WithError(err) + return err + } + } +} + +// slasherOldAttestationFeeder a function to kick start slashing detection +// after all the included attestations in the canonical chain have been +// slashing detected. latest epoch is being updated after each iteration +// in case it changed during the detection process. +func (s *Service) slasherOldAttestationFeeder() error { + ch, err := s.getChainHead() + if err != nil { + return err + } + errOut := make(chan error) + startFromEpoch, err := s.getLatestDetectedEpoch(err) + if err != nil { + return err + } + for ep := startFromEpoch; ep < ch.FinalizedEpoch; ep++ { + ats, bcs, err := s.getDataForDetection(ep) + if err != nil || bcs == nil { + log.Error(err) + continue + } + log.Infof("detecting slashable events on: %v attestations from epoch: %v", len(ats.Attestations), ep) + for _, attestation := range ats.Attestations { + err := s.detectAttestation(attestation, bcs) + if err != nil { + continue + } + } + s.slasherDb.SetLatestEpochDetected(ep) + s.getChainHead() + } + close(errOut) + for err := range errOut { + log.Error(errors.Wrap(err, "error while writing to db in background")) + } + return nil +} + +func (s *Service) detectAttestation(attestation *ethpb.Attestation, beaconCommittee *ethpb.BeaconCommittees) error { + slotCommittees, ok := beaconCommittee.Committees[attestation.Data.Slot] + if !ok || slotCommittees == nil { + err := fmt.Errorf("beacon committees object doesnt contain the attestation slot: %d, number of committees: %d", + attestation.Data.Slot, len(beaconCommittee.Committees)) + log.WithError(err) + return err + } + if attestation.Data.CommitteeIndex > uint64(len(slotCommittees.Committees)) { + err := fmt.Errorf("committee index is out of range in slot wanted: %d, actual: %d", attestation.Data.CommitteeIndex, len(slotCommittees.Committees)) + log.WithError(err) + return err + } + attesterCommittee := slotCommittees.Committees[attestation.Data.CommitteeIndex] + validatorIndices := attesterCommittee.ValidatorIndices + ia, err := attestationutil.ConvertToIndexed(s.context, attestation, validatorIndices) + if err != nil { + log.WithError(err) + return err + } + sar, err := s.slasher.IsSlashableAttestation(s.context, ia) + if err != nil { + log.WithError(err) + return err + } + s.slasherDb.SaveAttesterSlashings(db.Active, sar.AttesterSlashing) + if len(sar.AttesterSlashing) > 0 { + for _, as := range sar.AttesterSlashing { + log.WithField("attesterSlashing", as).Info("detected slashing offence") + } + } + return nil +} + +func (s *Service) getDataForDetection(epoch uint64) (*ethpb.ListAttestationsResponse, *ethpb.BeaconCommittees, error) { + ats, err := s.beaconClient.ListAttestations(s.context, ðpb.ListAttestationsRequest{ + QueryFilter: ðpb.ListAttestationsRequest_TargetEpoch{TargetEpoch: epoch}, + }) + if err != nil { + log.WithError(err).Errorf("Could not list attestations for epoch: %d", epoch) + } + bcs, err := s.beaconClient.ListBeaconCommittees(s.context, ðpb.ListCommitteesRequest{ + QueryFilter: ðpb.ListCommitteesRequest_Epoch{ + Epoch: epoch, + }, + }) + if err != nil { + log.WithError(err).Errorf("Could not list beacon committees for epoch: %d", epoch) + } + return ats, bcs, err +} + +func (s *Service) getLatestDetectedEpoch(err error) (uint64, error) { + e, err := s.slasherDb.GetLatestEpochDetected() + if err != nil { + log.Error(err) + s.Stop() + return 0, err + } + return e, nil +} + +func (s *Service) getChainHead() (*ethpb.ChainHead, error) { + if s.beaconClient == nil { + return nil, fmt.Errorf("can't feed old attestations to slasher. beacon client has not been started") + } + ch, err := s.beaconClient.GetChainHead(s.context, &ptypes.Empty{}) + if err != nil { + log.Error(err) + return nil, err + } + if ch.FinalizedEpoch < 2 { + log.Info("archive node doesnt have historic data for slasher to proccess. finalized epoch: %d", ch.FinalizedEpoch) + } + log.WithField("finalizedEpoch", ch.FinalizedEpoch).Info("Current finalized epoch on archive node") + return ch, nil +} diff --git a/slasher/service/service.go b/slasher/service/service.go index dc0c399f2f..2238495848 100644 --- a/slasher/service/service.go +++ b/slasher/service/service.go @@ -16,6 +16,7 @@ import ( recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" slashpb "github.com/prysmaticlabs/prysm/proto/slashing" "github.com/prysmaticlabs/prysm/shared/cmd" @@ -44,6 +45,7 @@ func init() { type Service struct { slasherDb *db.Store grpcServer *grpc.Server + slasher *rpc.Server port int withCert string withKey string @@ -87,7 +89,9 @@ func NewRPCService(cfg *Config, ctx *cli.Context) (*Service, error) { if err := s.startDB(s.ctx); err != nil { return nil, err } - + s.slasher = &rpc.Server{ + SlasherDB: s.slasherDb, + } return s, nil } @@ -99,9 +103,25 @@ func (s *Service) Start() { }).Info("Starting hash slinging slasher node") s.context = context.Background() s.startSlasher() - s.startBeaconClient() - go s.finalisedChangeUpdater() + if s.beaconClient == nil { + if err := s.startBeaconClient(); err != nil { + log.WithError(err).Errorf("failed to start beacon client") + s.failStatus = err + return + } + } stop := s.stop + err := s.slasherOldAttestationFeeder() + if err != nil { + err = errors.Wrap(err, "couldn't start attestation feeder from archive endpoint. please use "+ + "--beacon-rpc-provider flag value if you are not running a beacon chain service with "+ + "--archive flag on the local machine.") + log.WithError(err) + s.failStatus = err + return + } + go s.attestationFeeder() + go s.finalisedChangeUpdater() s.lock.Unlock() go func() { @@ -196,7 +216,7 @@ func (s *Service) loadSpanMaps(err error, slasherServer rpc.Server) { } } -func (s *Service) startBeaconClient() { +func (s *Service) startBeaconClient() error { var dialOpt grpc.DialOption if s.beaconCert != "" { @@ -223,12 +243,12 @@ func (s *Service) startBeaconClient() { } conn, err := grpc.DialContext(s.context, s.beaconProvider, beaconOpts...) if err != nil { - log.Errorf("Could not dial endpoint: %s, %v", s.beaconProvider, err) - return + return fmt.Errorf("could not dial endpoint: %s, %v", s.beaconProvider, err) } log.Info("Successfully started gRPC connection") s.beaconConn = conn s.beaconClient = eth.NewBeaconChainClient(s.beaconConn) + return nil } // Stop the service. diff --git a/slasher/service/service_test.go b/slasher/service/service_test.go index 9dcf3d755e..936dbb2ad8 100644 --- a/slasher/service/service_test.go +++ b/slasher/service/service_test.go @@ -8,11 +8,13 @@ import ( "testing" "time" - "github.com/urfave/cli" - + "github.com/golang/mock/gomock" + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/mock" "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/sirupsen/logrus" logTest "github.com/sirupsen/logrus/hooks/test" + "github.com/urfave/cli" ) func init() { @@ -24,6 +26,9 @@ func TestLifecycle_OK(t *testing.T) { hook := logTest.NewGlobal() app := cli.NewApp() set := flag.NewFlagSet("test", 0) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + context := cli.NewContext(app, set, nil) rpcService, err := NewRPCService(&Config{ Port: 7348, @@ -31,6 +36,16 @@ func TestLifecycle_OK(t *testing.T) { if err != nil { t.Error("gRPC Service fail to initialize:", err) } + client := mock.NewMockBeaconChainClient(ctrl) + rpcService.beaconClient = client + client.EXPECT().GetChainHead( + gomock.Any(), + gomock.Any(), + ).Return(ðpb.ChainHead{HeadSlot: 1}, nil) + client.EXPECT().StreamAttestations( + gomock.Any(), + gomock.Any(), + ).Return(nil, nil) waitForStarted(rpcService, t) rpcService.Close() testutil.AssertLogsContain(t, hook, "Starting service") @@ -43,6 +58,9 @@ func TestRPC_BadEndpoint(t *testing.T) { hook := logTest.NewGlobal() app := cli.NewApp() set := flag.NewFlagSet("test", 0) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + context := cli.NewContext(app, set, nil) rpcService, err := NewRPCService(&Config{ Port: 99999999, @@ -50,6 +68,16 @@ func TestRPC_BadEndpoint(t *testing.T) { if err != nil { t.Error("gRPC Service fail to initialize:", err) } + client := mock.NewMockBeaconChainClient(ctrl) + rpcService.beaconClient = client + client.EXPECT().GetChainHead( + gomock.Any(), + gomock.Any(), + ).Return(ðpb.ChainHead{HeadSlot: 1}, nil) + client.EXPECT().StreamAttestations( + gomock.Any(), + gomock.Any(), + ).Return(nil, nil) testutil.AssertLogsDoNotContain(t, hook, "Could not listen to port in Start()") testutil.AssertLogsDoNotContain(t, hook, "Could not load TLS keys") testutil.AssertLogsDoNotContain(t, hook, "Could not serve gRPC") @@ -76,12 +104,25 @@ func TestRPC_InsecureEndpoint(t *testing.T) { app := cli.NewApp() set := flag.NewFlagSet("test", 0) context := cli.NewContext(app, set, nil) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + rpcService, err := NewRPCService(&Config{ Port: 5555, }, context) if err != nil { t.Error("gRPC Service fail to initialize:", err) } + client := mock.NewMockBeaconChainClient(ctrl) + rpcService.beaconClient = client + client.EXPECT().GetChainHead( + gomock.Any(), + gomock.Any(), + ).Return(ðpb.ChainHead{HeadSlot: 1}, nil) + client.EXPECT().StreamAttestations( + gomock.Any(), + gomock.Any(), + ).Return(nil, nil) waitForStarted(rpcService, t) testutil.AssertLogsContain(t, hook, "Starting service") diff --git a/slasher/usage.go b/slasher/usage.go index f1bc77d13b..6db0e3d3e4 100644 --- a/slasher/usage.go +++ b/slasher/usage.go @@ -53,6 +53,7 @@ var appHelpFlagGroups = []flagGroup{ cmd.MonitoringPortFlag, cmd.LogFormat, cmd.LogFileName, + cmd.ClearDB, }, }, { diff --git a/tools/blocktree/BUILD.bazel b/tools/blocktree/BUILD.bazel index fc20d8eacf..e67e8e7c1d 100644 --- a/tools/blocktree/BUILD.bazel +++ b/tools/blocktree/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/db:go_default_library", "//beacon-chain/db/filters:go_default_library", + "//shared/attestationutil:go_default_library", "//shared/bytesutil:go_default_library", "//shared/params:go_default_library", "@com_github_emicklei_dot//:go_default_library", diff --git a/tools/blocktree/main.go b/tools/blocktree/main.go index 77621bec09..edac503053 100644 --- a/tools/blocktree/main.go +++ b/tools/blocktree/main.go @@ -20,6 +20,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/db" "github.com/prysmaticlabs/prysm/beacon-chain/db/filters" + "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" ) @@ -99,7 +100,7 @@ func main() { if err != nil { panic(err) } - indices, err := helpers.AttestingIndices(att.AggregationBits, committee) + indices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { panic(err) } diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 14c63ba4ef..b17bd4bffb 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -65,6 +65,7 @@ go_test( "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", "//shared/keystore:go_default_library", + "//shared/mock:go_default_library", "//shared/params:go_default_library", "//shared/roughtime:go_default_library", "//shared/testutil:go_default_library", diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 4a3745fa4f..e2af541641 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -14,6 +14,7 @@ import ( pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" "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" "github.com/prysmaticlabs/prysm/validator/internal" @@ -276,7 +277,7 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { func TestCanonicalHeadSlot_FailedRPC(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - client := internal.NewMockBeaconChainClient(ctrl) + client := mock.NewMockBeaconChainClient(ctrl) v := validator{ keyManager: testKeyManager, beaconClient: client, @@ -294,7 +295,7 @@ func TestCanonicalHeadSlot_FailedRPC(t *testing.T) { func TestCanonicalHeadSlot_OK(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - client := internal.NewMockBeaconChainClient(ctrl) + client := mock.NewMockBeaconChainClient(ctrl) v := validator{ keyManager: testKeyManager, beaconClient: client, diff --git a/validator/internal/BUILD.bazel b/validator/internal/BUILD.bazel index ee3c77faa7..a831881067 100644 --- a/validator/internal/BUILD.bazel +++ b/validator/internal/BUILD.bazel @@ -5,7 +5,6 @@ go_library( testonly = True, srcs = [ "aggregator_service_mock.go", - "beacon_chain_service_mock.go", "beacon_node_validator_service_mock.go", "node_mock.go", "validator_service_mock.go",