// Package testing includes useful mocks for writing unit // tests which depend on logic from the blockchain package. package testing import ( "bytes" "context" "sync" "time" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/async/event" "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block" opfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/operation" statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/db" "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray" "github.com/prysmaticlabs/prysm/beacon-chain/state" fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" "github.com/prysmaticlabs/prysm/config/params" "github.com/prysmaticlabs/prysm/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/sirupsen/logrus" ) var ErrNilState = errors.New("nil state") // ChainService defines the mock interface for testing type ChainService struct { State state.BeaconState Root []byte Block block.SignedBeaconBlock FinalizedCheckPoint *ethpb.Checkpoint CurrentJustifiedCheckPoint *ethpb.Checkpoint PreviousJustifiedCheckPoint *ethpb.Checkpoint BlocksReceived []block.SignedBeaconBlock Balance *precompute.Balance Genesis time.Time ValidatorsRoot [32]byte CanonicalRoots map[[32]byte]bool Fork *ethpb.Fork ETH1Data *ethpb.Eth1Data DB db.Database stateNotifier statefeed.Notifier blockNotifier blockfeed.Notifier opNotifier opfeed.Notifier ValidAttestation bool ForkChoiceStore *protoarray.Store VerifyBlkDescendantErr error Slot *types.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect. SyncCommitteeIndices []types.CommitteeIndex SyncCommitteeDomain []byte SyncSelectionProofDomain []byte SyncContributionProofDomain []byte PublicKey [fieldparams.BLSPubkeyLength]byte SyncCommitteePubkeys [][]byte InitSyncBlockRoots map[[32]byte]bool } // StateNotifier mocks the same method in the chain service. func (s *ChainService) StateNotifier() statefeed.Notifier { if s.stateNotifier == nil { s.stateNotifier = &MockStateNotifier{} } return s.stateNotifier } // BlockNotifier mocks the same method in the chain service. func (s *ChainService) BlockNotifier() blockfeed.Notifier { if s.blockNotifier == nil { s.blockNotifier = &MockBlockNotifier{} } return s.blockNotifier } // MockBlockNotifier mocks the block notifier. type MockBlockNotifier struct { feed *event.Feed } // BlockFeed returns a block feed. func (mbn *MockBlockNotifier) BlockFeed() *event.Feed { if mbn.feed == nil { mbn.feed = new(event.Feed) } return mbn.feed } // MockStateNotifier mocks the state notifier. type MockStateNotifier struct { feed *event.Feed feedLock sync.Mutex recv []*feed.Event recvLock sync.Mutex recvCh chan *feed.Event RecordEvents bool } // ReceivedEvents returns the events received by the state feed in this mock. func (msn *MockStateNotifier) ReceivedEvents() []*feed.Event { msn.recvLock.Lock() defer msn.recvLock.Unlock() return msn.recv } // StateFeed returns a state feed. func (msn *MockStateNotifier) StateFeed() *event.Feed { msn.feedLock.Lock() defer msn.feedLock.Unlock() if msn.feed == nil && msn.recvCh == nil { msn.feed = new(event.Feed) if msn.RecordEvents { msn.recvCh = make(chan *feed.Event) sub := msn.feed.Subscribe(msn.recvCh) go func() { select { case evt := <-msn.recvCh: msn.recvLock.Lock() msn.recv = append(msn.recv, evt) msn.recvLock.Unlock() case <-sub.Err(): sub.Unsubscribe() } }() } } return msn.feed } // OperationNotifier mocks the same method in the chain service. func (s *ChainService) OperationNotifier() opfeed.Notifier { if s.opNotifier == nil { s.opNotifier = &MockOperationNotifier{} } return s.opNotifier } // MockOperationNotifier mocks the operation notifier. type MockOperationNotifier struct { feed *event.Feed } // OperationFeed returns an operation feed. func (mon *MockOperationNotifier) OperationFeed() *event.Feed { if mon.feed == nil { mon.feed = new(event.Feed) } return mon.feed } // ReceiveBlockInitialSync mocks ReceiveBlockInitialSync method in chain service. func (s *ChainService) ReceiveBlockInitialSync(ctx context.Context, block block.SignedBeaconBlock, _ [32]byte) error { if s.State == nil { return ErrNilState } if !bytes.Equal(s.Root, block.Block().ParentRoot()) { return errors.Errorf("wanted %#x but got %#x", s.Root, block.Block().ParentRoot()) } if err := s.State.SetSlot(block.Block().Slot()); err != nil { return err } s.BlocksReceived = append(s.BlocksReceived, block) signingRoot, err := block.Block().HashTreeRoot() if err != nil { return err } if s.DB != nil { if err := s.DB.SaveBlock(ctx, block); err != nil { return err } logrus.Infof("Saved block with root: %#x at slot %d", signingRoot, block.Block().Slot()) } s.Root = signingRoot[:] s.Block = block return nil } // ReceiveBlockBatch processes blocks in batches from initial-sync. func (s *ChainService) ReceiveBlockBatch(ctx context.Context, blks []block.SignedBeaconBlock, _ [][32]byte) error { if s.State == nil { return ErrNilState } for _, block := range blks { if !bytes.Equal(s.Root, block.Block().ParentRoot()) { return errors.Errorf("wanted %#x but got %#x", s.Root, block.Block().ParentRoot()) } if err := s.State.SetSlot(block.Block().Slot()); err != nil { return err } s.BlocksReceived = append(s.BlocksReceived, block) signingRoot, err := block.Block().HashTreeRoot() if err != nil { return err } if s.DB != nil { if err := s.DB.SaveBlock(ctx, block); err != nil { return err } logrus.Infof("Saved block with root: %#x at slot %d", signingRoot, block.Block().Slot()) } s.Root = signingRoot[:] s.Block = block } return nil } // ReceiveBlock mocks ReceiveBlock method in chain service. func (s *ChainService) ReceiveBlock(ctx context.Context, block block.SignedBeaconBlock, _ [32]byte) error { if s.State == nil { return ErrNilState } if !bytes.Equal(s.Root, block.Block().ParentRoot()) { return errors.Errorf("wanted %#x but got %#x", s.Root, block.Block().ParentRoot()) } if err := s.State.SetSlot(block.Block().Slot()); err != nil { return err } s.BlocksReceived = append(s.BlocksReceived, block) signingRoot, err := block.Block().HashTreeRoot() if err != nil { return err } if s.DB != nil { if err := s.DB.SaveBlock(ctx, block); err != nil { return err } logrus.Infof("Saved block with root: %#x at slot %d", signingRoot, block.Block().Slot()) } s.Root = signingRoot[:] s.Block = block return nil } // HeadSlot mocks HeadSlot method in chain service. func (s *ChainService) HeadSlot() types.Slot { if s.State == nil { return 0 } return s.State.Slot() } // HeadRoot mocks HeadRoot method in chain service. func (s *ChainService) HeadRoot(_ context.Context) ([]byte, error) { if len(s.Root) > 0 { return s.Root, nil } return make([]byte, 32), nil } // HeadBlock mocks HeadBlock method in chain service. func (s *ChainService) HeadBlock(context.Context) (block.SignedBeaconBlock, error) { return s.Block, nil } // HeadState mocks HeadState method in chain service. func (s *ChainService) HeadState(context.Context) (state.BeaconState, error) { return s.State, nil } // CurrentFork mocks HeadState method in chain service. func (s *ChainService) CurrentFork() *ethpb.Fork { return s.Fork } // FinalizedCheckpt mocks FinalizedCheckpt method in chain service. func (s *ChainService) FinalizedCheckpt() *ethpb.Checkpoint { return s.FinalizedCheckPoint } // CurrentJustifiedCheckpt mocks CurrentJustifiedCheckpt method in chain service. func (s *ChainService) CurrentJustifiedCheckpt() *ethpb.Checkpoint { return s.CurrentJustifiedCheckPoint } // PreviousJustifiedCheckpt mocks PreviousJustifiedCheckpt method in chain service. func (s *ChainService) PreviousJustifiedCheckpt() *ethpb.Checkpoint { return s.PreviousJustifiedCheckPoint } // ReceiveAttestation mocks ReceiveAttestation method in chain service. func (_ *ChainService) ReceiveAttestation(_ context.Context, _ *ethpb.Attestation) error { return nil } // ReceiveAttestationNoPubsub mocks ReceiveAttestationNoPubsub method in chain service. func (_ *ChainService) ReceiveAttestationNoPubsub(context.Context, *ethpb.Attestation) error { return nil } // AttestationTargetState mocks AttestationTargetState method in chain service. func (s *ChainService) AttestationTargetState(_ context.Context, _ *ethpb.Checkpoint) (state.BeaconState, error) { return s.State, nil } // HeadValidatorsIndices mocks the same method in the chain service. func (s *ChainService) HeadValidatorsIndices(ctx context.Context, epoch types.Epoch) ([]types.ValidatorIndex, error) { if s.State == nil { return []types.ValidatorIndex{}, nil } return helpers.ActiveValidatorIndices(ctx, s.State, epoch) } // HeadSeed mocks the same method in the chain service. func (s *ChainService) HeadSeed(_ context.Context, epoch types.Epoch) ([32]byte, error) { return helpers.Seed(s.State, epoch, params.BeaconConfig().DomainBeaconAttester) } // HeadETH1Data provides the current ETH1Data of the head state. func (s *ChainService) HeadETH1Data() *ethpb.Eth1Data { return s.ETH1Data } // ProtoArrayStore mocks the same method in the chain service. func (s *ChainService) ProtoArrayStore() *protoarray.Store { return s.ForkChoiceStore } // GenesisTime mocks the same method in the chain service. func (s *ChainService) GenesisTime() time.Time { return s.Genesis } // GenesisValidatorRoot mocks the same method in the chain service. func (s *ChainService) GenesisValidatorRoot() [32]byte { return s.ValidatorsRoot } // CurrentSlot mocks the same method in the chain service. func (s *ChainService) CurrentSlot() types.Slot { if s.Slot != nil { return *s.Slot } return types.Slot(uint64(time.Now().Unix()-s.Genesis.Unix()) / params.BeaconConfig().SecondsPerSlot) } // Participation mocks the same method in the chain service. func (s *ChainService) Participation(_ uint64) *precompute.Balance { return s.Balance } // IsValidAttestation always returns true. func (s *ChainService) IsValidAttestation(_ context.Context, _ *ethpb.Attestation) bool { return s.ValidAttestation } // IsCanonical returns and determines whether a block with the provided root is part of // the canonical chain. func (s *ChainService) IsCanonical(_ context.Context, r [32]byte) (bool, error) { if s.CanonicalRoots != nil { _, ok := s.CanonicalRoots[r] return ok, nil } return true, nil } // HasInitSyncBlock mocks the same method in the chain service. func (s *ChainService) HasInitSyncBlock(rt [32]byte) bool { if s.InitSyncBlockRoots == nil { return false } return s.InitSyncBlockRoots[rt] } // HeadGenesisValidatorRoot mocks HeadGenesisValidatorRoot method in chain service. func (_ *ChainService) HeadGenesisValidatorRoot() [32]byte { return [32]byte{} } // VerifyBlkDescendant mocks VerifyBlkDescendant and always returns nil. func (s *ChainService) VerifyBlkDescendant(_ context.Context, _ [32]byte) error { return s.VerifyBlkDescendantErr } // VerifyLmdFfgConsistency mocks VerifyLmdFfgConsistency and always returns nil. func (_ *ChainService) VerifyLmdFfgConsistency(_ context.Context, a *ethpb.Attestation) error { if !bytes.Equal(a.Data.BeaconBlockRoot, a.Data.Target.Root) { return errors.New("LMD and FFG miss matched") } return nil } // VerifyFinalizedConsistency mocks VerifyFinalizedConsistency and always returns nil. func (s *ChainService) VerifyFinalizedConsistency(_ context.Context, r []byte) error { if !bytes.Equal(r, s.FinalizedCheckPoint.Root) { return errors.New("Root and finalized store are not consistent") } return nil } // ChainHeads mocks ChainHeads and always return nil. func (_ *ChainService) ChainHeads() ([][32]byte, []types.Slot) { return [][32]byte{ bytesutil.ToBytes32(bytesutil.PadTo([]byte("foo"), 32)), bytesutil.ToBytes32(bytesutil.PadTo([]byte("bar"), 32)), }, []types.Slot{0, 1} } // HeadPublicKeyToValidatorIndex mocks HeadPublicKeyToValidatorIndex and always return 0 and true. func (_ *ChainService) HeadPublicKeyToValidatorIndex(_ [fieldparams.BLSPubkeyLength]byte) (types.ValidatorIndex, bool) { return 0, true } // HeadValidatorIndexToPublicKey mocks HeadValidatorIndexToPublicKey and always return empty and nil. func (s *ChainService) HeadValidatorIndexToPublicKey(_ context.Context, _ types.ValidatorIndex) ([fieldparams.BLSPubkeyLength]byte, error) { return s.PublicKey, nil } // HeadSyncCommitteeIndices mocks HeadSyncCommitteeIndices and always return `HeadNextSyncCommitteeIndices`. func (s *ChainService) HeadSyncCommitteeIndices(_ context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) { return s.SyncCommitteeIndices, nil } // HeadSyncCommitteePubKeys mocks HeadSyncCommitteePubKeys and always return empty nil. func (s *ChainService) HeadSyncCommitteePubKeys(_ context.Context, _ types.Slot, _ types.CommitteeIndex) ([][]byte, error) { return s.SyncCommitteePubkeys, nil } // HeadSyncCommitteeDomain mocks HeadSyncCommitteeDomain and always return empty nil. func (s *ChainService) HeadSyncCommitteeDomain(_ context.Context, _ types.Slot) ([]byte, error) { return s.SyncCommitteeDomain, nil } // HeadSyncSelectionProofDomain mocks HeadSyncSelectionProofDomain and always return empty nil. func (s *ChainService) HeadSyncSelectionProofDomain(_ context.Context, _ types.Slot) ([]byte, error) { return s.SyncSelectionProofDomain, nil } // HeadSyncContributionProofDomain mocks HeadSyncContributionProofDomain and always return empty nil. func (s *ChainService) HeadSyncContributionProofDomain(_ context.Context, _ types.Slot) ([]byte, error) { return s.SyncContributionProofDomain, nil } // IsOptimistic mocks the same method in the chain service. func (s *ChainService) IsOptimistic(_ context.Context) (bool, error) { return false, nil } // IsOptimisticForRoot mocks the same method in the chain service. func (s *ChainService) IsOptimisticForRoot(_ context.Context, _ [32]byte, _ types.Slot) (bool, error) { return false, nil }