mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-23 11:57:18 +00:00
77d3ccb9ad
* Clean up state types * rename package
311 lines
11 KiB
Go
311 lines
11 KiB
Go
package slasher
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/prysmaticlabs/prysm/v3/async/event"
|
|
mock "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing"
|
|
dbtest "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing"
|
|
slashertypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/slasher/types"
|
|
params2 "github.com/prysmaticlabs/prysm/v3/config/params"
|
|
"github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
|
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
|
"github.com/prysmaticlabs/prysm/v3/testing/util"
|
|
logTest "github.com/sirupsen/logrus/hooks/test"
|
|
)
|
|
|
|
func TestSlasher_receiveAttestations_OK(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
s := &Service{
|
|
serviceCfg: &ServiceConfig{
|
|
IndexedAttestationsFeed: new(event.Feed),
|
|
StateNotifier: &mock.MockStateNotifier{},
|
|
},
|
|
attsQueue: newAttestationsQueue(),
|
|
}
|
|
indexedAttsChan := make(chan *ethpb.IndexedAttestation)
|
|
defer close(indexedAttsChan)
|
|
|
|
exitChan := make(chan struct{})
|
|
go func() {
|
|
s.receiveAttestations(ctx, indexedAttsChan)
|
|
exitChan <- struct{}{}
|
|
}()
|
|
firstIndices := []uint64{1, 2, 3}
|
|
secondIndices := []uint64{4, 5, 6}
|
|
att1 := createAttestationWrapper(t, 1, 2, firstIndices, nil)
|
|
att2 := createAttestationWrapper(t, 1, 2, secondIndices, nil)
|
|
indexedAttsChan <- att1.IndexedAttestation
|
|
indexedAttsChan <- att2.IndexedAttestation
|
|
cancel()
|
|
<-exitChan
|
|
wanted := []*slashertypes.IndexedAttestationWrapper{
|
|
att1,
|
|
att2,
|
|
}
|
|
require.DeepEqual(t, wanted, s.attsQueue.dequeue())
|
|
}
|
|
|
|
func TestService_pruneSlasherDataWithinSlidingWindow_AttestationsPruned(t *testing.T) {
|
|
ctx := context.Background()
|
|
params := DefaultParams()
|
|
params.historyLength = 4 // 4 epochs worth of history.
|
|
slasherDB := dbtest.SetupSlasherDB(t)
|
|
s := &Service{
|
|
serviceCfg: &ServiceConfig{
|
|
Database: slasherDB,
|
|
},
|
|
params: params,
|
|
}
|
|
|
|
// Setup attestations for 2 validators at each epoch for epochs 0, 1, 2, 3.
|
|
err := slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{
|
|
createAttestationWrapper(t, 0, 0, []uint64{0}, bytesutil.PadTo([]byte("0a"), 32)),
|
|
createAttestationWrapper(t, 0, 0, []uint64{1}, bytesutil.PadTo([]byte("0b"), 32)),
|
|
createAttestationWrapper(t, 0, 1, []uint64{0}, bytesutil.PadTo([]byte("1a"), 32)),
|
|
createAttestationWrapper(t, 0, 1, []uint64{1}, bytesutil.PadTo([]byte("1b"), 32)),
|
|
createAttestationWrapper(t, 0, 2, []uint64{0}, bytesutil.PadTo([]byte("2a"), 32)),
|
|
createAttestationWrapper(t, 0, 2, []uint64{1}, bytesutil.PadTo([]byte("2b"), 32)),
|
|
createAttestationWrapper(t, 0, 3, []uint64{0}, bytesutil.PadTo([]byte("3a"), 32)),
|
|
createAttestationWrapper(t, 0, 3, []uint64{1}, bytesutil.PadTo([]byte("3b"), 32)),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Attempt to prune and discover that all data is still intact.
|
|
currentEpoch := primitives.Epoch(3)
|
|
err = s.pruneSlasherDataWithinSlidingWindow(ctx, currentEpoch)
|
|
require.NoError(t, err)
|
|
|
|
epochs := []primitives.Epoch{0, 1, 2, 3}
|
|
for _, epoch := range epochs {
|
|
att, err := slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(0), epoch)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, att)
|
|
att, err = slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(1), epoch)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, att)
|
|
}
|
|
|
|
// Setup attestations for 2 validators at epoch 4.
|
|
err = slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{
|
|
createAttestationWrapper(t, 0, 4, []uint64{0}, bytesutil.PadTo([]byte("4a"), 32)),
|
|
createAttestationWrapper(t, 0, 4, []uint64{1}, bytesutil.PadTo([]byte("4b"), 32)),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Attempt to prune again by setting current epoch to 4.
|
|
currentEpoch = primitives.Epoch(4)
|
|
err = s.pruneSlasherDataWithinSlidingWindow(ctx, currentEpoch)
|
|
require.NoError(t, err)
|
|
|
|
// We should now only have data for epochs 1, 2, 3, 4. We should
|
|
// have pruned data from epoch 0.
|
|
att, err := slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(0), 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, att == nil)
|
|
att, err = slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(1), 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, att == nil)
|
|
|
|
epochs = []primitives.Epoch{1, 2, 3, 4}
|
|
for _, epoch := range epochs {
|
|
att, err := slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(0), epoch)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, att)
|
|
att, err = slasherDB.AttestationRecordForValidator(ctx, primitives.ValidatorIndex(1), epoch)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, att)
|
|
}
|
|
}
|
|
|
|
func TestService_pruneSlasherDataWithinSlidingWindow_ProposalsPruned(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Override beacon config to 1 slot per epoch for easier testing.
|
|
params2.SetupTestConfigCleanup(t)
|
|
config := params2.BeaconConfig().Copy()
|
|
config.SlotsPerEpoch = 1
|
|
params2.OverrideBeaconConfig(config)
|
|
|
|
params := DefaultParams()
|
|
params.historyLength = 4 // 4 epochs worth of history.
|
|
slasherDB := dbtest.SetupSlasherDB(t)
|
|
s := &Service{
|
|
serviceCfg: &ServiceConfig{
|
|
Database: slasherDB,
|
|
},
|
|
params: params,
|
|
}
|
|
|
|
// Setup block proposals for 2 validators at each epoch for epochs 0, 1, 2, 3.
|
|
err := slasherDB.SaveBlockProposals(ctx, []*slashertypes.SignedBlockHeaderWrapper{
|
|
createProposalWrapper(t, 0, 0, bytesutil.PadTo([]byte("0a"), 32)),
|
|
createProposalWrapper(t, 0, 1, bytesutil.PadTo([]byte("0b"), 32)),
|
|
createProposalWrapper(t, 1, 0, bytesutil.PadTo([]byte("1a"), 32)),
|
|
createProposalWrapper(t, 1, 1, bytesutil.PadTo([]byte("1b"), 32)),
|
|
createProposalWrapper(t, 2, 0, bytesutil.PadTo([]byte("2a"), 32)),
|
|
createProposalWrapper(t, 2, 1, bytesutil.PadTo([]byte("2b"), 32)),
|
|
createProposalWrapper(t, 3, 0, bytesutil.PadTo([]byte("3a"), 32)),
|
|
createProposalWrapper(t, 3, 1, bytesutil.PadTo([]byte("3b"), 32)),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Attempt to prune and discover that all data is still intact.
|
|
currentEpoch := primitives.Epoch(3)
|
|
err = s.pruneSlasherDataWithinSlidingWindow(ctx, currentEpoch)
|
|
require.NoError(t, err)
|
|
|
|
slots := []primitives.Slot{0, 1, 2, 3}
|
|
for _, slot := range slots {
|
|
blk, err := slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(0), slot)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, blk)
|
|
blk, err = slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(1), slot)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, blk)
|
|
}
|
|
|
|
// Setup block proposals for 2 validators at epoch 4.
|
|
err = slasherDB.SaveBlockProposals(ctx, []*slashertypes.SignedBlockHeaderWrapper{
|
|
createProposalWrapper(t, 4, 0, bytesutil.PadTo([]byte("4a"), 32)),
|
|
createProposalWrapper(t, 4, 1, bytesutil.PadTo([]byte("4b"), 32)),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Attempt to prune again by setting current epoch to 4.
|
|
currentEpoch = primitives.Epoch(4)
|
|
err = s.pruneSlasherDataWithinSlidingWindow(ctx, currentEpoch)
|
|
require.NoError(t, err)
|
|
|
|
// We should now only have data for epochs 1, 2, 3, 4. We should
|
|
// have pruned data from epoch 0.
|
|
blk, err := slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(0), 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, blk == nil)
|
|
blk, err = slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(1), 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, blk == nil)
|
|
|
|
slots = []primitives.Slot{1, 2, 3, 4}
|
|
for _, slot := range slots {
|
|
blk, err := slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(0), slot)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, blk)
|
|
blk, err = slasherDB.BlockProposalForValidator(ctx, primitives.ValidatorIndex(1), slot)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, blk)
|
|
}
|
|
}
|
|
|
|
func TestSlasher_receiveAttestations_OnlyValidAttestations(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
s := &Service{
|
|
serviceCfg: &ServiceConfig{
|
|
IndexedAttestationsFeed: new(event.Feed),
|
|
StateNotifier: &mock.MockStateNotifier{},
|
|
},
|
|
attsQueue: newAttestationsQueue(),
|
|
}
|
|
indexedAttsChan := make(chan *ethpb.IndexedAttestation)
|
|
defer close(indexedAttsChan)
|
|
|
|
exitChan := make(chan struct{})
|
|
defer close(exitChan)
|
|
go func() {
|
|
s.receiveAttestations(ctx, indexedAttsChan)
|
|
exitChan <- struct{}{}
|
|
}()
|
|
firstIndices := []uint64{1, 2, 3}
|
|
secondIndices := []uint64{4, 5, 6}
|
|
// Add a valid attestation.
|
|
validAtt := createAttestationWrapper(t, 1, 2, firstIndices, nil)
|
|
indexedAttsChan <- validAtt.IndexedAttestation
|
|
// Send an invalid, bad attestation which will not
|
|
// pass integrity checks at it has invalid attestation data.
|
|
indexedAttsChan <- ðpb.IndexedAttestation{
|
|
AttestingIndices: secondIndices,
|
|
}
|
|
cancel()
|
|
<-exitChan
|
|
// Expect only a single, valid attestation was added to the queue.
|
|
require.Equal(t, 1, s.attsQueue.size())
|
|
wanted := []*slashertypes.IndexedAttestationWrapper{
|
|
validAtt,
|
|
}
|
|
require.DeepEqual(t, wanted, s.attsQueue.dequeue())
|
|
}
|
|
|
|
func TestSlasher_receiveBlocks_OK(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
s := &Service{
|
|
serviceCfg: &ServiceConfig{
|
|
BeaconBlockHeadersFeed: new(event.Feed),
|
|
StateNotifier: &mock.MockStateNotifier{},
|
|
},
|
|
blksQueue: newBlocksQueue(),
|
|
}
|
|
beaconBlockHeadersChan := make(chan *ethpb.SignedBeaconBlockHeader)
|
|
defer close(beaconBlockHeadersChan)
|
|
exitChan := make(chan struct{})
|
|
go func() {
|
|
s.receiveBlocks(ctx, beaconBlockHeadersChan)
|
|
exitChan <- struct{}{}
|
|
}()
|
|
|
|
block1 := createProposalWrapper(t, 0, 1, nil).SignedBeaconBlockHeader
|
|
block2 := createProposalWrapper(t, 0, 2, nil).SignedBeaconBlockHeader
|
|
beaconBlockHeadersChan <- block1
|
|
beaconBlockHeadersChan <- block2
|
|
cancel()
|
|
<-exitChan
|
|
wanted := []*slashertypes.SignedBlockHeaderWrapper{
|
|
createProposalWrapper(t, 0, block1.Header.ProposerIndex, nil),
|
|
createProposalWrapper(t, 0, block2.Header.ProposerIndex, nil),
|
|
}
|
|
require.DeepEqual(t, wanted, s.blksQueue.dequeue())
|
|
}
|
|
|
|
func TestService_processQueuedBlocks(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
slasherDB := dbtest.SetupSlasherDB(t)
|
|
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
currentSlot := primitives.Slot(0)
|
|
require.NoError(t, beaconState.SetSlot(currentSlot))
|
|
mockChain := &mock.ChainService{
|
|
State: beaconState,
|
|
Slot: ¤tSlot,
|
|
}
|
|
|
|
s := &Service{
|
|
params: DefaultParams(),
|
|
serviceCfg: &ServiceConfig{
|
|
Database: slasherDB,
|
|
StateNotifier: &mock.MockStateNotifier{},
|
|
HeadStateFetcher: mockChain,
|
|
},
|
|
blksQueue: newBlocksQueue(),
|
|
}
|
|
s.blksQueue.extend([]*slashertypes.SignedBlockHeaderWrapper{
|
|
createProposalWrapper(t, 0, 1, nil),
|
|
})
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
tickerChan := make(chan primitives.Slot)
|
|
exitChan := make(chan struct{})
|
|
go func() {
|
|
s.processQueuedBlocks(ctx, tickerChan)
|
|
exitChan <- struct{}{}
|
|
}()
|
|
|
|
// Send a value over the ticker.
|
|
tickerChan <- 0
|
|
cancel()
|
|
<-exitChan
|
|
assert.LogsContain(t, hook, "Processing queued")
|
|
}
|