Detect Slashable Blocks in Optimized Slasher (#9693)

* pass

* Update beacon-chain/slasher/detect_blocks.go

Co-authored-by: terence tsao <terence@prysmaticlabs.com>

* Update beacon-chain/slasher/detect_blocks.go

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Raul Jordan
2021-09-28 14:33:45 -05:00
committed by GitHub
parent c32090aae5
commit 1816906bc7
4 changed files with 249 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"chunks.go",
"detect_blocks.go",
"doc.go",
"helpers.go",
"metrics.go",
@@ -36,6 +37,7 @@ go_library(
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
@@ -43,6 +45,7 @@ go_test(
name = "go_default_test",
srcs = [
"chunks_test.go",
"detect_blocks_test.go",
"helpers_test.go",
"params_test.go",
"process_slashings_test.go",

View File

@@ -0,0 +1,92 @@
package slasher
import (
"context"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"go.opencensus.io/trace"
)
// detectProposerSlashings takes in signed block header wrappers and returns a list of proposer slashings detected.
func (s *Service) detectProposerSlashings(
ctx context.Context,
proposedBlocks []*slashertypes.SignedBlockHeaderWrapper,
) ([]*ethpb.ProposerSlashing, error) {
ctx, span := trace.StartSpan(ctx, "slasher.detectProposerSlashings")
defer span.End()
// We check if there are any slashable double proposals in the input list
// of proposals with respect to each other.
slashings := make([]*ethpb.ProposerSlashing, 0)
existingProposals := make(map[string]*slashertypes.SignedBlockHeaderWrapper)
for i, proposal := range proposedBlocks {
key := proposalKey(proposal)
existingProposal, ok := existingProposals[key]
if !ok {
existingProposals[key] = proposal
continue
}
if isDoubleProposal(proposedBlocks[i].SigningRoot, existingProposal.SigningRoot) {
doubleProposalsTotal.Inc()
slashing := &ethpb.ProposerSlashing{
Header_1: existingProposal.SignedBeaconBlockHeader,
Header_2: proposedBlocks[i].SignedBeaconBlockHeader,
}
slashings = append(slashings, slashing)
}
}
proposerSlashings, err := s.serviceCfg.Database.CheckDoubleBlockProposals(ctx, proposedBlocks)
if err != nil {
return nil, errors.Wrap(err, "could not check for double proposals on disk")
}
if err := s.saveSafeProposals(ctx, proposedBlocks, proposerSlashings); err != nil {
return nil, errors.Wrap(err, "could not save safe proposals")
}
slashings = append(slashings, proposerSlashings...)
return slashings, nil
}
// Check for double proposals in our database given a list of incoming block proposals.
// For the proposals that were not slashable, we save them to the database.
func (s *Service) saveSafeProposals(
ctx context.Context,
proposedBlocks []*slashertypes.SignedBlockHeaderWrapper,
proposerSlashings []*ethpb.ProposerSlashing,
) error {
ctx, span := trace.StartSpan(ctx, "slasher.saveSafeProposals")
defer span.End()
return s.serviceCfg.Database.SaveBlockProposals(
ctx,
filterSafeProposals(proposedBlocks, proposerSlashings),
)
}
func filterSafeProposals(
proposedBlocks []*slashertypes.SignedBlockHeaderWrapper,
proposerSlashings []*ethpb.ProposerSlashing,
) []*slashertypes.SignedBlockHeaderWrapper {
// We initialize a map of proposers that are safe from slashing.
safeProposers := make(map[types.ValidatorIndex]*slashertypes.SignedBlockHeaderWrapper, len(proposedBlocks))
for _, proposal := range proposedBlocks {
safeProposers[proposal.SignedBeaconBlockHeader.Header.ProposerIndex] = proposal
}
for _, doubleProposal := range proposerSlashings {
// If a proposer is found to have committed a slashable offense, we delete
// them from the safe proposers map.
delete(safeProposers, doubleProposal.Header_1.Header.ProposerIndex)
}
// We save all the proposals that are determined "safe" and not-slashable to our database.
safeProposals := make([]*slashertypes.SignedBlockHeaderWrapper, 0, len(safeProposers))
for _, proposal := range safeProposers {
safeProposals = append(safeProposals, proposal)
}
return safeProposals
}
func proposalKey(proposal *slashertypes.SignedBlockHeaderWrapper) string {
header := proposal.SignedBeaconBlockHeader.Header
return uintToString(uint64(header.Slot)) + ":" + uintToString(uint64(header.ProposerIndex))
}

View File

@@ -0,0 +1,149 @@
package slasher
import (
"context"
"testing"
types "github.com/prysmaticlabs/eth2-types"
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/testing/require"
"github.com/prysmaticlabs/prysm/testing/util"
logTest "github.com/sirupsen/logrus/hooks/test"
)
func Test_processQueuedBlocks_DetectsDoubleProposals(t *testing.T) {
hook := logTest.NewGlobal()
slasherDB := dbtest.SetupSlasherDB(t)
beaconDB := dbtest.SetupDB(t)
ctx, cancel := context.WithCancel(context.Background())
beaconState, err := util.NewBeaconState()
require.NoError(t, err)
// Initialize validators in the state.
numVals := params.BeaconConfig().MinGenesisActiveValidatorCount
validators := make([]*ethpb.Validator, numVals)
privKeys := make([]bls.SecretKey, numVals)
for i := range validators {
privKey, err := bls.RandKey()
require.NoError(t, err)
privKeys[i] = privKey
validators[i] = &ethpb.Validator{
PublicKey: privKey.PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
}
}
err = beaconState.SetValidators(validators)
require.NoError(t, err)
domain, err := signing.Domain(
beaconState.Fork(),
0,
params.BeaconConfig().DomainBeaconProposer,
beaconState.GenesisValidatorRoot(),
)
require.NoError(t, err)
mockChain := &mock.ChainService{
State: beaconState,
}
s := &Service{
serviceCfg: &ServiceConfig{
Database: slasherDB,
StateNotifier: &mock.MockStateNotifier{},
HeadStateFetcher: mockChain,
StateGen: stategen.New(beaconDB),
SlashingPoolInserter: &slashings.PoolMock{},
},
params: DefaultParams(),
blksQueue: newBlocksQueue(),
}
parentRoot := bytesutil.ToBytes32([]byte("parent"))
err = s.serviceCfg.StateGen.SaveState(ctx, parentRoot, beaconState)
require.NoError(t, err)
currentSlotChan := make(chan types.Slot)
exitChan := make(chan struct{})
go func() {
s.processQueuedBlocks(ctx, currentSlotChan)
exitChan <- struct{}{}
}()
signedBlkHeaders := []*slashertypes.SignedBlockHeaderWrapper{
createProposalWrapper(t, 4, 1, []byte{1}),
createProposalWrapper(t, 4, 1, []byte{1}),
createProposalWrapper(t, 4, 1, []byte{1}),
createProposalWrapper(t, 4, 1, []byte{2}),
}
// Add valid signatures to the block headers we are testing.
for _, proposalWrapper := range signedBlkHeaders {
proposalWrapper.SignedBeaconBlockHeader.Header.ParentRoot = parentRoot[:]
headerHtr, err := proposalWrapper.SignedBeaconBlockHeader.Header.HashTreeRoot()
require.NoError(t, err)
container := &ethpb.SigningData{
ObjectRoot: headerHtr[:],
Domain: domain,
}
signingRoot, err := container.HashTreeRoot()
require.NoError(t, err)
privKey := privKeys[proposalWrapper.SignedBeaconBlockHeader.Header.ProposerIndex]
proposalWrapper.SignedBeaconBlockHeader.Signature = privKey.Sign(signingRoot[:]).Marshal()
}
s.blksQueue.extend(signedBlkHeaders)
currentSlot := types.Slot(4)
currentSlotChan <- currentSlot
cancel()
<-exitChan
require.LogsContain(t, hook, "Proposer slashing detected")
}
func Test_processQueuedBlocks_NotSlashable(t *testing.T) {
hook := logTest.NewGlobal()
slasherDB := dbtest.SetupSlasherDB(t)
ctx, cancel := context.WithCancel(context.Background())
beaconState, err := util.NewBeaconState()
require.NoError(t, err)
currentSlot := types.Slot(4)
require.NoError(t, beaconState.SetSlot(currentSlot))
mockChain := &mock.ChainService{
State: beaconState,
Slot: &currentSlot,
}
s := &Service{
serviceCfg: &ServiceConfig{
Database: slasherDB,
StateNotifier: &mock.MockStateNotifier{},
HeadStateFetcher: mockChain,
},
params: DefaultParams(),
blksQueue: newBlocksQueue(),
}
currentSlotChan := make(chan types.Slot)
exitChan := make(chan struct{})
go func() {
s.processQueuedBlocks(ctx, currentSlotChan)
exitChan <- struct{}{}
}()
s.blksQueue.extend([]*slashertypes.SignedBlockHeaderWrapper{
createProposalWrapper(t, 4, 1, []byte{1}),
createProposalWrapper(t, 4, 1, []byte{1}),
})
currentSlotChan <- currentSlot
cancel()
<-exitChan
require.LogsDoNotContain(t, hook, "Proposer slashing detected")
}

View File

@@ -148,8 +148,11 @@ func (s *Service) processQueuedBlocks(ctx context.Context, slotTicker <-chan typ
start := time.Now()
// Check for slashings.
// TODO(#8331): Detect slashings.
slashings := make([]*ethpb.ProposerSlashing, 0)
slashings, err := s.detectProposerSlashings(ctx, blocks)
if err != nil {
log.WithError(err).Error("Could not detect proposer slashings")
continue
}
// Process proposer slashings by verifying their signatures, submitting
// to the beacon node's operations pool, and logging them.