package blockchain import ( "context" "fmt" "github.com/pkg/errors" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/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/featureconfig" "github.com/prysmaticlabs/prysm/shared/params" "go.opencensus.io/trace" ) // This defines size of the upper bound for initial sync block cache. var initialSyncBlockCacheSize = 2 * params.BeaconConfig().SlotsPerEpoch // onBlock is called when a gossip block is received. It runs regular state transition on the block. // The block's signing root should be computed before calling this method to avoid redundant // computation in this method and methods it calls into. // // Spec pseudocode definition: // def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: // block = signed_block.message // # Parent block must be known // assert block.parent_root in store.block_states // # Make a copy of the state to avoid mutability issues // pre_state = copy(store.block_states[block.parent_root]) // # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. // assert get_current_slot(store) >= block.slot // // # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) // finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) // assert block.slot > finalized_slot // # Check block is a descendant of the finalized block at the checkpoint finalized slot // assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root // // # Check the block is valid and compute the post-state // state = state_transition(pre_state, signed_block, True) // # Add new block to the store // store.blocks[hash_tree_root(block)] = block // # Add new state for this block to the store // store.block_states[hash_tree_root(block)] = state // // # Update justified checkpoint // if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: // if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: // store.best_justified_checkpoint = state.current_justified_checkpoint // if should_update_justified_checkpoint(store, state.current_justified_checkpoint): // store.justified_checkpoint = state.current_justified_checkpoint // // # Update finalized checkpoint // if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: // store.finalized_checkpoint = state.finalized_checkpoint // // # Potentially update justified if different from store // if store.justified_checkpoint != state.current_justified_checkpoint: // # Update justified if new justified is later than store justified // if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: // store.justified_checkpoint = state.current_justified_checkpoint // return // // # Update justified if store justified is not in chain with finalized checkpoint // finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) // ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) // if ancestor_at_finalized_slot != store.finalized_checkpoint.root: // store.justified_checkpoint = state.current_justified_checkpoint func (s *Service) onBlock(ctx context.Context, signed *ethpb.SignedBeaconBlock, blockRoot [32]byte) error { ctx, span := trace.StartSpan(ctx, "blockChain.onBlock") defer span.End() if signed == nil || signed.Block == nil { return errors.New("nil block") } b := signed.Block preState, err := s.getBlockPreState(ctx, b) if err != nil { return err } set, postState, err := state.ExecuteStateTransitionNoVerifyAnySig(ctx, preState, signed) if err != nil { return errors.Wrap(err, "could not execute state transition") } valid, err := set.Verify() if err != nil { return errors.Wrap(err, "could not batch verify signature") } if !valid { return errors.New("signature in block failed to verify") } if err := s.savePostStateInfo(ctx, blockRoot, signed, postState, false /* reg sync */); err != nil { return err } // Update justified check point. if postState.CurrentJustifiedCheckpoint().Epoch > s.justifiedCheckpt.Epoch { if err := s.updateJustified(ctx, postState); err != nil { return err } } // Update finalized check point. if postState.FinalizedCheckpointEpoch() > s.finalizedCheckpt.Epoch { if err := s.beaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil { return err } s.clearInitSyncBlocks() if err := s.updateFinalized(ctx, postState.FinalizedCheckpoint()); err != nil { return err } fRoot := bytesutil.ToBytes32(postState.FinalizedCheckpoint().Root) if err := s.forkChoiceStore.Prune(ctx, fRoot); err != nil { return errors.Wrap(err, "could not prune proto array fork choice nodes") } if err := s.finalizedImpliesNewJustified(ctx, postState); err != nil { return errors.Wrap(err, "could not save new justified") } // Update deposit cache. finalizedState, err := s.stateGen.StateByRoot(ctx, fRoot) if err != nil { return errors.Wrap(err, "could not fetch finalized state") } // We update the cache up to the last deposit index in the finalized block's state. // We can be confident that these deposits will be included in some block // because the Eth1 follow distance makes such long-range reorgs extremely unlikely. eth1DepositIndex := int64(finalizedState.Eth1Data().DepositCount - 1) s.depositCache.InsertFinalizedDeposits(ctx, eth1DepositIndex) if featureconfig.Get().EnablePruningDepositProofs { // Deposit proofs are only used during state transition and can be safely removed to save space. if err = s.depositCache.PruneProofs(ctx, eth1DepositIndex); err != nil { return errors.Wrap(err, "could not prune deposit proofs") } } } defer reportAttestationInclusion(b) return s.handleEpochBoundary(ctx, postState) } // onBlockInitialSyncStateTransition is called when an initial sync block is received. // It runs state transition on the block and without fork choice and post operation pool processes. // The block's signing root should be computed before calling this method to avoid redundant // computation in this method and methods it calls into. func (s *Service) onBlockInitialSyncStateTransition(ctx context.Context, signed *ethpb.SignedBeaconBlock, blockRoot [32]byte) error { ctx, span := trace.StartSpan(ctx, "blockChain.onBlockInitialSyncStateTransition") defer span.End() if signed == nil || signed.Block == nil { return errors.New("nil block") } b := signed.Block // Retrieve incoming block's pre state. if err := s.verifyBlkPreState(ctx, b); err != nil { return err } preState, err := s.stateGen.StateByRootInitialSync(ctx, bytesutil.ToBytes32(signed.Block.ParentRoot)) if err != nil { return err } if preState == nil { return fmt.Errorf("nil pre state for slot %d", b.Slot) } // Exit early if the pre state slot is higher than incoming block's slot. if preState.Slot() >= signed.Block.Slot { return nil } postState, err := state.ExecuteStateTransition(ctx, preState, signed) if err != nil { return errors.Wrap(err, "could not execute state transition") } if err := s.savePostStateInfo(ctx, blockRoot, signed, postState, true /* init sync */); err != nil { return err } // Save the latest block as head in cache. if err := s.saveHeadNoDB(ctx, signed, blockRoot, postState); err != nil { return err } // Rate limit how many blocks (2 epochs worth of blocks) a node keeps in the memory. if uint64(len(s.getInitSyncBlocks())) > initialSyncBlockCacheSize { if err := s.beaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil { return err } s.clearInitSyncBlocks() } if postState.CurrentJustifiedCheckpoint().Epoch > s.justifiedCheckpt.Epoch { if err := s.updateJustifiedInitSync(ctx, postState.CurrentJustifiedCheckpoint()); err != nil { return err } } // Update finalized check point. Prune the block cache and helper caches on every new finalized epoch. if postState.FinalizedCheckpointEpoch() > s.finalizedCheckpt.Epoch { if err := s.updateFinalized(ctx, postState.FinalizedCheckpoint()); err != nil { return err } } return s.handleEpochBoundary(ctx, postState) } func (s *Service) onBlockBatch(ctx context.Context, blks []*ethpb.SignedBeaconBlock, blockRoots [][32]byte) ([]*ethpb.Checkpoint, []*ethpb.Checkpoint, error) { ctx, span := trace.StartSpan(ctx, "blockChain.onBlockBatch") defer span.End() if len(blks) == 0 || len(blockRoots) == 0 { return nil, nil, errors.New("no blocks provided") } if blks[0] == nil || blks[0].Block == nil { return nil, nil, errors.New("nil block") } b := blks[0].Block // Retrieve incoming block's pre state. if err := s.verifyBlkPreState(ctx, b); err != nil { return nil, nil, err } preState, err := s.stateGen.StateByRootInitialSync(ctx, bytesutil.ToBytes32(b.ParentRoot)) if err != nil { return nil, nil, err } if preState == nil { return nil, nil, fmt.Errorf("nil pre state for slot %d", b.Slot) } jCheckpoints := make([]*ethpb.Checkpoint, len(blks)) fCheckpoints := make([]*ethpb.Checkpoint, len(blks)) sigSet := &bls.SignatureSet{ Signatures: [][]byte{}, PublicKeys: []bls.PublicKey{}, Messages: [][32]byte{}, } var set *bls.SignatureSet boundaries := make(map[[32]byte]*stateTrie.BeaconState) for i, b := range blks { set, preState, err = state.ExecuteStateTransitionNoVerifyAnySig(ctx, preState, b) if err != nil { return nil, nil, err } // Save potential boundary states. if helpers.IsEpochStart(preState.Slot()) { boundaries[blockRoots[i]] = preState.Copy() if err := s.handleEpochBoundary(ctx, preState); err != nil { return nil, nil, errors.Wrap(err, "could not handle epoch boundary state") } } jCheckpoints[i] = preState.CurrentJustifiedCheckpoint() fCheckpoints[i] = preState.FinalizedCheckpoint() sigSet.Join(set) } verify, err := sigSet.Verify() if err != nil { return nil, nil, err } if !verify { return nil, nil, errors.New("batch block signature verification failed") } for r, st := range boundaries { if err := s.stateGen.SaveState(ctx, r, st); err != nil { return nil, nil, err } } // Also saves the last post state which to be used as pre state for the next batch. lastB := blks[len(blks)-1] lastBR := blockRoots[len(blockRoots)-1] if err := s.stateGen.SaveState(ctx, lastBR, preState); err != nil { return nil, nil, err } if err := s.saveHeadNoDB(ctx, lastB, lastBR, preState); err != nil { return nil, nil, err } return fCheckpoints, jCheckpoints, nil } // handles a block after the block's batch has been verified, where we can save blocks // their state summaries and split them off to relative hot/cold storage. func (s *Service) handleBlockAfterBatchVerify(ctx context.Context, signed *ethpb.SignedBeaconBlock, blockRoot [32]byte, fCheckpoint, jCheckpoint *ethpb.Checkpoint) error { b := signed.Block s.saveInitSyncBlock(blockRoot, signed) if err := s.insertBlockToForkChoiceStore(ctx, b, blockRoot, fCheckpoint, jCheckpoint); err != nil { return err } if err := s.beaconDB.SaveStateSummary(ctx, &pb.StateSummary{ Slot: signed.Block.Slot, Root: blockRoot[:], }); err != nil { return err } // Rate limit how many blocks (2 epochs worth of blocks) a node keeps in the memory. if uint64(len(s.getInitSyncBlocks())) > initialSyncBlockCacheSize { if err := s.beaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil { return err } s.clearInitSyncBlocks() } if jCheckpoint.Epoch > s.justifiedCheckpt.Epoch { if err := s.updateJustifiedInitSync(ctx, jCheckpoint); err != nil { return err } } // Update finalized check point. Prune the block cache and helper caches on every new finalized epoch. if fCheckpoint.Epoch > s.finalizedCheckpt.Epoch { if err := s.updateFinalized(ctx, fCheckpoint); err != nil { return err } } return nil } // Epoch boundary bookkeeping such as logging epoch summaries. func (s *Service) handleEpochBoundary(ctx context.Context, postState *stateTrie.BeaconState) error { if postState.Slot()+1 == s.nextEpochBoundarySlot { // Update caches for the next epoch at epoch boundary slot - 1. if err := helpers.UpdateCommitteeCache(postState, helpers.NextEpoch(postState)); err != nil { return err } if err := helpers.UpdateProposerIndicesInCache(postState, helpers.NextEpoch(postState)); err != nil { return err } } else if postState.Slot() >= s.nextEpochBoundarySlot { if err := reportEpochMetrics(ctx, postState, s.head.state); err != nil { return err } var err error s.nextEpochBoundarySlot, err = helpers.StartSlot(helpers.NextEpoch(postState)) if err != nil { return err } // Update caches at epoch boundary slot. // The following updates have short cut to return nil cheaply if fulfilled during boundary slot - 1. if err := helpers.UpdateCommitteeCache(postState, helpers.CurrentEpoch(postState)); err != nil { return err } if err := helpers.UpdateProposerIndicesInCache(postState, helpers.CurrentEpoch(postState)); err != nil { return err } } return nil } // This feeds in the block and block's attestations to fork choice store. It's allows fork choice store // to gain information on the most current chain. func (s *Service) insertBlockAndAttestationsToForkChoiceStore(ctx context.Context, blk *ethpb.BeaconBlock, root [32]byte, st *stateTrie.BeaconState) error { fCheckpoint := st.FinalizedCheckpoint() jCheckpoint := st.CurrentJustifiedCheckpoint() if err := s.insertBlockToForkChoiceStore(ctx, blk, root, fCheckpoint, jCheckpoint); err != nil { return err } // Feed in block's attestations to fork choice store. for _, a := range blk.Body.Attestations { committee, err := helpers.BeaconCommitteeFromState(st, a.Data.Slot, a.Data.CommitteeIndex) if err != nil { return err } indices, err := attestationutil.AttestingIndices(a.AggregationBits, committee) if err != nil { return err } s.forkChoiceStore.ProcessAttestation(ctx, indices, bytesutil.ToBytes32(a.Data.BeaconBlockRoot), a.Data.Target.Epoch) } return nil } func (s *Service) insertBlockToForkChoiceStore(ctx context.Context, blk *ethpb.BeaconBlock, root [32]byte, fCheckpoint, jCheckpoint *ethpb.Checkpoint) error { if err := s.fillInForkChoiceMissingBlocks(ctx, blk, fCheckpoint, jCheckpoint); err != nil { return err } // Feed in block to fork choice store. if err := s.forkChoiceStore.ProcessBlock(ctx, blk.Slot, root, bytesutil.ToBytes32(blk.ParentRoot), bytesutil.ToBytes32(blk.Body.Graffiti), jCheckpoint.Epoch, fCheckpoint.Epoch); err != nil { return errors.Wrap(err, "could not process block for proto array fork choice") } return nil } // This saves post state info to DB or cache. This also saves post state info to fork choice store. // Post state info consists of processed block and state. Do not call this method unless the block and state are verified. func (s *Service) savePostStateInfo(ctx context.Context, r [32]byte, b *ethpb.SignedBeaconBlock, st *stateTrie.BeaconState, initSync bool) error { ctx, span := trace.StartSpan(ctx, "blockChain.savePostStateInfo") defer span.End() if initSync { s.saveInitSyncBlock(r, b) } else if err := s.beaconDB.SaveBlock(ctx, b); err != nil { return errors.Wrapf(err, "could not save block from slot %d", b.Block.Slot) } if err := s.stateGen.SaveState(ctx, r, st); err != nil { return errors.Wrap(err, "could not save state") } if err := s.insertBlockAndAttestationsToForkChoiceStore(ctx, b.Block, r, st); err != nil { return errors.Wrapf(err, "could not insert block %d to fork choice store", b.Block.Slot) } return nil }